Skip to content

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 into
mainfrom
claude/issue-18548-census-unresolved-type-diagnostic
Sep 18, 2026
Merged

os-elon-musk merged 2 commits into
mainfrom
claude/issue-18548-census-unresolved-type-diagnostic

Conversation

@os-elon-musk

Copy link
Copy Markdown
Collaborator

Fixes #18548
Clause-②: no

The defect, in one sentence

scripts/tenant-audit-census.mjs builds its engine type index from git ls-files
and 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 certified
population
, 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 reach kind: 'other' through a
declared type or a constructed value; 17 more arrive through an
UNTYPED_RECEIVERS row. 116 of the 146 subtractions are made because the
receiver'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:

why the subtraction stands sites
constructed-locally — the census watched new X being constructed 92
ledger-row — an UNTYPED_RECEIVERS row says what the receiver is 17
corpus-type — a type THIS corpus declares; the door rule read it and said no 15
builtin-import — the receiver is imported from a node: builtin 13
platform-type — a language global this corpus cannot declare 6
⚠️ anonymous-type — an inline type literal: no name for the index to be keyed on 2
⚠️ type-not-in-corpus — no declaration of that name exists in the tracked corpus 1

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 / update with a first parameter named object or name and typed
string), which is the census's own definition of an engine, applied to named
declarations 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 add experiment, reproduced)

packages/services/service-settings/src/settings-service.types.ts declares
SettingsEngine. Untracking it with git rm --cached leaves the bytes on disk
untouched (blob 90c0966 before and after) and removes only the declaration the
index can see. Measured four times on this tree:

census tracked declaration untracked
pre-change (02bdeaaf2) 225 sites / 146 subtracted, exit 0, stderr 0 bytes 223 sites / 148 subtracted, exit 0, stderr 0 bytes
this PR 225 / 146, exit 0, 3 named warnings 223 / 148, exit 0, 6 named warnings, type-not-in-corpus 1 to 4

The four names the diagnostic prints under the control are SettingsEngine (2
sites), SettingsSecretStore (1) and Hono (1). Pre-change, the same run wrote
zero 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, collected
    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, 103 MB,
    8154 names, 0.6 s. Zero names refuses.
  • NON_ENGINE_REASONS — a closed set of seven. Five name a fact that defends
    the subtraction; two admit the census could not place the receiver's type.
  • typeTextDeclaresEngineDoor() reuses memberIsEngineDoor, so "an engine door
    in its own text" is one rule with one implementation, not two spellings.
  • The undefended ones are printed per site on every run
    (::warning::[receiver-type-not-placed]), 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.
  • The reason breakdown deliberately stays OUT of the enforced artefacts: every
    commit that adds a Map moves it, which is the ambient churn with no safety
    content 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.

⚠️ Nothing got quieter and nothing moved. 225 sites, 146 subtracted, 0
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 scanned 570 to 573 and
the dated marker 2026-09-16 at 11daf7f69 to 2026-09-18 at 02bdeaaf2.

⚠️ The generator warns rather than refuses, deliberately: this class is not empty
on a clean tree, so refusing would red main for 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:

  • type IS in the index ⇒ engine/IProbeEngine
  • type is NOT in the index ⇒ other/type-not-in-corpus, naming IProbeEngine

Both arms are also pinned at the classifier: a name the corpus declares reads
corpus-type; the same name absent from the corpus reads type-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):

mutation result
const unseen = ids.filter(...) to const unseen = [] (collapse the tracked-only distinction) ✗ 2 of 44 — exactly the two criterion pins
door = true to door = false in the door reader ✗ 1 of 44 — the door pin

Restored: blob back to b87aade77de1, git diff HEAD empty, 44 of 44 green.

Premises tested

  1. The relayed headline reproduces exactly. The card relays 223 / 146 to 148 versus 225 / 146 from card [finding] tenant-audit 普查器的 ERROR 方向送不到 CI —— 门禁一次都没读 unledgered / staleLedgerRows #18211's dev and states it did not reproduce
    them. They reproduce on this tree, to the digit, with the file named above.
  2. The enumeration claim holds, with one correction. On 02bdeaaf2:
    buildEngineTypeIndex is at :215, trackedTs at :163, the ls-files
    call at :164, and grep -c "ls-files" is 1 — one mouth, no second path.
    ⚠️ But trackedTs( appears 5 times: one definition and four
    consumers (collectSources:171, buildEngineTypeIndex:217,
    widenIndexThroughAliases:313, declaredObjects:638). The card's control
    proves 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.
  3. It is subtraction, not an under-reported row. Under the control the
    population itself moves 225 to 223 and object name chosen at run time moves
    76 to 74: the two sites leave census.totals entirely rather than being
    counted and mis-verdicted. Confirmed structurally too — kind === 'other'
    increments nonEngineCalls and never pushes into sites.
  4. origin/main merged before opening this PR: already up to date at
    02bdeaaf2, and every reading above is from this tree.

Acceptance notes

维护者速读(草稿)

改了什么 —— 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

…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
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation labels Sep 18, 2026
@os-elon-musk os-elon-musk added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 18, 2026 — with Claude
@os-elon-musk
os-elon-musk marked this pull request as ready for review September 18, 2026 07:17
@os-elon-musk
os-elon-musk added this pull request to the merge queue Sep 18, 2026
Merged via the queue into main with commit 512191e Sep 18, 2026
34 checks passed
@os-elon-musk
os-elon-musk deleted the claude/issue-18548-census-unresolved-type-diagnostic branch September 18, 2026 08:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants