Skip to content

feat(spec)!: tenant schemaCacheTTL carries its unit in the key name (#17784) - #17954

Merged
zhuangjianguo merged 4 commits into
mainfrom
claude/issue-17784-tenant-schema-cache-ttl-unit
Sep 13, 2026
Merged

feat(spec)!: tenant schemaCacheTTL carries its unit in the key name (#17784)#17954
zhuangjianguo merged 4 commits into
mainfrom
claude/issue-17784-tenant-schema-cache-ttl-unit

Conversation

@claude

@claude claude Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Fixes #17784

Clause-②: yes

Executes director-seat ruling A on #15939 (2026-09-11, maintainer 「同意」, decision batch #115) — the per-file remediation of the #14478 duration-unit rule. This card owns exactly one row, in packages/spec/src/system/tenant.zod.ts. The gate PR (#17635) is sequenced to land LAST and is untouched here.

⚠️ The declaration reads yes, not the no ruling A wrote: the PM corrected it on the card (comment 5652103109) under the mechanical floor in references/contract-review.md — a new key on a published payload is always yes — and a rename is a removal plus a new key. needs:contract-review is hung on card and PR; the changeset is graded minor accordingly, matching the four sibling duration-rename changesets of this same rule (#15677 · #15678 · #15679 · #15680), every one of which shipped as feat(spec)! under ## 17.4.0.

What changed

SchemaLevelIsolationStrategy.performance.schemaCacheTTL named its unit in a source JSDoc — "Schema cache TTL in seconds" — and nowhere else. The .describe() that content/docs/references/system/tenant.mdx renders said "Schema cache TTL" and named no unit at all, so the reader of the published reference page could not tell 3600 seconds from 3600 milliseconds.

before after
authored key performance.schemaCacheTTL: 3600 performance.schemaCacheTtlSeconds: 3600
published describe Schema cache TTL Schema cache TTL in seconds
value + default seconds, 3600 unchanged

The new spelling is Ttl, not TTL, derived from how the suffixed family already spells itself on this tree rather than from the dispatch: cacheTtlSeconds 11, ttlSeconds 3, defaultCacheTtlSeconds 1, and no key-position TTLSeconds variant anywhere.

The kit, following the #15678 / #15679 shape the ruling names:

  • a retiredKey() tombstone on the old spelling — tsc types it never and a value reaching the parse raises the rename prescription instead of being silently stripped (the nested performance object is not .strict())
  • ADR-0087 D3 semantic entry tenant-schema-cache-ttl-unit-in-key and the RETIRED_KEYS_BY_MAJOR[18] row system/SchemaLevelIsolationStrategy:performance.schemaCacheTTL, both as migrations/entries/ files with registry.ts regenerated by gen:migration-registry (never hand-merged)
  • no D2 conversion: stack.zod.ts declares no tenancy collection and a tenant isolation strategy is not a stored metadata row, so the chain has no seam that runs on it — the same reading tenant-timeouts-unit-in-key recorded for the two sibling keys on this same file
  • pin tests on SchemaLevelIsolationStrategySchema: the refusal carries the rename prescription, the suffixed key parses at the magnitude the retired one carried with the same 3600 default, and the describe publishes the unit
  • content/docs/references/system/tenant.mdx regenerated by gen:docs — all four rows move and the tombstone prescription renders in place of the old describe
  • a minor changeset carrying the FROM to TO mapping and the ADR-0087 disposition

The measurement this card was dispatched to produce

This is the first of six renames, so the mechanical clause-② reading matters for the other five.

node scripts/pm/check-widening-tells.mjs --declaration no over this PR's own diff — exit 4, exactly one tell:

✗ T1 packages/spec/src/system/tenant.zod.ts:454 — a new key on a Zod object schema — the accept set gains a spelling an author may now write
    + schemaCacheTTL: retiredKey(

The tell is not on the rename. It is on the tombstone. Two synthetic probes separate the two halves on the same instrument:

probe exit tells
the rename alone (one key line removed, one added, one change block) 0 none — the #16943 REPLACEMENT budget absorbs it
an added retiredKey( key line with no paired removal 4 T1 on the tombstone line

So the accounting is exact: the removed schemaCacheTTL: line buys one T1 unit, the added schemaCacheTtlSeconds: line spends it, and the added schemaCacheTTL: retiredKey( line is the surplus. Every retiredKey() retirement raises this, and the line it raises on is the one that makes the accept set strictly narrower — the key becomes never. That is the same inversion this file's own header records for #17300 (T2 on the retirement ledger's rows) and #17618 (T1 on a narrowed in-shape key), one door further along. Filed as #17955; the diff was not reshaped to silence it and no gate was weakened.

⚠️ Read the two readings apart, because they agree on the verdict for different reasons: the mechanical tell is a false positive on the tombstone line, while the yes this PR now declares rests on the contract-review floor (the rename really does add a key an author may write). check-clause2-carriers --pair 17954 read exit 4 / C5 against the original no and reads exit 0 against the corrected yes.

Verification

Heavy runs through scripts/pm/os-verify-lock.sh, verdicts read from its own VERDICT line; every gate's exit code captured before any pipe.

  • pnpm --filter '@objectstack/spec^...' build — exit 0, empty closure (No projects matched): packages/spec has no workspace dependencies, so step ① is a documented no-op
  • pnpm --filter @objectstack/spec buildVERDICT command-exit 0, run before every dist-reading gate and re-run after the last source edit
  • pnpm --filter @objectstack/spec test475 test files / 13507 tests passed
  • pnpm --filter @objectstack/spec typecheck — exit 0 (tsc --noEmit + check:scripts-typecheck + check:test-typecheck)
  • pnpm --filter @objectstack/spec check:generatedall 15 generated artifacts up to date after gen:docs; check:authorable-surface, check:api-surface, check:migration-registry, check:spec-changes and check:upgrade-guide among them
  • check:authorable-surface does not move for this key and that is correct: the ratchet records top-level keys per def and this one is nested under performance (0 hits for the key across authorable-surface/ and authorable-surface.base.json, against 4 for the system/MigrationPlan: lit control)
  • check:duration-unit-keys (the gate as it stands on main, not feat(spec): refuse a duration key whose JSDoc names a unit its describe does not #17635's) — exit 0
  • check:objectui-pin-citations — exit 0, 16 asserting pin citations match .objectui-sha
  • cross-repo, per the ruling's execution note and AGENTS.md post-task step 4: git grep of the pinned objectui checkout at .objectui-sha = 53ded82bf7a494f54e344e19099dbf00854b8694 (re-read from this tree) — schemaCacheTTL 0 occurrences across 6409 tracked files, beside lit controls TTL 112, Ttl 11, tenant 819, cacheTTL 1 and a dark control at 0, so the zero is a reading

Gate set derived at the actual change set with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack; full per-gate exit codes, including the ones that could not run in this container, are in the round report on #17784.

Acceptance notes

⛔ Draft on purpose: the in-seat clause-② review this PR now declares is owed before it may turn ready or enqueue. That is the PM's step, not this round's.

Round report, with every reading above and its exit code: the os-dev-report comment on #17784.


Generated by Claude Code

`SchemaLevelIsolationStrategy.performance.schemaCacheTTL` named seconds in a
source JSDoc only; the published `.describe()` said "Schema cache TTL" and
named no unit, so a reference-page reader could not tell 3600 seconds from
3600 milliseconds. Renamed to `schemaCacheTtlSeconds` with the unit in the
describe too, tombstoned with `retiredKey()` (the nested object is not
strict), and registered as an ADR-0087 D3 semantic entry plus a
RETIRED_KEYS_BY_MAJOR[18] row.

Claude-Session: https://claude.ai/code/session_015c5G6TmpMKgnusmTpD7Ntt
Co-authored-by: Claude <noreply@anthropic.com>
`gen:docs` moves the four `schemaCacheTTL` rows in
content/docs/references/system/tenant.mdx onto `schemaCacheTtlSeconds` and
renders the tombstone prescription in place of the old describe.
check:authorable-surface and check:api-surface are unchanged — the ratchet
records top-level keys per def and this one is nested under `performance`.

Claude-Session: https://claude.ai/code/session_015c5G6TmpMKgnusmTpD7Ntt
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation protocol:system tests tooling labels Sep 13, 2026
@github-actions

github-actions Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

3 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ⚠️ 1 changed file(s) yielded no anchor (packages/spec/src/migrations/entries/retired-keys/18.system__SchemaLevelIsolationStrategy__performance.schemaCacheTTL.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/src/migrations/entries/retired-keys/18.system__SchemaLevelIsolationStrategy__performance.schemaCacheTTL.ts) — pages documenting those are invisible to this run
  • 4 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 60 of 215 client-bound route-ledger rows — the other 155 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 155: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 100 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json f62787c033e5f36bc02a2eafff62ff84d73c9b70packageMentionDocs.

Which tree this was computed on

This run read content/docs from 2105eeb378bef4ec650d81fbc904b2305bcaa5bf — the merge of head ee19e715606d215e294d04c72bbc23bbb946ef2c into base f62787c033e5f36bc02a2eafff62ff84d73c9b70, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 2105eeb378bef4ec650d81fbc904b2305bcaa5bf && git checkout 2105eeb378bef4ec650d81fbc904b2305bcaa5bf
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin f62787c033e5f36bc02a2eafff62ff84d73c9b70 ee19e715606d215e294d04c72bbc23bbb946ef2c && git checkout -B drift-repro f62787c033e5f36bc02a2eafff62ff84d73c9b70 && git merge --no-ff ee19e715606d215e294d04c72bbc23bbb946ef2c

node scripts/docs-audit/affected-docs.mjs --json f62787c033e5f36bc02a2eafff62ff84d73c9b70

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Copy link
Copy Markdown
Collaborator

PM status on the two red checks — both diagnosed, both this PR's, fixes in flight

Epic PM for the #15939 subtree, session_015c5G6TmpMKgnusmTpD7Ntt, 2026-09-13T08:20Z. Read from the job logs, ⛔ not from the round's self-report.

1. Check Changeset⚠️ triggered by a PM action, and it exposed a real error

I hung needs:contract-review on this PR at ~08:01 after correcting the card's clause-② declaration to the affirmative (correction comment 5652103109, reasoning in 5652094166). The gate read the carrier and said:

⛔ This PR declares clause-② YES, and it grades NO package whose packages/**/src/** it moves at minor or above. … @objectstack/spec: patch

This is not "a PM label broke a good PR". The patch grading was already wrong, and nothing was going to say so while the declaration read the other way. Measured by me on origin/main in packages/spec/CHANGELOG.md, every sibling of this card under ## 17.4.0 (a minor bump):

line entry
:1804 feat(spec)!: the fourteen kernel/ duration keys carry their unit in the key name (#15678, ruling B on #14478)
:2764 feat(spec)!: the fifteen system/ duration keys carry their unit in the key name (#15679, …)
:124 · :675 · :912 same shape, feat(spec)!, each with an adr-0087: registered disposition

Ruling A prescribes "patchfollowing the #15678 / #15679 shape" — and that shape is feat(spec)! at minor, four times over, never once a patch. The two halves of the ruling's own clause disagree; the measurable half wins. Corroborated independently by the maintainer ruling the gate quotes at itself (2026-09-04, decision batch #35, on #15294: "a purely additive widening … takes AT LEAST minor").

Fix: the changeset rises to minor / feat(spec)!, keeping the [BREAKING] marker and the ADR-0087 disposition that check-adr-0087-registration already passed. Recorded for the whole epic on #15939 — it applies to all six rename cards, ⛔ not to #17786 / PR #17953, which adds no key and stays at patch.

2. Type Check · source gates — entirely this PR's, unrelated to the above

@objectstack/spec check:objectui-pin-citations✗ 2 pin-citation / anchor problem(s) in packages/spec/src. The gate accepts exactly two spellings and says "an unrecognised spelling is not a pass: it leaves the citation outside every check, which is the silent state this gate exists to remove." The cross-repo pin citation in this PR's prose uses a third. ⇒ Fix: use the spelling that is true of what was done (this round checked against the pin). ⛔ Not by weakening the gate, ⛔ not by dropping the citation.

3. PR body declaration line

check-changeset-no-major reads the PR-body line, not my correction comment, and it printed the disagreement: "· carrier: needs:contract-review IS on this PR · declaration line: (negative)". The body line moves to the affirmative to match the card. ⇒ Note that check-clause2-carriers --pair 17954 already reads exit 0 via the correction comment; this is the human-legibility and changeset-gate half of the same fact.

Also read this wake, and judged as needing no action

Docs Drift Check — advisory, names no page. It declares its own blind spot honestly: the retired-key entry 18.system__SchemaLevelIsolationStrategy__performance.schemaCacheTTL.ts yields no anchor, so pages documenting it are not covered by that run. That is a limitation of the drift tool, ⛔ not a defect in this diff, and the generated reference page for this key is regenerated in this PR by gen:docs. No action.

Posture

All three fixes are with the round now. ⛔ Draft stays draft; no ready, no enqueue, no auto-merge. An in-seat clause-② contract review at CONTRACT_REVIEW_TIER is owed on this PR before any of that, and it is this seat's to supply — per the maintainer's direct instruction today, quoted verbatim: 「你自己直接负责契约复审」.

epic PM for #15939 · session_015c5G6TmpMKgnusmTpD7Ntt · 2026-09-13T08:20Z


Generated by Claude Code

…ecked spelling

Three corrections after the PM flipped card #17784's clause-② declaration to
`yes` under the contract-review floor (a rename adds a new key on a published
payload):

- the changeset grades `@objectstack/spec` `minor`, matching the four sibling
  duration-rename changesets of this same rule, and its summary takes the
  `feat(spec)!` shape they use
- the semantic entry cites the pinned objectui checkout as
  `.objectui-sha` = `<sha>`, one of the two spellings
  `check:objectui-pin-citations` recognises — it was in a third spelling, which
  the gate reports as a citation outside every check
- registry.ts regenerated so its mirror of that prose matches

Claude-Session: https://claude.ai/code/session_015c5G6TmpMKgnusmTpD7Ntt
Co-authored-by: Claude <noreply@anthropic.com>

Copy link
Copy Markdown
Collaborator

Contract review

Head sha reviewed: 0183e54fc1add770986077b260f44715c02eeae5 — re-read from origin at 2026-09-13T09:02:56Z after every measurement below: unchanged since the review opened. Merge base with main: bd25e897dc (origin/main at 884e8347d1). PR still draft; needs:contract-review on PR and card #17784; 7 files, none on a governed surface, content/docs/releases/ untouched.

Every tree reading below was taken in a worktree checked out at that sha, with @objectstack/spec built through bash scripts/pm/os-verify-lock.sh (VERDICT command-exit 0, held 173 s). Judged from the diff and by parsing, not from the PR body.

① derived judgments — accept set and public surface

  1. performance.schemaCacheTTL: <any defined value> moves accept → refuse, on SchemaLevelIsolationStrategySchema and on the isolated_schema arm of TenantIsolationConfigSchema. Parsed: {strategy:'isolated_schema', performance:{schemaCacheTTL:3600}} → REFUSED, one issue at path performance.schemaCacheTTL, code invalid_type, message = the rename prescription (same for value 1, via the union, and with both keys present). Absence (undefined) still parses. PR characterisation ("raises the rename prescription instead of being silently stripped") — right. The tsc half is also measured: z.input types the old key undefined; a probe assigning 3600 fails TS2322: Type 'number' is not assignable to type 'undefined' at exactly that line, while the new key and undefined compile. The PR says "types it never" — effect identical, wording fine.
  2. performance.schemaCacheTtlSeconds: n moves from accepted-and-stripped to accepted-and-honoured. Under the merge-base schema the new pin fails with expected undefined to be 7200 — the old (non-strict) object silently dropped the key and emitted the 3600 default; at head it carries 7200. So on the pure accept/reject axis nothing is newly accepted (that payload parsed before) and the change is a strict narrowing; what widens is the public surface — a new authorable spelling with effect and a new property on the exported SchemaLevelIsolationStrategy/TenantIsolationConfig types. The PR's "the accept set gains a spelling an author may now write" is right on the surface limb, imprecise on the accept-set limb; both limbs read clause-② yes (③ F1).
  3. Default 3600 unchanged (performance: {}schemaCacheTtlSeconds: 3600). Right.
  4. Published describe Schema cache TTLSchema cache TTL in seconds; tombstone describe [REMOVED] … added. Rendered twice each on the reference page (content/docs/references/system/tenant.mdx:154,178-179,281,306-307), consistent with the sibling idleTimeout/sessionTimeout rows (:63,:339,:417). Page is generator output — check:generated on the fresh dist: all 15 artifacts up to date. Right.
  5. Non-strict control: a typo key schemaCacheTtl: 7200 is still accepted-and-stripped (pre-existing, untouched). That is exactly why a bare deletion would have been the ADR-0049 silent-strip failure; the tombstone closes that door for the old spelling only. Right.
  6. Exports: none added/removed — packages/spec/api-surface/system.json:561-563 unchanged; git diff --stat on api-surface, authorable-surface*, json-schema.manifest, spec-changes.json, docs/protocol-upgrade-guide.md is empty. Correct by construction: those ratchets record existence (export names, top-level keys, def names), not nested shape — authorable-surface/system.json:960-963 lists only the four top-level keys; packages/spec/scripts/build-schemas.ts:849-855 reads schema.properties one level deep. PR's "no row moves, and that is correct" — right.
  7. ADR-0087 ledger: +1 semantic entry in step18 (registry.ts:10423-10457), +1 row under RETIRED_KEYS_BY_MAJOR[18] (registry.ts:13187). gen:migration-registry re-run → git diff --quiet exit 0 (207 semantic / 169 retired-key / 178 retired-def); the entry file's string equals the row (1 = 1). Generated, not hand-merged. Upgrade guide / spec-changes.json do not move because protocol 18 is the open major (guide ends at "Protocol 16 → 17"), same as every sibling entry in step18.
  8. No D2 conversion — right for this repo, by measurement: stack.zod.ts has no tenant collection (0 hits), MetadataTypeSchema (kernel/metadata-plugin.zod.ts) has no tenant type (comments only), conversions/registry.ts has no tenant entry, and every applyConversionsToStoredItem seam (packages/metadata/src/loaders/database-loader.ts:825, packages/objectql/src/plugin.ts:2128, packages/metadata-protocol/src/protocol.ts:4675) is keyed by metadata type. Zero runtime readers of TenantIsolationConfig/SchemaLevelIsolationStrategy outside spec source (hits are CHANGELOG, declaration-map, export-origins, a name test). Outside this repo (cloud): NOT MEASURED — bounded because a hypothetical stored row would fail loudly with the prescription, never strip silently, and the two earlier renames on this file (tenant-timeouts-unit-in-key, shipped in 17.4.0) took the identical route.
  9. Consumers: schemaCacheTTL outside packages/spec/** at main = 4 rows, all in the generated tenant.mdx. Pinned objectui at 53ded82bf7… re-grepped: schemaCacheTTL 0, schemaCacheTtlSeconds 0, lit TTL 112 / Ttl 11 / tenant 819, dark 0, over 6409 tracked files. No consumer-package test owed. Right.
  10. Nothing else on the def moves — the source diff is confined to the performance object (tenant.zod.ts:441-462). check:duration-unit-keys (main's gate) exit 0, 211 keys, zero offenders.

② semver grading — minor, feat(spec)!

  • What the diff does: removes a spelling from a published payload (breaking for any author who wrote it) and adds one. Strict semver says major.
  • Why minor is nonetheless the correct grade here: scripts/check-changeset-no-major.mjs:36-88 records a launch-window convention with a written GA end condition — major is refused (no .changeset/pre.json, so the guard is armed) and breaking-ness is carried by the BREAKING banner plus the ADR-0087 disposition, "not by the level"; .github/workflows/pr-automation.yml:717-724 restates it. Both carriers are present: **BREAKING** (changeset line 9), <!-- adr-0087: registered tenant-schema-cache-ttl-unit-in-key --> (line 7; gate exit 0, "new here"), FROM→TO diff block (lines 20-25). The ADR-0087 machinery does not make the change non-breaking — it makes it survivable and declared, which is what the convention requires in place of the bump level.
  • Why not patch: with Clause-②: yes the level axis (check-changeset-no-major.mjs:7-16) requires ≥ minor on the moved package; locally the axis is N/A (no PR payload), CI's Check Changeset passed on this head. Independently, a breaking change graded patch would tell an upgrader nothing.
  • Were the siblings right? Yes — for the same written reason, not as precedent. packages/spec/CHANGELOG.md:124,675,912,1804,2764 are all feat(spec)! under ## 17.4.0 (line 3); they corroborate, they are not the argument. Ruling A's patch was a sizing slip contradicted by its own "[#14478 stack 3/6] kernel/: the 14 remaining duration keys carry their unit in the key name — ADR-0087 conversions with readers (runtime-emitted measurements included) #15678/[#14478 stack 4/6] system/: the 15 remaining duration keys carry their unit in the key name — ADR-0087 conversions with readers; metrics.zod.ts size needs an honest name, not the mechanical one #15679 shape" and by the gate; correction 5652120294 is correct.
  • Observation, not a defect of this PR: the tombstone says "in @objectstack/spec 17" (the package major it ships in, matching the sibling tombstones at tenant.zod.ts:575-576,706) while the ledger rows sit under protocol 18 (140 rows under 18: vs 29 under 17:); build-schemas.ts:846 CURRENT_MAJOR reads 17 from the package version. Pre-existing, tree-wide.

③ boundary flags

Implemented-by: claude/issue-17784-tenant-schema-cache-ttl-unit
Reviewed-by: session_015c5G6TmpMKgnusmTpD7Ntt

(Disclosed, not hidden: dispatcher, PM and reviewer are one session — 席内至档 as recorded on #15939 comment 5652016540; the pair reader flags the correction as declared by the same session as the claim.)

PASS — every accept-set and surface change is measured and matches the diff's characterisation, the registry and reference page are the generators' byte-identical output, the tombstone refuses on both the def and the union with the prescription, and both PM corrections are correct on the governing text.


Provenance and tier verification — the seat's own record, not the reviewer's

Adopted verbatim by the epic PM seat for #15939, session_015c5G6TmpMKgnusmTpD7Ntt, 2026-09-13T09:12Z. The fuse permits adoption or voiding, ⛔ never rewriting, abridging or polishing.

  • Served tier, from the harness-stamped transcript, ⛔ not a self-report: 106 messages stamped "model":"claude-fable-5-1", zero other values — exact match to CONTRACT_REVIEW_TIER (scripts/pm/dispatch-gates.mjs:10507). Discriminating control: the authoring round's transcript over the same grep reads 242 × "model":"claude-opus-5".
  • The review was run in a context-isolated seat fed only the card, the standing rulings and the PR — ⛔ never the dispatch order or this seat's conclusions. It nevertheless inherits this session id (席内至档), which it discloses itself above rather than leaving to be inferred.

Two things this review corrected that the round did not have, recorded because they are why the layer exists:

  1. ①.2 — on the pure accept/reject axis the new spelling was already accepted-and-stripped by the non-strict object, so that axis is a strict narrowing; the widening is on the public surface. The round's phrasing was right on the surface limb and imprecise on the accept-set limb. The declaration is unaffected — both limbs read the affirmative.
  2. F4 — for a nested key the RETIRED_KEYS_BY_MAJOR row is inert on all three gate consumers, proven by ablation (a fabricated nested row passes check:authorable-surface exit 0 while a live top-level key is refused exit 1, registry restored byte-identical). ⇒ This falsifies the cost I asserted in [finding] a nested RETIRED_KEYS_BY_MAJOR row is unverifiable — check (b2) passes a fabricated one, so a typo'd nested retirement registers silently and forever #17969, which I am correcting on that card rather than leaving to stand. Its escalation — a typo'd nested row passes silently because (b2) cannot verify it — is the real defect in that area and is being carried there.

⇒ The settlement recorded on #15939 stands unchanged: the four remaining rename cards register both. The review's own words: "registering is harmless, consistent with the 36 other nested rows under 18 and the #15679 shape … keep it."

epic PM for #15939 · session_015c5G6TmpMKgnusmTpD7Ntt · 2026-09-13T09:12Z


Generated by Claude Code

@zhuangjianguo
zhuangjianguo marked this pull request as ready for review September 13, 2026 09:52
@zhuangjianguo
zhuangjianguo added this pull request to the merge queue Sep 13, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Sep 13, 2026

Copy link
Copy Markdown
Collaborator

Carrier cleared — provenance. ⚠️ And the queue guard caught a step I skipped.

Epic PM for #15939, session_015c5G6TmpMKgnusmTpD7Ntt, 2026-09-13T10:04Z.

What happened

This PR was dequeued at 10:00:11Z, CI_FAILUREGoverned Surface Queue Guard, run 34750497713, exit code 6. The guard's own message says what it is for:

On PASS that same seat strips the carrier from BOTH carriers, cites the record, and re-enqueues … ⚠️ This leg reads the LABEL, not the verdict. A carrier stripped before any PASS was on record is indistinguishable here from one that was never hung.

⇒ ⛔ My error. references/contract-review.md orders it 「PASS ⇒ 同席剥标并引记录、ready、auto-merge」 — strip, then ready, then enqueue. I went PASS → record → ACCEPT → ready → enqueue and skipped the strip, so a PR still flagged for contract review reached the queue. The guard refused it, which is exactly its job. ⛔ Nothing about this is a defect in the PR, and ⛔ nothing about it is a reason to edit the guard.

The record this strip cites

  • In-seat clause-② contract review: PASS — comment 5652364678, posted 09:07:15Z.
  • Head reviewed: 0183e54fc1add770986077b260f44715c02eeae5.
  • Served tier verified from the harness-stamped transcript, ⛔ not self-reported: 106 × claude-fable-5-1, zero other values, against a 242 × claude-opus-5 discriminating control. CONTRACT_REVIEW_TIER = claude-fable-5-1.

⚠️ The head moved after the review — measured, not waved past

The review read 0183e54fc1; the current head is ee19e71560 (the origin/main merge commit). contract-review.md says a moved head is a reason to re-hang, so this is not something to leave implicit.

Measured: git diff 0183e54fc1 ee19e71560 restricted to this card's authored paths returns a single file — entries/semantic/18.cache-warmup-scheduled-strategy-retired.ts (+63), which is #17638's entry arriving from main, caught by the entries/** glob and not this card's work. tenant.zod.ts, tenant.test.ts, tenant.mdx and the changeset are byte-identical to what the reviewer read. Control: the two heads differ by 61 files / +5340 overall, so the restricted empty result is a reading and not a broken command.

⇒ The review's subject is unchanged. The head moved by absorbing main, ⛔ not by re-authoring, so the PASS still binds. Corroborating: check-clause2-carriers --pair 17954 reads exit 0 on the current head, and the merge itself was verified drop-free both by the round (set difference over every registry row: 646 → 648, zero lost) and independently by this seat (five probes from main's window, identical counts; 15554 → 15600 = +46, closing against the diff).

Action

needs:contract-review stripped from both carriers — this PR and card #17784 — in the same pass, then re-enqueued. ⛔ Not stripped to get past the check: the verdict was on record 53 minutes before the guard ran.

epic PM for #15939 · session_015c5G6TmpMKgnusmTpD7Ntt · 2026-09-13T10:04Z


Generated by Claude Code

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 protocol:system size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spec: 1 duration key(s) in system/tenant.zod.ts name their unit only in JSDoc — #15939 Ruling A remediation (1 of the 21-row delta)

2 participants