|
| 1 | +--- |
| 2 | +"@objectstack/spec": minor |
| 3 | +--- |
| 4 | + |
| 5 | +feat(spec)!: retire the `scheduled` cache-warmup strategy — the cron it selected left in this same major, and nothing ever warmed on a cadence (ADR-0049) |
| 6 | + |
| 7 | +<!-- adr-0087: registered cache-warmup-scheduled-strategy-retired --> |
| 8 | + |
| 9 | +**BREAKING** in the accept-set sense, landing in the launch window as `minor` (the |
| 10 | +lockstep convention: `major` is refused by `check-changeset-no-major`, and breaking-ness |
| 11 | +is carried by this banner plus the ADR-0087 disposition above). |
| 12 | + |
| 13 | +`CacheWarmup.strategy` no longer accepts `'scheduled'`. |
| 14 | + |
| 15 | +| | before | after | |
| 16 | +|:--|:--|:--| |
| 17 | +| accept set | `'eager' \| 'lazy' \| 'scheduled'` | `'eager' \| 'lazy'` | |
| 18 | +| describe | `… lazy (on first access), scheduled (cron)` | `… lazy (on first access)` | |
| 19 | +| a document writing it | parsed green | **refused**, with the prescription | |
| 20 | + |
| 21 | +**The one-line fix:** write `strategy: 'eager'` (warm at startup) or `strategy: 'lazy'` |
| 22 | +(warm on first access). For a warmup on a **cadence**, declare a `job` — that is the one |
| 23 | +cron slot this platform evaluates: |
| 24 | + |
| 25 | +```ts |
| 26 | +defineStack({ |
| 27 | + jobs: [{ name: 'warm_config_cache', schedule: { expression: '0 * * * *' }, handler: 'warmConfigCache' }], |
| 28 | +}); |
| 29 | +``` |
| 30 | + |
| 31 | +## Why |
| 32 | + |
| 33 | +`cron-typed-positions-retired` (17.x → 18, #16320) deleted `CacheWarmup.schedule`, the |
| 34 | +cron key this enum member selected, and left the member standing on the reading that it is |
| 35 | +"a value, not a position the ruling names". That was a statement about that ruling's |
| 36 | +**scope**, not a finding that the value was sound. After the deletion the member declared a |
| 37 | +warmup cadence with **no key left to configure it and no engine that has ever run one**, |
| 38 | +while its own `.describe()` still promised `(cron)` — ADR-0049 declared-not-enforced, in |
| 39 | +the form Prime Directive 10 names outright: a capability advertised that the runtime does |
| 40 | +not deliver. |
| 41 | + |
| 42 | +Nothing on the platform reads `CacheWarmupSchema`: outside its declaring file it resolves |
| 43 | +to the generated reference page's import line, the `declaration-map` / `export-origins` |
| 44 | +catalogues, the ADR-0058 D7 ledger comment and two of this package's own test files — zero |
| 45 | +runtime consumers, measured beside a lit control (`ConnectorSchema`, 46 files, same sweep). |
| 46 | +So **no runtime behaviour changes**: no warmup has ever run on a schedule, before or after. |
| 47 | +What changes is that the contract stops promising it. |
| 48 | + |
| 49 | +## The retirement kit |
| 50 | + |
| 51 | +- the member leaves `z.enum(['eager','lazy','scheduled'])` and the `.describe()` stops |
| 52 | + saying `(cron)` (`system/cache.zod.ts`) |
| 53 | +- the prescription hangs on **the enum's own `error` map, dispatched by `issue.input`** — |
| 54 | + the established route for an enum-VALUE retirement (`crypto.hash` on |
| 55 | + `HookBodyCapability`, `object.managedBy: 'system'`, `HotReloadConfig.stateStrategy`). |
| 56 | + There is no value-level analogue of `retiredKey()` and none is invented here. Only the |
| 57 | + value that **used to be legal** gets the "was removed" sentence; `strategy: 'sheduled'` |
| 58 | + keeps zod's own enum message, which already lists the legal values |
| 59 | +- an **ADR-0087 D3 semantic entry**, `cache-warmup-scheduled-strategy-retired` — a semantic |
| 60 | + entry rather than a D2 conversion because there is **no source to rewrite**: `CacheWarmup` |
| 61 | + is bound to no metadata type and embedded in no stack collection, so no authored document |
| 62 | + and no stored row has ever carried this value, and `os migrate meta` has nothing to list. |
| 63 | + That is also why the prescription carries **no `os migrate meta` sentence** — it would |
| 64 | + promise a listing the tool cannot produce, which is the very defect this card is about |
| 65 | +- **nothing in `RETIRED_KEYS_BY_MAJOR`** — no authorable *key* changed — and **no |
| 66 | + `retiredKey()` tombstone**, which tombstones keys, not values |
| 67 | +- pin tests (`system/cache.test.ts`): the refusal and its prescription, a **lit control** |
| 68 | + that a typo is *not* told it "was removed", and that the surviving members and the |
| 69 | + `'lazy'` default still parse. `cron-typed-positions-retirement.test.ts`'s warmup fixture |
| 70 | + moves to `'eager'`, since a fixture must be well-formed under the current schema |
| 71 | + |
| 72 | +## ⚠️ The four surface ratchets are byte-identical across this change, and that is correct |
| 73 | + |
| 74 | +An enum-VALUE narrowing moves no position, no exported name and no expression-typed slot: |
| 75 | +`authorable-surface/` keys on **positions** (`system/CacheWarmup:strategy` stays — the key |
| 76 | +is untouched), the ADR-0058 D7 ledger on **expression-typed slots**, and `api-surface/` / |
| 77 | +`json-schema.manifest/` on **names**. None of them reads a def's *value set*, so none of |
| 78 | +them can fail on this change — the `crypto.hash` precedent measured exactly this. The pin |
| 79 | +tests above are therefore not a formality: they are the only instrument this retirement |
| 80 | +has, and a green CI run on its own says nothing about whether the value is gone. |
0 commit comments