Repository navigation
fix(tooling): a derived parse's verdict comes BACK, so one unparseable synthesis stops taking the whole run - #19112
Conversation
…e synthesis stops taking the whole run `scripts/ts-parse.mjs` answers "could I read this tree?" by ending the process, which is right for a tree the gate's own author controls. A gate that re-parses text it SYNTHESISED from a source it already read is asking a different question, and it was getting that answer: `tenant-audit-census.mjs` stores a declared type's text whitespace-collapsed and re-parses it as a synthetic type alias, so a receiver whose members are separated by a newline alone -- legal TypeScript, 0 diagnostics as authored -- collapses to an unparseable alias and takes the entire census down with EXIT_UNPARSEABLE, naming a file that does not exist in the tree. `parseDerivedText` answers the synthesis question and returns the verdict. The floor is held by construction rather than by review: it requires an `origin` this module has certified, and the only two doors that certify one exit on a source that does not parse -- so a source the gate could not read cannot reach the returnable door. An uncertified origin is itself a refusal. On failure no tree comes back, so there is no recovered wreckage to walk and no reading of the result that says "nothing to report". The three existing refusals are untouched: every line from each diagnostics read through its `process.exit` is byte-identical, and the only change inside an existing door is one `vouchedSources.add` on a success path, after the refusal has already been decided. Claude-Session: https://claude.ai/code/session_019srGWGCBBCBHqcDoRZpQRh Co-authored-by: Claude <noreply@anthropic.com>
…, both lit controls, and the floor
Five batteries, 18 cases, on the fixtures the census itself produces: a
receiver type authored across lines, put through `.replace(/\s+/g, ' ')` --
the collapse the census performs -- and wrapped in the synthetic alias it
builds.
• the collapsed receiver STILL exits through `parseSourceFile`, and
through `parseDerivedText` returns a located verdict with the run
surviving. Two lit controls hold the fixture family capable of
SUCCEEDING: the semicolon spelling (the card's own control) and the
authored spelling both parse, so the defect is the collapse and not the
shape.
• a genuinely unparseable input is still refused at every door: an exit
at `parseSourceFile`, and NO TREE back from `parseDerivedText`, so
there is nothing to walk and no result that reads as "nothing to
report". Without this leg a repair and a switched-off check are the
same green.
• the fourth door is unreachable from an uncertified origin -- a raw
tree, null, undefined, a string -- and that refusal is not swallowable
either; a source file a checked Program was built over IS certified.
• the knobs are compared across the two doors rather than asserted in
prose, and the census counts a rejection as a run that continued.
Claude-Session: https://claude.ai/code/session_019srGWGCBBCBHqcDoRZpQRh
Co-authored-by: Claude <noreply@anthropic.com>
|
Ready for the queue: green, non-governed, and its Seat: domain:spec#3 Flipped draft → ready at 2026-09-18T20:48Z via
⭐ Why
|
…ED, not a census takedown `tenant-audit-census.mjs` stores a declared type's text whitespace-collapsed and re-parses it as a synthetic alias to read the engine-door rule off it. A type literal may separate its members by a NEWLINE alone -- legal TypeScript -- and the collapse turns that separator into nothing, so the synthesis does not parse. Through `parseSourceFile` that did not fail the SITE: it ended the process, and every other site in the corpus lost its verdict with it. The synthesis now goes through `parseDerivedText` (the returnable door landed by PR #19112), and the verdict is acted on rather than swallowed: - `readTypeTextDoor` reads the door rule and reports the round trip separately, taking the origin tree the text was derived from -- so an unreadable SOURCE still cannot reach the returnable door. - `nonEngineReason` reads it once and classifies such a site on a new declared arm, `type-text-not-round-trippable`, placed in `UNDEFENDED_REASONS`. - the run prints the located parse verdict against the SITE and exits non-zero; `check-tenant-audit-census.mjs` refuses the same class through the one shared spelling, `notRoundTrippableSites`, because CI reads the gate and never the generator. The exchange is a loud process exit for a loud per-site refusal, never for a quiet subtraction. The corpus door is untouched: sources are still read through `parseSourceFile`, so a source that does not parse still ends the run. Census over the live corpus, before and after on `215840f4353`: exit 0, 576 sources, output byte-identical. Claude-Session: https://claude.ai/code/session_01LvwGppdonww4zGLWZo5rho Co-authored-by: Claude <noreply@anthropic.com>
…ED, not a census takedown (objectstack-ai#19290) Fixes objectstack-ai#19077 Clause-②: no Item **2** of objectstack-ai#19077 — the census-side application. Item 1 (`parseDerivedText`, the returnable door) landed as PR objectstack-ai#19112, squash `956e0107a`; nothing here re-does it. Base: `origin/main` = `215840f4353`, fetched 2026-09-20T09:07Z. Every reading below was taken on that base in a dedicated worktree, never on the shared checkout. ## The defect, and what it did `scripts/tenant-audit-census.mjs` stores a declared type's text whitespace-collapsed and re-parses it as a synthetic alias (`type CensusReceiver = ...;`) to read the engine-door rule off it. A type literal may separate its members by a **newline alone** — legal TypeScript — and the collapse turns that separator into nothing, so the synthesis does not parse. Through `parseSourceFile` that did not fail the SITE: it ended the process, and every other site in the corpus lost its verdict with it, under a refusal naming `census-receiver-type.ts`, a file that does not exist in the tree. ## The repair — the four points of the minimal hunk, and where each landed | point | landed | |---|---| | ① the door rule takes the origin `sf` the type text came from | `readTypeTextDoor(typeText, origin)`; `resolveReceiver` hands its own `sf` at all four `inlineEngineDoorOrOther` call sites | | ② the synthetic alias goes through `parseDerivedText` | `readTypeTextDoor`, one call site, the only synthesis in the file | | ③ on failure the report is printed against the SITE and the door reads `false` | the located verdict rides on the site as `derivedFailure`; `main()` prints it under a per-site `::error::` line | | ④ a **declared** `NON_ENGINE_REASONS` arm, placed in `UNDEFENDED_REASONS` | `type-text-not-round-trippable`, in both artefacts under ENFORCEMENT | ## ⛔ Point ④ and the floor — why this PR also edits the gate The non-negotiable is that no importer ends up exiting 0 where it exits non-zero today. Measured, rather than assumed: `lint.yml:2007-2008` invokes `scripts/check-tenant-audit-census.mjs` and **never the generator**, and that gate's own docblock says why (`censusRefusals` exists because `runCensus`'s findings were read by nothing on the way to a CI verdict). So on a corpus holding such a receiver: - **today** — `runCensus` calls `parseSourceFile`, which exits 3; the gate dies with it, CI red. - **with the repair and no gate arm** — the site would be declared in the artefacts, the author would regenerate, and the gate would go **green**. That is the loud process exit traded for a quiet subtraction: the same floor drop in a different costume. - **as landed** — the site is classified, declared in both artefacts, printed against its own file and line with the parse verdict under it, **and refused** by `censusRefusals` through `notRoundTrippableSites`, the one spelling the generator and the gate share. `main()` counts it in its exit code too, for the local run. ⇒ the exchange is a process-wide takedown for a located, per-site refusal. Nothing that exits non-zero today exits 0 after this, and no site leaves the population in silence. ## Acceptance — triage's two inputs, as a pair Both run in `tenant-audit-census.mjs`'s own self-test, which CI drives through `node scripts/check-tenant-audit-census.mjs --self-test` (`lint.yml:2007`). Ten new cases; the file's self-test goes 49 to 59, the gate's 24 to 27, and the gate's `census refusals` battery floor is raised 5 to 7 so the new cases cannot stop running unnoticed. 1. **A newline-separated inline type literal is CLASSIFIED.** The case runs the REAL round trip — a source is parsed, `declaredTypesIn` collapses the declared type exactly as the census does, and the resolver reads the door off the stored text — and reads `other/type-text-not-round-trippable`. **Lit control:** the semicolon spelling of the SAME literal is still PLACED as `engine/inline type literal stating an engine door`, so the first case is not a door that rejects everything. Two more: the arm is in `UNDEFENDED_REASONS` (control: a defensible arm is not), and the failure is carried as located data naming the SOURCE it was derived from. 2. **A genuinely unparseable input is still REFUSED.** A garbage type text lands on the same declared arm and no door is read off it, so it is never placed. The corpus door is untouched: sources are still read through `parseSourceFile`, whose process-exit refusal is pinned by `ts-parse.mjs --self-test` (landed with item 1) and enforced for every `scripts/**` caller by `check:parse-guard` (exit 0 here). **Boundary, pinned:** the verb gate runs first, so a newline-separated literal naming no write verb is never synthesised and cannot reach the new arm — the repair's reach is exactly the defect's reach. ## The census over the live corpus, before and after `node scripts/tenant-audit-census.mjs`, base `215840f4353`: - **before** (2026-09-20T09:08Z): `exit 0`, **576 sources**, 227 write call sites. - **after** (2026-09-20T09:20Z): `exit 0`, **576 sources** — output **byte-identical** (`diff` exits 0), one undefended subtraction, reason `type-not-in-corpus`, door-shaped 0.⚠️ The previous round measured 573 sources; `main` moved between the rounds. The escalation trigger did **not** fire: exit 0 means no receiver of this shape exists in the tree, so the card stays latent and the grading stays triage's. ## Reverse verification — three ablations, each restored byte-identical Driven through `scripts/ablation-replace.mjs`, which proves the mutation landed on disk (anchor count fell, replacement count rose, blob hash changed) and proves each restore (`blob == HEAD blob`, `git diff HEAD` empty). 1. **The door reverts to `parseSourceFile`** — the self-test does not fail, it **dies**: `exit 3`, printing the card's own refusal for `census-receiver-type.ts` with `1:78 ';' expected`. That is the defect, reproduced from the acceptance case. 2. **The arm is removed from `UNDEFENDED_REASONS`** (point ④'s own ablation) — RED, the declaration pin fails, 1 of 59. 3. **The gate's refusal loop is emptied** — RED, 2 of 27 gate cases fail, which is the "quiet subtraction" costume failing to pass.⚠️ Reported rather than quietly retried: ablation 2's first attempt was **refused** by the helper because the replacement text was a substring of the anchor, so the on-disk count could not rise. It restored and never ran the command; the rerun used a dropping anchor. ## Gates `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`, derived from this tree at `ce1b5304101` (4 paths vs merge base `215840f43`, three-dot): **64 families, all exit 0**, each captured BEFORE any pipe, reconciled with `--ran`: `64 derived, 64 run, 0 NOT-MEASURED, 0 UNRUN` — a DERIVED zero, every family carrying a code. Five of the 64 first answered `PREREQUISITE NOT MET` (exit 3 / exit 1: not findings, nothing measured) and were re-run after building what they read — `pnpm --filter '@objectstack/lint...' build`, then `pnpm --filter '@objectstack/client-react...' --filter '@objectstack/client...' build`, both through `scripts/pm/os-verify-lock.sh` (`VERDICT command-exit 0`). All five then exit 0. Outside that total by the tool's own accounting: 52 artifact-roster families, 11 declared wide-population families, 14 pending-changeset families, 2 path-scheduled CI jobs. `pnpm lint` repo-wide is CI's run. The narrowing here is DECLARED and carries all three readings: (1) the reachable population comes from eslint's own config text — `eslint.config.mjs:328` records that this repo runs one config that never enables type-aware linting (no `parserOptions.project`, no typed rules) for ANY file, measured there with a positive control; (2) the targeted count is read from `--format json`: 4 files, 0 errors, exit 0 — the two markdown artefacts report "File ignored because no matching configuration was supplied", i.e. eslint does not lint them at all; (3) invariance — with no type-aware linting anywhere, a 4-file diff cannot move the verdict on a file it does not contain. Taken at `ce1b5304101`. ## Changeset `skip-changeset`, on three readings: (1) the root package is `private: true`; (2) all **70** non-private workspace packages declare a `files[]` (lit control) and **0** of them have an entry that climbs out of its own package directory; (3) none of the four changed paths is under any package directory. ⇒ root `scripts/**`, `content/docs/**` and `docs/audits/**` cannot be in any published payload. `Clause-②: no` follows: this moves a build-time gate's failure convention, not `packages/spec`'s accept set or any public surface. ##⚠️ Declared deviation — three files outside the dispatched file surface The dispatch declared `scripts/tenant-audit-census.mjs` and new tests under `scripts/`. This PR also touches: - `scripts/check-tenant-audit-census.mjs` — the gate-side half of point ④, argued above. Without it the repair lowers the floor, which the dispatch forbids outright. - `content/docs/permissions/tenant-audit-census.mdx` and `docs/audits/2026-08-tenant-audit-write-call-sites.counts.md` — GENERATED regions, rewritten by `node scripts/tenant-audit-census.mjs --write`, which is the only legal way to edit them and is what `check-tenant-audit-census.mjs` demands. Point ④ puts the arm in the enforced undefended table, so the table's prose had to name the third cause or the artefact would explain a row it does not cover. Measured before touching them, 2026-09-20T09:1xZ: **22 open PRs, 623 file rows** (PR objectstack-ai#17076 paged past the 100-row cap so the negative is not a truncation artefact) — **no open PR holds any of the four paths**, nor `scripts/ts-parse.mjs`. Firing control: the map does carry `scripts/` rows (objectstack-ai#19024, objectstack-ai#18414, objectstack-ai#19153, objectstack-ai#18723). Dark control: `scripts/zzz-no-such-file.mjs` has no row.⚠️ Also in the regenerated artefacts and NOT caused by this change: the unenforced corpus-scale block re-stamps its date and sha and moves 573 to 576 tracked sources, because `main` moved since it was last written. ## Acceptance notes — noted, not filed - Triage's **route ①** (stop collapsing the declared type text at storage time) stays available and would let such a receiver be PLACED rather than declared-undefended. It is a much wider change — the collapsed text is also the display text, the index-lookup input and the ledger key — and it is not what the sequenced hunk asked for. Successor: any later card that revisits the census's stored type text. Not a defect in the tree. - The same defect class at the transpile door (`transpileChecked` on a lifted snippet) was already filed as its own card by the `domain:spec#3` seat, with the boundary carried over. ⛔ No second card from here. - No path literal was added to `scripts/tenant-audit-census.mjs` or `ts-parse.mjs`; `check:watch-hint-literal` exits 0. Successor: none needed. --- _Generated by [Claude Code](https://claude.ai/code/session_01LvwGppdonww4zGLWZo5rho)_ Co-authored-by: Claude <noreply@anthropic.com>
Refs #19077 (item 1 — the returnable derived-parse door; the census-side application is item 2, sequenced after PR #19073)
Seat: domain:spec#3,session_019srGWGCBBCBHqcDoRZpQRh— the closing keyword was changed to a reference, and that is the only change to this body. The dispatched dev reportedstatus: reworkdeliberately and per its dispatch word: acceptance input 1's census-level half ("that receiver is CLASSIFIED") cannot be delivered from this surface, because it needsscripts/tenant-audit-census.mjs, which this seat serialised behind open PR #19073. ⇒ this PR makes the repair available and provable; it does not apply it. AFixeskeyword would have closed card #19077 on merge while the defect it describes was still live onmain— the charter's partial-landing rule (SKILL.md: 部分落地 PR 带Refs #N (item k),⛔ 不Fixes) is exactly this case. ⛔ Nothing about the diff, its tests or its declarations was touched by this edit.Clause-②: no— measured, not assumed; the three readings are under "Clause ② and the changeset" below.[...]where the source has anglebrackets (
Promise[void]isPromiseofvoidin the real fixture). The body sanitizereats tag-shaped fragments, backticked ones included, so the real spelling cannot be written
here. The fixtures in
scripts/ts-parse.mjscarry the real characters.What was wrong
scripts/ts-parse.mjsanswers one question — "could I read this tree?" — and answers it byending the process. That is right for a tree the gate's own author controls, and the module's
header argues it at length: a throw is swallowable, and
try { parse } catch { continue }isalready written in this repo.
tenant-audit-census.mjswas asking a different question through that same door. Itreads a receiver's declared type out of a source that parsed, stores the type's text
whitespace-collapsed (
:459,getText(sf).replace(/\s+/g, ' ')), and later re-parses thestored text as a synthetic type alias (
:723,parseSourceFile('census-receiver-type.ts', ...)) to ask whether it declares a write door.A type literal may separate its members by a newline alone. The collapse turns that
separator into nothing, the synthetic alias does not parse, and the census takes
EXIT_UNPARSEABLEfor the whole run — with a refusal naming a file that does not exist inthe tree.
Reproduced on this branch's base (
origin/main@07c6f822e) before any edit:And the shape itself is legal TypeScript — so the collapse is the defect, not the receiver:
.replace(/\s+/g, ' ')';' expected.The refusal's own reasoning is false for this call: "a file the gate could not read, reported
as a file with nothing to report". The gate read the file. What did not round-trip is the
gate's own re-serialisation of a fragment of it — a fact about the synthesiser, never about
the corpus.
The repair
parseDerivedText(origin, fileName, text, scriptKind)answers the synthesis question andhands the verdict back, so one unanswerable site costs one site instead of every site.
The floor is held by construction rather than by review. The door requires an
origin—a
ts.SourceFilethis module has already certified — and the only two doors that certify one(
parseSourceFile,createProgramChecked) exit on a source that does not parse. So asource the gate could not read cannot reach the returnable door: it hits the refusal
first. An uncertified origin is itself a refusal (
EXIT_DERIVED_MISUSE, exit 4), an exit andnot a throw for the reason the module already gives.
Two deliberate divergences from
packages/lint/src/checked-parse.ts, the repo's existingruling on this same axis (it reports rather than refuses "because a
scripts/**gate auditsa tree its own author controls"), both in the strict direction:
sourceFile: null), where the sibling always returns therecovered one. There is no recovered wreckage to walk and no reading of the result that says
"nothing to report". A caller that forgets to branch gets a TypeError — loud, non-zero.
parseCensus()gainsderivedandrejections. A refusal ended the run, so every later source went unread andthe numerator is short by an unknown amount; a rejection left the run going. One combined
total would make the report whose job is to say how much was measured say it wrong.
⭐ Deliverable 1 — the dependency census: who depends on the fact that it EXITS
Instrument: TypeScript AST over every git-tracked
.mjs/.cjs/.js/.mts/.cts/.ts/.tsxfile in the repo (6873 scanned), resolving each module specifier to an absolute path and
counting call expressions per imported binding. ⛔ No
head -Nanywhere in the pipeline.Measured at
b09c8f550.ts-parseat allscripts/ts-parse.mjs(import / re-export / dynamic)Lit control, as triage required.
scripts/tenant-audit-census.mjsprovably callsparseSourceFile— it is the subject of this card — and the instrument counts it, atparseSourceFile x12. Verified independently:grep -n parseSourceFileon that file showsone import at
:177and 12 call sites (:319 :363 :416 :723 :860 :1189 :1686 :1738 :1757 :1778 :1827 :1897). The two numbers agree.A second, discriminating control. The instrument tells the three doors apart, which a
text match on
parseSourceFilecannot:check-published-readme-exports.mjsis counted atcreateProgramChecked x1and callsparseSourceFilezero times;platform-object-tenancy-census.mjsattranspileChecked x1, likewise zero.⭐ The seat's 35 is 34.
scripts/check-parse-guard.mjsmatchesfrom './ts-parse.mjs'as text — at:555inside its own failure message, and at:694 :697 :733inside self-test fixture strings — and imports nothing from the module.A
git grep -lon the specifier counts it; an AST edge does not. It is row D, not row B.Reachable radius, and what is outside it. The instrument sees static
import/export ... from, namespace imports, and dynamicimport()with a literalspecifier, over git-tracked source files, one hop, resolved by path. ⛔ Outside it:
a specifier built at runtime from a variable; a
createRequire/requireedge; an edgethrough
node_modules; untracked files; and any second-order importer reached through are-exporter — that last one measured to be vacuous here, because the re-exporter count is
0. Also outside it:
scripts/pm/dispatch-gates.mjsdeliberately does not fanwatch-hints out to the importers of the five shared utilities (
ts-parse.mjsis one), sothe derived gate list for this diff does not name those 34 families, by that file's own
measured decision. That is why the floor below is argued mechanically and not from the
gate list.
Acceptance — triage's two inputs, both pinned
Both live in
scripts/ts-parse.mjs's own--self-test, which CI runs(
.github/workflows/lint.yml:507). 30 cases before, 48 after; five new batteriesdeclaring 18 cases, and the roster floor rose 12 to 17 so deleting one reds.
① the newline-separated inline type literal. The fixtures are the census's own round
trip, spelled the way the census spells it — a receiver authored across lines, put through
.replace(/\s+/g, ' '), wrapped in the alias:723builds:parseSourceFile(the refusal is untouched);parseDerivedTextreturns a located verdict and the runcontinues (
status 0,tree null,failure.count 1,failure.line 1);same door and returns a usable tree;
indistinguishable from a door that rejects everything.
② a genuinely unparseable input is still REFUSED. Merge-conflict markers and a truncated
body, each through both doors:
parseSourceFilestill ends the run, andparseDerivedTexthands back no tree with
failure.countat least 1. Triage's words are the reason this legexists — without it "fixed" and "the check was switched off" are the same green.
⛔ Floor — nothing exits 0 where it exited non-zero
Every deleted line in the whole diff, all 9 of them —
git diff 07c6f822e..b09c8f550,439 insertions, 9 deletions:
⇒ not one of them is in a refusal path. Every line from each door's diagnostics read
through its
process.exitis byte-identical:The only change inside an existing door is one
vouchedSources.add(...)on each successpath — after the refusal has already been decided.
The covering mechanism for all 34, rather than 34 readings. The new door has zero
call sites anywhere in the tree outside the module that declares it
(
git grepforparseDerivedText,EXIT_DERIVED_MISUSE,derivedMisuseReport,derivedFailureReportover all tracked files: 0 files outsidescripts/ts-parse.mjs). Noimporter reaches it, no refusal path changed ⇒ no importer's behaviour on an unparseable
source can have moved.
Direct per-door reading at
b09c8f550, each fed a truncated body:parseSourceFilecreateProgramCheckedtranspileCheckedThe census's live behaviour is unmoved.
node scripts/tenant-audit-census.mjsover thelive corpus: exit 0, 573 sources scanned, identical counts — the repair adds a capability
and adopts it nowhere (see Acceptance notes).
The new cases can fail — three ablations, each on a different limb
Every mutation was confirmed on disk before the reading (anchor count to 0, injected marker
counted), driven through
scripts/ablation-replace.mjs, and every restore verifiedbyte-identical (
blob == HEAD blob be83f4e875df,git diff HEADempty).process.exit(EXIT_UNPARSEABLE)3 to 4vouchedSources.has(origin)1 to 0nullsourceFile: null,1 to 0Checks
scripts/pm/dispatch-gates.mjsderived the families from the changeset itself (1 path vsmerge base
07c6f822e, three-dot). 29 derived, 29 run, 0 NOT-MEASURED, 0 UNRUN, and thereconciliation's zero is DERIVED — every one of the 29 carries a recorded exit code and none
of them is 3. Every family exit 0, including
check:parse-guard,check:nul-bytes,check:watch-hint-literal,check:scripts-symbol-anchors,check:self-test-wired,check:self-test-workflow-commands,check:cross-package-test-inputsandcheck:pm-dispatch-gates.node scripts/ts-parse.mjs --self-testexit 0, 48 cases.Outside the derived total and therefore CI's, as that tool prints: the 53 artifact-roster
families, the 11 declared wide-population families, the 14 pending-changeset families and
the 1 path-scheduled job.
pnpm lintwas narrowed, and the narrowing is a measurement. The repo-wideeslint . --no-inline-configrun is CI's. Three readings, all three required:① the reachable population comes from eslint's own config text, not a guess —
eslint.config.mjs:328records that "this repo runs oneeslint.config.mjs, which neverenables type-aware linting (no
parserOptions.project, no typed@typescript-eslintrules)for ANY file", measured there with a positive control; ② the targeted run's file count is
read from
--format json: 1 file, 0 errors, 0 warnings, exit 0; ③ invariance — withno type-aware linting enabled anywhere, a one-file diff cannot move the verdict on any file
it does not contain. The targeted run is at the final commit,
b09c8f550.Clause ② and the changeset
skip-changeset, and the reading behind it. Nothing published moves.① the root package is
private: true; ② of the 70 non-private workspace packages,0 have a
files[]entry that climbs out of its own package directory, so rootscripts/**cannot be in any published payload; ③ the new symbols(
parseDerivedText,EXIT_DERIVED_MISUSE,derivedFailureReport,derivedMisuseReport)appear in 0 files under
packages/,apps/orexamples/— with the same grep lit asa control, finding 48 files for a symbol that is published
(
MetadataTypeSchema, reached bypackages/spec'ssrc/**/*.zod.tsentry).Clause-②: no, on the same three readings plus two more: the accept set of all threerefusing doors is byte-identical (above), and the new door has zero adopters, so nothing in
this tree accepts anything it previously refused. ⭐ The judgement left for the seat, stated
rather than buried: this does add four exports to a module's surface — that surface is
measured unpublished, which is why the answer is
no, and the seat can overrule withoutre-deriving anything.
Acceptance notes
scripts/tenant-audit-census.mjsis serialised behind PR fix(scripts): apply the tenant-audit census's engine-door rule to inline type literals (225 -> 227) #19073 (card [finding] tenant-audit census applies its engine-door rule to NAMED declarations only, so two real engine writes with inline type-literal receivers are subtracted from the certified population — placing them moves it 225 to 227 and rewrites both artefacts #18944) and thedispatch holds it: if the sound repair also has to edit the store/re-parse pair, stop and
report the minimal hunk rather than edit it. It does. The minimal hunk, for the
sequenced PR: in
typeTextDeclaresEngineDoor, take the already-parsedsfof the sourcethe type text came from as the origin, call
parseDerivedText(origin, 'census-receiver-type.ts', ...)instead ofparseSourceFile, and onfailureprintfailure.reportagainst the site and returnfalse; then add oneNON_ENGINE_REASONSarm (suggested:
type-text-not-round-trippable) and put it inUNDEFENDED_REASONS, sothe site is declared in both artefacts instead of dropping out of the population
silently. Until that lands, the defect on
mainis unchanged — this PR makes the repairavailable and provable, it does not apply it.
scripts/tenant-audit-census.mjs,content/docs/permissions/tenant-audit-census.mdxanddocs/audits/2026-08-tenant-audit-write-call-sites.counts.md, and it does not changethe collapse at
:459or the re-parse at:723. What it does is route four morekind: 'other'returns throughinlineEngineDoorOrOther, which callstypeTextDeclaresEngineDoor— i.e. it enlarges the set of type texts that reach thedefect without creating it. The seat's reading is confirmed; the finding is not
discharged.
:485(parseSourceFile) gets the derivedcounterpart.
:575(createProgramChecked) needs none: a Program is built from rootpaths on disk, and its one caller passes real paths — and its source files are
certified, so a Program-based gate can use the new door for text it synthesises.
:617(
transpileChecked) is not answered here; see the next note for why, and it is afinding rather than silence.
transpileChecked's three callers are alreadyin the same derived-text position as the census.
check-objectql-double-limit.mjs:535andcheck-where-matcher-conformance.mjs:543bothtranspile a lifted snippet, named
${candidate.file}#L${candidate.line}.lifted.ts—synthesised text, not a file. A legal source whose lifted span is not independently
parseable would take either gate down process-wide, exactly as the census. Not fixed here:
it is a different call site with a different acceptance input (that span), and adding a
second new door while
check-parse-guard.mjs— the gate that polices which doorsscripts/**may use — is out of this card's surface is worse than adding one.(
platform-object-tenancy-census.mjs:226transpiles a real file's source, so therefusal is correct there.)
node scripts/tenant-audit-census.mjsover the live corpus exits 0 — no such receiver existsin the tree today, so the card stays latent. ⭐ The zero is lit on the same subject:
the run reached
typeTextDeclaresEngineDoorand that function returnedtruefor two realsites (
plugin-auth/src/audience-gate-test-support.ts:72,plugin-auth/src/sso-client-secret.ts:303, both reported as door-shaped subtractions), sothe instrument was not silently skipping the code path. ⛔ Grading is triage's — this
changes no priority.
scripts/ts-parse.mjs, on purpose.scripts/pm/dispatch-gates.mjsrecords that the five shared utilities it follows declareno path literals at all, and it follows them one hop; a literal here would fan a watch-hint
into every one of the 34 importer families — the precision loss that file explicitly
refuses. So the door's safety is the origin-vouch rather than a roster with a scan.
check:watch-hint-literalexits 0.🤖 Generated with Claude Code
https://claude.ai/code/session_019srGWGCBBCBHqcDoRZpQRh
Generated by Claude Code
Generated by Claude Code