fix(scripts): apply the tenant-audit census's engine-door rule to inline type literals (225 -> 227) - #19073
Conversation
…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>
|
What was failing, in the gate's own words
Read from the job log ( Route 0 does not match — measured, because it is the one case where the label is REFUSEDRoute 0 fires when a PR's only
⇒ 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
⇒ this PR publishes nothing, so route 2 applies, which the gate's own log marks Why the dev did not do thisThe dispatch forbade the dev any label write and anticipated exactly this case in writing — a repo-root script may owe What to expect on the check itselfPer the same log: 「applying it now still fires a 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 |
|
Correction to this seat's own reading in comment 5732653264 — the post-label run is 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:
⇒ 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 Does a skipped required context satisfy the required set here? — measured by behaviour, because the config is unreadable
⇒ a
What this changes operationallyNothing to do now: the PR is a draft awaiting the landing act that this session's write classifier refuses ( ⛔ No re-run of the stale 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>
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'andsubtracted 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 theclassifier. Sites selected by symbol — file + verb + receiver text — never by
the card's line numbers.
plugin-auth/src/audience-gate-test-support.ts·engine.insert· re-derived at :72sys_invitationoptions.contextplugin-auth/src/sso-client-secret.ts·e.update· re-derived at :303sys_sso_provideroptions.contextControls, because a "no" needs one:
(
collectSources().includes(...)→truefor each). A "no violation" from afile the instrument never reads would be void.
(
tenancy === 'enabled' && carriesTenantContext === false) lights on 9other 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.
{ context: { isSystem: true } }argument (→carries: false), orsys_invitationdeclaringtenancy.enabled: false.⇒ The figure downstream cards cite does not move.
9 / 225becomes9 / 227: the numerator is unchanged and the denominator becomes truthful. Therepair 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), sothe branch is reported as open rather than closed.
The fix
inlineEngineDoorOrOther()judges a declared type text one more time when noindexed name is found in it, by reading the same door rule off the text
itself. It calls
typeTextDeclaresEngineDoor→memberIsEngineDoor— the onefunction 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
resolveReceiverreturns that carry a declared type text (fromEntry, anascast, a member access, a call's return type), not only at the two thathappen 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):Ablation, through
scripts/ablation-replace.mjsso the write is verifiedagainst the disk rather than against an exit code (anchor: the helper's own
guard, replaced with a dead one):
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:
One now does — the legacy client-secret migration writes
sys_sso_providerunder an elevated context, which is why the generated
declares tenancy offrowreads 1 rather than 0.
sys_api_keystill has no write site on this surface(measured: no site in
--jsonnames it). Nine further hand-written figuresmoved and are updated (the gate holds every one of them: 23 prose figures).
Two things stated rather than rewritten on a guess:
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.
(47%)→(48%)parenthetical is hand-written and unenforced (thegate requires digits-then-
%, not a value). 108/227 = 47.58%, so floor gives47 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/objectstackderived at
6c23858e5, the final commit (3 paths vs merge base0ec81857a;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 METin a fresh worktree —@objectstack/spec,@objectstack/lint,@objectstack/formula, then@objectstack/client-reactwere 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) andthe 52 artifact-roster families whose
silentverdict is a fact about a listrather than about these paths.
scripts/check-tenant-audit-census.mjsis this census's gate and it judges:⛔ No gate was weakened: no ratchet raised, no floor lowered, no ledger row
deleted, no test skipped. The
doorShapeddiagnostic is kept — now 0 byconstruction — 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-changesetlabel is owed but not applied by thisPR — 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'sfiles[]ships can move. Positive control:
packages/specis published and itsfiles[]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
with a
ts-parserefusal (exit 3) on a receiver whose inline type literalseparates its members by a newline only:
declaredTypesIncollapseswhitespace, so the separator is lost and the synthetic
type CensusReceiver = ...re-parse fails with "';' expected". Reproduceddirectly; control lit (the semicolon-separated form of the same literal
returns
true). Live onmainthrough theanonymous-typearm, which hascalled
typeTextDeclaresEngineDooron the same collapsed text since PRfix(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).
NON_ENGINE_REASONSis exported and read by nothing — itskeys are produced by
nonEngineReasonand its descriptions are documentationonly, 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.
(
any) receivers reads 44 on this tree (18 + 15 + 11), a pre-existingone-off drift in a comment, untouched here.
Generated by Claude Code