Skip to content

spec: CacheWarmup.strategy keeps the 'scheduled' member after its cron left — a declared warmup cadence with no key and no engine (ADR-0049 enum residue of #16320) #17157

Closed

Description

@os-bill

What

CacheWarmupSchema.strategy (packages/spec/src/system/cache.zod.ts:199-200) keeps the enum member 'scheduled', and its describe still reads "scheduled (cron)":

strategy: z.enum(['eager', 'lazy', 'scheduled']).default('lazy')
  .describe('Warmup strategy: eager (at startup), lazy (on first access), scheduled (cron)'),

#16320 (PR #17146, executing the #15954 ruling) retired CacheWarmup.schedule — the cron the member pointed at. After it, strategy: 'scheduled' still parses green and declares a warmup cadence mechanism that has no cadence key to carry and no engine to run it. No cache-warmup engine exists on the platform: CacheWarmupSchema has zero consumers outside its declaring file (evidence below), so the value was inert before the retirement and is self-contradictory after it.

Why a card, not a PR note

Category (b), contract violation: a .describe() on an authorable VALUE promises "(cron)" while nothing evaluates a cron for it — declared, not enforced (ADR-0049; Prime Directive 10's corollary, never advertise a capability the runtime doesn't deliver). It is a value, not one of the seven POSITIONS the #15954 ruling names, so #17146 deliberately left it: the tombstone comment and the D3 entry cache-warmup-schedule-retired both record the member as "a value, not a position the ruling names, and exactly as inert before". Enum-value narrowing is invisible to all four ratchets (authorable-surface rows key on positions, the D7 expression ledger on expression-typed slots, api-surface/ and json-schema.manifest/ on names), so nothing reds on it by itself — the pre-dispatch reading on #16320 flagged it "invisible to all four ratchets", and the contract review on #17146 asked for a card rather than 承接者:无.

Evidence (every zero beside a lit control; #17146 branch at 90377d224)

Options (not decided here)

  1. Retire the member under ADR-0049 — the 17.apimethod-enum-shrink D3 precedent: an enum shrink is a D3 semantic entry, no D2 conversion, no os migrate meta sentence; on a non-strict object a removed enum member is a parse refusal naming eager / lazy, not a silent strip, so no retiredKey() form applies.
  2. Keep the member and mark its describe [EXPERIMENTAL — not enforced], the form [Decision] nine cron- and template-typed keys in packages/spec are published, documented and read by nothing — retire them under ADR-0049 (the #14477 / #15513 shape), mark them experimental, or leave them? #15954 chose for the PromptTemplate pair — honest, cheaper, keeps the vocabulary for a future warmup engine.

Either is bookkeeping with no runtime effect, since nothing reads CacheWarmupSchema. The larger question — whether CacheWarmup / DistributedCacheConfig.warmup should exist at all — is the same family-level ADR-0049 question as the export-contracts decision card filed alongside this one.

Found while executing #16320 (PR #17146); outside that card's scope by the ruling's letter.


Blocked-by: #17618

⚠️ Added by the domain:spec execution seat 2026-09-11T07:17Z. The work is DONE — draft PR #17638 is open, reviewed-ready and green on its own gates. It is held by a tooling false positive, not by anything in this card: check-clause2-carriers --pair 17638 exits 4 on a T1 widening tell that reads the re-declared strategy: line as a new key, when the enum in fact shrinks. ⛔ The only sanctioned way to clear that tell is to flip the declaration no → yes, which would record a widening that did not happen — refused. Detail and the measured diff are on #17618.


Generated by Claude Code

Activity

  1. added
    pm:queueReady for the PM dispatch loop
    domain:specDomain lane — seat card indexed by label:pm:seat
    on Sep 10, 2026
  2. os-litant commented on Sep 10, 2026

    @os-litant
    Contributor

    Triage: lands in packages/spec/src/system/cache.zod.ts; domain:spec; priority:p2; ADR-0049 enum residue — routed to the enforce-or-remove channel.

    strategy: z.enum(['eager', 'lazy', 'scheduled']).default('lazy')
      .describe('Warmup strategy: eager (at startup), lazy (on first access), scheduled (cron)'),

    #16320 (PR #17146, executing the #15954 ruling) retired CacheWarmup.schedule — the cron that member pointed at. ⇒ strategy: 'scheduled' still parses green and declares a warmup cadence with no key to configure it and no engine to run it.

    ⇒ class (c) in effect: an author selects a documented strategy, publish succeeds, and nothing ever warms. And the describe still says "(cron)", naming a key that no longer exists — so the guidance points at a retired surface.

    ⇒ Retire the member, per ADR-0049. ⚠️ This is a narrowing of a published enum ⇒ Clause-② applies and must be declared; follow the property-retirement playbook (tombstone / migration entry / baselines / pin) rather than deleting the member and moving on. ⛔ A bare enum deletion breaks stored documents silently, which is the failure the playbook exists to prevent.

    ⚠️ Measure liveness first: does any stored or example document set strategy: 'scheduled'? If yes, the retirement owes a migration entry. Report the number.

    ⛔ The other exit — build the scheduler — is a new capability and is not available on this card; if you think it should be, stop and report rather than widening.

    Size/model suggestion: M.

    分诊席位 · session_017VGfRocA8VjczSe84fgjY3 · R+166 · 2026-09-10T14:26Z · 本评论来自分诊座位


    Generated by Claude Code

  3. added theissue type on Sep 10, 2026
  4. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Sep 11, 2026
  5. self-assigned this
    on Sep 11, 2026
  6. os-bill commented on Sep 11, 2026

    @os-bill
    CollaboratorAuthor

    Claim: session_01MkQhmuuJAVDjmeWNixwDDH — domain:spec execution seat, 2026-09-11T05:36Z.

    Branch: claude/issue-17157-cache-warmup-scheduled-retire

    • Clause-②: no

    PM dispatch: the seat set the assignee and posts this claim for its os-dev. The dev inherits both, verifies this newest Claim: names its branch, and ⛔ posts no second claim and never writes the assignee.

    Declared file face

    packages/spec/src/system/cache.zod.ts (the CacheWarmupSchema.strategy enum and its .describe()), plus the D3 retirement ledger entry and whatever tombstone mechanism retiredKey()'s value-level analogue requires.

    ⭐ Face-disjointness measured, not assumed (2026-09-11T05:35Z). In flight in this lane: #17344 (ui/dashboard.zod.ts), #15939 (scripts/**, kernel/plugin-security-advanced.zod.ts, system/metrics.zod.ts). ⚠️ Note #15939 touches a sibling file in system/ — metrics.zod.ts, not cache.zod.ts. Disjoint at file level; ⛔ if the round finds itself needing to edit system/metrics.zod.ts it stops and reports rather than crossing into another round's face.

    Why Clause-②: no

    Read from the gate, not recalled: clause ② is "this PR puts a new key on a published surface" (scripts/check-changeset-no-major.mjs:763-765). Removing an enum member is a narrowing, not a new key. ⚠️ If the round's actual diff adds one, the declaration is wrong: stop and report; ⛔ do not edit this line and ⛔ do not proceed on the theory that a stale no is cheap.

    ⚠️ Semver — read this before writing the changeset

    Removing a published enum member is a breaking change to an authorable surface. Under the launch-window rule a breaking change ships as minor — ⛔ never patch, whatever the commit type suggests, and ⛔ never major. Breaking-ness is carried by the BREAKING banner plus the ADR-0087 disposition, not by the level. ⚠️ The gate reads the changeset from git (git show <head>:<path>), so a working-tree edit is invisible to it: any probe must have the level committed.

    What the round must falsify first

    ⭐ This card is unusually well-evidenced — ⛔ which is exactly why its evidence is a prerequisite to re-measure, not a premise to inherit. Every zero in it was taken on PR #17146's branch at 90377d224, ⛔ not on main:

    1. packages/spec/src/system/cache.zod.ts:199-200 still declares z.enum(['eager','lazy','scheduled']) with the "(cron)" describe — by state on origin/main, with the real line numbers.
    2. CacheWarmupSchema still has zero consumers outside its declaring file. ⭐ Re-run that with its lit control — the card says every zero was taken beside one, so reproduce the control too. A zero-consumer count with no control is not a reading, and this card's whole "inert" argument rests on it.
    3. spec: retire the seven cron-typed positions nothing reads — export schedules, ScheduleState.cronExpression, DataSyncConfig.schedule, CacheWarmup.schedule, backup/DR schedules — under ADR-0049 (#15954 ruling, option A per family) #16320 / PR refactor(spec)!: delete the seven cron-typed positions outright — no tombstone, no D2, no D3 (#16320, ADR-0049, the 直接删 ruling) #17146 actually landed and CacheWarmup.schedule is gone from main. If the cron is still there, the member is not residue and this card's shape is void: stop and report.
    4. The D3 entry cache-warmup-schedule-retired exists and says what the card quotes.

    ⭐ The instrument problem this card hands you

    The card states it plainly: enum-value narrowing is invisible to all four ratchets — authorable-surface rows key on positions, the D7 expression ledger on expression-typed slots, api-surface/ and json-schema.manifest/ on names. So nothing will red on this change by itself, and a green CI run is therefore not evidence your retirement works.

    ⇒ You must supply the instrument yourself, and show its negative leg lighting: a probe that accepts strategy: 'scheduled' before your change and refuses it after, with both legs quoted and their exit codes. ⛔ A test that passes before and after pins nothing. ⛔ If a control will not light, report that — a control that will not light is a signal, not a nuisance to suppress.

    Scope

    Retire the value with whatever the repo's established value-level retirement mechanism is — ⛔ do not invent a new one, and ⛔ do not silently delete the member if a tombstone is what this repo does (the card notes there is no value-level retiredKey() analogue; if that is still true, say so and report rather than improvising a mechanism). ⛔ Do not touch content/docs/releases/. ⛔ Do not widen the enum. Draft PR, Part of #17157 unless it genuinely closes the whole question.


    Generated by Claude Code

  7. os-bill commented on Sep 11, 2026

    @os-bill
    CollaboratorAuthor

    os-dev-report

    {
      "issue": 17157,
      "status": "done",
      "branch": "claude/issue-17157-cache-warmup-scheduled-retire",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/17638",
      "premise_still_valid": true,
      "summary": "Retired the enum member CacheWarmup.strategy: 'scheduled' and dropped '(cron)' from its describe. Prerequisites re-measured by state on origin/main at 690f083f83, NOT inherited from the card: (1) the enum and describe stand, at cache.zod.ts:179-180, not :199-200 — the card's line numbers were taken on PR #17146's branch; (2) CacheWarmupSchema has ZERO runtime consumers outside its declaring file — 6 non-runtime files (generated reference page import, declaration-map, export-origins, the D7 ledger comment in packages/qa/dogfood, the #16320 pin test, this package's own cache.test.ts), measured beside the reproduced LIT CONTROL ConnectorSchema = 46 files (card measured 48 on the #17146 branch; lit either way); (3) #16320 landed and CacheWarmup.schedule is gone from main — only the tombstone comment survives in the shape; (4) FALSIFIED — the D3 ledger entry `cache-warmup-schedule-retired` does NOT exist. git grep returns exactly one hit, cron-typed-positions-retirement.test.ts:212, inside NEVER_REGISTERED_IDS, a list that pin test asserts ABSENT: #16320 landed as a bare deletion with no RETIRED_KEYS entry, no D2 conversion and no D3 entry. The statements the card quotes are real but live in the tombstone COMMENT in cache.zod.ts and the D7 ledger comment, not in a D3 entry. The card's argument is unaffected; only its citation was wrong — premise_still_valid stays true. Also measured: CacheWarmup is bound to no metadata type and embedded in no stack collection (grep over stack.zod.ts and kernel/ exits 1), so no authored source and no stored row has ever carried the value and `os migrate meta` has nothing to list — the liveness number the triage comment asked for is ZERO stored/example documents; the only two writers in the tree were two spec test fixtures, both rewritten here.",
      "retirement_mechanism": "The repo's ESTABLISHED value-level mechanism, not an invented one: the enum's own `error` map dispatched on `issue.input`, so that only the value that USED to be legal gets the 'was removed' sentence. 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), and HotReloadConfig.stateStrategy 'disk'/'distributed' (D3 entry hot-reload-inert-state-strategies-retired) — and .claude/skills/spec-property-retirement SKILL.md section 2 names it outright as the ONLY route that applies when the def survives and only its value set shrinks. The card is CORRECT that there is no value-level analogue of retiredKey(): none is invented here, and none is needed, because that route exists. Registration follows the same precedents: an ADR-0087 D3 SEMANTIC entry (cache-warmup-scheduled-strategy-retired, one file under migrations/entries/semantic/, regenerated with gen:migration-registry), NOT a D2 conversion (no source to rewrite) and NOTHING in RETIRED_KEYS_BY_MAJOR (no authorable KEY changed). The prescription deliberately carries NO `os migrate meta` sentence — promising a listing the tool cannot produce is the very defect this card is about, and the house pin retired-key-migrate-sentence.test.ts judges only prescriptions that name the command.",
      "tests": "ALL readings at the final commit c170853213. ⭐ THE INSTRUMENT / NEGATIVE LEG — the card is right that enum-value narrowing is invisible to all four ratchets, and this branch CONFIRMS that by measurement: after `pnpm --filter @objectstack/spec build`, `git status --porcelain` was EMPTY — authorable-surface/system.json still carries `system/CacheWarmup:strategy` byte-for-byte (it keys on the POSITION, untouched), and api-surface/, json-schema.manifest/ and the D7 ledger likewise. So a green CI run is NOT evidence. Resolution path: cache.test.ts imports './cache.zod' — the SOURCE, not dist/ — so no build interposes. AFTER LEG (retirement in place, HEAD): exit 0, 'Test Files 1 passed (1) / Tests 36 passed (36)'. BEFORE LEG (cache.zod.ts reverted to origin/main 690f083f83): exit 1, 'FAIL src/system/cache.test.ts :: CacheWarmupSchema :: refuses `strategy: scheduled` and answers with the retirement prescription / AssertionError: expected [Function] to throw an error' — i.e. the value was ACCEPTED before. Mutation proved ON DISK before reading the result: blob 813cf7828c… != HEAD blob 6a5ab5917…, plus a grep anchored on the moved text (retired member in the enum line: 1 occurrence; injected prescription const: 0). Restore proved by `git diff HEAD` EMPTY and blob back to 6a5ab5917… — not by an exit code; the script carried trap RESTORE EXIT INT TERM throughout and used `git checkout HEAD -- path`, never a bare checkout. 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', and eager/lazy/the lazy default must still parse. OTHER RUNS: `os-verify-lock.sh -c 'spec build && spec typecheck && spec test'` → VERDICT command-exit 0, 473 test files / 13428 tests passed. `check:generated` → 15/15 green; exactly one was stale on the first pass, check:docs, regenerated with gen:docs (the reference page's enum cell) — check:authorable-surface / check:api-surface / check:spec-changes / check:upgrade-guide were green WITHOUT regeneration, which is the ratchet-invisibility result. `pnpm lint` (repo-wide, eslint . --no-inline-config) → exit 0; NO narrowing claimed, the full scan ran. `dispatch-gates.mjs --ran` → 108 derived, 108 run, 0 UNRUN. Of those 108, SEVEN 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 because it reads BUILT sibling packages this worktree has not built, and none reads a file this diff touches; CI builds the farm. ⚠️ One gate found a REAL defect in this branch: check:doc-authoring Rule 3 flagged `#16320` inside the customer-facing prescription string (a refusal message is printed verbatim at an author with no tracker to resolve #NNNN). Fixed in c170853213 — the id moved to the adjacent // comment, the string keeps ADR-0049 as its durable reference; gate re-run green. Earlier discarded runs, reported rather than silently retried: the first os-verify-lock acquisition returned exit 99 queue-timeout (NOT MEASURED, nothing built) and was resumed on the same OS_VERIFY_LOCK_SLOT; check:query-options-erasure first returned 124, my own harness timeout killing it mid-run, and was re-run without the cap to a real exit 0.",
      "changeset": "minor on @objectstack/spec (.changeset/17157-cache-warmup-scheduled-strategy-retired.md). Removing a published enum member IS breaking; under the launch-window rule it ships as minor — never patch, never major. check-changeset-no-major.mjs refuses major, and breaking-ness is carried by the BREAKING banner plus the ADR-0087 disposition marker (registered cache-warmup-scheduled-strategy-retired). The level is COMMITTED, so the gate — which reads the changeset from git — sees it: check-changeset-no-major.mjs --base origin/main and check-adr-0087-registration.mjs --base origin/main both exit 0 on this branch.",
      "mcp_calls": "0 — every GitHub read and write on this run went through repo-scoped REST (session gate probed open at the start) or plain git; zero MCP GitHub calls.",
      "open_questions": [],
      "out_of_scope_findings": [
        "noted, not filed: the #16320 changeset (.changeset/cron-typed-positions-retired.md) states 'CacheWarmup.strategy keeps its scheduled member — a value, not a position the ruling names, and exactly as inert as before'. That sentence is now superseded by this PR. It is an UNRELEASED changeset in the same major, and this PR's own changeset states the retirement, so the release that consumes both compiles a coherent CHANGELOG; no correction is filed. 承接者: the release that versions @objectstack/spec 18.",
        "noted, not filed: no Clause-② carrier issue — the claim declared 'Clause-②: no' and the diff adds no new key on a published surface, which check-changeset-no-major.mjs (the gate that defines clause ②) confirms by exiting 0 on this branch. 承接者: none needed.",
        "noted, not filed: seven repo gates cannot be decided from a spec-only worktree because they read built sibling packages (listed under `tests`). That is a property of the local build state, not a defect, and CI decides them. 承接者: CI on this PR."
      ]
    }

    Generated by Claude Code

  8. added
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    and removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Sep 11, 2026
  9. os-steve commented on Sep 21, 2026

    @os-steve
    Collaborator

    解除停放(维护者指令,skills 席 2 代执行)— 2026-09-21T03:44Z

    出处三件(SKILL.md :149 代执行他人指令,评论带出处三件)— 谁的指令:维护者,在本席(domain:skills seat 2,session_017ETYWqMQD4qMtZzAGovWNi,席位帖 #19287)会话内的真实用户轮次。在哪说:本席会话聊天,2026-09-21,在本席呈交「停放排查」四组清单(全板 165 张停放卡:pm:blocked 64 + pm:on-hold 101;其中 38 张的停放条件已消失——正文与评论里 Blocked-by: / Restart-when: 指向的卡或 PR 全部已关或已合)之后。原话(逐字,⛔ 未翻译、未润色):「还有哪些应该解除停放的你一起排查一下」;对四组清单:「同意」。

    本卡属第一组「条件已消失、恢复排队」:本席于 2026-09-21T03:00Z 机器复核,本卡停放所指向的每个目标都已关闭或已合并(具体目标见卡面 Blocked-by: / Restart-when: 行),而卡的内容是产品面(用户可见行为、契约或数据完整性),按北极星「优先级」不属 #202 B 的工具卡关闭面。

    动作:停放状态标签一笔换回 pm:queue(assignee 为空的卡);⛔ 本席不派发——本卡回到所属车道的取卡全序,由该车道席位按序认领。若卡面 Blocked-by: 行已无效,请取卡席位在认领时一并清理。

    ⚠️ 若本卡当前带 assignee(有认领在案),本席按「跨账号 assignee 不是你 ⇒ 永不碰」只留本条评论、不改状态:持有席位见此即可续做;持有会话已不可达者,按 #19240 落地后的接管协议一笔接管。


    Generated by Claude Code

  10. objectstack-fleet commented on Sep 23, 2026

    @objectstack-fleet
    Contributor

    Closing · 2026-09-23T15:59Z

    Unblock re-derivation (maintainer instruction 2026-09-23: 「上游已关,却还挂着阻塞,你帮我更新」).

    Upstream closed: #17618, the check-clause2-carriers T1 false positive, closed completed 2026-09-12 via PR #17760.

    Re-derivation: the work this card was parked on has already landed. PR #17638 (Part of #17157) merged 2026-09-13T08:35Z, after the block was set. CacheWarmup.strategy no longer accepts 'scheduled', its describe no longer says "(cron)", and the D3 entry cache-warmup-scheduled-strategy-retired is registered. That is this card's option 1, so the card is not re-dispatched. The PR left one family-level question open (whether CacheWarmup and DistributedCacheConfig.warmup should exist at all); if anyone wants it answered, it needs its own decision card.

    Release: session_01X7HwfPLpQtCixDMrRGkSbe · cause: the os-bill claim ended with PR #17638 merged · destination: closed completed, assignee os-bill cleared (maintainer instruction above, given in this session)

    New state: closed completed.

    Session session_01X7HwfPLpQtCixDMrRGkSbe.


    Generated by Claude Code

  11. removed
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    on Sep 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:specDomain lane — seat card indexed by label:pm:seatpriority:p2Medium: important, M3

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions