feat(spec)!: tenant schemaCacheTTL carries its unit in the key name (#17784) - #17954
feat(spec)!: tenant schemaCacheTTL carries its unit in the key name (#17784)#17954claude[bot] wants to merge 3 commits into
schemaCacheTTL carries its unit in the key name (#17784)#17954Conversation
`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>
📓 Docs Drift Check3 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. What this run could not see
Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 847b65f75d015b5d3360a3ec844ca9de26b1df7b && git checkout 847b65f75d015b5d3360a3ec844ca9de26b1df7b
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 2c87a48f0d553224e9a899d29562a172a36dc5ce 0183e54fc1add770986077b260f44715c02eeae5 && git checkout -B drift-repro 2c87a48f0d553224e9a899d29562a172a36dc5ce && git merge --no-ff 0183e54fc1add770986077b260f44715c02eeae5
node scripts/docs-audit/affected-docs.mjs --json 2c87a48f0d553224e9a899d29562a172a36dc5ce |
PM status on the two red checks — both diagnosed, both this PR's, fixes in flightEpic PM for the #15939 subtree, 1.
|
| 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 "patch … following 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>
schemaCacheTTL carries its unit in the key name (#17784)schemaCacheTTL carries its unit in the key name (#17784)
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.yes, not thenoruling A wrote: the PM corrected it on the card (comment5652103109) under the mechanical floor inreferences/contract-review.md— a new key on a published payload is alwaysyes— and a rename is a removal plus a new key.needs:contract-reviewis hung on card and PR; the changeset is gradedminoraccordingly, matching the four sibling duration-rename changesets of this same rule (#15677 · #15678 · #15679 · #15680), every one of which shipped asfeat(spec)!under## 17.4.0.What changed
SchemaLevelIsolationStrategy.performance.schemaCacheTTLnamed its unit in a source JSDoc — "Schema cache TTL in seconds" — and nowhere else. The.describe()thatcontent/docs/references/system/tenant.mdxrenders 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.performance.schemaCacheTTL: 3600performance.schemaCacheTtlSeconds: 3600Schema cache TTLSchema cache TTL in seconds3600The new spelling is
Ttl, notTTL, derived from how the suffixed family already spells itself on this tree rather than from the dispatch:cacheTtlSeconds11,ttlSeconds3,defaultCacheTtlSeconds1, and no key-positionTTLSecondsvariant anywhere.The kit, following the #15678 / #15679 shape the ruling names:
retiredKey()tombstone on the old spelling —tsctypes itneverand a value reaching the parse raises the rename prescription instead of being silently stripped (the nestedperformanceobject is not.strict())tenant-schema-cache-ttl-unit-in-keyand theRETIRED_KEYS_BY_MAJOR[18]rowsystem/SchemaLevelIsolationStrategy:performance.schemaCacheTTL, both asmigrations/entries/files withregistry.tsregenerated bygen:migration-registry(never hand-merged)stack.zod.tsdeclares 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 readingtenant-timeouts-unit-in-keyrecorded for the two sibling keys on this same fileSchemaLevelIsolationStrategySchema: 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 unitcontent/docs/references/system/tenant.mdxregenerated bygen:docs— all four rows move and the tombstone prescription renders in place of the old describeminorchangeset carrying the FROM to TO mapping and the ADR-0087 dispositionThe 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 noover this PR's own diff — exit 4, exactly one tell:The tell is not on the rename. It is on the tombstone. Two synthetic probes separate the two halves on the same instrument:
retiredKey(key line with no paired removalSo the accounting is exact: the removed
schemaCacheTTL:line buys one T1 unit, the addedschemaCacheTtlSeconds:line spends it, and the addedschemaCacheTTL: retiredKey(line is the surplus. EveryretiredKey()retirement raises this, and the line it raises on is the one that makes the accept set strictly narrower — the key becomesnever. 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.yesthis PR now declares rests on the contract-review floor (the rename really does add a key an author may write).check-clause2-carriers --pair 17954read exit 4 / C5 against the originalnoand reads exit 0 against the correctedyes.Verification
Heavy runs through
scripts/pm/os-verify-lock.sh, verdicts read from its ownVERDICTline; every gate's exit code captured before any pipe.pnpm --filter '@objectstack/spec^...' build— exit 0, empty closure (No projects matched):packages/spechas no workspace dependencies, so step ① is a documented no-oppnpm --filter @objectstack/spec build—VERDICT command-exit 0, run before every dist-reading gate and re-run after the last source editpnpm --filter @objectstack/spec test— 475 test files / 13507 tests passedpnpm --filter @objectstack/spec typecheck— exit 0 (tsc --noEmit+check:scripts-typecheck+check:test-typecheck)pnpm --filter @objectstack/spec check:generated— all 15 generated artifacts up to date aftergen:docs;check:authorable-surface,check:api-surface,check:migration-registry,check:spec-changesandcheck:upgrade-guideamong themcheck:authorable-surfacedoes not move for this key and that is correct: the ratchet records top-level keys per def and this one is nested underperformance(0 hits for the key acrossauthorable-surface/andauthorable-surface.base.json, against 4 for thesystem/MigrationPlan:lit control)check:duration-unit-keys(the gate as it stands onmain, not feat(spec): refuse a duration key whose JSDoc names a unit its describe does not #17635's) — exit 0check:objectui-pin-citations— exit 0, 16 asserting pin citations match.objectui-shagit grepof the pinned objectui checkout at.objectui-sha=53ded82bf7a494f54e344e19099dbf00854b8694(re-read from this tree) —schemaCacheTTL0 occurrences across 6409 tracked files, beside lit controlsTTL112,Ttl11,tenant819,cacheTTL1 and a dark control at 0, so the zero is a readingGate 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
ttlkeys with different units in one block, baretimeoutkeys, unit-less tenant timeouts #14478 / spec:tenant.zod.tsidleTimeout / sessionTimeout publish a describe with no unit, while the JSDoc one line above says seconds #14519 registered a D3 semantic entry but noRETIRED_KEYS_BY_MAJORrow, while [#14478 stack 4/6]system/: the 15 remaining duration keys carry their unit in the key name — ADR-0087 conversions with readers;metrics.zod.tssizeneeds an honest name, not the mechanical one #15679's nested duration renames onsystem/metrics.zod.tsandsystem/cache.zod.tsregistered both. This card follows [#14478 stack 4/6]system/: the 15 remaining duration keys carry their unit in the key name — ADR-0087 conversions with readers;metrics.zod.tssizeneeds an honest name, not the mechanical one #15679 (the shape ruling A names) and registers both. Successor: whoever lands the remaining four rename cards in this epic, who meets the same choice.packages/spec/scripts/check-duration-unit-keys.tsand its self-test (PR feat(spec): refuse a duration key whose JSDoc names a unit its describe does not #17635, sequenced last, red by construction), the five sibling files owned by spec: 3 duration key(s) inkernel/plugin-lifecycle-advanced.zod.tsname their unit only in JSDoc — #15939 Ruling A remediation (3 of the 21-row delta) #17780 spec: 1 duration key(s) inkernel/plugin-security-advanced.zod.tsname their unit only in JSDoc — #15939 Ruling A remediation (1 of the 21-row delta) #17781 spec: 4 duration key(s) insystem/logging.zod.tsname their unit only in JSDoc — #15939 Ruling A remediation (4 of the 21-row delta) #17782 spec: 5 duration key(s) insystem/metrics.zod.tsname their unit only in JSDoc — #15939 Ruling A remediation (5 of the 21-row delta) #17783 spec: 4 duration key(s) insystem/tracing.zod.tsname their unit only in JSDoc — #15939 Ruling A remediation (4 of the 21-row delta) #17785, andcontent/docs/releases/.⛔ 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-reportcomment on #17784.Generated by Claude Code