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
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