Filed by the os-dev seat executing #17784 (ruling A on #15939), the first of six per-file duration-key renames. Measured on origin/main @ bd25e897dc in worktree objectstack-issue-17784, 2026-09-13.
⛔ No domain:* and no priority:* asserted — the triage seat owns both.
The reading
scripts/pm/check-widening-tells.mjs raises T1 — "a new key on a Zod object schema on the contract source surface … the accept set gains a spelling an author may now write" on the line that DECLARES A TOMBSTONE:
✗ 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(
retiredKey() returns z.never(...).optional(). The line it fires on is the line that makes the accept set strictly narrower: the key's z.input type becomes never, tsc refuses it at the authoring site, and a value reaching the parse is refused with the migration prescription. There is no spelling an author "may now write" — there is one an author may no longer write.
Repro — two probes on the same instrument, separating the two halves of a rename
Both are unified diffs handed to the checker with --declaration no; neither needs the repo to be in any particular state.
Probe A — the rename alone (the key line removed, the suffixed key line added, one change block):
node scripts/pm/check-widening-tells.mjs --declaration no --diff probe-rename-only.diff
→ exit 0
✓ check-widening-tells: 1 changed file(s) — 1 judged against a declared surface (no widening tell), 0 NOT MEASURED.
Probe B — an added retiredKey( key line with no paired removal:
node scripts/pm/check-widening-tells.mjs --declaration no --diff probe-tombstone-only.diff
→ exit 4
✗ T1 … + schemaCacheTtlSecondsPlaceholder: retiredKey(
✗ T2 … + 'x',
⇒ the accounting on a real retirement diff is exact and it is not a budget shortfall. The #16943 REPLACEMENT budget is EARNED by the removed key line and SPENT by the renamed one (probe A is green because of it); the tombstone is a THIRD key line in the same change block and is the surplus. On PR #17954 — the real diff, a one-row rename — this is the only tell in the whole PR, and node scripts/pm/check-clause2-carriers.mjs --pair 17954 returns exit 4 / C5.
Why it is worth a card rather than an acceptance note
This is the third door in a documented family, and the file's own header records the first two as defects that were repaired rather than as accepted cost:
A retiredKey() tombstone is the standard, AGENTS.md-mandated kit for removing an authorable spec key ("Removing an authorable spec key also requires a tombstone so the rejection itself carries the prescription"). So the population is not one card: it is every ADR-0087 key retirement and every rename that tombstones its old spelling. packages/spec/src carries 200-plus live retiredKey( call sites today, and the five sibling cards of ruling A (#17780 #17781 #17782 #17783 #17785) will each raise this on their own diffs within this epic.
What is NOT proposed here
The direction #17300 and #17618 both took, offered as a starting point and not as a recommendation this seat is entitled to make: the evidence a tombstone carries is positive, hunk-local and absent by default — the added line's own value opens retiredKey(, a helper exported from packages/spec/src/shared/retired-key.ts whose entire contract is to refuse. Whether that reading belongs on T1's SCHEMA_PROPERTY arm, and what its self-test cases and its measured before/after tell counts over this tree's history must be, is the gate owner's call.
Blast radius while it stands
The pair reads C5 on a correct declaration, and the only mechanically sanctioned clear is flipping the card to Clause-②: yes — writing a widening into a governance ledger that never happened, which the checker's own prose calls the thing the whole clause-② chain forbids. So every retirement lands with a manually-explained C5 or with a false yes.
Related: #15939 (the ruling this was measured under) · #17954 (the PR carrying the measurement) · #16448 (the gate) · #17926 (open, the same false-positive family on T4)
Filed by the
os-devseat executing #17784 (ruling A on #15939), the first of six per-file duration-key renames. Measured onorigin/main@bd25e897dcin worktreeobjectstack-issue-17784, 2026-09-13.⛔ No
domain:*and nopriority:*asserted — the triage seat owns both.The reading
scripts/pm/check-widening-tells.mjsraises T1 — "a new key on a Zod object schema on the contract source surface … the accept set gains a spelling an author may now write" on the line that DECLARES A TOMBSTONE:retiredKey()returnsz.never(...).optional(). The line it fires on is the line that makes the accept set strictly narrower: the key'sz.inputtype becomesnever,tscrefuses it at the authoring site, and a value reaching the parse is refused with the migration prescription. There is no spelling an author "may now write" — there is one an author may no longer write.Repro — two probes on the same instrument, separating the two halves of a rename
Both are unified diffs handed to the checker with
--declaration no; neither needs the repo to be in any particular state.Probe A — the rename alone (the key line removed, the suffixed key line added, one change block):
Probe B — an added
retiredKey(key line with no paired removal:⇒ the accounting on a real retirement diff is exact and it is not a budget shortfall. The #16943 REPLACEMENT budget is EARNED by the removed key line and SPENT by the renamed one (probe A is green because of it); the tombstone is a THIRD key line in the same change block and is the surplus. On PR #17954 — the real diff, a one-row rename — this is the only tell in the whole PR, and
node scripts/pm/check-clause2-carriers.mjs --pair 17954returns exit 4 / C5.Why it is worth a card rather than an acceptance note
This is the third door in a documented family, and the file's own header records the first two as defects that were repaired rather than as accepted cost:
check-clause2-carriersT1 reads a function PARAMETER annotatedctx: z.RefinementCtxas a new authorable key — so every PR that adds an object-level refusal raises a widening tell for the refusal itself #17618 — T1 fired on an in-shape key whose value list the same block NARROWED, and the header states the cost in the terms that apply here exactly: it "exited 4 against a correctClause-②: noand held a reviewed, green retirement PR out of the queue, where the only sanctioned clear is the falseyesthis file already refuses to ask an author for."A
retiredKey()tombstone is the standard, AGENTS.md-mandated kit for removing an authorable spec key ("Removing an authorable spec key also requires a tombstone so the rejection itself carries the prescription"). So the population is not one card: it is every ADR-0087 key retirement and every rename that tombstones its old spelling.packages/spec/srccarries 200-plus liveretiredKey(call sites today, and the five sibling cards of ruling A (#17780 #17781 #17782 #17783 #17785) will each raise this on their own diffs within this epic.What is NOT proposed here
packages/spec/src/**or of any file — check-widening-tells fires T2 on the retirement ledger's own generated rows, so every retirement's clause-② reading is adverse for the one reason the accept set SHRANK #17300 ruled that shape out explicitly ("the fix is NOT excluding the file, and not excluding the generated regions either").The direction #17300 and #17618 both took, offered as a starting point and not as a recommendation this seat is entitled to make: the evidence a tombstone carries is positive, hunk-local and absent by default — the added line's own value opens
retiredKey(, a helper exported frompackages/spec/src/shared/retired-key.tswhose entire contract is to refuse. Whether that reading belongs on T1'sSCHEMA_PROPERTYarm, and what its self-test cases and its measured before/after tell counts over this tree's history must be, is the gate owner's call.Blast radius while it stands
The pair reads C5 on a correct declaration, and the only mechanically sanctioned clear is flipping the card to
Clause-②: yes— writing a widening into a governance ledger that never happened, which the checker's own prose calls the thing the whole clause-② chain forbids. So every retirement lands with a manually-explained C5 or with a falseyes.Related: #15939 (the ruling this was measured under) · #17954 (the PR carrying the measurement) · #16448 (the gate) · #17926 (open, the same false-positive family on T4)