Repository navigation
fix(scripts): make the tenant-audit census say when it could not place a receiver's type - #18943
Merged
os-elon-musk merged 2 commits intoSep 18, 2026
Merged
Conversation
…e a receiver's type `buildEngineTypeIndex` enumerates through `git ls-files`, so a receiver typed with an untracked, generated or dependency-owned declaration has no type the index can find. Such a site resolved to `kind: 'other'`, was subtracted from the certified population, and printed nothing -- indistinguishable from "read the declared type, it is a `Set`". The population could shrink at exit 0 and the part that shrank was invisible. The enumeration is unchanged and stays tracked-only, deliberately: a census whose verdict moved with the working tree would be measuring the working tree. What changes is that the subtraction now states the fact it rests on. - `corpusTypeNames()` collects every type NAME the tracked corpus declares, from the SAME `git ls-files` enumeration as the index, so the diagnostic can never see more of the tree than the index it reports on (6520 files, 0.6s). - `NON_ENGINE_REASONS`: a closed set of seven. Five name a fact that DEFENDS the subtraction (`node:` builtin, locally constructed, language global, a type this corpus declares and the door rule rejected, an `UNTYPED_RECEIVERS` row); two admit the census could not place the type (`type-not-in-corpus`, `anonymous-type`). - The undefended ones are printed per site on every run, carried per site in `--json`, and counted under ENFORCEMENT in both artefacts, with the rows in the audit ledger. A type that leaves the index now lands in the diff by name. - `typeTextDeclaresEngineDoor()` reuses `memberIsEngineDoor`, so an inline receiver type that states a write door says so. Two sites on this tree do. No verdict, count or exit code moves: 225 sites, 146 subtracted, 0 unresolved, before and after. No subtraction is withdrawn -- it is declared. The self-test pins the criterion in BOTH directions: one receiver, one source text, index membership the only variable, reading `engine` in the index and a subtraction that NAMES the unplaceable type out of it (30 -> 44 cases). Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019srGWGCBBCBHqcDoRZpQRh
…nsus-unresolved-type-diagnostic
os-elon-musk
marked this pull request as ready for review
September 18, 2026 07:17
os-elon-musk
deleted the
claude/issue-18548-census-unresolved-type-diagnostic
branch
September 18, 2026 08:11
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 28, 2026
…he tree still shows (objectstack-ai#19075) Fixes objectstack-ai#19007 Clause-②: no Three same-cause prose statements were left behind when objectstack-ai#13585 loosened owner resolution. Two of them are replaced here with statements this tree still shows; the third is left alone, because it is the evidence rather than the defect. **Prose only — no behaviour change**, and ⛔ no `merge=os-regen` registration is added, ⛔ no `.gitattributes` row is touched. ## 1. `scripts/tenant-audit-census.mjs` — the reason emitted INTO the generated counts file Before (verbatim, as `renderCountsFile()` emitted it): ``` merge cleanly and WRONG. This file is deliberately NOT `merge=os-regen`: that driver resolves an artefact's `gen:`/`check:` scripts in `@objectstack/spec` only, and these are root-level tooling. The gate is the backstop — a wrongly ``` After: ``` merge cleanly and WRONG. This file is NOT `merge=os-regen`: no `.gitattributes` row names it, so `git check-attr merge` over it reads `unspecified`. Routing it would take a `REGEN_ARTIFACTS` row whose `gen:`/`check:` names exist in the manifest that row declares as owner, and no manifest declares such a pair for this census — the gate runs straight from the lint workflow. Root-level tooling is no obstacle by itself: the driver resolves those names in whichever manifest the row names, the root one included. The gate is the backstop — a wrongly ``` The FACT is kept (this file is not routed). The REASON is replaced, and every clause of the new one is measurable on this tree: | new clause | how it was measured | reading | | :--- | :--- | ---: | | no `.gitattributes` row names it | `grep -n tenant-audit .gitattributes` | 0 hits | | `git check-attr merge` reads `unspecified` | `git check-attr merge -- docs/audits/2026-08-tenant-audit-write-call-sites.counts.md` | `unspecified` | | a row needs `gen:`/`check:` in its owner's manifest | `reconcileScripts()` fails a row whose names are missing — fired below | red on demand | | no manifest declares such a pair for this census | every tracked `package.json`, `scripts` keys AND values | 0 of 601 entries in 83 manifests | | the gate runs straight from the lint workflow | `.github/workflows/lint.yml`, step "Tenant-audit census matches the tree" | `node scripts/check-tenant-audit-census.mjs` | | root-level tooling is no obstacle | `REGEN_ARTIFACTS` rows declaring `owner: ROOT_OWNER`, and what git says about them | 2 rows, both `merge: os-regen` | Fire control for the 0-hit row: the same instrument searching for `strictness-ledger` — the neighbouring `.counts.md` that IS registered — returns 2 hits (`gen:` and `check:` in `packages/spec`). Reachable radius: manifest `scripts` text. A known target outside it: a manifest script that reaches the census through an indirection that never spells its name (`"gen:x": "node scripts/wrapper.mjs"` where the wrapper spawns it) — this instrument reads manifests, it never follows a spawn. ## 2. `scripts/git-merge-regen.mjs` — "every row is spec-owned" Before (verbatim): ``` * `reconcileScripts` above is green on this tree for the same reason it was green * before objectstack-ai#13585: every row is spec-owned, so it exercises exactly one manifest and * would keep passing if the loosening were reverted. ``` After (verbatim, plus the paragraph break the replacement adds): ``` * `reconcileScripts` above no longer rests on a single manifest. Rows that declare * no owner default to `DEFAULT_OWNER`; rows that declare `ROOT_OWNER` send the * lookup to the root manifest instead (measured on this tree: 18 rows — 16 * defaulted, 2 root-owned, none naming `DEFAULT_OWNER` explicitly). The four * `gen:`/`check:` names those two root-owned rows carry exist ONLY in the root * manifest, so reverting objectstack-ai#13585's loosening would now turn that reconciliation RED * rather than leave it green. * * What a live tree still cannot show is the RULE. Which manifests get exercised is * a property of whatever the table happens to hold, and a table that fell back to a * single owner would stop touching the root path with nothing saying so. ``` What the paragraph exists to explain — why the pinned owner-resolution cases are needed even though the live tree is green — is kept, and now rests on something true: the composition that exercises the root path is a property of today's table, not of the rule. ## 3. `scripts/git-merge-regen.mjs:769-777` — deliberately NOT touched That block quotes objectstack-ai#13585 correctly. It is the evidence the two statements above drifted away from, and the diff contains zero lines from it. ## The registry re-count, taken from the registry's own data structure The card reported `4 ROOT_OWNER + 19 named + 41 default = 64`. Re-counted by importing `scripts/regen-artifacts.mjs` and bucketing with the module's own `ownerOf(entry) = entry.owner ?? DEFAULT_OWNER`, at merge base `0ec81857a`: | table | rows | `owner: ROOT_OWNER` | named package owner | no owner, defaults to `DEFAULT_OWNER` | | :--- | ---: | ---: | ---: | ---: | | `REGEN_ARTIFACTS` (registered; what `reconcileScripts` iterates) | 18 | 2 | 0 | 16 | | `NOT_DRIVER_MANAGED` (deliberately not registered) | 33 | 2 | 19 | 12 | | both tables | 51 | 4 | 19 | 28 | Each row reconciles: 2+0+16 = 18, 2+19+12 = 33, 4+19+28 = 51. Three consequences: 1. The card's `4 + 19` is a BOTH-TABLES count. The registration table declares `ROOT_OWNER` twice, and both of those rows really are routed (`content/docs/permissions/system-context.mdx` and `scripts/platform-object-tenancy-census.json`, each `merge: os-regen`). The card's other two `ROOT_OWNER` rows are `NOT_DRIVER_MANAGED` entries — reading `unspecified` is those rows working as recorded, not a failure to route. 2. `41`, and the `64` total it implies, reproduce under NEITHER instrument: structurally the defaulted bucket is 16 (registration table) or 28 (both tables), against totals of 18 and 51. 3. `reconcileScripts` itself prints the refutation of "exercises exactly one manifest": `all 36 gen:/check: names resolve in their declared owner (@objectstack/spec (packages/spec/package.json), @objectstack/spec-monorepo (package.json))` — 36 = 18 rows x 2 names, across TWO manifests. ## Evidence **Behaviour leg — the generated artefact changes in the prose and nowhere else.** `node scripts/tenant-audit-census.mjs --write`, then both the committed and the regenerated counts file put through the gate's own `maskUnenforced()` (the mask `scripts/check-tenant-audit-census.mjs` applies to both sides of its byte comparison): the masked diff is exactly the replaced paragraph, 3 lines out, 7 lines in, nothing else. The raw diff carries one more line, the `Measured on ... at ...` marker, in the counts file and in the census page. That marker is **not** attributable to this edit: a regeneration on the PRISTINE tree, with zero source edits, already moved exactly those two lines and nothing else. The gate masks its value deliberately (`UNENFORCED_MEASURED_AT`: "The date and the ref are deliberately NOT compared to anything"), its self-test pins "a stale measurement date passes", and the last commit to touch these artefacts (objectstack-ai#18943) carried the same marker bump. Both artefacts are committed as the generator wrote them. **`reconcileScripts` leg, with a fire control.** The card said it had not been measured whether that reconciliation is still green under today's owner distribution. - `node scripts/git-merge-regen.mjs --self-test` → exit 0, including `✓ all 36 gen:/check: names resolve in their declared owner`. Its CI spelling `pnpm check:merge-driver` → exit 0. - Fire control (a green that can go red, and proof the ROOT manifest is really read): through `scripts/ablation-replace.mjs`, the root manifest's `gen:system-context-census` key was renamed. The anchor hit once, the blob moved (`57ab936beaf0` to `1074c54635`), and the self-test went red naming the row: `content/docs/permissions/system-context.mdx → gen:system-context-census [owner @objectstack/spec-monorepo, package.json]`. A spec-only resolution could not produce that failure — which is the measured form of "reverting the loosening would now turn it red". - Restored and proven restored: blob back to `57ab936beaf0` = the HEAD blob, `git diff HEAD` empty, `git status --porcelain` empty. **Gates.** `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 64 families from this tree; all 64 were run with the exit code captured before any pipe, and `--ran` reconciles: `64 derived, 64 run, 0 NOT-MEASURED, 0 UNRUN`, every one exit 0. Five of them first exited on a PREREQUISITE NOT MET (unbuilt `@objectstack/lint`, `@objectstack/formula`, `@objectstack/client-react`, and `gen:schema`); the closures were built and all five then passed — none was a finding. `pnpm lint` over the whole repo: exit 0 in 1m27s. `pnpm check:nul-bytes`: exit 0, plus a direct control-character scan of both edited files: 0 hits. **Changeset.** Measured rather than assumed: each of the 4 changed paths against every tracked manifest's `files[]`. The root manifest is `private: true`, and all four paths lie outside every publishable package directory, so nothing already published moves — hence `skip-changeset`. Fire control: the same instrument scores `packages/spec/src/index.ts` as inside a published surface. ## Acceptance notes Observed while working, out of scope here, not filed: - The docblock this PR edits sits immediately above ANOTHER docblock, so it documents no declaration; the pinned cases it describes (`reconcileOwnership`) are about 330 lines below. `pnpm check:scripts-symbol-anchors` is green on it, so the repo's own instrument does not read this as a violation. Successor: the next PR editing these self-test blocks. - The `NOT_DRIVER_MANAGED` disposition for `docs/audits/**` explains itself in terms of hand-written ledgers and names the strictness `.counts.md` as "the ONE exception", while a second generated `.counts.md` — this census's — sits under the same prefix. Whether that one ought to route is the question objectstack-ai#18960 deliberately left open, so this is that neighbour, not a new card. - No manifest script names this census, so its generator is invisible to the generator disposition reconciliation — a bound that reconciliation states about itself in its own docblock, and `pnpm check:ratchet-remedy-authority` (which owns that population) is green. --- _Generated by [Claude Code](https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3)_ Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 28, 2026
…ine type literals (225 -> 227) (objectstack-ai#19073) Fixes objectstack-ai#18944 Clause-②: no The census applied its engine-door rule to **named declarations only**. A write call site whose receiver type is an inline type literal had no name for the engine type index to be keyed on, so it was classified `kind: 'other'` and subtracted from the certified population — however plainly its own text stated an ObjectQL write door. The census already **printed** both such sites on every run and called the subtraction "probably WRONG"; it then took the subtraction anyway. A diagnostic that names a subtraction as wrong and still takes it is a deferral, not a report. ## JOB ONE — the crux, answered before the classifier was touched **Both sites PASS. This is a classification repair, not a live violation.** Measured with a probe that calls the census's own exported functions (`resolveReceiver`, `resolveObjectNameArg`, `declaredObjects`, `tenantContextOf`) on the **unmodified** module, i.e. before any edit to the classifier. Sites selected by symbol — file + verb + receiver text — never by the card's line numbers. | site (re-derived by symbol) | object | tenancy | context | elevated | violation? | |---|---|---|---|---|---| | `plugin-auth/src/audience-gate-test-support.ts` · `engine.insert` · re-derived at **:72** | `sys_invitation` | **enabled** | `options.context` | yes | **no** | | `plugin-auth/src/sso-client-secret.ts` · `e.update` · re-derived at **:303** | `sys_sso_provider` | **disabled** (declared opt-out) | `options.context` | yes | **no** | Controls, because a "no" needs one: - **corpus control (HITS)** — both files are in the census corpus on this tree (`collectSources().includes(...)` → `true` for each). A "no violation" from a file the instrument never reads would be void. - **predicate control (HITS)** — the same violation predicate (`tenancy === 'enabled' && carriesTenantContext === false`) lights on **9** other sites of 227, three named in the report (`auth-plugin.ts`, `scim-connection-service.ts`, `datasource-admin-plugin.ts`). The instrument can say "yes"; it said "no" here. - what would have changed the reading: either site dropping its `{ context: { isSystem: true } }` argument (→ `carries: false`), or `sys_invitation` declaring `tenancy.enabled: false`. ⇒ **The figure downstream cards cite does not move.** `9 / 225` becomes `9 / 227`: the numerator is unchanged and the denominator becomes truthful. The repair is to what this page can be read as having examined — ⛔ not to any count that anything cites as a violation. On triage's still-open escalation branch (「被减掉的两个写点里任一个在生产路径上执行过 ⇒ 升 p1」): still **not run**, and it still needs a runtime reading. What this PR adds is that the branch's safety premise is spent for these two sites either way — once placed, neither breaks the rule the population exists to enforce. One of them is nonetheless production-shaped (`migrateLegacySsoClientSecrets`, a boot-path migration), so the branch is reported as open rather than closed. ## The fix `inlineEngineDoorOrOther()` judges a declared type text one more time when no indexed **name** is found in it, by reading the same door rule off the text itself. It calls `typeTextDeclaresEngineDoor` → `memberIsEngineDoor` — the one function that already answers that question for a named declaration — so there is no second spelling of the rule to drift from. Applied at all four `resolveReceiver` returns that carry a declared type text (`fromEntry`, an `as` cast, a member access, a call's return type), not only at the two that happen to fire today. A named engine type still wins and still reports its own name; an inline literal with no **write** door is still a subtraction that says why. ⛔ Not a widening of the definition: it IS the definition, applied where it had only been reported. ## Before / after, with the ablation proof Same tree, same command (`node scripts/tenant-audit-census.mjs`): | | before | after | |---|---:|---:| | write call sites | 225 | **227** | | object name decidable | 149 | 151 | | tenancy enabled | 149 | 150 | | declares tenancy off | 0 | **1** | | threading a tenant context | 141 | 143 | | **provably none AND tenancy-enabled** | **9** | **9** | | options unreadable AND tenancy-enabled | 32 | 32 | | decidably elevated | 106 | 108 | | non-engine calls subtracted | 146 | 144 | | subtractions the census could NOT defend | 3 | **1** | | …of those, type text states an engine door | 2 | **0** | Ablation, through `scripts/ablation-replace.mjs` so the write is verified against the disk rather than against an exit code (anchor: the helper's own guard, replaced with a dead one): ```text anchor hits 1 time(s), as declared anchor 1 -> 0, replacement 0 -> 1 blob 40921ef -> f76872fe8b03 (git hash-object, before -> under mutation) under mutation: census reads 225; anonymous-type 2; undefended 3; doorShaped 2 under mutation: check-tenant-audit-census.mjs EXIT=1 committed : | write call sites on the application surface | **227** | census : | write call sites on the application surface | **225** | restore: git checkout HEAD -- PATH (the explicit-HEAD form, absolute path) blob after restore 40921ef blob at HEAD 40921ef git diff HEAD empty ``` Predicted direction before running it: reverting the classifier turns the gate red. Observed: exactly that — and the census reverts to the pre-fix reading in every one of the eleven rows above. So the artefacts in this PR are held to the fixed classifier, and the file that was edited is the file that ran. ## Both artefacts AND the prose Both generated artefacts were rewritten by the census's own tooling (`node scripts/tenant-audit-census.mjs --write`), ⛔ never by hand. The page's **hand-written** prose was re-read against the new numbers. One claim was **falsified** by the placement and is corrected: > exactly two opt out (`sys_api_key`, `sys_sso_provider`), and no write call site > on this surface targets either. One now does — the legacy client-secret migration writes `sys_sso_provider` under an elevated context, which is why the generated `declares tenancy off` row reads **1** rather than 0. `sys_api_key` still has no write site on this surface (measured: no site in `--json` names it). Nine further hand-written figures moved and are updated (the gate holds every one of them: 23 prose figures). Two things stated rather than rewritten on a guess: - **"18 classifier cases, 6 of which red against the old reading"** — could not be verified. The generator's self-test reports 44 cases before this change and 49 after, and the gate's own reports 24; none is 18, so the claim counts some subset this PR cannot identify. It is left as written and its sentence is narrowed to the two over-claims it was written for, rather than being silently extended over the third instance this PR adds. - **the `(47%)` → `(48%)` parenthetical** is hand-written and unenforced (the gate requires digits-then-`%`, not a value). 108/227 = 47.58%, so floor gives 47 and nearest gives 48. Both surviving data points on the page (77% and 47%) round the same way under either rule, so the convention is undetermined from the page itself; nearest-integer was used and is flagged here for the seat. ## Verification Gate families derived from the **real** change set after the final commit: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived at `6c23858e5`, the final commit (3 paths vs merge base `0ec81857a`; the tool's own repo+commit declaration is in its stderr, and the list is byte-identical to the derivation taken before the last commit). **All 62 commands it printed were run on that HEAD, each exit code captured before any pipe: 62 run, 62 exit 0.** Five of them first refused as `PREREQUISITE NOT MET` in a fresh worktree — `@objectstack/spec`, `@objectstack/lint`, `@objectstack/formula`, then `@objectstack/client-react` were not built — so those packages' closures were built and the families were re-run rather than reported unmeasured. The per-command table is in the report comment on objectstack-ai#18944. NOT MEASURED, and named rather than counted as passes: the 14 families that apply only once a changeset exists (this PR declares none, see below), the 11 that declare a whole-root population no path narrows, the 2 that take a value from the workflow, the 2 path-scheduled CI jobs (`Test Core`, `Build Docs`) and the 52 artifact-roster families whose `silent` verdict is a fact about a list rather than about these paths. `scripts/check-tenant-audit-census.mjs` is this census's gate and it judges: ```text ✓ check-tenant-audit-census: OK -- 227 write call sites certified (151 decidable; 9 tenancy-enabled sites PROVABLY carry no tenant context, 32 more unreadable), 23 prose figures held to the census, every write call site placed and every `UNTYPED_RECEIVERS` row matched. ``` ⛔ No gate was weakened: no ratchet raised, no floor lowered, no ledger row deleted, no test skipped. The `doorShaped` diagnostic is **kept** — now 0 by construction — as the alarm for this hole reopening, and five new self-test cases pin the rule in both directions. ## Changeset No changeset, and the `skip-changeset` label is **owed but not applied by this PR** — label management was withheld by this dispatch. Measured, not assumed: no package directory contains any of the three files (30 manifests inspected; the repo-root manifest is `private: true`), so nothing any package's `files[]` ships can move. Positive control: `packages/spec` is published and its `files[]` is non-empty, so the instrument can say "shipped" when it is. An empty-frontmatter changeset is explicitly not an option here (the gate rejects newly added ones). ## Acceptance notes - **to file (class (a), pre-existing, ⛔ not fixed here)** — the census aborts with a `ts-parse` refusal (exit 3) on a receiver whose inline type literal separates its members by a **newline only**: `declaredTypesIn` collapses whitespace, so the separator is lost and the synthetic `type CensusReceiver = ...` re-parse fails with "';' expected". Reproduced directly; control lit (the semicolon-separated form of the same literal returns `true`). Live on `main` through the `anonymous-type` arm, which has called `typeTextDeclaresEngineDoor` on the same collapsed text since PR objectstack-ai#18943; this PR widens the same call to brace-free type texts that mention a write verb. No such receiver exists in the corpus today (573 sources, exit 0). - noted, not filed: `NON_ENGINE_REASONS` is exported and read by nothing — its keys are produced by `nonEngineReason` and its descriptions are documentation only, so its docblock's "closed set of seven" is closed by review, not by a gate. True today. Successor: the next author of this classifier. - noted, not filed: the module docblock's "There are 45 of them" for erased (`any`) receivers reads 44 on this tree (18 + 15 + 11), a pre-existing one-off drift in a comment, untouched here. --- _Generated by [Claude Code](https://claude.ai/code/session_019srGWGCBBCBHqcDoRZpQRh)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
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.
Fixes #18548
Clause-②: no
The defect, in one sentence
scripts/tenant-audit-census.mjsbuilds its engine type index fromgit ls-filesand nothing else, so a receiver typed with a declaration that enumeration cannot
see — untracked, generated, or a dependency's — has no type the index can find.
That site resolved to
kind: 'other', was subtracted from the certifiedpopulation, and printed nothing. It was spelled exactly like "I read the
declared type and it is a Set": same row, same exit 0, no diagnostic.
⛔ The enumeration is unchanged and stays tracked-only. Acceptance item 3 is
explicit that reading untracked files would make the repository's verdict a
function of somebody's working tree, and this PR does not do it. Acceptance
item 4 is what landed instead: the criterion is now the script's own, written
down at
CORPUS_TYPE_DECL, and pinned in both directions.Acceptance item 1 — the reading, re-measured on this tree
origin/main=02bdeaaf2. 129 write calls reachkind: 'other'through adeclared type or a constructed value; 17 more arrive through an
UNTYPED_RECEIVERSrow. 116 of the 146 subtractions are made because thereceiver's declared type is not in the engine type index — the path the card
asks about. Broken out by the fact each one now rests on:
constructed-locally— the census watchednew Xbeing constructedledger-row— anUNTYPED_RECEIVERSrow says what the receiver iscorpus-type— a type THIS corpus declares; the door rule read it and said nobuiltin-import— the receiver is imported from anode:builtinplatform-type— a language global this corpus cannot declareanonymous-type— an inline type literal: no name for the index to be keyed ontype-not-in-corpus— no declaration of that name exists in the tracked corpus⇒ 3 undefended subtractions on a clean tree, and 2 of them are provably
wrong: their own inline type text states an ObjectQL write door
(
insert/updatewith a first parameter namedobjectornameand typedstring), which is the census's own definition of an engine, applied to nameddeclarations only:
packages/plugins/plugin-auth/src/audience-gate-test-support.ts:72—engine.insert('sys_invitation', row, { context: { isSystem: true } })packages/plugins/plugin-auth/src/sso-client-secret.ts:303—e.update(SSO_PROVIDER_OBJECT, patch, { context: { isSystem: true } })Both are real engine writes, and both are subtracted from the population today.
⛔ This PR does not re-place them — that moves a security-relevant population
other cards cite, and acceptance item 2 says diagnostics before behaviour. It
makes them loud and names them for a follow-up card.
The firing control (the card's
git addexperiment, reproduced)packages/services/service-settings/src/settings-service.types.tsdeclaresSettingsEngine. Untracking it withgit rm --cachedleaves the bytes on diskuntouched (blob
90c0966before and after) and removes only the declaration theindex can see. Measured four times on this tree:
02bdeaaf2)type-not-in-corpus1 to 4The four names the diagnostic prints under the control are
SettingsEngine(2sites),
SettingsSecretStore(1) andHono(1). Pre-change, the same run wrotezero bytes to stderr in both columns — which is the defect the card describes,
measured rather than relayed.
What changed
corpusTypeNames()— every type NAME the tracked corpus declares, collectedfrom the same
git ls-filesenumeration as the index, so the diagnostic cannever see more of the tree than the index it reports on. 6520 files, 103 MB,
8154 names, 0.6 s. Zero names refuses.
NON_ENGINE_REASONS— a closed set of seven. Five name a fact that defendsthe subtraction; two admit the census could not place the receiver's type.
typeTextDeclaresEngineDoor()reusesmemberIsEngineDoor, so "an engine doorin its own text" is one rule with one implementation, not two spellings.
(
::warning::[receiver-type-not-placed]), carried per site in--json, andcounted under enforcement in both artefacts with the rows in the audit
ledger. A type that leaves the index now lands in the diff by name.
commit that adds a
Mapmoves it, which is the ambient churn with no safetycontent the maintainer's 2026-08-31 enforced/unenforced ruling exists to keep
out. The undefended rows move only when the population's own boundary moves, so
they belong on the enforced side by that ruling's own criterion.
unresolved receivers, generator exit 0 and gate exit 0 — before and after, on a
clean tree. No subtraction is withdrawn, no verdict is recomputed, no exit code
changed. The only committed-number movement is in the two unenforced rows a
regeneration always touches:
tracked non-test sources scanned570 to 573 andthe dated marker
2026-09-16 at 11daf7f69to2026-09-18 at 02bdeaaf2.on a clean tree, so refusing would red
mainfor findings nobody has ruled on,and a gate that reds on arrival is a gate that gets weakened.
Acceptance item 4 — the criterion, pinned in both directions
The census self-test goes 30 to 44 cases. The load-bearing pair is one receiver,
one source text, and index membership as the only variable:
engine/IProbeEngineother/type-not-in-corpus, namingIProbeEngineBoth arms are also pinned at the classifier: a name the corpus declares reads
corpus-type; the same name absent from the corpus readstype-not-in-corpus.The five defensible arms are pinned too, so the diagnostic cannot decay into
"everything is undefended" — a pile that flags all 146 hides the 3 that matter
exactly as effectively as flagging none.
Both ablations fired (
scripts/ablation-replace.mjs, on-disk blob evidence,restore verified against
HEAD):const unseen = ids.filter(...)toconst unseen = [](collapse the tracked-only distinction)door = truetodoor = falsein the door readerRestored: blob back to
b87aade77de1,git diff HEADempty, 44 of 44 green.Premises tested
223 / 146 to 148versus225 / 146from card [finding] tenant-audit 普查器的 ERROR 方向送不到 CI —— 门禁一次都没读 unledgered / staleLedgerRows #18211's dev and states it did not reproducethem. They reproduce on this tree, to the digit, with the file named above.
02bdeaaf2:buildEngineTypeIndexis at:215,trackedTsat:163, thels-filescall at
:164, andgrep -c "ls-files"is 1 — one mouth, no second path.trackedTs(appears 5 times: one definition and fourconsumers (
collectSources:171,buildEngineTypeIndex:217,widenIndexThroughAliases:313,declaredObjects:638). The card's controlproves there is no second way in; it does not show that four separate answers
are derived from that one way. This PR adds a fifth consumer on purpose, for
exactly that reason.
population itself moves 225 to 223 and
object name chosen at run timemoves76 to 74: the two sites leave
census.totalsentirely rather than beingcounted and mis-verdicted. Confirmed structurally too —
kind === 'other'increments
nonEngineCallsand never pushes intosites.origin/mainmerged before opening this PR: already up to date at02bdeaaf2, and every reading above is from this tree.Acceptance notes
anonymous-typesites above satisfy the census's own door rule and aresubtracted anyway, because the rule is keyed on named declarations while these
types are spelled inline. Re-placing them moves the certified population 225 to
227 and rewrites both artefacts plus the page's hand-written deviation prose —
a numbers change other cards cite, which wants its own card and its own review,
not a rider on a diagnostics card. They are now named on every run and counted
under enforcement, so they are no longer invisible. Carrier: the seat that
files it.
check-bash32-floor.mjs'slistPopulationshrinking on a partial checkout) is the nearest relative and isnot touched here. If it helps whoever takes it: the shared shape is
"an index-derived population shrinks silently", and the repair shape this PR
uses is make the subtraction state the fact it rests on, and enforce the
residue that states none — the population stays where it is and the silence is
what goes away. [regression] check:cross-package-test-inputs 的裁决取决于被 gitignore 的空目录 packages/spec/dist 存不存在 —— 构建过的工作树一律红,CI 绿只因那一步跑在构建之前(#18340 引入) #18348 is likewise untouched.
docs/audits/ledger and the page were regenerated withnode scripts/tenant-audit-census.mjs --write, the one repair path.维护者速读(草稿)
改了什么 —— tenant-audit 普查器在把一个写调用点判成「非引擎调用」并从被认证总体里减掉时,
现在必须说出它凭的是哪条事实。七个理由构成一个封闭集:五个是站得住的(
node:内建、就地new构造、语言全局、本仓声明且门规则读过后否掉的类型、台账行),两个是坦白「我没能把这个接收者的类型放到位」。⛔ 枚举口没动,仍然只从
git ls-files建 —— 这是设计,不是缺陷。为什么改 —— 原来这两类印出来完全同形:总体静默变小、exit 0、零诊断。本席实测:把一个声明
引擎接口的文件
git rm --cached(磁盘字节不变),改前的普查器给出 223 个站点(原本 225)、减法 148(原本 146),而 stderr 一个字节都没有。⇒ 这个仪器的裁决确实取决于你有没有先跑
git add,而它对此一言不发。风险与代价(含回滚) —— 本次没有任何判定、计数或退出码移动(225/146/0,改前改后一致),
没有撤回任何一次减法。代价是两份产物各多一节「无法辩护的减法」并进入强制比对:今后一个类型离开
索引会让门禁红着报出该类型的名字,而不是安静少算一个站点。回滚 = 单 commit revert 再跑一次
node scripts/tenant-audit-census.mjs --write,无数据迁移、无发布面。席位意见 ——
你要做的 —— 无需动作;这是仓内普查脚本,不发布任何东西(声明行在正文首部)。⚠️ 若关心那两个
「自己的类型文本就写着引擎门、却仍被减掉」的站点(
audience-gate-test-support.ts:72、sso-client-secret.ts:303),它们会独立立卡:把它们放回总体会把 225 推到 227 并改写页面里手写的偏差表,该由一次专门的评审决定。
🤖 Generated with Claude Code
https://claude.ai/code/session_019srGWGCBBCBHqcDoRZpQRh
Generated by Claude Code