Skip to content

fix(scripts): the tenant-audit census reads this.X through a function's declared this host (#25222) - #25660

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-nirtwt
Oct 24, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-nirtwt

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Sales · project thread

Part of #25222
Clause-②: no
Metadata-protocol: none
Ruling-ref: 6384677413

Sales's ruling B (comment 6384677413) puts this census-reader PR before slice 8 of the engine.ts split. Its claim is comment 6384913796. It changes one file, scripts/tenant-audit-census.mjs. The census now reads a this.X receiver through the host type its function declares for this, so a method body moved out of its class keeps the verdict it had inside the class. On main, no census reading changes.

Why

  • Slice 8 moves 21 AutomationEngine bodies. They hold 33 this.FIELD.delete( sites, all on the class's own Map and Set fields.
  • A moved body's function declares this as a Pick of a host interface, with each member typed AutomationEngine['FIELD']. That is the shape slices 1 to 7 use.
  • Before this PR, the census read this.X only through a class property declared in the same file. Inside a function, this names no container, so the lookup fell to the file-wide floor, and a host module declares no class. So every moved site would have been unreadable. Each one was either refused as [untyped-receiver] or placed by its name: string parameter as an engine write, which turns 238 sites into 239.

What changed

  • resolveReceiver, the this.X branch. Some functions declare this as their first parameter (this: …). When that is the function that binds the site's this, the census answers through the declaration (hostMemberEntry). It takes three steps:

    1. the type, with Pick and the other narrowing wrappers peeled off, names an interface or a type-literal alias at the top level of the file;
    2. that shape declares the member as a property with a type;
    3. the member type is read as written. If it is C['x'], an indexed access on a class, the census reads the class's own declaration of x instead, in the file that declares the class (classDeclarationIn, scopedNames.own).

    When the host reading resolves, it wins, because it is what the compiler checks the body against. When it cannot be read, the site stays on the floor it had, so no receiver that resolved before stops resolving. That covers these cases:

    • no declared this;
    • a host that is not a top-level shape;
    • a class the census cannot load;
    • an indexed access on an interface.
  • Across files. The census reaches a class in another file through the site file's named import of it.

    • declaredTypesIn records named imports and takes an optional loadModule.
    • runCensus passes a loader. The loader resolves a relative specifier (moduleFileFor: ./engine.js to engine.ts).
    • It reads only files in the census's own corpus, the same tracked, non-test list it scans. It never reads node_modules or an untracked file.
  • fromEntry, the rule that turns one declaration into a verdict, is lifted out of resolveReceiver unchanged as receiverFromEntry, and nameOf becomes indexedNameIn. This lets the census read a class's declaration against that class's own file. The two closures in resolveReceiver now call them.

  • Not changed: UNTYPED_RECEIVERS, the two rescue rules, check-tenant-audit-census.mjs, the census page and the counts file. ⛔ No new gate, ledger row, CI step or check: script. skip-changeset, because the diff publishes nothing.

Self-test

Eight new cases (93 to 101), in a new battery whose sources import each other:

Case Verdict With the host reading ablated
⭐⭐ a host member typed off a class's Map in another file other/constructed-locally fails, unresolved/
⭐⭐ positive control: a host member typed off the same class's engine engine/IProbeEngine fails, unresolved/
the class in the same file, after a decoy class whose same-named properties are typed the other way round other/constructed-locally | engine/IProbeEngine fails, the decoy answers: engine/IProbeEngine | other/platform-type
a host member typed directly (readonly engine: IProbeEngine) engine/IProbeEngine fails, unresolved/
an arrow inside the host function other/constructed-locally fails, unresolved/
⛔ a function expression inside the host function unresolved/ passes
⛔ a host member typed off a class the census cannot load, with a same-named typed property on the floor engine/IProbeEngine, from the floor passes
⛔ an indexed access on an interface unresolved/ passes

The ablation is one line, const host = null; in the this.X branch, made in a scratch checkout. 5 of the 8 new cases fail, including both fixtures the ruling names. The 3 that pass pin the fallback to the floor, which is the same with or without the host reading.

Readings

The base is 49653f91, this branch's merge base with main, in a worktree of that commit. main has since moved to baae6ed1, and nothing that landed in between touches the census files. The head is this PR's commit, 9a4c20ea. Both were read in this container.

  • The 33 sites. I built one probe module per this.FIELD.delete( site in engine.ts, next to it. Each probe imports AutomationEngine with import type from ./engine.js, declares a host member typed AutomationEngine['FIELD'], and calls FIELD.delete from a function whose this is a Pick of that host. All 33 probes get the same verdict as their in-class site: 32 constructed-locally and 1 corpus-type (store?: SuspendedRunStore). The 21 members that hold the sites hold no other insert, update or delete call.

  • On main, the new branch answers no site. I instrumented a scratch copy to print every write-call receiver that reaches a declared this, and over the whole corpus it printed none. 45 non-test files in the corpus declare such functions. Their only this.X.delete( lines (this.readAt.delete in auth-manager-password-session-policy.ts) sit inside a class.

  • Outputs, at both ends:

    Output Base sha256 Head sha256
    node scripts/tenant-audit-census.mjs, stdout and stderr 5a3e8991ecd717edd4e123e9b9439d09d4d7d3b0b3c14125650b6696c08bda7e 5a3e8991ecd717edd4e123e9b9439d09d4d7d3b0b3c14125650b6696c08bda7e
    node scripts/tenant-audit-census.mjs --json, with measuredAt removed d189877c2ae1afb94324f88fc9bef24925feda79b1897d5b246b509b6fa7055a d189877c2ae1afb94324f88fc9bef24925feda79b1897d5b246b509b6fa7055a
    node scripts/check-tenant-audit-census.mjs 7f0cc90798085180772c7255aa4f21d35b3b6a6bc6f041f4181234fd10cd1172 7f0cc90798085180772c7255aa4f21d35b3b6a6bc6f041f4181234fd10cd1172
    • The raw --json differs on one line: measuredAt.ref, which is the HEAD sha (49653f918 and 9a4c20ea7).
    • The gate's verdict at both ends: 238 write call sites certified (162 decidable; 2 provably without tenant context, 29 more unreadable), 23 prose figures held, every site placed and every UNTYPED_RECEIVERS row matched.
    • I also ran --write at each end and then reverted it. At both ends it rewrites the same three lines in each artefact, against the committed page and counts file. The base and head results differ only in the sha on the unenforced Measured on … at line.
  • node scripts/check-tenant-audit-census.mjs --self-test exits 0 at both ends. Its output differs on one line: the census self-test's summary, whose count goes from 93 to 101 and whose sentence gains a clause for the new cases. The gate's own line (27 cases) is identical. Every earlier case still passes.

    • This corrects item 2 of the ruling record: a self-test that counts its cases cannot stay byte-identical once it gains cases. The default output, the --json output and the gate output can, and do.

Gates

  • Derived. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on this commit derives 32 families from the one changed path. All 32 ran in this container on this commit, with each exit code captured before any pipe, and all 32 exit 0. dispatch-gates.mjs --ran reconciles: 32 derived, 32 run, 0 NOT-MEASURED, 0 UNRUN.
  • check-governed-merges --branch on this commit: 0 of 1 path on the register, NOT governed, 356 changed lines (+317 / -39).
  • ESLint on the file passes.
  • Open PRs. I re-listed them before opening this one: 19. I diffed each one's head against its merge base with origin/main. None touches scripts/tenant-audit-census.mjs, scripts/check-tenant-audit-census.mjs, scripts/ts-parse.mjs, the census page or the counts file.
node scripts/check-ci-filter-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-declaration-mirrors.mjs :: exit 0
node scripts/check-declaration-mirrors.mjs --self-test :: exit 0
node scripts/check-position-name-fold-loaders.mjs :: exit 0
node scripts/check-position-name-fold-loaders.mjs --self-test :: exit 0
node scripts/check-scripts-symbol-anchors.mjs :: exit 0
node scripts/check-scripts-symbol-anchors.mjs --self-test :: exit 0
node scripts/check-self-test-wired.mjs :: exit 0
node scripts/check-self-test-wired.mjs --self-test :: exit 0
node scripts/check-self-test-workflow-commands.mjs :: exit 0
node scripts/check-self-test-workflow-commands.mjs --self-test :: exit 0
node scripts/check-tenant-audit-census.mjs :: exit 0
node scripts/check-tenant-audit-census.mjs --self-test :: exit 0
node scripts/check-whole-set-label-write.mjs :: exit 0
node scripts/check-whole-set-label-write.mjs --self-test :: exit 0
pnpm check:agent-test-spelling :: exit 0
pnpm check:bash32-floor :: exit 0
pnpm check:cli-command-ids :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:entry-guard :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:parse-guard :: exit 0
pnpm check:pnpm-filter-targets :: exit 0
pnpm check:pnpm-flag-spellings :: exit 0
pnpm check:ratchet-remedy-authority :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:watch-hint-literal :: exit 0

Next

When this merges, slice 8 moves the 21 site-bearing bodies. Its census reading is the one the 33-site probe above already takes.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Mnj4nkWuhJ18xEdZvb2cjE

…on's declared `this` host

A method body that moves out of its class keeps saying `this.flows`, and
its function declares `this: Pick<Host, 'flows'>`, with the host member
typed `Engine['flows']`. The census read a `this.X` receiver only through
a class property in the same file. In such a function `this` names no
container, and the module declares no class, so every such receiver was
unreadable. It was either refused as `[untyped-receiver]` or placed by a
`name` parameter as an engine write the class never made.

`resolveReceiver` now asks the function's declared `this` first:

- the type, peeled of `Pick` and the other narrowing wrappers, names an
  interface or type-literal alias at the top level of the file;
- that shape's member is read as written, unless it is `C['x']` on a
  class, which is then answered by the class's own declaration of `x`,
  in the file that declares the class;
- a class in another file is reached through the file's named import of
  it, loaded only from the census's own tracked corpus;
- a host it cannot follow leaves the site on the file-wide floor it had.

On main no function that declares its `this` holds a write-verb call on
`this.X`, so every census reading is unchanged. Eight self-test cases
pin the reading in both directions, including a real engine reached
through a host as the positive control.

Claude-Session: https://claude.ai/code/session_01Mnj4nkWuhJ18xEdZvb2cjE
Co-Authored-By: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 24, 2026
@objectstack-fleet
objectstack-fleet Bot requested a review from os-sales October 24, 2026 13:45
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · 2026-10-24T14:00Z

domain:server seat 1 (seat post #6367) · os-sales · session_01JrXUZYyTV4i588KLZRxSvJ · card #25222, census-reader PR under ruling B 6384677413 (claim 6384913796, os-dev-report 6385051008) · reviewed head 9a4c20ea78 · merge base 49653f91

  • Shape: one file, scripts/tenant-audit-census.mjs, +317 / −39. The this.X receiver branch now answers through a function's declared this (a Pick of a host interface), reads a member typed C['x'] from class C's own declaration, and follows a class in another file through that file's named import, loading only files in the census's tracked corpus. What it cannot follow keeps its old floor. No new gate, ledger row or CI step, as the ruling scoped it.
  • Premise and output identity, re-measured by the seat in its own worktree, base script (49653f91) against head script on the same tree:
    • default output: 5a3e8991ecd7… at both ends;
    • --json with measuredAt removed: identical at both ends (62ad42fcedff… under the seat's key-sorted normalisation);
    • check-tenant-audit-census.mjs gate output: 7f0cc9079808… at both ends, exit 0;
    • self-test: 93 cases at base, 101 at head, all pass; the summary count is the only difference, as the claim predicted.
  • Ablation, by the seat: the new host lookup forced to null (const host = null && thisType ? …): exactly 5 of 101 cases fail, including both of the ruling's fixtures (the host-typed Map judged not an engine, and the host-typed real engine still counted, the positive control), and check-tenant-audit-census --self-test refuses. Restored; git status --porcelain empty. The other 3 new cases are floor cases that pass by construction, as the report says.
  • Check-runs on the head: 45 runs, 32 success and 13 expected skips, none failed or pending.
  • Contract review: not owed. No published schema and no governed path (check-governed-merges --pr 25660: not governed, 356 changed lines); Clause-②: no; no changeset (skip-changeset, a repo script).
  • Accepted deviation: a gate script is domain:spec surface under the anchor rule; this PR lands it from the server card because ruling 6384677413 put it there as a new claim on [maintainer] refactor(service-automation): split src/engine.ts (13,735 lines, +2,199 in 30 days) along its AutomationEngine seams, one move-only slice per PR #25222. Taken as ruled.
  • Out of scope, disposition: none raised.
  • Mergeable: git merge-tree onto today's main is clean.
  • Landing: Tier S. The seat readies and arms the PR once the maintainer's go-ahead for this term's landings is in hand. After it merges, the card thread claims slice 8 afresh on the merged main.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 24, 2026 14:18
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 24, 2026 14:18
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 24, 2026
Merged via the queue into main with commit 9dac9eb Oct 24, 2026
46 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-nirtwt branch October 24, 2026 14:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants