Repository navigation
docs(governed): the retirement route is retiredKey() on any shape, with the retiredAfter stamp and the step-18 registry shape - #20797
Conversation
…dAfter stamp; ADR-0087 window per entry The retirement kit's route table and AGENTS.md Post-Task Checklist step 3 now prescribe a retiredKey() tombstone whether or not the schema is strict: on a closed shape a bare deletion is loud but reports only an unrecognized key, losing both the prescription and the tsc channel (retired-key.ts header). The strictObject guidance map is named only for a spelling the shape never declared, such as a retired key's old alias, where a tombstone has no property to replace (data/mapping.zod.ts). The retiredFromLoadPath checklist item gains the required retiredAfter stamp: tsc refuses its absence, a new retirement carries the current packages/spec label, and retired-after.census.test.ts pins each value. ADR-0087's artifact-window bullet said a floor at or above the runtime replays nothing; since e956924 the door replays every retired entry whose retiredAfter the floor does not exceed. Amended in the dated style. Lines paid by deleting narrative with another home: the orphan-leg history (orphans.mts header) and the withdrawn-example sentence. Claude-Session: https://claude.ai/code/session_01KTZmMfzVzjNvyaLyQ8mHvg Co-authored-by: Claude <noreply@anthropic.com>
…ape on main For protocol 18 the retirement kit no longer tells an author to append to MIGRATIONS_BY_MAJOR[18].conversionIds and extend a rationale string. On main the conversion goes only into MAJOR_18_CONVERSIONS in conversions/registry.ts, inserted at its identifier's sorted position per that list's header (CONVERSIONS_BY_MAJOR[18] and the step's conversionIds both derive from it), and the rationale gains one STEP18_RATIONALE fragment at its D3 semantic id's sorted position. The merge pins each header names refuse an append at the tail. Earlier steps keep the old wording, and the misspelled-id warning is scoped to them: it cannot happen on step 18, whose ids are derived. Claude-Session: https://claude.ai/code/session_01KTZmMfzVzjNvyaLyQ8mHvg Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Reviewed for PR #20797 (family PR: #20465 chain head, #20575 member), two commits on base ① Derived judgments1. The route — kit §2 row 1 「任何 shape」(strict 与否) and AGENTS.md step 3's first clause: right. 2. Row 2 「从未声明的拼写」→ guidance map, and the departure from the triage's example: right, and the measurement holds. 3. The term rename 「strict 删除」→「无墓碑删键」 (§2 ledger table 4. Every deleted sentence has a home — no lost rule.
5. The 6. The step-18 D3-chain item (kit 7. AGENTS.md step 3 against AGENTS.md's own header (lines 11-19): right. The rewritten clause is rule text only: one executable rule per sentence, no incident narrative, no ruling date or quotation, no issue-number citation, no model name; its provenance is the code it names ( 8. The ADR-0087 amendment against the ruling's words: right.
Check-run state read on
② Semver levelThe diff is three files — ③ Boundary flagsDev report (
Reviewer's own findings, none a FAIL:
Implemented-by: VERDICT: PASS |
维护者速读(终稿)— PR #20797 · 退役路线统一为
|
… — no queued PR left the queue on a draft conversion (objectstack-ai#20845) Fixes objectstack-ai#20764 Clause-②: no ## What changes `.claude/skills/pm-dispatch/references/platform-readings.md`, lines 41-43 only. The rewrite is net 0 lines, and the three new lines are 120 / 118 / 120 bytes (cap 120). | line | before | after | |:--|:--|:--| | `:41` | 转 draft 不是可靠的踢队手段:两向相反读数并存,处置按最坏走。 | 转 draft 不是可靠的踢队手段:本仓与姊妹仓均见已入队转 draft 仍保位照合,处置按最坏走。 | | `:42` | 本仓转 draft 同时掉 auto-merge 与队列成员资格,不自动恢复,转正后重挂;姊妹仓曾保位照合。 | 本仓转 draft 同秒掉 auto-merge 仅见于未入队时,转正后重挂;已入队者 2026-08-08、09-29 照合。 | | `:43` | 补救:转 draft 与卸载 auto-merge 都做 —— 本仓卸载 auto-merge 单独不踢队。 | 补救:转 draft 与卸载 auto-merge 都做,本仓卸载单独不踢队;两手齐做亦未见踢队(08-08、09-28)。 | The remedy on `:43` is unchanged: do both acts. No rule is added. The in-file citations are dates, because `pnpm check:pm-skill-id-lint` goes red on a `#`-number anywhere under `.claude/skills/pm-dispatch/`, and this file cites no PR number today. The PR numbers and timeline events are in this body. That is where the id-lint header puts a reading's provenance. ## The readings (re-readable: `GET /repos/objectstack-ai/objectstack/issues/N/timeline`) I ran a read-only timeline scan over objectstack-ai#4700-objectstack-ai#4900, objectstack-ai#6650-objectstack-ai#6850 and objectstack-ai#20400-objectstack-ai#20840. It looked for two patterns: a `convert_to_draft` between `added_to_merge_queue` and the next `removed_from_merge_queue`, and a `convert_to_draft` on a PR that was armed but not queued. The scan found every row below. The table is complete for those windows only, not for the whole repo. **Queued, then converted to draft.** None of the four left the queue because of the conversion. | PR | queued | draft | disable sent? | what followed | |:--|:--|:--|:--|:--| | objectstack-ai#6732 | 2026-08-08T14:01:50Z | 14:04:32Z | claimed in objectstack-ai#6799's text; no `auto_merge_disabled` event | the queue merged it at 14:38:56Z, 34 min later, still draft | | objectstack-ai#20420 | 2026-09-28T13:43:01Z | 14:02:28Z | claimed in the seat's 14:04Z comment; no event | its own group `pr-20420-3cf6449` had `Lint & Type Check` = failure at 14:01:49Z, before the draft; `github-merge-queue[bot]` removed it at 14:11:24Z, unmerged | | objectstack-ai#20442 | 2026-09-28T13:45:42Z | 14:02:50Z | claimed in the seat's 14:04Z comment; no event | the queue built a new group `pr-20442-b285508` for it at 14:11:29Z, 9 min after the draft; that group's `CI` = failure at 14:30:14Z; removed at 14:35:44Z, unmerged | | objectstack-ai#20695 | 2026-09-29T23:32:51Z | 23:57:02Z | no (the card discloses: draft alone) | the queue merged it at 00:04:21Z, 7 min later, still draft | **Armed, not queued, then converted to draft.** Each one dropped auto-merge. | PR | armed | draft | `auto_merge_disabled` | after | |:--|:--|:--|:--|:--| | objectstack-ai#4745 | 2026-08-03T01:28:46Z | 01:29:30Z | 01:29:30Z (same second) | ready, re-armed 01:30:52Z, queued, merged | | objectstack-ai#6727 | 2026-08-08T13:42:58Z | 13:44:36Z | 13:44:36Z (same second) | ready, re-armed 13:46:02Z | | objectstack-ai#6829 | 2026-08-08T23:37:05Z | 23:40:25Z | 23:40:26Z (1 s) | ready and enqueued 6 h later | Each disable event carries the converter as its actor. A timeline cannot tell whether the platform wrote it or a scripted follow-up did. The line records what was observed ("同秒"). ## Where the old reading came from - The "drops queue membership" reading first appears in `db6581a5` (objectstack-ai#4893, card objectstack-ai#4892, 2026-08-03). It cites no PR. That is the same day objectstack-ai#4745 dropped auto-merge on an armed PR that was not queued. The queue-membership half has no instance behind it. - `cd704cc4` (objectstack-ai#6799) later cited objectstack-ai#6732 for "only draft evicts; disable alone does not". In objectstack-ai#6732's own timeline the PR stays queued after the draft and is merged by the queue 34 minutes later. - The sister-repo half ("姊妹仓曾保位照合", objectui 2026-08-25) is carried over as it was. I did not re-read it. ## How this departs from the triage direction (5903760674) I did not choose these quietly. Each one is a place where a measurement changed what the direction assumed: 1. **`:42` is split by queue state, not "mixed" in the sense of opposite readings.** No queued PR in the scanned windows left the queue because of a draft conversion. The direction assumed the old half had its own measurement. It has none, and the one PR later cited for it reads the other way. What does go both ways in this repo is the effect by state: unqueued, auto-merge drops; queued, the PR keeps its place. 2. **`:43` says "not seen to dequeue", not "not measured".** Three PRs (objectstack-ai#6732, objectstack-ai#20420, objectstack-ai#20442) have both acts claimed in writing, and none was removed by them. The caveat: on a queued PR, the disable leaves no timeline event, so whether it was sent rests on the seats' own comments. 3. **`:41` loses "两向相反读数并存".** No opposite reading survived the re-read. ## Acceptance notes - **Out of scope, same family, Tier H.** `AGENTS.md` states the falsified reading as a premise in three places. Prime Directive objectstack-ai#14 says "draft is what removes queue membership, disabling alone drops only the arming". Multi-agent §7 says "flipping back to draft drops auto-merge and queue membership at once". The "State on your PR" paragraph says the draft flag "flipped back destroys auto-merge and queue membership at once". I did not touch them. They are reported to the seat. - **Eviction.** The only dequeue acts measured in this repo are these two. First, the queue's own ejection after a red group build (objectstack-ai#20420, objectstack-ai#20442). Second, a manual removal by a maintainer with no draft conversion (objectstack-ai#20797, `removed_from_merge_queue` 2026-09-30T07:33:36Z). Whether the remedy should name a different act is a question for the seat. It is not a rule this PR adds. - **Changeset.** `.claude/**` is not in any package's `files[]`, so this PR publishes nothing. `skip-changeset` is the seat's to apply. - **Governed surface, Tier S (`.claude/**`).** This PR stays a draft until a `## Contract review` record at `CONTRACT_REVIEW_TIER` exists for its head. ## Verification (head `02ea176a1`) - The 20 gates from `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` all exit 0. They are `check-closing-keyword-parity` (plus `--self-test`), `check-comment-mask-corpus`, `check-governed-queue-guard --self-test`, `check-harness-current --self-test`, `lint check:doc-formula-expressions` (after building `@objectstack/lint` and its dependency closure under `os-verify-lock.sh`), `check:agent-test-spelling`, `check:cross-package-test-inputs`, `check:doc-authoring`, `check:driver-memory-census`, `check:gitlink-declared`, `check:nul-bytes`, `check:pm-governed-merges`, `check:pm-half-states`, `check:pm-skill-id-lint`, `check:pm-skill-ratchet`, `check:refd-timer-probe`, `check:required-contexts`, `check:skill-frame-sync` and `check:watch-hint-literal`. - `pnpm check:pm-settings-deny-roster` was also run, because its roster lives under `.claude`. Exit 0. - `--ran` reconciliation: 20 derived, 20 run, 0 NOT-MEASURED. All 20 recorded an exit code. - `check:pm-skill-ratchet` printed: `platform-readings.md is 469 lines (ceiling 469; headroom 0)`, widest table row 0 bytes (pin 0). - Not measured locally: `check-required-contexts --verify-required-set` and `check-half-states --provenance`, which read the workflow event, and the four CI type-check lanes. This diff touches no TypeScript. ## 维护者速读(草稿) - **改了什么**:PM 协议参考文件 `platform-readings.md` 第 41-43 行,关于「已入队 PR 转 draft 能否踢出合并队列」的三条平台读数。行数不变。 - **为什么改**:旧文说本仓转 draft 会掉出队列。回查时间线后发现,本仓 4 个已入队后转 draft 的 PR 都没有因此出队。其中 2 个带着 draft 状态被队列直接合入,另外 2 个是在自身队列构建变红之后才被移出。旧说法唯一引用的实例,时间线恰好反向。转 draft 会掉 auto-merge,这一点只在「未入队」时成立。 - **风险与代价(含回滚)**:只改文档读数,不改任何规则或代码。补救仍是两手都做。风险在于读者以为有可靠的踢队手段,而实际没有。回滚就是 revert 这一个提交。 - **席位意见**: - **你要做的**:无需操作,由席位按 Tier S 复核后落地。若要让 `AGENTS.md` 里的同一旧说法一并修正(Tier H),需要你点头另开 PR。 --- _Generated by [Claude Code](https://claude.ai/code/session_01KTZmMfzVzjNvyaLyQ8mHvg)_ Co-authored-by: Claude <noreply@anthropic.com>
Fixes #20465
Fixes #20575
Clause-②: no
Family PR, one commit per card. Tier H as a whole (
AGENTS.md,docs/adr/**), so it stays a draft for the maintainer's review. Nothing here publishes:skip-changeset.What changed
#20465, commit
249b7836(the route, theretiredAfterstamp, and the ADR-0087 window sentence from the spec seat 4 fold5873572274):.claude/skills/spec-property-retirement/SKILL.md§2 route table. Row 1 is now "any shape, strict or not" →retiredKey()tombstone. On a strict shape a bare deletion is loud, but it reports only an unrecognized key and loses both the prescription and thetscchannel. Row 2 keeps the guidance map only for a spelling the shape never declared (a retired key's old alias, a wrong-layer pointer). Such a spelling has no property for a tombstone to replace. The example is nowdata/mapping.zod.ts. The same section's ledger table, and two lines in §4, rename "strict 删除" to "无墓碑删键", because the route table no longer names a strict deletion.retiredFromLoadPathchecklist item now names the requiredretiredAfter: 'x.y.z'.tscrefuses a retirement without it. A new retirement carries the currentpackages/spec/package.jsonlabel, andretired-after.census.test.tspins each value. The artifact door opens its window per entry from this stamp.AGENTS.mdPost-Task Checklist step 3 says the same as the kit, in one rule:retiredKey()whether or not the schema is.strict(), and a*_RETIRED_KEY_GUIDANCEentry only for a spelling the shape never declared. The closing clause now says what actually differs between the routes: a tombstone keeps its liveness-ledger row, and a key deleted without one loses it.docs/adr/0087-metadata-protocol-upgrade-contract.md, in the 2026-09-13 addendum's window bullet: the sentence "floor >= runtimereplays nothing" is struck through and amended in the dated style that ADR-0005 and ADR-0053 used on 2026-09-30. The amendment note gives the provenance (the Forward conversion never opens on unreleased main: spec still labelled 17.4.0 while main refuses 17.5.0 retirements, so artifacts built by the published 17.4.0 CLI are refused #20390 ruling A, landed ase956924e), and the status line gains an Amended entry.#20575, commit
2e3f8aa0(the step-18 D3-chain item, worded for the shape onmainafter #20535 and #20574):MAJOR_18_CONVERSIONSinconversions/registry.ts. It is inserted at its identifier's sorted position, as the list's header says.CONVERSIONS_BY_MAJOR[18]and the step'sconversionIdsboth derive from that list. Therationalegains oneSTEP18_RATIONALEfragment at the sorted position of its D3 semantic id. The merge pin that each header names refuses an append at the tail.Measured first (premise holds), at
7a09eee1retired-key.tsheader (:22–:28) gives the reason tombstones stay on closed shapes. TheacceptsNothingdoc instrict-object.tscalls a tombstone "strictly stronger than aguidanceentry". The precedents are real:action.aria(dcd3bceaa, astrictObject, whose comment ataction.zod.ts:1678says "retiredKey()rather than a bare deletion although this is astrictObject"), the list view'stabs(6e3e5462c, in the strictListViewShapeSchema), and the cube members' innername(analytics.zod.ts:243,:298).data/mapping.zod.tslists the retired keys' old alias spellings (query,onError) in its guidance map so that "an author who learned the alias should land on the prescription". Those spellings were never declared keys, so a tombstone has nothing to replace.app.zod.ts(showall,location) has the same shape.retiredAfter.conversions/types.tstypes it as REQUIRED on the retired arm of the union. The census test pins an entry that no published tarball carries retired to the package label (17.5.0on this tree).main.idsTheFloorPostdatesinartifact-forward-conversion.tsreplays a retired entry when the floor is at or below itsretiredAfter, even when the floor is at or above the runtime label (verdict'converted-retired-after'). On this tree three entries carryretiredAfter: '17.5.0', which equals the label (time-default-utc-suffix-dropped,cube-refresh-key-removed,connector-triggers-removed). An artifact whose floor is17.5.0therefore replays three entries, where the ADR said it replays none.artifact-forward-conversion.test.ts:564pins the same case one release back.step18.conversionIdsisCONVERSIONS_BY_MAJOR[18]!.map((c) => c.id), andCONVERSIONS_BY_MAJOR[18]isinApplicationOrder(MAJOR_18_CONVERSIONS).rationaleisjoinRationale(STEP18_RATIONALE). The pins arepackages/spec/scripts/conversions-major18-merge.test.tsandstep18-rationale-merge.test.ts.step17still carries a hand-keptconversionIdslist.For the reviewer: one point where the text departs from the triage wording
The #20465 triage direction says to keep the guidance map "only for the case that needs it: a shape where a tombstone cannot sit, such as a
z.preprocessstage ahead of the closed shape (retired-key.ts:177)". The route follows that direction, but the example did not hold up when measured:retired-key.ts:177describesacceptRetiredDefaultResidue. That preprocess stage strips an emitted default ahead of a closed shape that still declares the key as aretiredKey()tombstone. The tombstone sits there.tabstombstone (6e3e5462c) sits in a strict member ofViewMetadataSchema, which is itself az.preprocessahead of its union.unrecognized_keyspath of astrictObject, so a preprocess stage cannot carry one either.So both texts name the case that was measured: a spelling the shape never declared. If the maintainer means a different case, the Tier H review is the place to say so, and the kit follows.
Scope notes
:86–:87,:207and:215–:220. Consequential edits in the same file: the §2 ledger table row and the ORPHAN bullet (:102,:107), and §4:242and:257(the "strict 删除" term). The file is otherwise unchanged.aria/performance" story), whichpackages/spec/scripts/liveness/orphans.mtscarries verbatim in its header;retiredFromLoadPathitem. The rule it taught is the item's own preceding sentence and theconversions/types.tsdocblock.Line budget (
node scripts/pm/check-skill-line-ratchet.mjs, exit 0).claude/skills/spec-property-retirement/SKILL.mdlinesAGENTS.mdlinesEvery added line of the kit and of
AGENTS.mdis within 120 bytes, except table rows, which are structurally exempt. After commit 1 the kit read 335 / 337. The ratchet prints an informational hint to lower the row pin to 324; the pin lives inscripts/pm/check-skill-line-ratchet.mjs, which is outside this surface, so it is left for the owning seat.Gates, at head
2e3f8aa0These were derived by
node scripts/pm/dispatch-gates.mjs --commandsfrom this change set (31 families, the same set the dispatch named). All exited 0. Reconciled with--ran: "31 derived, 31 run, 0 NOT-MEASURED, 0 UNRUN", a derived zero, with every exit code recorded.check-adr-0087-registration(and its self-test),check-adr-links(and self-test; 691 links resolve),check-adr-symbol-anchors(and self-test; 2157 anchors across 140 records resolve),check-ci-filter-parity,check-closing-keyword-parity(and self-test),check-comment-mask-corpus,check-harness-current --self-test.pnpm check:adr-anchors, agent-test-spelling, changeset-gate-self-tests, cross-package-test-inputs, doc-authoring, docs-audit-scope, driver-memory-census, future-spec-major, gitlink-declared, nul-bytes, pm-governed-merges, pm-governed-prose (2 instruction surfaces name all 6 governed surfaces), pm-prior-rulings, pm-skill-id-lint (34 files clean), pm-skill-ratchet, refd-timer-probe, required-contexts, skill-frame-sync, watch-hint-literal.pnpm --filter @objectstack/lint run check:doc-formula-expressions. The first run exited 3 (PREREQUISITE NOT MET:@objectstack/formulaand@objectstack/lintwere unbuilt in the fresh worktree), which measured nothing. Afterturbo run buildfor those two packages under the verify lock, the re-run exited 0.packages/spec/src/shared/retired-key-migrate-sentence.test.tsreads this kit as part of its corpus. 1 file and 14 tests passed.Acceptance notes
RETIRED_KEYS_BY_MAJORitem still says to add the entry "inpackages/spec/src/migrations/registry.ts". Since Splitmigrations/registry.ts's two append tables into per-entry files (registry half of #6957's ruling) #7297, an entry is one file underpackages/spec/src/migrations/entries/retired-keys/followed bygen:migration-registry, and the region inregistry.tsis generated. The kit is echoing thebuild-schemas.tsgate (b) failure text, which still says to paste the key intoregistry.tsunder the current major. That gate text is the producer, and it is outside this surface. It is reported to the PM in the dev report, not filed.维护者速读(草稿)
改了什么
retiredKey()墓碑。guidance map 只留给 shape 从未声明过的拼写(如退役键的旧别名),那里没有属性可供墓碑替换。retiredFromLoadPath条目补上必填的retiredAfter版本戳。MAJOR_18_CONVERSIONS,rationale 只加一个排序片段。为什么改
retiredAfter等于当前版本标签的条目,在 floor 等于 runtime 时仍会被重放。风险与代价(含回滚)
席位意见
你要做的
Generated by Claude Code