Skip to content

spec-property-retirement teaches that retiredFromLoadPath keeps a conversion off every load path — three seams replay it deliberately, and its worked example was deleted by #16693 #17895

Description

@os-bill

Filed by the domain:spec execution seat (session session_01MkQhmuuJAVDjmeWNixwDDH) while landing #16864's first share. ⛔ Filed unassigned and unlabelled: routing, grading and type are the triage seat's. ⚠️ The landing surface is .claude/skills/**, which is not this seat's lane — filed here rather than fixed, per the rule that any cross-seat request becomes a card in the target lane's queue.

This is share 3 of 3 of #16864's correction. Share 1 landed as PR #17888 (packages/spec carriers); share 2 is the ADR-0087 carrier.

What the checklist teaches, and why it is wrong

.claude/skills/spec-property-retirement/SKILL.md, around lines 209-215, teaches every future retirement author that only os migrate meta --from OLD_MAJOR may apply a retired conversion — i.e. that setting retiredFromLoadPath: true keeps the rewrite off every load path.

Measured, and it is not so. Three includeRetired: true literals in non-test source deliberately replay retired entries, reached by four runtime callers:

  • packages/spec/src/conversions/stored.ts — every stored-row rehydration seam. ⭐ The flag is pinned there, not offered: StoredConversionOptions is an Omit over the option, so a caller cannot turn it off.
  • packages/services/service-automation/src/engine.ts — flow rehydration at runtime.
  • packages/metadata-core/src/artifact-forward-conversion.ts — the artifact-ingestion door at boot, with two callers.

The flag's real jurisdiction is the authoring funnel normalizeStackInput, and that much is true and load-bearing — but it is not what the checklist says.

⇒ An author who reads this checklist, sets the flag on a default flip (old and new shapes both legal, meaning different things) and believes the rewrite is confined to history will be wrong. That is not hypothetical: #17885 is a measured instance where exactly that happens at boot.

And its worked example no longer exists

The same passage uses field-required-notnull-explicit as its worked example of the default-flip rule. #16693 removed that entry from the registry, so the checklist points a reader at a conversion that is not in the tree. Confirmed absent on origin/main.

What a fix looks like — ⛔ stated as inputs, not prescribed

The corrected jurisdiction sentence already landed in packages/spec (PR #17888) and can be read there rather than re-derived; ADR-0087's half is share 2. A replacement worked example has to be a default-flip entry that still exists — app-hidden-to-unpublished is one, and its own docblock argues the rule in the same terms the removed one did. ⛔ The owning lane decides.

Refs

#16864 (the parent correction) · PR #17888 (share 1, landed, carries the corrected sentence) · #17885 (a measured default-flip reaching a boot seam) · #16693 (removed the worked example) · #3903 / PR #4317 and #12772 (the two rulings that narrowed the flag's jurisdiction)


Generated by Claude Code

Activity

  1. claude commented on Sep 13, 2026

    @claude
    Contributor

    Claim: PM loop round 1
    Session: session_01DAcomhvR9kKizeYgg89Vo8 (GitHub os-project-manager, skills seat), claimed at 2026-09-13T07:11Z
    Branch: claude/issue-17895-retirement-checklist-jurisdiction
    Worktree: objectstack-issue-17895
    Domain: domain:skills (graded p2 Bug by the triage seat, 5651642939; governed .claude/skills/**)
    File surface (region-declared): .claude/skills/spec-property-retirement/SKILL.md, the retiredFromLoadPath: true checklist item at :209–:231 only — (1) the jurisdiction sentence is replaced by the one PR #17888 landed in packages/spec (copied, ⛔ not a third wording): the flag governs the authoring funnel normalizeStackInput only and does NOT keep a retired entry off the three includeRetired: true seams (stored-row rehydration packages/spec/src/conversions/stored.ts, flow rehydration packages/services/service-automation/src/engine.ts, the artifact-ingestion door packages/metadata-core/src/artifact-forward-conversion.ts); (2) the worked example field-required-notnull-explicit (withdrawn; packages/spec/src/conversions/registry.ts :2022 carries its tombstone) is replaced by a default-flip entry that exists on the tree at dispatch (app-hidden-to-unpublished, registry.ts :6242 at 84e6b05, re-confirmed by the dev); (3) one sentence records WHY the old example was withdrawn (its docblock's claim was false of this tree; packages/spec/CHANGELOG.md :2998). Net line budget: at most +2 lines, 0 preferred; if the file is under a ratchet the addition is paid by deletion inside the same item, ⛔ no re-wrap as currency. ⛔ Not #17894 (the ADR-0087 half, share 2), ⛔ not #17885.
    Container & model: S, mode:subagent, model: opus (default tier — --tier at 84e6b05 derives no mandate for .claude/skills/spec-property-retirement/**; not a clause-① file); seat review at the contract-review tier; governed (.claude/**) ⇒ draft PR at the human terminal with the four-piece, ⛔ never flipped ready by the seat
    Clause-②: no
    Thread-read: 5651642939 (triage grading, 2026-09-13T06:22Z — the newest comment at this claim)
    Serial constraints cleared: no open PR touches .claude/skills/spec-property-retirement/** (19 file lists read at 2026-09-13T06:58Z; PR #17515 and PR #17803 touch pm-dispatch/references/** only); last touch 2d34f32; verify lock free; H17 index ∩ this face = ∅; #17894 (share 2 of the same correction, docs/adr/0087) ⛔ not folded — different file, different acceptance, and held behind PR #17776; disjoint from #17656 and #17787.


    Generated by Claude Code

  2. claude commented on Sep 13, 2026

    @claude
    Contributor

    os-dev-report

    {
    "issue": 17895,
    "status": "done",
    "branch": "claude/issue-17895-retirement-checklist-jurisdiction",
    "pr": "#17947",
    "premise_still_valid": true,
    "summary": "Rewrote the retiredFromLoadPath: true item in .claude/skills/spec-property-retirement/SKILL.md. The jurisdiction clause now names the authoring funnel normalizeStackInput as the flag's only reach and says in as many words that it stops none of the three includeRetired seams, naming each (applyConversionsToStoredItem, automation-engine flow rehydration, applyArtifactForwardConversions); the worked example moved from the withdrawn field-required-notnull-explicit to app-hidden-to-unpublished; one sentence records why the old example was withdrawn, cited to packages/spec/CHANGELOG.md. Substance copied from the docblocks PR #17888 landed in packages/spec, not a third wording. The card's assignee was already os-project-manager at dispatch (the PM's step 1); never written by me. The PR is a DRAFT on a governed surface and was never flipped ready, queued or armed.",
    "tests": "No package was touched, so no dependency closure and no package test/typecheck is owed; the diff is one Markdown file under .claude/. Gates: derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths) at c4838a3, giving 17 commands; all 17 run in the foreground with $? captured before any pipe; reconciled with --ran carrying each exit code: 'dispatch-gates --ran: 17 derived famil(ies) accounted for - 17 run, 0 NOT-MEASURED (a DERIVED zero - all 17 recorded an exit code and none of them is 3).' All 17 exited 0. check:doc-formula-expressions first exited 3 = PREREQUISITE NOT MET (not a finding: @objectstack/formula and @objectstack/lint unbuilt); after turbo run build --concurrency=2 --filter=@objectstack/formula --filter=@objectstack/lint through scripts/pm/os-verify-lock.sh (VERDICT command-exit 0, waited 0s) it exited 0. Ratchet verdicts: 'check-skill-line-ratchet: .claude/skills/spec-property-retirement/SKILL.md is 337 lines (ceiling 337; headroom 0).' and 'widest table row is 326 bytes (pin 326; headroom 0).' Acceptance greps at c4838a3, both directions: field-required-notnull-explicit in SKILL.md = 0 with literal control retiredFromLoadPath = 3; app-hidden-to-unpublished = 1 in SKILL.md and 1 in packages/spec/src/conversions/registry.ts; normalizeStackInput = 1, applyConversionsToStoredItem = 1, applyArtifactForwardConversions = 1, 'flow rehydration' = 1, includeRetired = 1; negative control (a conversion id that does not exist) = 0. Seam re-measurement on origin/main, git grep -c 'includeRetired: true' over the three files: 1 / 1 / 1, no fourth non-test seam. Repo-wide pnpm lint is CI's; the narrowing here is a MEASUREMENT, three pieces: (1) population read from eslint.config.mjs itself, which declares no files: glob matching .md, every block being {ts,tsx,mts,cts,js,jsx,mjs,cjs}; (2) count from --format json on the changed file, 1 file, 0 errors, message 'File ignored because no matching configuration was supplied'; (3) invariance, the diff being one Markdown file that enters no eslint program, so no untouched file's verdict can move. No ablation or reverse verification was owed: this is a documentation correction with no guard to mutate. Control-character self-scan over the changed file: grep -naP over the control class, 0 hits; check:nul-bytes green over 8549 files.",
    "gates": "17 derived, 17 run, 17 exit 0, 0 NOT-MEASURED, 0 UNRUN (dispatch-gates --ran, exit codes recorded). Also run: node scripts/check-skills-token-ratchet.mjs, exit 0 (it covers the published skills/ bundle, not .claude/skills/). CI convergence not awaited, per the dispatch.",
    "line_budget": "Dispatch allowed at most +2 net lines; MEASURED, the real budget is 0 because check-skill-line-ratchet pins this file at 337 and the file was already at 337, headroom 0. Landed at 337 lines, ceiling untouched, no raise. Paid entirely by deletion, not by re-wrap: the retiredFromLoadPath item went 7 lines to 8, funded by deleting the withdrawn worked example, the false 'only migrate meta may apply the flip' sentence, and the parenthetical on why a retired entry still exists (the same fact is stated at the top of the same section); the fixture-disjointness item went 6 lines to 5 by deleting its stale illustration. No two lines were merged to free a line. Widest new line 113 bytes against the gate's 120-byte cap.",
    "deviations": "1) The dispatch's +2-line budget was falsified by the ratchet; landed at 0 net. 2) One same-class in-place fix inside the declared region :209-:231, the fixture item's illustration, which named the same withdrawn conversion as if it still fired (measured: registry.ts has no conversion writing storage.notNull; the only live entry touching required is field-conditionalRequired-to-requiredWhen, which fires on conditionalRequired). Deleted; the rule it illustrated is intact. Named with evidence in the PR body. 3) NEW MEASUREMENT the dispatch did not carry: packages/metadata-core/src/artifact-forward-conversion.ts now passes excludeConversionIds: DEFAULT_FLIPS_NOT_REPLAYED_HERE, a module-local list naming app-hidden-to-unpublished, landed by PR #17899 at 134b410; the card that reported it went to completed on 2026-09-12, before this card was graded. P3's three seams are unchanged and the card's premise stands, but the corrected item now teaches the per-seam opt-out duty rather than implying the flag confines a default flip by itself. Without that, the new worked example would read as 'a default flip the flag holds back', which is the same failure mode this card exists to end. #17885 itself was not touched. 4) Acceptance notes carried as a PR COMMENT, not in the body: a bare REST PATCH /pulls on a body already ending in the rule-line plus footer block is measured here to append a second footer, and that cell's prescription is to send no footer, which would downgrade the created body's session-URL form. 5) The standing decision frame in the dispatch matches the tree (md5 3327d02c56f8a0eca88569dad2270f32 over pm-dispatch SKILL.md :734-:755 at 84e6b05); no genuine fork arose, so it was not used.",
    "files_changed": [
    ".claude/skills/spec-property-retirement/SKILL.md (12 insertions, 12 deletions, 337 lines before and after)"
    ],
    "mcp_calls": "0 - every GitHub read and write went through REST via curl, and every card/commit read through git; no mcp__github__* call in the whole run",
    "open_questions": [],
    "out_of_scope_findings": [
    "fixed in place (bounded, same defect class, inside the declared region :209-:231): the fixture-disjointness item's illustration named the withdrawn field-required-notnull-explicit conversion as if it still fired; illustration deleted, the rule kept. Evidence in the PR body.",
    "noted, not filed: the idempotency item ends 默认值翻转不是幂等安全的,靠它自己的守卫加 retiredFromLoadPath. With the jurisdiction corrected, the flag removes only the authoring replay, and at the three data-at-rest seams idempotency rests on the entry's own guard alone. Not false, so below the filing bar, and the 0-line budget left no room. Carrier: this PR's review and the next edit to this item; written up in the PR's Acceptance notes comment.",
    "noted, not filed: check:skills-token-ratchet covers the published skills/ bundle only (34 authored files), so .claude/skills/ carries a line ceiling and no token ceiling, and there is no token reading to pair with the line reading on this path. A fact about the gate's declared roster, not a defect. Carrier: none; recorded in the PR's Acceptance notes comment so the next author does not re-derive it."
    ]
    }


    Generated by Claude Code

  3. claude commented on Sep 13, 2026

    @claude
    Contributor

    ACCEPT — skills seat (session session_01DAcomhvR9kKizeYgg89Vo8, GitHub os-project-manager), contract-review tier, read on PR #17947 head c4838a338 at 2026-09-13T07:40Z (report 5651977207 read after the head).

    Scope against the claim (5651866193): one file, the retiredFromLoadPath: true item and the adjacent fixture-disjointness item, both inside the declared region :209–:231 — +12/−12; 337 → 337 lines. The dispatch's budget (at most +2) was wrong and the dev corrected it from the gate: check-skill-line-ratchet pins this file at 337 with headroom 0, so the item grew 7 → 8 lines funded by deleting the withdrawn example, the false 「只有 migrate meta 可以应用翻转」 sentence and a redundant parenthetical, and the fixture item shrank 6 → 5 by deleting its stale illustration; no two lines were merged as currency. Recorded on the seat post as a dispatch-word correction (read the ratchet, not the file, before budgeting). Single-parent commit; origin/main has moved to bdb247d9 without touching this file.

    Read on the head, not the report: field-required-notnull-explicit 1 → 0 (lit control retiredFromLoadPath 3 → 3); app-hidden-to-unpublished 0 → 1 here and 1 in packages/spec/src/conversions/registry.ts; normalizeStackInput, applyConversionsToStoredItem, applyArtifactForwardConversions, includeRetired 0 → 1 each; migrate meta --from <old> 1 → 0; the CHANGELOG pointer present. The three seams re-measured on origin/main (1 / 1 / 1). A premise the dispatch did not carry, found by the dev and verified by the seat: PR #17899 landed DEFAULT_FLIPS_NOT_REPLAYED_HERE = ['app-hidden-to-unpublished'] with excludeConversionIds at the artifact door (artifact-forward-conversion.ts :291 / :339), so the item now teaches the per-seam opt-out duty instead of implying the flag confines a default flip — the right adaptation; without it the new example would have re-taught the old error. Draft, base main, first line Fixes #17895, no other closing keyword, one footer, ## 维护者速读(草稿) present, #17894 named as the separate ADR half; skip-changeset applied (.claude/**). Gates: 17 derived / 17 run / all exit 0 per the report (check:doc-formula-expressions first exit 3, re-run 0 after the locked build); check-clause2-carriers --pair 17947 exit 0 (seat-run). CI on c4838a33 at 07:39Z: 21 success, 11 skipped, 2 in progress (Lint & Repo Gates); TypeScript Type Check success; Governed Surface Queue Guard success on the PR leg. Widest new line 113 B per the dev; the 326-byte row is the pinned pre-existing table row.

    Contract review of record: PASS, comment 5652008844 on the PR (head c4838a33).

    In-place fix accepted (four conditions read: same defect class, mechanical, no other claim on the file, same gate family; region inside the claim; evidence in the PR body). Out-of-scope notes (the idempotency item's wording; .claude/skills/ carries a line ceiling and no token ceiling) stay noted, carriers as the dev named them.

    Governed ⇒ human terminal: needs-user-decision on PR #17947, 速读终稿 posted there, reviews requested from os-zhuang and hotlong. ⛔ Not flipped ready, queued or approved by this seat. The card stays pm:dispatched + assigned until MERGED.


    Generated by Claude Code

  4. claude commented on Sep 13, 2026

    @claude
    Contributor

    LANDED — skills seat (session session_01DAcomhvR9kKizeYgg89Vo8), 2026-09-13T09:01Z. PR #17947 merged by os-zhuang through the merge queue at 2026-09-13T08:59Z (see the PR's merged_at; merge commit on origin/main at this reading). Two readings: the queue ref gh-readonly-queue/main/pr-17947-* is gone, and origin/main carries the content — .claude/skills/spec-property-retirement/SKILL.md has excludeConversionIds 1 and app-hidden-to-unpublished 1, field-required-notnull-explicit 0, 337 lines; git log origin/main has (#17947) 1 with controls (#17946) 1 / (#99999) 0. The card was closed completed by the PR's Fixes line; the pm:dispatched residue and the assignee are stripped in this pass and read back (labels now bug · priority:p2 · domain:skills, no pm:*, no assignee). Contract review of record 5652008844 (head c4838a33, unchanged to landing); ACCEPT 5652008955. The ADR half stays #17894 (separate card, behind PR #17776).


    Generated by Claude Code

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

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions