Repository navigation
fix(scripts): the tenant-audit census reads this.X through a function's declared this host (#25222) - #25660
Merged
Conversation
…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>
Contributor
Author
席位核对 · ACCEPT · 2026-10-24T14:00Z
Generated by Claude Code |
This was referenced Oct 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.tssplit. Its claim is comment 6384913796. It changes one file,scripts/tenant-audit-census.mjs. The census now reads athis.Xreceiver through the host type its function declares forthis, so a method body moved out of its class keeps the verdict it had inside the class. Onmain, no census reading changes.Why
AutomationEnginebodies. They hold 33this.FIELD.delete(sites, all on the class's ownMapandSetfields.thisas aPickof a host interface, with each member typedAutomationEngine['FIELD']. That is the shape slices 1 to 7 use.this.Xonly through a class property declared in the same file. Inside afunction,thisnames 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 itsname: stringparameter as an engine write, which turns 238 sites into 239.What changed
resolveReceiver, thethis.Xbranch. Some functions declarethisas their first parameter (this: …). When that is the function that binds the site'sthis, the census answers through the declaration (hostMemberEntry). It takes three steps:Pickand the other narrowing wrappers peeled off, names an interface or a type-literal alias at the top level of the file;C['x'], an indexed access on a class, the census reads the class's own declaration ofxinstead, 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:
this;Across files. The census reaches a class in another file through the site file's named import of it.
declaredTypesInrecords named imports and takes an optionalloadModule.runCensuspasses a loader. The loader resolves a relative specifier (moduleFileFor:./engine.jstoengine.ts).node_modulesor an untracked file.fromEntry, the rule that turns one declaration into a verdict, is lifted out ofresolveReceiverunchanged asreceiverFromEntry, andnameOfbecomesindexedNameIn. This lets the census read a class's declaration against that class's own file. The two closures inresolveReceivernow 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 orcheck: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:
Mapin another fileother/constructed-locallyunresolved/engine/IProbeEngineunresolved/other/constructed-locally | engine/IProbeEngineengine/IProbeEngine | other/platform-typereadonly engine: IProbeEngine)engine/IProbeEngineunresolved/other/constructed-locallyunresolved/functionexpression inside the host functionunresolved/engine/IProbeEngine, from the floorunresolved/The ablation is one line,
const host = null;in thethis.Xbranch, 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 withmain, in a worktree of that commit.mainhas since moved tobaae6ed1, 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 inengine.ts, next to it. Each probe importsAutomationEnginewithimport typefrom./engine.js, declares a host member typedAutomationEngine['FIELD'], and callsFIELD.deletefrom a function whosethisis aPickof that host. All 33 probes get the same verdict as their in-class site: 32constructed-locallyand 1corpus-type(store?: SuspendedRunStore). The 21 members that hold the sites hold no otherinsert,updateordeletecall.On
main, the new branch answers no site. I instrumented a scratch copy to print every write-call receiver that reaches a declaredthis, and over the whole corpus it printed none. 45 non-test files in the corpus declare such functions. Their onlythis.X.delete(lines (this.readAt.deleteinauth-manager-password-session-policy.ts) sit inside a class.Outputs, at both ends:
node scripts/tenant-audit-census.mjs, stdout and stderr5a3e8991ecd717edd4e123e9b9439d09d4d7d3b0b3c14125650b6696c08bda7e5a3e8991ecd717edd4e123e9b9439d09d4d7d3b0b3c14125650b6696c08bda7enode scripts/tenant-audit-census.mjs --json, withmeasuredAtremovedd189877c2ae1afb94324f88fc9bef24925feda79b1897d5b246b509b6fa7055ad189877c2ae1afb94324f88fc9bef24925feda79b1897d5b246b509b6fa7055anode scripts/check-tenant-audit-census.mjs7f0cc90798085180772c7255aa4f21d35b3b6a6bc6f041f4181234fd10cd11727f0cc90798085180772c7255aa4f21d35b3b6a6bc6f041f4181234fd10cd1172--jsondiffers on one line:measuredAt.ref, which is the HEAD sha (49653f918and9a4c20ea7).UNTYPED_RECEIVERSrow matched.--writeat 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 unenforcedMeasured on … atline.node scripts/check-tenant-audit-census.mjs --self-testexits 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.--jsonoutput and the gate output can, and do.Gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackon 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 --ranreconciles: 32 derived, 32 run, 0 NOT-MEASURED, 0 UNRUN.check-governed-merges --branchon this commit: 0 of 1 path on the register, NOT governed, 356 changed lines (+317 / -39).origin/main. None touchesscripts/tenant-audit-census.mjs,scripts/check-tenant-audit-census.mjs,scripts/ts-parse.mjs, the census page or the counts file.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