Repository navigation
fix(cli): os migrate meta --from N lists the conversions its tombstones prescribe, and an empty range stops reading as success - #17462
Conversation
…nswer an empty range as an empty range `--to` defaulted to `PROTOCOL_MAJOR`, but retirements land throughout a major's line: `@objectstack/spec@17.4.0` tombstones keys whose ADR-0087 conversion is registered `toMajor: 18`. The tombstone sentence names the major the source was AUTHORED against (`--from 17`), so the prescribed invocation composed `17 → 17` — a range `composeMigrationChain` selects no step for — and reported `Nothing to migrate` for the very conversions that sent the author there. 29 shipped tombstones across 15 files prescribe it. The terminus is now read off `MIGRATION_MAJORS`, floored at `PROTOCOL_MAJOR`, so the tombstone template's presumption holds in every window rather than only after a major release. Second half: an empty range no longer borrows the success answer. A range holding no step replayed nothing, so `already canonical` was a green verdict on a check that never ran; it now says so, names the range that would list the conversions, and no longer returns past the schema verdict that contradicted it. Claude-Session: https://claude.ai/code/session_01DapQyvYrFb1MxSYe7BL2nt Co-authored-by: Claude <noreply@anthropic.com>
…; correct the upgrade skill's replay check The new spawned suite drives the card's reproduction — five sites authoring a tombstoned key — through the real CLI, and derives every expectation from `MIGRATION_MAJORS` / `PROTOCOL_MAJOR` so no major is written down. `skills/objectstack-upgrade` prescribed `os migrate meta --from <target>` and "must say Nothing to migrate" as its acceptance check. With the terminus default that check is unsatisfiable — and it was vacuous before it, since the range it composed selected no step and answered the same way for every input. It now reads `applied` from `--json`, with the reason stated once. Claude-Session: https://claude.ai/code/session_01DapQyvYrFb1MxSYe7BL2nt Co-authored-by: Claude <noreply@anthropic.com>
…grate-meta-default-range
📓 Docs Drift CheckThis PR changes 1 package(s): 11 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 5 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 23 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 6aeda441bba045d4647e72967337d1d28e5eaa79 && git checkout 6aeda441bba045d4647e72967337d1d28e5eaa79
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e758131b3900eb13260f03643e295ca6d625c42b f08b8ddfc56da030ab241b7daaeefc9a9a7a193a && git checkout -B drift-repro e758131b3900eb13260f03643e295ca6d625c42b && git merge --no-ff f08b8ddfc56da030ab241b7daaeefc9a9a7a193a
node scripts/docs-audit/affected-docs.mjs --json e758131b3900eb13260f03643e295ca6d625c42b
|
…the whole verdict sentence `*.e2e.test.ts` selects the nightly population, so a p1 pinned only there is outside the merge queue's required set. The file keeps its spawn (a flag default and a printed sentence have no in-process seam) and drops the name, so it is queue by name and `integration` by behaviour — the combination `vitest-tiers.ts` sanctions — and shares one process per distinct invocation. The headline assertion now names the whole sentence: two step-18 semantic entries open with "Nothing to migrate to, because …", so the bare phrase appears in prose that is not this command's verdict. Claude-Session: https://claude.ai/code/session_01DapQyvYrFb1MxSYe7BL2nt Co-authored-by: Claude <noreply@anthropic.com>
…grate-meta-default-range
维护者速读(终稿)
改了什么 — 为什么改 — 墓碑里写的 ⭐ 本席复核时确认了一处比卡面更重的事实:那个空区间不只是「没帮上忙」,而是不可证伪 —— 风险与代价(含回滚) — 跨 major 的运行会多跑一跳,输出体积从 516 字节涨到约 342KB; 席位意见 — 建议采纳。
你要做的 — 确认一件事即可:**终点默认值改为「注册表最高 major」**这个方向对不对。
Generated by Claude Code |
…eview to the skills seat (objectstack-ai#17806) Fixes objectstack-ai#17490 ## What changed One line of `.claude/skills/pm-dispatch/SKILL.md` (the governed ACCEPT fork, rule ①) is replaced in place. Nothing else moves: same line count, the four-axis frame block untouched, no reference twin, the tiering ruling's own text untouched. | | line 610 | bytes (no newline) | |:--|:--|--:| | before (`a77b4e92e`, identical on the dispatch tip `6682c5d8`) | `- ① 复核结论照常写在 issue 上;技能面 PR 的复核席须跑在契约复审档位。` | 96 | | graded candidate (comment 5624178855, re-measured on the base) | `- ① 复核结论照常写在 issue 上;技能面 PR 须由契约复审档的席复核,档外席交 skills 席。` | 114 | | **after (this PR)** | `- ① 复核结论照常写在 issue 上;技能面 hunk 须由契约复审档的席复核,档外席先交 skills 席。` | **119** | Line cap is 120 bytes (`scripts/pm/check-skill-line-ratchet.mjs`, "Why 120 bytes"); 119 is under it. File stays 812 lines, ceiling 812, headroom 0 — no line added, no rule deleted, no re-wrap, no density payment. ### The three ruled elements, mapped to the words The ruled direction is option 3 on the card (5624178855): the seat that cannot run the tier hands the `skills/**` hunk's review to the skills seat, which records it on the card before the four-piece terminal fires; the lane's own review of the code half stands. | element | words | |:--|:--| | (a) the conclusion still goes on the issue | `复核结论照常写在 issue 上` — unchanged from the old line | | (b) the review of the `skills/**` hunk runs at the contract-review tier | `技能面 hunk 须由契约复审档的席复核` — the skills-face hunk must be reviewed by a seat AT the contract-review tier | | (c) a seat outside that tier hands that review to the skills seat | `档外席先交 skills 席` — an out-of-tier seat hands it to the skills seat first | Two wording changes against the graded 114 B candidate, both inside the ruled direction and inside the cap: - `PR` → `hunk` (+2 B). The ruling hands over the **hunk's** review and keeps the lane's review of the code half; with `PR` the sentence read as handing the whole PR review across seats, which contradicts that second clause. `hunk` is already vocabulary in this skill (`references/dispatch-runbook.md` lines 101 and 151). For a PR that is entirely `skills/**`, hunk = PR, so nothing narrows. - `交` → `先交` (+3 B). The ruling says the handed-off review is recorded on the card **before the four-piece fires**; `先` carries that ordering, closing the failure mode where a seat fires the four-piece and hands off afterwards, leaving the PR at the maintainer with no at-tier read. Kept from the candidate: `技能面` (the term the old line and line 600's `触 skills/** 的 PR` pairing already use), `契约复审档`, `skills 席`. ### core-rules twin check `git grep -n '契约复审档位' origin/main -- .claude/skills/pm-dispatch/references/core-rules.md` ⇒ exit 1 (no hit). A wider probe `git grep -n '复核席\|技能面 PR' origin/main -- .claude/skills/pm-dispatch/references/core-rules.md` ⇒ exit 1 (no hit). The only tier sentence in core-rules (line 112, `语义面卡恒契约复审档施工,契约卡达档复核归 spec 席`) is about the build tier and the spec seat's clause-② review, not the governed fork's rule ① — not a twin. `references/core-rules.md` is untouched. The phrase `契约复审档位` also appears in `references/contract-review.md` line 57 (`产出裁决的每轮都须读到契约复审档位`) — that is the contract-review reading's own tier requirement, a different rule; untouched. ### Premise check (all three PM readings held) 1. Line 610 on `6682c5d8` and on the actual base `a77b4e92e` (origin/main moved by one commit between dispatch and branch cut — PR objectstack-ai#17794, which touches only `references/dispatch-runbook.md`; `git diff --stat 6682c5d HEAD -- SKILL.md` is empty) is the 96 B sentence above, 97 B with its newline. Ratchet verdict on the base: `SKILL.md is 812 lines (ceiling 812; headroom 0)`. No open PR touches SKILL.md or core-rules.md: the 30 most recently updated open PRs (newest at 06:19Z, i.e. none after the PM's 06:29Z reading) were listed via REST; the two pm/ci PRs in that list (objectstack-ai#17799, objectstack-ai#17801) were file-listed and touch `scripts/pm/check-clause2-carriers.mjs` and workflow/script files only. 2. The interim was already executed at tier by the previous skills seat on card 17134 (PR objectstack-ai#17462); that card was read only, not touched. 3. `node scripts/pm/dispatch-gates.mjs --tier .claude/skills/pm-dispatch/SKILL.md` prints `Model tier — MANDATORY: claude-fable-5-1`. This PR was authored at that tier (claude-fable-5-1). ### Governed verdict `node scripts/pm/check-governed-merges.mjs --test .claude/skills/pm-dispatch/SKILL.md` ⇒ exit 3: `governed-surface predicate: 1 of 1 path(s) hit the register (5 surfaces, repo-agnostic).` / `⛔ GOVERNED — a human merge is the review record for this PR`. ⇒ draft at the human terminal; no ready flip, no queue, no auto-merge from any seat. `skip-changeset` applied additively (`.claude/**` publishes nothing). ## Verification (final commit `8b7dae6c0`) Gate set derived, not recalled: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (change set derived three-dot from the merge base `a77b4e92e`, 1 path) ⇒ 16 commands. All 16 run on `8b7dae6c0` with exit captured before any pipe (`cmd > log 2>&1; ex=$?`), all exit 0; reconciliation `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran FILE` ⇒ `Run reconciliation — 16 derived, 16 run, 0 NOT-MEASURED, 0 UNRUN.` The same 16 ran once before the commit on the identical bytes; the command list was byte-identical pre/post commit. Verdict lines as printed by the gates on `8b7dae6c0`: - `✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/SKILL.md is 812 lines (ceiling 812; headroom 0).` - `✓ check-skill-id-lint: 27 file(s) clean (pattern /#[0-9]{3,}/g).` - `✓ check-skill-frame-sync: the one declared copy of the decision frame is internally coherent (.claude/skills/pm-dispatch/SKILL.md; no second copy to compare — …)` — plus the direct proof the block did not move: `sed -n 734,755p SKILL.md | md5sum` ⇒ `3327d02c56f8a0eca88569dad2270f32` before and after the edit (the dispatch's expected hash). - `✓ check-governed-prose: 2 instruction surface(s) name all 5 registered governed surfaces (…) and claim no others.` - `check-nul-bytes: OK (scanned 8456 text file(s) … no raw ASCII control bytes).` — plus the manual control-byte self-scan on SKILL.md (`grep -naP` over the C0/DEL range) ⇒ no hits. - `pnpm check:pm-governed-merges`, `check-governed-queue-guard --self-test`, `check-closing-keyword-parity` (+ `--self-test`), `check-comment-mask-corpus`, `check:agent-test-spelling`, `check:doc-authoring`, `check:driver-memory-census`, `check:refd-timer-probe`, `check:watch-hint-literal` — each printed its own ✓/OK line, exit 0. - `pnpm --filter @objectstack/lint run check:doc-formula-expressions`: first run exit 3 `PREREQUISITE NOT MET` (`@objectstack/formula` / `@objectstack/lint` not built — NOT MEASURED, not a finding). Built both under the shared verify lock (`os-verify-lock.sh`, `VERDICT command-exit 0`, held 182 s), re-run ⇒ `✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 438 files / 1374 TS blocks judged clean`, exit 0; included in the post-commit union above. On-disk proof of the edit: `grep -cF` of the old line 1 → 0 and of the new line 0 → 1; `git show HEAD:…SKILL.md | sed -n 610p` prints the new line. No repo-wide `pnpm lint` run locally (CI-owned); no package build/test owed — the diff touches no package (no ① closure, no ②). Not measured locally: CI convergence (owned by the review side). ## Acceptance notes - noted, not filed: `references/contract-review.md` line 57 uses the spelling `契约复审档位` for the contract-review reading's own tier, while SKILL.md line 610 now spells the same tier `契约复审档` (as lines 505–527 do). Both spellings already coexisted on the tip; no gate reads either. Carrier: whoever next edits contract-review.md; none queued. - Out of scope, untouched by design: the tiering ruling's text (card 17285 / PR 17294 — those cards remain as they are), card 17134's labels, the four-axis block (lines 734–755). ## 维护者速读(草稿) **改了什么**:`.claude/skills/pm-dispatch/SKILL.md` 第 610 行(治理面 ACCEPT 分叉的规则①)原地换了一句话,96 字节变 119 字节,行数 812 不变。旧句要求「技能面 PR 的复核席须跑在契约复审档位」;新句改为「技能面 hunk 须由契约复审档的席复核,档外席先交 skills 席」。 **为什么改**:旧规则①与分档裁决(契约复审档只留给 skills 席、spec 席的条款②复核与维护者召集的 director)构成一对谁都满足不了的矛盾:一个 `domain:cli` 席复核一张顺手修了 `skills/**` 里一行的 PR,被要求跑一个它被禁止跑的档位。这不是边角:「已发布的说法本轮变假就地修、不立卡」这条规则天然会让普通车道的 PR 顺带碰到 `skills/**`。一张 p1(PR objectstack-ai#17462)已经撞上。方向由 skills 席自分诊定为选项 3(5624178855,轮次报告否决窗已过):档外席把 `skills/**` 那一块的复核交给 skills 席,skills 席在四件套之前记到卡上;代码那一半仍由本车道自己复核。 **风险与代价(含回滚)**:每次顺带的 skills 修补多一次跨席交接 —— 几行 hunk、一条评论,不是一张卡。文字层面只动这一行,分档裁决原文、四轴框架块、objectstack-ai#17134 的标签都没碰;`references/core-rules.md` 无孪生句(已 grep 核实),未动。回滚 = revert 这一个 commit,不牵连任何生成物。 **席位意见**:(留空,席位定稿时填) **你要做的**:读第 610 行这一句,认可则人工直合;不认可则在此 PR 评论里写下你要的措辞,由 skills 席改。 --- _Generated by [Claude Code](https://claude.ai/code/session_01MCLBsUgfykL74aU716rzVK)_ Co-authored-by: Claude <noreply@anthropic.com>
Fixes #17134
os migrate meta --from 17— the invocation 29 shipped tombstones prescribe — replayed an empty chain and answered✓ Nothing to migrate, exit 0, for the very conversions that sent the author there.What was actually wrong
--todefaulted toPROTOCOL_MAJOR, the major the runtime implements. Retirements land throughout a major's line and their ADR-0087 conversions register under the next one:@objectstack/spec@17.4.0tombstonesdashboard.refreshIntervalwhiledashboard-refresh-interval-to-refresh-interval-secondsistoMajor: 18.The
retiredKey()house sentence names the major the source was authored against, so the tombstone is correct to say--from 17. What the template presumes is that the default terminus is at least the conversion's owntoMajor— a presumption that holds only after the next major ships. For the whole 17.x line it composed17 → 17, andcomposeMigrationChainkeepsm > fromMajor, so no step was selected at all.appliedandtodosare empty for every input, so the answer is unfalsifiable:os migrate meta --fromat the installed major could not have reported anything else, for any stack, ever. The published acceptance check inskills/objectstack-upgradewas built on exactly that answer.The repair
Both halves live in
packages/cli/src/commands/migrate/meta.ts.--todefaults to the chain terminus —Math.max(PROTOCOL_MAJOR, ...MIGRATION_MAJORS), the highest major this build of@objectstack/speccarries a step for. The tombstone template's presumption now holds in every window instead of only after a major release, and it stays true one major on by construction. ⛔ This is not "migrating past what the runtime runs": every registered conversion maps a shape the installed schemas already refuse onto the one they accept, which is why the terminus is the only target for which this command's ownschemaValidverdict is reachable.Math.maxkeeps the runtime's major as the floor for the reverse case.already canonicalwas a green verdict on a check that never ran. It now says so, names the range that would list the conversions, and no longerreturns past the schema verdict that contradicted it. That early return was the second half of the defect: the same run reportedschemaValid: falsein--jsonwhile the human output claimed canonical and stopped.packages/specis untouched — it is the evidence here, andmigrations/registry.tsis held by three open PRs.Acceptance notes
All readings below were taken on
f08b8ddfunless a different sha is named.The tombstone count triage asked for
29 occurrences across 15 shipped source files prescribe
os migrate meta --from 17:The same command with
--from 16returns 78 across 25 files — those work today and keep working, which is what identifies--from 17as the broken class rather than the sentence template being wrong.Before / after, driven on the card's reproduction
Five dashboards authoring
dashboard.refreshInterval, real CLI process,NO_COLOR=1. Before is the branch point331a1a2b; after isf08b8ddf.--from 17, no--to✓ Nothing to migrate — the metadata is already canonical for this range.· exit 0 · 516 bytesApplied 5 mechanical change(s)then✓ Migrated stack is schema-valid· exit 0--from 17 --jsonapplied: 0, todos: 0, schemaValid: falseapplied: 5, schemaValid: true--from 17 --to 17— the old default, now reachable only by typing it✓⚠ No migration step exists for protocol 17 → 17, so this run replayed nothing …then⚠ Protocol 17 → 18 has 5 mechanical and 112 manual change(s) for this stack — re-run with --to 18 …then⚠ Stack does not pass schema validation, and this run replayed no conversion …· exit 0--from 13 --to 14on a canonical stack (control)✓ Nothing to migrate✓ Nothing to migrate— unchanged, plus the schema verdict the early return used to skipExit codes — read, deliberately not changed
Triage flagged the exit code as half of why this read as success. It stays 0 on every path above, and that is a decision rather than an oversight: this command reports findings instead of exiting on them — its existing⚠️ Flagged for the maintainer rather than decided silently; the pin asserts the 0 explicitly so a later ruling changes it deliberately.
schemaValid: falsearm has always been a warning at exit 0 — so moving that convention is a wider contract change than this card, and the text now carries the signal.Cost, measured rather than asserted
--from 17now replays one hop further, so it prints that hop's semantic TODOs: the run above is 341,968 bytes (5 mechanical, 112 manual).--from 16composes a real step today and prints 239,560 bytes (77 manual) for the same reason. The old--from 17was 516 bytes only because it replayed nothing. The mechanical rewrite list is printed first, above the TODOs.Reverse verification — both halves, from the committed state
Each leg mutated the committed source, proved the mutation reached disk by anchor counts plus a
git hash-objectdiffering from theHEADblob, ran the pins, then restored withgit checkout HEAD --and proved the restored blob hash equals theHEADblob hash with an emptygit diff HEAD.--todefault reverted toPROTOCOL_MAJORresult.hops.length === 0arms)The sixth test —
Nothing to migratestill answers a range that HAS steps and rewrote nothing — stays green under both, which is its job: it guards against over-fixing.Tier placement
The pin spawns the real CLI (a flag default and a printed sentence have no in-process seam) but deliberately does not carry the
.e2ename, because that name selects the nightly population and a p1 pinned only there sits outside the merge queue's required set. Queue by name,integrationby behaviour — the combinationvitest-tiers.tssanctions. One process per distinct invocation; the file runs in ~34s.skills/**line budgetskills/objectstack-upgrade/SKILL.mdlinesskills/objectstack-upgrade/SKILL.mdtokensSKILL.mdlines in the packagepnpm check:skills-token-ratchetexits 0. The three edits are corrections, not expansion: that skill prescribed a replay check that was vacuous before this PR (empty range, same answer for every input) and unsatisfiable after it, so it now readsappliedfrom--jsonwith the one-sentence reason. This is breakage the diff causes, not scope growth.Noted, not filed
replacementwith "Nothing to migrate to, because …", so the command's own verdict phrase appears inside its own TODO prose. A grep for the phrase can report the verdict present on a run that never printed it — which is a second, independent reason the corrected acceptance check readsappliedrather than the headline. Successor: the skill correction in this PR. Carrier: none beyond it.toMajor: 18— is apackages/specfact, out of this lane and on a file three open PRs hold. This PR makes the CLI correct for it rather than changing it. Successor: none needed if the terminus default is accepted.Verification
dispatch-gates --ran: 75 derived families, 75 run, 0 NOT-MEASURED, 0 UNRUN — every one recorded with its exit code, all0.pnpm lint(eslint . --no-inline-config, whole repo, no narrowing): exit 0 over 6,562 files, 0 errors, 0 warnings.pnpm --filter @objectstack/cli typecheck: exit 0.pnpm --filter @objectstack/spec check:generated: exit 0, 15/15 artifacts current.packages/clipins: 6 passed / 6.Clause-②: no — no new flag, no new exported symbol; the change pulls a prescribed invocation back to the contract its own tombstones declare.
Authored by Claude Code, session
session_01DapQyvYrFb1MxSYe7BL2nt.维护者速读(草稿)
改了什么 —
os migrate meta的--to默认值,从「运行时自己的 protocol major」改为「本次构建的迁移注册表里最高的那个 major」。外加:一个不含任何迁移步骤的区间,不再借用成功文案,并且不再跳过与它相矛盾的 schema 判定行。为什么改 — 墓碑里写的
os migrate meta --from 17是 29 处已发布提示语共同指定的命令。它组合出的区间17 → 17一个步骤都选不中,于是对任何输入都回答「没有要迁移的」并以 0 退出。作者照着平台自己的处方走,得到的是一个永远不会失败的检查——已发布技能里的验收步骤正是建立在这个答案上的。风险与代价(含回滚) — 跨 major 的运行会多跑一跳,输出体积 516 字节涨到 341,968 字节;但这正是
--from 16今天已有的形态(239,560 字节),不是本次新造的噪音,机械改写清单仍在最上方。退出码一律不变。回滚 = 还原meta.ts的一处默认值与一个分支,无数据迁移、无 schema 改动、无新增公开面。席位意见 — (留空,待定稿)
你要做的 — 本 PR 触及受管面
skills/**,按 Prime Directive #14 只能由维护者手动合入。请确认两点:① 终点默认值改为「注册表最高 major」这一裁决;② 退出码保持 0 是否接受(triage 曾点名它,本 PR 明确不改并把它写进了 pin)。Generated by Claude Code