Skip to content

docs(skills): correct the retiredFromLoadPath jurisdiction and worked example in the retirement checklist - #17947

Queued
claude[bot] wants to merge 1 commit into
mainfrom
claude/issue-17895-retirement-checklist-jurisdiction
Queued

docs(skills): correct the retiredFromLoadPath jurisdiction and worked example in the retirement checklist#17947
claude[bot] wants to merge 1 commit into
mainfrom
claude/issue-17895-retirement-checklist-jurisdiction

Conversation

@claude

@claude claude Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Fixes #17895

Share 3 of 3 of #16864's correction. Share 1 (the packages/spec carriers) landed as PR #17888; share 2 is the ADR-0087 half, tracked as #17894 and left to its own PR — different file, different acceptance. This PR touches no packages/ path at all: the whole diff is one file, .claude/skills/spec-property-retirement/SKILL.md, 12 insertions / 12 deletions.

What the checklist taught, and why it was wrong

The retiredFromLoadPath: true item asserted two things that are not true of this tree.

1. Jurisdiction. It said that only os migrate meta --from OLD_MAJOR may apply a retired conversion — that setting the flag keeps the rewrite off every load path. Measured on origin/main at 84e6b05, three non-test includeRetired: true literals replay retired entries deliberately:

seam file count
stored-row rehydration (pinned, not offered) packages/spec/src/conversions/stored.ts 1
flow rehydration, automation engine packages/services/service-automation/src/engine.ts 1
artifact-ingestion door packages/metadata-core/src/artifact-forward-conversion.ts 1

StoredConversionOptions is an Omit over includeRetired, so no caller of the first seam can turn it off.

2. Worked example. It argued the default-flip rule with field-required-notnull-explicit, which the registry deliberately does not contain — packages/spec/src/conversions/registry.ts carries a tombstone at that spot reading ⛔ WITHDRAWN — there is deliberately NO field-required-notnull-explicit conversion in this registry. A reader grepping the name met the checklist's worked example and the tombstone at the same time.

What changed

The corrected jurisdiction wording is copied in substance from what PR #17888 already landed in packages/spec (the retiredFromLoadPath docblock on MetadataConversion in conversions/types.ts and the includeRetired docblock in conversions/apply.ts) — deliberately not a third wording. The checklist translates register, not meaning.

  • The flag's jurisdiction is named as the authoring funnel normalizeStackInput and nothing else, and the item now says in as many words that it stops none of the three seams, naming each one.
  • The default-flip half is restated as the landed docblocks state it: soundness belongs to the seam, not to the entry or the flag — only a seam that can treat "this input predates the flip" as a fact may replay one, and a seam that cannot opts the id out through excludeConversionIds.
  • The worked example is now app-hidden-to-unpublished (registry.ts :6242), re-confirmed on the tree: retiredFromLoadPath: true, old and new shapes both legal and meaning different things, and its own docblock argues the rule in the same terms the removed entry did. It is also the live instance of the opt-out sentence above — artifact-forward-conversion.ts lists exactly that id in DEFAULT_FLIPS_NOT_REPLAYED_HERE.
  • One sentence records why the previous example was withdrawn: its docblock's "only migrate meta may apply it" claim was false of this tree, the artifact door applied it at boot, and the conversion was withdrawn as a result. Cited to packages/spec/CHANGELOG.md, whose entry carries the measurement.
  • The fixture-disjointness item lost its stale illustration, which named the same withdrawn conversion as if it still fired (see Bounded in-place fix below).

Line budget — the dispatch's budget was wider than the ratchet's

The dispatch allowed at most +2 net lines. Measured, the real budget is 0: scripts/pm/check-skill-line-ratchet.mjs pins this file at 337 and the file was at 337 lines, headroom 0. The item is therefore paid for entirely out of deletions, and the file lands at 337 again.

✓ check-skill-line-ratchet: .claude/skills/spec-property-retirement/SKILL.md is 337 lines (ceiling 337; headroom 0).
✓ check-skill-line-ratchet: .claude/skills/spec-property-retirement/SKILL.md: widest table row is 326 bytes (pin 326; headroom 0).

Where the lines came from, per the rule that additions are paid by deleting content and re-wrap is not currency:

item before after what was deleted
retiredFromLoadPath: true 7 8 the withdrawn worked example; the false migrate meta exclusivity sentence; the parenthetical on why a retired entry still exists (already carried by this section's own opening, which names spec-changes.json, the upgrade guide and the spec_changes MCP tool as the main channel)
fixture disjointness 6 5 the illustration (a new objects[].fields fixture may not carry a bare required: true, or the notNull conversion fires on it) — false today, see below
file 337 337

No line was merged into another to free a line; every line of headroom above is a clause that was removed because it was false or because the same fact is stated elsewhere in the same section. Every line of the new item is under the gate's 120-byte cap (widest 113).

Bounded in-place fix — the fixture item's illustration

packages/spec/src/conversions/registry.ts has no conversion that reads required: true and writes storage.notNull; the only notNull occurrences in that file are inside the tombstone comment. The required key is touched by exactly one live entry, field-conditionalRequired-to-requiredWhen, which fires on conditionalRequired. So the illustration described the same withdrawn conversion as live. It sits inside the region this card declared (:209–:231), it is the same defect class, and the rule it illustrates (before stays minimal and avoids other entries' keys) survives intact — only the stale example is gone.

Acceptance — both directions

Run on the branch at c4838a3, against .claude/skills/spec-property-retirement/SKILL.md unless stated:

check expected measured
field-required-notnull-explicit in this file 0 0
literal control: retiredFromLoadPath in this file > 0 3
app-hidden-to-unpublished in this file > 0 1
app-hidden-to-unpublished in packages/spec/src/conversions/registry.ts > 0 1
normalizeStackInput in this file > 0 1
applyConversionsToStoredItem in this file > 0 1
applyArtifactForwardConversions in this file > 0 1
flow rehydration in this file > 0 1
includeRetired in this file > 0 1
negative control: a conversion id that does not exist 0 0

Seam re-measurement on origin/main, git grep -c 'includeRetired: true' over the three files: 1 / 1 / 1. No fourth non-test seam appeared; the other hits in that grep are CHANGELOG prose and the types.ts docblock.

Gates

Derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths — the tool takes its own change set from the merge base), all 17 run, then 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. One needed a second pass: check:doc-formula-expressions first exited 3 — PREREQUISITE NOT MET, not a finding (@objectstack/formula and @objectstack/lint unbuilt); after turbo run build --filter=@objectstack/formula --filter=@objectstack/lint through the shared verify lock it exited 0.

Repo-wide pnpm lint is CI's run, and the narrowing here is measured rather than assumed. ① Population, read from eslint's own config: eslint.config.mjs declares no files: glob matching .md — every block is {ts,tsx,mts,cts,js,jsx,mjs,cjs}. ② File count, from --format json on the one changed file: 1 file, 0 errors, message File ignored because no matching configuration was supplied. ③ Invariance: the diff is one Markdown file and nothing in it enters any eslint program, so no verdict on any untouched file can move.

check:skills-token-ratchet covers the published skills/ bundle (34 authored files) and does not carry .claude/skills/, so there is no token reading to pair with the line reading here; it was run anyway and exited 0.

Changeset

skip-changeset, applied as a label. .claude/** publishes nothing: it is not in any package's files[], ships in no tarball, and no released symbol moves.

A measurement the dispatch did not carry

While confirming the seams I found that the artifact-ingestion door has changed since the card was written. 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, #17885, went to completed on 2026-09-12, before this card was graded. The door still opens the retired window; it refuses the default-flip class by id inside it.

That does not weaken the card — the flag's jurisdiction is unchanged and the checklist was still teaching the false sentence — but it does change what the corrected item has to say, so the item now teaches the opt-out duty rather than implying the flag confines a default flip by itself. Leaving it out would have let the new worked example read as "a default flip the flag holds back", which is the same failure mode this card exists to end. Not touching #17885 itself, per the dispatch.

维护者速读(草稿)

改了什么 —— 只改一份内部 agent 手册 .claude/skills/spec-property-retirement/SKILL.md 的一条清单项(外加同一区域里一句同类的过期举例)。不碰任何 packages/ 代码、不碰协议、不发布任何东西。

为什么改 —— 这份 checklist 是每个做「属性退役」的 agent 照着执行的作业单。它教的一句话是假的:它说设了 retiredFromLoadPath: true,改写就只会发生在 os migrate meta 里。实际上有三处运行期入口故意重放退役条目。这条假话已经造成过一次已发布的事故 —— 一个 conversion 在 boot 时把 NOT NULL 写到作者声明为可空的字段上,最后以撤销那个 conversion 收场。而 checklist 至今仍在教同一句话,并且仍拿那个已被撤销的 conversion 当样例。这一改把它拉回到 packages/spec 里已经落地的说法上,并换成树上还活着的样例。

风险与代价(含回滚) —— 风险极低:纯文档面,不进 npm 包,不影响运行时,也不影响任何构建产物。行数棘轮 337/337 原地不动,没有抬任何上限。回滚就是 revert 这一个 commit,无任何后续清理。唯一的取舍是行数预算为 0,所以为了写下正确的说法,删掉了三处内容:已撤销的样例、那句假的管辖权断言,以及一句在本节开头已经说过的括注。

席位意见 ——

你要做的 —— 这是 governed 面(.claude/**),按 Prime Directive #14 只能由维护者手合。PR 停在 draft,席位不会转 ready、不入队、不挂 auto-merge。请确认两件事:① 新写的管辖权句子与 packages/spec 里已落地的 docblock 说的是同一件事(而不是第三种说法);② 为了守住 0 行预算而删掉的三处内容,你认可它们的确可删。


Generated by Claude Code

…irement checklist

The `retiredFromLoadPath: true` checklist item taught that only
`os migrate meta --from <old>` may apply a retired conversion, and used a
worked example that no longer exists in the registry.

Both halves are corrected in place, at zero net lines:

- Jurisdiction: the flag governs the authoring funnel `normalizeStackInput`
  and nothing else, and it stops none of the three data-at-rest seams that
  replay retired entries with `includeRetired: true` — stored-row rehydration
  (pinned), automation-engine flow rehydration, the artifact-ingestion door.
  Wording follows the docblocks already landed in `packages/spec`.
- Default flips: soundness belongs to the seam, not to the flag; a seam that
  cannot treat "this input predates the flip" as a fact opts the id out via
  `excludeConversionIds`.
- Worked example replaced by `app-hidden-to-unpublished`, which exists on the
  tree and is exactly that case.
- One sentence records why the previous example was withdrawn.
- The fixture-disjointness item loses its stale illustration, which named the
  same withdrawn conversion as if it still fired.

Claude-Session: https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8
Co-authored-by: Claude <noreply@anthropic.com>
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 13, 2026
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Sep 13, 2026
@claude

claude Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

Acceptance notes

Carried as a comment rather than appended to the body: a bare REST PATCH /pulls on a body that already ends in the rule-line + footer block is measured in this repository to append a second footer, and the prescription for that cell is to send no footer at all — which would downgrade the created body's session-URL footer. The body is intact and was read back byte-identical after creation; this is the safer carrier for an addendum.

Noted, not filed — one clause in the same section that points the same way, left alone on purpose:

The idempotency item ends 默认值翻转**不是**幂等安全的,靠它自己的守卫加 retiredFromLoadPath. With the jurisdiction corrected, the second half carries much less than it reads: at the three data-at-rest seams the entry is replayed on every rehydration, so idempotency there rests on the entry's own guard alone — app-hidden-to-unpublished's if (app._unpublished != null) return app; is the whole of it. The flag removes only the authoring replay, which is real but is not what makes a default flip safe to run twice.

It is not false, so it does not meet the bar for a card (no reproducible defect, no declared-contract violation, no metadata-authoring trap), and the zero-line budget on this file left no room to restate it. Carrier: this PR's own review, and the next edit to this item — whoever next touches the retiredFromLoadPath passage will be reading four lines above it.

Two facts recorded so the next author does not re-derive them:

  • scripts/check-skills-token-ratchet.mjs covers the published skills/ bundle only (34 authored files); .claude/skills/ carries a line ceiling and no token ceiling. There is no token reading to pair with the line reading for a diff on this path.
  • The standing decision frame pasted into this card's dispatch matches the tree: sed -n '734,755p' .claude/skills/pm-dispatch/SKILL.md at 84e6b05 hashes to 3327d02c56f8a0eca88569dad2270f32, as declared. No genuine fork arose, so the frame was not needed.

Generated by Claude Code

@claude

claude Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

Contract review

Head: c4838a338 (PR #17947, card #17895) — read at 2026-09-13T07:40Z by the skills seat at the contract-review tier (served model claude-fable-5-1, get_session external_metadata.last_served_model at 2026-09-13T07:35Z); the build was default-tier (model: opus), so this record is the compensating control.

① derived judgments: the diff makes three falsifiable claims about packages/spec conversion behaviour in an internal playbook, each checked on origin/main bdb247d9: (a) retiredFromLoadPath reaches only the authoring funnel normalizeStackInput and none of the three includeRetired: true seams — git grep -c 'includeRetired: true' over packages/spec/src/conversions/stored.ts, packages/services/service-automation/src/engine.ts, packages/metadata-core/src/artifact-forward-conversion.ts → 1 / 1 / 1, stored.ts pins the flag via Omit on StoredConversionOptions: correct; (b) a default flip opts out per seam by id — DEFAULT_FLIPS_NOT_REPLAYED_HERE = ['app-hidden-to-unpublished'] and excludeConversionIds: at artifact-forward-conversion.ts :291 / :339 (landed by PR #17899): correct, and the new worked example app-hidden-to-unpublished exists in packages/spec/src/conversions/registry.ts (1 hit); (c) the withdrawn example's cause is recorded by pointer to packages/spec/CHANGELOG.md: matches the entry the triage grading quoted. No accept-set or public-surface change (check-clause2-carriers --pair 17947 exit 0, no widening tell).
② semver: .claude/** publishes nothing ⇒ skip-changeset (label applied, read back); consistent with the declared Clause-②: no.
③ boundary flags: none raised (open_questions: []); one bounded in-place fix inside the declared region (:209–:231) — the fixture item's illustration named the withdrawn conversion as if it still fired; deleted, the rule kept, evidence in the PR body — accepted under the four in-place conditions.

Implemented-by: claude/issue-17895-retirement-checklist-jurisdiction (mode:subagent)
Reviewed-by: session_01DAcomhvR9kKizeYgg89Vo8

Verdict: PASS. Governed surface (.claude/**): no gate carrier was hung (the content limb is reviewed here); the PR stays a draft for the maintainer's hand merge.


Generated by Claude Code

@claude

claude Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

维护者速读

改了什么 —— 内部退役 playbook .claude/skills/spec-property-retirement/SKILL.mdretiredFromLoadPath: true 那一条清单项。原文教作者「这个标志把翻转关在所有加载路径之外,只有 migrate meta 能应用」;新文写明它只管 authoring 漏斗,三处 data-at-rest seam(存量行、flow、artifact 门)都会重放退役条目,默认值翻转要按 seam 用 excludeConversionIds 退订;样例从已撤的 field-required-notnull-explicit 换成仍在的 app-hidden-to-unpublished,并写明旧样例为什么被撤。337 → 337 行,零余量下全靠删旧句支付。

为什么改 —— 旧句已经造成过一次已发布事故(boot 时把 NOT NULL 盖到作者写为可空的字段上,以撤销那个 conversion 收场),而 playbook 还在教下一位退役作者同一句话。这是 #16864 更正的第三份(spec 侧 PR #17888 已合,ADR 侧 #17894 另卡)。

风险与代价(含回滚) —— 纯文档面,不动代码;回滚 = revert 单文件 12 行。dev 顺手删了相邻 fixture 项里一条也引用旧样例的过期插图(同区域、同类、有证据)。

席位意见 —— 收。本席在 head c4838a33 上亲核:旧样例 0、新样例在 playbook 与 registry 各 1、三处 seam 与 normalizeStackInput 各 1、excludeConversionIds 机制在 origin/main(PR #17899,:291 / :339)核实;17 个门禁 exit 0;--pair 17947 exit 0;契约复核 PASS 记录 5652008844。CI 在读时 21 绿 2 在跑。

你要做的(一个动作) —— 受管面(.claude/**):CI 转绿后 approve 并合并。


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review September 13, 2026 08:09
@os-zhuang
os-zhuang enabled auto-merge September 13, 2026 08:10
@os-zhuang
os-zhuang added this pull request to the merge queue Sep 13, 2026
Any commits made after this event will not be merged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation needs-user-decision size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants