feat(spec)!: retire the scheduled cache-warmup strategy — the cron it selected left in this same major (ADR-0049) - #17638
Conversation
`CacheWarmup.strategy` no longer accepts `'scheduled'`, and its `.describe()` stops promising "(cron)". #16320 deleted `CacheWarmup.schedule` — the cron key this enum member selected — and declined the member itself as "a value, not a position this ruling names". That is a statement about that ruling's scope, not a finding that the value was sound: after the deletion the member declared a warmup cadence with no key left to configure it and no engine that has ever run one. ADR-0049 enforce-or-remove. An enum-VALUE narrowing is invisible to all four surface ratchets, so the prescription hangs on the enum's own `error` map dispatched by `issue.input` — the established route (`crypto.hash`, `managedBy: 'system'`, `HotReloadConfig.stateStrategy`) — and the ADR-0087 D3 semantic entry `cache-warmup-scheduled-strategy-retired` is the declaration. No D2 conversion and no `RETIRED_KEYS_BY_MAJOR` entry: no authorable key changed, and `CacheWarmup` is bound to no metadata type and embedded in no stack collection, so there is no authored source and no stored row to rewrite. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
…up enum `gen:docs` — the only one of the 15 generated artifacts `check:generated` proved stale. The four surface ratchets are byte-identical, as an enum-VALUE narrowing predicts. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
…up prescription `check:doc-authoring` Rule 3: a refusal message is printed verbatim at an author who has no tracker to resolve `#NNNN` with. The id moves to the adjacent `//` comment; the string keeps ADR-0049 as its durable reference. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
📓 Docs Drift Check3 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 135 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 8e4c4501d008fb9d665ce57cebb8775be4f1dbc7 && git checkout 8e4c4501d008fb9d665ce57cebb8775be4f1dbc7
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8f751cdc5652afb7dcd8e906d0f281234fc886c3 c1708532137a8de0fdfb47b8500a3156dbeec48d && git checkout -B drift-repro 8f751cdc5652afb7dcd8e906d0f281234fc886c3 && git merge --no-ff c1708532137a8de0fdfb47b8500a3156dbeec48d
node scripts/docs-audit/affected-docs.mjs --json 8f751cdc5652afb7dcd8e906d0f281234fc886c3 |
Part of #17157
CacheWarmup.strategyno longer accepts'scheduled', and its.describe()stops promising(cron).#16320 (PR #17146) deleted
CacheWarmup.schedule— the cron key this enum member selected — and declined the member itself as "a value, not a position this ruling names". That is a statement about that ruling's scope, not a finding that the value was sound. After the deletion the member declared a warmup cadence with no key left to configure it and no engine that has ever run one, while its own.describe()still promised(cron). ADR-0049 enforce-or-remove.The four prerequisite readings, re-measured on
origin/mainat690f083f83— one is FALSIFIEDmaincache.zod.ts:199-200:179-180— the card's numbers were taken on PR #17146's branchCacheWarmupSchemahas zero consumers outside its declaring fileCacheWarmup.scheduleis gone frommaincache-warmup-schedule-retiredexists and says what the card quotes#4 in full.
git grep -rn "cache-warmup-schedule-retired"onmainreturns exactly one hit:packages/spec/src/cron-typed-positions-retirement.test.ts:212, insideNEVER_REGISTERED_IDS— a list of ids that pin test asserts ABSENT. #16320 landed as a bare deletion (直接删, maintainer ruling 2026-09-10) with noRETIRED_KEYS_BY_MAJORentry, no D2 conversion and no D3 semantic entry. The statements the card quotes are real, but they live in the tombstone comment incache.zod.tsand in the ADR-0058 D7 ledger comment inpackages/qa/dogfood— not in a D3 ledger entry. The card's argument survives intact; only its citation was wrong.#2 with its lit control reproduced (the card's zeros were all taken beside one, so the control is re-measured too):
…and all six are non-runtime: the generated reference page's import line,
declaration-map/system.json,export-origins/system.json, the ADR-0058 D7 ledger comment inpackages/qa/dogfood, the #16320 pin test, and this package's ownsystem/cache.test.ts. Zero runtime readers.(The card measured 48 for the control on #17146's branch; 46 on
main. It is emphatically lit either way.)CacheWarmupis also bound to no metadata type and embedded in no stack collection:git grep -n 'DistributedCacheConfigSchema|CacheWarmupSchema' -- packages/spec/src/stack.zod.ts packages/spec/src/kernel/exits 1. So there is no authored source and no stored row anywhere that has ever carried this value, andos migrate metahas nothing to list.The retirement mechanism — established, not invented
The card is right that there is no value-level analogue of
retiredKey(), and none is invented here. The repo's established mechanism for an enum-VALUE retirement is the enum's ownerrormap, dispatched onissue.input— three precedents on this tree:object.managedBy: 'system'data/object.zod.ts(MANAGED_BY_SYSTEM_RETIRED)HookBodyCapability'crypto.hash'(#4391)data/hook-body.zod.ts(CRYPTO_HASH_RETIRED)HotReloadConfig.stateStrategy'disk'/'distributed'hot-reload-inert-state-strategies-retired.claude/skills/spec-property-retirement§2 names it outright as the only route that applies when the def survives and only its value set shrinks. Only the value that used to be legal gets the "was removed" sentence;strategy: 'sheduled'keeps zod's own enum message.Registration follows the same precedents: an ADR-0087 D3 semantic entry (
cache-warmup-scheduled-strategy-retired), not a D2 conversion (no source to rewrite) and nothing inRETIRED_KEYS_BY_MAJOR(no authorable key changed). The prescription deliberately carries noos migrate metasentence — promising a listing the tool cannot produce would be the very defect this card is about; the house pin (retired-key-migrate-sentence.test.ts) judges only prescriptions that name the command.⭐ The instrument — the negative leg, both legs quoted
An enum-value narrowing is invisible to all four ratchets, and this branch confirms it by measurement, not by argument: after
pnpm --filter @objectstack/spec build,git status --porcelainwas empty —authorable-surface/system.jsonstill carriessystem/CacheWarmup:strategybyte-for-byte (it keys on the position, which is untouched), andapi-surface/,json-schema.manifest/and the D7 ledger likewise. A green CI run is therefore not evidence this retirement works. The pin tests insystem/cache.test.tsare the only instrument, so both legs are shown.Resolution path:
cache.test.tsimports./cache.zod— the source, notdist/— so no build interposes between the mutation and the reading.Before: accepted. After: refused. The mutation is proved on disk by blob hash and by a grep anchored on the exact text that moved (an editor's exit code is not evidence); the restore is proved by
git diff HEADbeing empty, not by an exit code; and the script carriedtrap … EXIT INT TERMthroughout.The two neighbouring assertions pass in both legs, deliberately — they are controls, not the instrument: a typo (
'sheduled') must not be told it "was removed" (or the error map matches everything and the pin above proves nothing), and'eager'/'lazy'/ the'lazy'default must still parse.Changeset level
minor, on@objectstack/spec. Removing a published enum member is breaking; under the launch-window rule it ships asminor—check-changeset-no-major.mjsrefusesmajor, and breaking-ness is carried by the BREAKING banner plus the ADR-0087 disposition (anadr-0087: registered cache-warmup-scheduled-strategy-retiredmarker in the changeset body). The level is committed, so the gate — which reads viagit show HEAD:PATH— sees it.node scripts/check-changeset-no-major.mjs --base origin/mainandnode scripts/check-adr-0087-registration.mjs --base origin/mainboth exit 0 on this branch.Verification — all readings at
c170853213, the final commitpnpm --filter @objectstack/spec build && … typecheck && … test(under the shared verify lock)VERDICT command-exit 0; 473 test files, 13428 tests passedpnpm --filter @objectstack/spec check:generatedcheck:docs— regenerated withgen:docs(the reference page's enum cell);check:authorable-surface,check:api-surface,check:spec-changes,check:upgrade-guidewere green without regeneration, which is the ratchet-invisibility result abovepnpm lint(repo-wide,eslint . --no-inline-config)node scripts/pm/dispatch-gates.mjs --ranOf the 108, 7 returned
PREREQUISITE NOT MET(exit 3, or exit 1 with an explicit "nothing was measured" verdict) and are recorded as NOT MEASURED, never as green:check:doc-formula-expressions,check:doc-security-posture,check:skill-examples,check:docs-transcript-drift,check:dual-build-cjs-loads,check:lean-entry-closure,check:type-check-debt. Every one refuses for the same reason — it reads built sibling packages that this worktree has not built — and none reads a file this diff touches. CI builds the farm and decides them.One gate found a real defect in this branch and is fixed in
c170853213:check:doc-authoringRule 3 flagged#16320inside the customer-facing prescription string. A refusal message is printed verbatim at an author who has no tracker to resolve#NNNNwith, so the id moved to the adjacent//comment and the string keeps ADR-0049 as its durable reference. Gate now green.Sibling repo
git grep 'CacheWarmup' ../objectui→ no hits. The Console Pin Gate is not in play.Not in scope
The larger family question — whether
CacheWarmup/DistributedCacheConfig.warmupshould exist at all — is the ADR-0049 family-level decision the card names and is deliberately untouched here.Part of #17157rather thanFixes, since this closes the enum member and not that question.🤖 Generated with Claude Code
https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
Generated by Claude Code