Skip to content

fix(scripts): apply the tenant-audit census's engine-door rule to inline type literals (225 -> 227) - #19073

Merged
os-elon-musk merged 4 commits into
mainfrom
claude/issue-18944-census-inline-receiver-placement
Sep 18, 2026
Merged

os-elon-musk merged 4 commits into
mainfrom
claude/issue-18944-census-inline-receiver-placement

Conversation

@os-elon-musk

Copy link
Copy Markdown
Collaborator

Fixes #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 typeTextDeclaresEngineDoormemberIsEngineDoor — 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):

anchor hits 1 time(s), as declared     anchor 1 -> 0, replacement 0 -> 1
blob  40921ef8b3b3 -> 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 40921ef8b3b3f66bb657c24097d357ad26ce68b8
  blob at HEAD       40921ef8b3b3f66bb657c24097d357ad26ce68b8
  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 #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:

✓ 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
    fix(scripts): make the tenant-audit census say when it could not place a receiver's type #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

…ine type literals

The census's engine type index is keyed on declaration NAMES, so a write call
site whose receiver type is an inline type literal had no name to look up and
was classified `kind: 'other'` -- subtracted from the certified population --
however plainly its own text stated an ObjectQL write door. Two sites on a clean
tree were exactly that, both writing under an elevated context:
`resolveInsertEngine()`'s `{ insert: (name: string, ...) => ... } | null` and
`migrateLegacySsoClientSecrets`'s `engine as unknown as { find(object: string,
...); update(object: string, ...) }`. The census already PRINTED both, per site,
on every run, and called the subtraction probably wrong -- then took it anyway.

`inlineEngineDoorOrOther()` reads the same door rule off the type text when no
indexed name is found in it, reusing `typeTextDeclaresEngineDoor` ->
`memberIsEngineDoor` rather than spelling the rule a second time. 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.

Population 225 -> 227. The figure downstream cards cite -- sites provably
carrying no tenant context against a tenancy-enabled object -- is UNCHANGED at
9: both sites thread `{ context: { isSystem: true } }`, and one targets
`sys_sso_provider`, a declared tenancy opt-out. The repair is to the denominator
and to what this page can be read as having examined, not to any violation count.

Both artefacts regenerated with `--write`. The page's hand-written prose is
re-read against the new numbers, including the claim it falsified: "no write
call site on this surface targets either" opt-out object is no longer true, and
the generated `declares tenancy off` row now reads 1.

Claude-Session: https://claude.ai/code/session_019srGWGCBBCBHqcDoRZpQRh
Co-authored-by: Claude <noreply@anthropic.com>
…lacement moved them

Two corrections to text this change would otherwise leave subtly false:

- the generator docblock claimed the `/inline-door` suffix on `how` is what
  `--json` shows for a placed site. It is not -- `how` is carried onto
  diagnostics, never onto a placed site's record, whose `engineType` is the
  observable. The unused `inlineType` payload goes with it.
- inserting a third instance ahead of the page's "pins all of it" sentence
  silently extended the unverifiable "18 classifier cases, 6 of which red"
  claim over the new one. That sentence now names the two it was written for,
  and the door rule states its own both-directions pin.

Claude-Session: https://claude.ai/code/session_019srGWGCBBCBHqcDoRZpQRh
Co-authored-by: Claude <noreply@anthropic.com>
@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

Copy link
Copy Markdown
Collaborator Author

Check Changeset red on 6c23858e584dbdd2b8ef671ef80cf6fd01c69cc6 — cleared by the seat, ⛔ not by the dev, because the remedy is a label and labels are the seat's write. Seat: domain:spec#3 · taken 2026-09-18T16:01Z

What was failing, in the gate's own words

This PR adds no changeset. FIRST: if its only .changeset rows are CHANGED, not added, this PR corrects somebody else's pending release note — do NOT apply skip-changeset … Otherwise, if it releases nothing … apply the skip-changeset label; otherwise run pnpm changeset and name the packages.

Read from the job log (105666980728), ⛔ not from the check-run summary, which is empty on this check.

Route 0 does not match — measured, because it is the one case where the label is REFUSED

Route 0 fires when a PR's only .changeset rows are CHANGED (correcting someone else's pending note — ruling ② B on #18375, which says the label is not applied there). This PR's diff carries zero .changeset rows of any status:

file status lines
content/docs/permissions/tenant-audit-census.mdx modified +64/−27
docs/audits/2026-08-tenant-audit-write-call-sites.counts.md modified +18/−12
scripts/tenant-audit-census.mjs modified +130/−10

⇒ route 0 is out by the diff, which the gate says is how it should be decided: 「Route 0 … is answered by the diff, not by judgement」.

"Releases nothing" — the premise, with a control, because it is what licenses the exemption

  • pnpm-workspace.yaml globs are packages/*, packages/apps/*, packages/drivers/*, packages/plugins/*, packages/qa/*, packages/triggers/*, packages/services/*, packages/adapters/*, packages/connectors/*, apps/*, examples/*. None of content/, docs/ or scripts/ at the repo root is a workspace member.
  • Every tracked package.json scanned (83 files, 0 unreadable): 0 declare a files[] entry naming content/, scripts/ or docs/.
  • Lit control, same instrument: 70 of those packages declare a files[] at all, and all 70 name a dist entry. ⇒ the zero above is a reading, ⛔ not a dead scan.

⚠️ First attempt at that control was spoiled by my own head -3 breaking the pipe and reporting "3" — re-run without truncation. Recorded because a truncated enumeration reads exactly like a small population.

⇒ this PR publishes nothing, so route 2 applies, which the gate's own log marks <<< PREFERRED, and skip-changeset is now on the PR (four-step write with read-back: documentation, size/m, skip-changeset).

Why the dev did not do this

The dispatch forbade the dev any label write and anticipated exactly this case in writing — a repo-root script may owe skip-changeset, and if so it is the seat's. ⛔ No fault of the dev's, and ⛔ nothing for it to push.

What to expect on the check itself

Per the same log: 「applying it now still fires a labeled event and that run goes green — but the red left here does not clear itself.」 ⇒ the new run is the verdict; this run stays red as a historical artifact. ⛔ Do not read the stale red as a live failure, and ⛔ do not re-run it to tidy the display.

The dev's round is still open; this comment records the seat's act so the dev's report is not written against a red it neither caused nor may clear.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

Correction to this seat's own reading in comment 5732653264 — the post-label run is skipped, ⛔ not success, and「green」was the wrong word. Seat: domain:spec#3 · taken 2026-09-18T16:08Z

The implementing dev re-read the live state instead of taking the seat's word for it and caught this. Verified here by reading both check runs directly, ⛔ not from an aggregate:

check run started status conclusion
105666980728 15:57:22Z completed failure (pre-label; ⛔ does not clear itself)
105668291851 16:01:15Z completed skipped (the labeled run; the job-level if: short-circuits on the live label)

⇒ the exemption is expressed as a SKIP, not as a pass. This seat's earlier sentence 「reads green on the latest run per check name」 came from an instrument that buckets success, skipped and neutral together as not-red — fine for 「is anything failing」, wrong for 「is this check satisfied」. The dev was right to refuse the word.

Does a skipped required context satisfy the required set here? — measured by behaviour, because the config is unreadable

GET /branches/main/protection returns 403 Resource not accessible by integration to this seat, so the configuration cannot be read. The behavioural control instead — three PRs that carry skip-changeset and merged, each read on its own head:

PR merged Check Changeset runs on that head, in order
#19026 2026-09-18T15:16:38Z success 12:15:25 → skipped 12:15:43
#18998 2026-09-18T15:16:08Z success 10:28:57 → skipped 10:29:31
#19050 2026-09-18T15:17:05Z success 14:22:17 → skipped 14:22:43

a skipped latest conclusion did not block a merge in three measured cases. That is the reassuring half.

⚠️ What the control does NOT establish, stated rather than glossed: in all three the earlier run on the head was success, while on this PR the earlier run is a failure. So 「skipped supersedes an earlier failure on the same head」 has zero measured cases here. GitHub evaluates the latest run per check name, which predicts it is fine, and the merge queue re-runs checks on its own test-merge commit anyway — but that is a prediction, ⛔ not a reading, and it is named as such.

What this changes operationally

Nothing to do now: the PR is a draft awaiting the landing act that this session's write classifier refuses ([Merge Without Review]), and the queue will produce its own fresh run. If an arm ever fails naming this check, that is the measurement the control above is missing, and it should be recorded here rather than re-derived.

⛔ No re-run of the stale failure — re-running to tidy a display is exactly what the gate's own log warns against.


Generated by Claude Code

…ement

Conflict set: exactly ONE line in each generated artefact — the deliberately
UNENFORCED `Measured on <date> at <ref>` marker, which sits inside the census
page's generated region and in the counts file's corpus-scale block. Both sides
were stale; main's side is kept so this merge never asserts a marker older than
what landed, and the generator rewrites it in the commit that follows.

Everything else text-merged with BOTH intents intact, verified rather than
assumed:

- scripts/tenant-audit-census.mjs auto-merged with no conflict and now carries
  #19075's corrected `merge=os-regen` rationale (the emitted "no
  `.gitattributes` row names it ... Root-level tooling is no obstacle by
  itself") AND this branch's inline-type-literal placement
  (inlineEngineDoorOrOther, 9 occurrences). Zero conflict markers.
- the counts file holds #19075's new rationale paragraph and this branch's
  population (227, `declares tenancy off` 1, both new site rows, the
  0-by-construction paragraph); #19075's superseded rationale is gone.
- the census page holds every hand-written prose edit from this branch,
  including the claim the placement falsified. Measured: main's ONLY change to
  that page between this branch's base and origin/main is the one in-region
  marker line, so there is no hand-written text of main's to carry over.

Main still reads population 225 on both artefacts, so #19075 re-baselined
nothing and this branch's 225 -> 227 is still the whole change. Regeneration
from the merged tree follows as its own commit, per the ordering rule: running
a generator while the tree is in MERGE state stamps the PRE-MERGE tip, because
`measuredAt()` reads `git rev-parse HEAD`.

Claude-Session: https://claude.ai/code/session_019srGWGCBBCBHqcDoRZpQRh
Co-authored-by: Claude <noreply@anthropic.com>
…rged tree

`node scripts/tenant-audit-census.mjs --write`, run AFTER the merge commit so
`measuredAt()` reads the merged HEAD rather than the pre-merge tip.

The staged diff is exactly two lines — the deliberately UNENFORCED
`Measured on ... at ...` marker in each artefact, now naming the merge commit
`30def652e` instead of main's `d4cb05cbf`. Nothing else moved, which is the
proof the merge resolution was byte-faithful: the generator, run against the
merged tree, reproduces both hand-resolved artefacts exactly. Had the semantic
merge dropped or mangled a row, a total or #19075's rationale paragraph, this
commit would carry it.

That also reconciles the two rules that govern this file. `os-regen-merge.sh`
requires a non-driver-managed generated artefact to be hand-resolved for the
merge commit and ⛔ not regenerated mid-merge (a mid-merge recompute describes
a half-merged tree); the file's own header requires a conflict to be resolved
BY REGENERATION and ⛔ never by hand. Both hold when the order is: resolve,
commit the merge, regenerate, and let the regeneration diff certify the
resolution.

#19075's corrected rationale re-measured on the merged tree with its own
instruments and still true: 0 `.gitattributes` rows name this census (fire
control: `strictness-ledger` returns 1, so the zero is not a dead reading),
`git check-attr merge` reads `unspecified`, 0 manifests name it in a `scripts`
entry, and the lint workflow runs the gate directly.

Claude-Session: https://claude.ai/code/session_019srGWGCBBCBHqcDoRZpQRh
Co-authored-by: Claude <noreply@anthropic.com>
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