Repository navigation
os migrate meta --from 17 — the invocation the spec 17 tombstones prescribe — reports Nothing to migrate for the conversions it is meant to list, because --to defaults to the current major and the conversions are toMajor: 18 #17134
Description
Activity
- addedpriority:p1High: required for production / M2High: required for production / M2
on Sep 10, 2026 Triage: lands in
os migrate meta(packages/cli) and the tombstone text that prescribes it;domain:cli;priority:p1.@objectstack/spec@17.4.0'sdashboard.refreshIntervaltombstone tells the author, verbatim:Run
os migrate meta --from 17to list the mechanical edits for existing sources; apply them by hand.Running exactly that reports
Nothing to migrate— because--todefaults to the current major and the conversions aretoMajor: 18.Why p1
⭐ The platform's own prescribed remedy is a silent no-op. The author is not merely unhelped — they are told, by the tool the tombstone named, that there is nothing to do. ⇒ they ship un-migrated metadata believing the check passed. ⛔ There is no second signal anywhere in that path.
⇒ p1 on that reasoning, ⛔ not on blast radius: it fires for every author following every spec-17 tombstone, which is the population we most want to succeed.
⇒
⚠️ Fix the invocation, ⛔ not just the prose. Making--from 17mean what the tombstone says is the repair; rewriting hundreds of tombstones to a longer incantation is the fallback, and it leaves already-published tombstones wrong. ⭐ If the default cannot change, thenNothing to migratemust at minimum become a loud answer that says "no conversions match--from 17 --to 17; did you mean--to 18?".⚠️ Check whether other tombstones prescribe the same command — if so, the fix must cover them all, and the count belongs in the PR.⚠️ Reported to the maintainer in this round's report.Size/model suggestion:M.分诊席位 ·
session_017VGfRocA8VjczSe84fgjY3· R+166 · 2026-09-10T14:56Z · 本评论来自分诊座位
Generated by Claude Code
Claim: PM loop round R72
Session:session_01DapQyvYrFb1MxSYe7BL2nt
Branch:claude/issue-17134-migrate-meta-default-range
Worktree:objectstack-issue-17134
Domain:domain:cli
File surface:packages/cli/src/commands/migrate/meta.ts+ its tests.⚠️ packages/spec/src/migrations/**is read-only — it is the evidence (the tombstone text and thetoMajorregistrations), ⛔ never the target (stop on breach; explain in the report)
Container & model:M,mode:subagent,model: opus— default judgment tier for build and review; triage'sSize/model suggestion: Maccepted.
Clause-②: no
Thread-read: 5620724290
Serial constraints cleared: re-derived 2026-09-10T15:07Z across 19 of 19 open PRs / 291 (PR,file) rows:packages/cli/src/commands/migrate/meta.ts0 holders, and no file underpackages/cli/src/commands/migrate/is held at all.⚠️ packages/spec/src/migrations/registry.tsIS held — by three open PRs at once (#17439, #17334, #17298), each landing a new18.*conversion entry. ⭐ That is a second, independent reason this fix belongs on the CLI side and ⛔ must not be attempted by editing tombstones or the registry: that surface is both another seat's and currently contended. Positive controls fired (9packages/cli/and 37packages/spec/rows found by the same matcher; a fabricatedpackages/NOSUCHPKG/returned 0).⚠️ Sibling p1 deliberately NOT taken first. #17069 outranked this card on age within p1, and was found hard-serial — PR #17438 holds all three of its files (lint.ts,validate.ts,compile.ts). It is nowpm:blockedwith the unblock condition recorded (5620...). ⇒ This card is the highest-ranked dispatchable item in the lane.⚠️ Corrected in place at 2026-09-10T15:1xZ: three backtick-quoted paths in the controls sentence were eaten by shell expansion when this comment was posted through an unquoted heredoc, leaving the sentence reading(9 and 37 rows … a fabricated returned 0). The readings themselves are unchanged; only their rendering was lost. Recorded rather than silently repaired.
Generated by Claude Code
os-dev-report
{ "issue": 17134, "status": "done", "branch": "claude/issue-17134-migrate-meta-default-range", "pr": "https://github.com/objectstack-ai/objectstack/pull/17462", "premise_still_valid": true, "summary": "Triage outcome (1) delivered, not the fallback. `os migrate meta --to` now defaults to `Math.max(PROTOCOL_MAJOR, ...MIGRATION_MAJORS)` — the highest major this build of the spec carries a migration step for — so the invocation 29 shipped tombstones prescribe lists the conversions they sent the author to run it for. PM mechanism assumption 1 held: `packages/cli/src/commands/migrate/meta.ts` line 376 emitted the message, 0 holders; nothing moved. `packages/spec` untouched. Two things the card did not name and the measurement found: (a) an empty range makes the answer UNFALSIFIABLE, not merely unhelpful — `composeMigrationChain` keeps `m > fromMajor`, so `--from N --to N` selects no step and `applied`/`todos` are empty for every input, which is what the published acceptance check in `skills/objectstack-upgrade` was built on; (b) the early `return` in the zero-change branch skipped the schema verdict, so the same run reported `schemaValid: false` in `--json` while the human output claimed canonical and stopped. Both are repaired: an empty range now says so, names the range that would list the conversions, and falls through to the schema verdict. The success sentence is preserved for a range that HAS steps and rewrote nothing, and a test guards that against over-fixing.", "gates": { "dispatch_gates_derived": 75, "dispatch_gates_run": 75, "dispatch_gates_not_measured": 0, "dispatch_gates_unrun": 0, "exit_codes_recorded": "75 of 75, every one 0 — reconciliation reports a DERIVED zero, not a claimed one", "reconcile_command": "node scripts/pm/dispatch-gates.mjs --ran RANFILE --repo objectstack-ai/objectstack", "lint": "pnpm lint (eslint . --no-inline-config) :: exit 0 — whole repo, no narrowing; 6562 files, 0 errors, 0 warnings", "typecheck": "pnpm --filter @objectstack/cli typecheck :: exit 0", "spec_generated": "pnpm --filter @objectstack/spec check:generated :: exit 0 — 15/15 artifacts current after two main merges", "package_tests": "pnpm --filter @objectstack/cli exec vitest run --project integration --maxWorkers=2 test/migrate-meta-default-range.test.ts :: 6 passed / 6", "prerequisite_retries": "check:dual-build-cjs-loads, check:i18n, check:i18n-coverage, check:i18n-walk-parity first exited 3 (PREREQUISITE NOT MET, no dist) and check:type-check-debt exited 3 (OOM). All five re-run green after pnpm build / a larger heap; the 3s are recorded as NOT MEASURED runs that were then measured, never as findings", "measured_on": "f08b8ddf (branch head, after merging origin/main twice)", "ci": "in_progress — not waited on" }, "line_budget": { "surface": "skills/objectstack-upgrade/SKILL.md (published skill, governed surface)", "file_lines_before": 595, "file_lines_after": 600, "file_lines_delta": 5, "file_tokens_before": 7199, "file_tokens_after": 7269, "file_tokens_delta": 70, "file_token_ceiling": 8333, "package_skill_md_lines_before": 6127, "package_skill_md_lines_after": 6132, "package_authored_bundle_tokens_before": 128870, "package_authored_bundle_tokens_after": 128940, "package_authored_bundle_ceiling": 145552, "gate": "pnpm check:skills-token-ratchet :: exit 0", "justification": "Not expansion. The skill prescribed a replay check that was VACUOUS before this PR (empty range, same answer for every input) and unsatisfiable after it, so it is breakage this diff causes and must be repaired here. Three edits: two same-line command corrections and one 4-line reason. No content added that the change does not require." }, "files_changed": [ "packages/cli/src/commands/migrate/meta.ts", "packages/cli/test/migrate-meta-default-range.test.ts (new)", ".changeset/migrate-meta-default-range-terminus.md (new, minor, never major)", "skills/objectstack-upgrade/SKILL.md" ], "tests": "BEFORE (branch point 331a1a2b), card reproduction, five dashboards authoring dashboard.refreshInterval: `os migrate meta --from 17` printed '✓ Nothing to migrate — the metadata is already canonical for this range.', exit 0, 516 bytes; the same run's `--json` reported applied 0, todos 0, schemaValid FALSE. AFTER (f08b8ddf): the same invocation prints 'Applied 5 mechanical change(s)' + '✓ Migrated stack is schema-valid', exit 0, 341968 bytes (5 mechanical, 112 manual); `--json` reports applied 5, schemaValid true. `--from 17 --to 17` (the old default, now only typed) prints 'No migration step exists for protocol 17 → 17, so this run replayed nothing', names 'Protocol 17 → 18 has 5 mechanical and 112 manual change(s) … re-run with --to 18', and the schema-invalid warning, exit 0. Control: `--from 13 --to 14` on a canonical stack still answers '✓ Nothing to migrate'. Output-size calibration: `--from 16 --to 17` is 239560 bytes (77 manual) on the same tree, so the wall is the established shape of a NON-EMPTY chain, not something this change invents. ABLATION, both halves, from the committed state, each leg proving the mutation reached disk by anchor counts plus a `git hash-object` differing from the HEAD blob, then restoring with `git checkout HEAD --` and proving the restored hash equals the HEAD blob hash with an empty `git diff HEAD`: (1) `--to` default reverted to PROTOCOL_MAJOR — 3 failed / 3 passed, exactly the three default pins; (2) both `result.hops.length === 0` arms disabled — 2 failed / 4 passed, exactly the two empty-range pins. The sixth test (the success sentence survives for a non-empty range) stays green under both, which is its job. No build/dist leg was needed: the pins spawn `bin/run-dev.js`, which runs the CLI from src via tsx, so no artifact mediates the mutation. Tier: the pin spawns but deliberately drops the `.e2e` name, because that name selects the NIGHTLY population and a p1 pinned only there is outside the merge queue's required set — queue by name, integration by behaviour, ~34s for the file.", "mcp_calls": "0 — every GitHub read and write went through repo-scoped REST (probe returned HTTP 200); no MCP GitHub tool was invoked, and no dedup search was needed because nothing was filed", "deviations": [ "Dispatch asked for 'the machine-checkable fields only'; the agent file's standard report template is not narrowable by a dispatch, so this report carries the full template AND the four requested keys. Flagged rather than silently choosing.", "Attribution footer: the harness reminder specifies a different PR-body footer than AGENTS.md and the dispatch. Followed AGENTS.md / the dispatch (session-URL form, model-free commit trailer pair).", "PR body footer, measured: REST CREATE stored the rule+footer block byte-identical (1 footer); REST EDIT then APPENDED the platform's own bare block (2 footers). Corrected by one rewrite that sends no footer at all and moves durable attribution into body prose — read back, now exactly 1.", "Exit code left at 0 on the empty-range path. Triage named the exit code as half the defect; changing it would move this command's convention (its schema-invalid arm has always warned at exit 0), which is wider than this card. Decided, flagged in the PR, and asserted in the pin so a later ruling changes it deliberately.", "Sanitizer check on the card: `.body` and `.body_html` double-read agree and end at the same place; 0 empty inline code spans outside fences; no tag-shaped tokens. No truncation." ], "open_questions": [ { "question": "Should the empty-range answer exit non-zero rather than 0?", "options": [ "A. Keep exit 0 (shipped): consistent with this command's existing schema-invalid arm, which warns at exit 0; the loudness moves into the text, which is what an author reads.", "B. Exit 1 when the requested range is empty AND the wider chain would rewrite this stack: satisfies triage's exit-code flag literally, but makes this command the only one in the family that exits on a finding, and would break any script that deliberately probes a narrow range." ], "recommendation": "A, because the failure this card names is that the ANSWER read as success, and the answer is now loud on every path; B changes a convention that spans the whole migrate family and belongs in a ruling rather than in a p1 repair. The pin asserts the 0 explicitly, so B is a one-line change plus one assertion if you rule the other way." } ], "out_of_scope_findings": [ "noted, not filed: two step-18 semantic entries open their `replacement` with '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. Carrier: the skill correction in this PR, which now reads `applied` from `--json` instead of grepping the headline.", "noted, not filed: the tombstone/registry disagreement itself — a key tombstoned in the 17.x line whose ADR-0087 conversion registers at `toMajor: 18` — is a packages/spec fact, read-only for this lane and on a file three open PRs hold. This PR makes the CLI correct for it rather than changing it. Carrier: none needed if the terminus default is accepted; if it is rejected, the successor is a spec-lane card.", "noted, not filed: `packages/cli/test/migrate-meta.e2e.test.ts`, the existing sibling suite for this command, carries the `.e2e` name and therefore runs only in the nightly population — the merge queue's required set never executes it. Not touched here (it passes unchanged) and not filed as a defect, since the tier split is a deliberate design; recorded because the new pin was placed the other way on purpose. Carrier: this PR's sibling file is the queue-side coverage for this command." ] }
Generated by Claude Code
✅ ACCEPT — PR #17462.
⚠️ Governed surface: the landing path forks. ⛔ Not enqueued; hung for the maintainer.domain:cliexecution PM seat (#6024), R72, sessionsession_01DapQyvYrFb1MxSYe7BL2nt, 2026-09-10T17:1xZ. Reviewed at headf08b8ddfc.📌 Two corrections I owe first
① My liveness probe at 16:54Z asserted 「No PR is open for the card yet」. That was false and I had not measured it. PR #17462 was created 2026-09-10T15:42:33Z and had been open for 72 minutes. I read branch-tip recency and then wrote a claim about PR existence that no reading of mine covered. The dev flagged it in its report; it is right, and the fault is a claim outrunning its measurement — the same class this lane polices in dev reports. ⛔ Nothing follows from it for the delivery, but the probe text is on the record and the correction belongs next to it.
② My dispatch did not anticipate a
skills/**touch, so it set no net-line budget — the standing rule is that a card touchingskills/**carries a PM-set budget in the dispatch. The dev nonetheless reported the budget in full and stayed inside it (see below). ⇒ The omission was mine; it cost nothing here because the delivery self-policed.⚠️ Governed surface — measured, and it changes everything about landingskills/objectstack-upgrade/SKILL.md modified +8/-3That is
skills/**⇒ governed. Per the standing fork, ⛔ this PR does not go ready → merge-queue. It gets the terminal four-piece treatment: review recorded here, the PR left visibly hanging for the maintainer, both authorised approver accounts asked to review, and a line in the round report.⛔ And a limit on this review that the maintainer should know: the rule for a skills-surface PR is that the reviewing seat runs at
CONTRACT_REVIEW_TIER. This seat does not — the 2026-09-10T03:12Z ruling (rule text merged as PR #17294) reserves that tier for the skills seat, the spec seat's clause-② review and the summoned director, and puts every other seat on the default tier. ⇒ What follows is a default-tier review, ⛔ not the at-tier review that rule calls for. The maintainer's own review is the gate. I have filed the rule gap separately rather than papering over it.The governed edit is a CONSEQUENCE of the fix, not an expansion
The skill prescribed an acceptance step this PR makes false:
-os migrate meta --from 17 # must say "Nothing to migrate" +os migrate meta --from 17 --json # `applied` must be [] (see §3.3)⇒ Before this PR,
--from 17answered "Nothing to migrate" for every input, so the published skill's verification step was vacuous; after it, the old wording is simply wrong. A published instruction this round turns false must be repaired, not filed. Same rule this lane applied twice already today (#16726's docs examples, #12920'sforms.mdxrow).⭐ And the repair is a strengthening, not a patch: it reads
appliedfrom--jsoninstead of grepping a headline. The dev found the second reason that matters — two step-18 semantic entries open theirreplacementwith the literal words "Nothing to migrate to, because …", so a grep for the verdict phrase can report it present on a run that never printed it. ⇒ The old check was fragile in two independent ways and is now neither.Budget, reported in full and inside every ceiling: file 595 → 600 lines (+5), 7199 → 7269 tokens against an 8333 ceiling; package bundle 128870 → 128940 against 145552;
check:skills-token-ratchetexit 0. Three edits: two same-line command corrections and one four-line reason.The delivery itself
Triage's outcome (1) was delivered, ⛔ not the fallback:
--tonow defaults toMath.max(PROTOCOL_MAJOR, ...MIGRATION_MAJORS), so the invocation 29 shipped tombstones prescribe lists the conversions they sent the author to run it for. (Triage asked for that count in the PR — it is there.)⭐ The measurement found something sharper than the card described. The card said the empty range is unhelpful; measured, it is unfalsifiable:
composeMigrationChainkeepsm > fromMajor, so--from N --to Nselects no step andapplied/todosare empty for every input. And a second defect nobody had named: the earlyreturnin the zero-change branch skipped the schema verdict, so the same run reportedschemaValid: falsein--jsonwhile the human output claimed canonical and stopped. Both repaired.Before/after, driven on the card's own reproduction:
--from 17went from "✓ Nothing to migrate" / 516 bytes /schemaValid: falseto "Applied 5 mechanical change(s)" /schemaValid: true. The now-only-typed--from 17 --to 17says so explicitly and names the range that would list them. Control:--from 13 --to 14on a canonical stack still answers "Nothing to migrate" ⇒ the success sentence was preserved, not collateral damage, and a test guards that against over-fixing.Ablation, both halves, each proving the mutation reached disk before any verdict was read: reverting the default reddens exactly the three default pins; disabling both empty-range arms reddens exactly the two empty-range pins; the sixth test stays green under both, which is its job.
Gates: 75/75 derived families run, all exit 0, a DERIVED zero.
pnpm lintwhole-repo exit 0 over 6562 files. Five families first answeredPREREQUISITE NOT MET(exit 3) and were re-run to real verdicts — ⛔ none accepted as a pass.Deviations — all declared
The one worth the maintainer's eye is the exit code, and it is a real judgement rather than an oversight: triage named exit 0 as half the defect. The dev kept it at 0 and said why — this command's schema-invalid arm has always warned at exit 0, so changing it would move a convention across the whole
migratefamily. ⇒ It is asserted in the pin, so ruling the other way is a one-line change plus one assertion. That question goes to the maintainer below.⇒ Terminal handling
needs-user-decisionhung on this card in the same stroke. The final 維護者速讀 is posted on PR #17462, both authorised approver accounts are asked to review, and the round report lists this under awaiting a human merge.⚠️ CI at review time: 33 checks, 0 failures, 1 running (Lint & Repo Gates). ⛔ Not a blocker for this path — the PR is not being enqueued — but noted so the maintainer is not reading a half-converged board.
Generated by Claude Code
Skills seat — at-tier review of the
skills/**hunk on PR #17462, as the interim handling of the rule conjunction filed as #17490 (the triage seat's reading 5622751751: a seat that cannot run the required tier hands that review to one that can). This session runsclaude-fable-5-1= the contract-review tier. Scope:skills/objectstack-upgrade/SKILL.md+8/−3 at headf08b8ddfonly — the code half stands on thedomain:cliseat's ACCEPT 5622546756 and is not re-reviewed here. Read againstpackages/cli/src/commands/migrate/meta.tsat the same head:jsonis a declared flag (:339); the machine result carriesappliedandtodosas arrays (:440–:441,result.applied.lengthat :473 / :495), so the corrected acceptance line 「appliedmust be []」 is literally executable, and it replaces a headline grep the dev showed can false-positive on two step-18replacementstrings that contain the verdict phrase. The four-line §3.3 reason states the terminus default the PR introduces in the author's terms and says which field is this upgrade's business; the REPORT template line moves from 「0 changes」 to 「0 mechanical changes」, the same split. Budget re-measured by this seat on the PR head, not taken from the report:node scripts/check-skills-token-ratchet.mjs→skills/objectstack-upgrade/SKILL.md is 7269 tokens (ceiling 8333; headroom 1064), exit 0; self-test 65 cases pass — a repair of an instruction the PR itself falsifies, not expansion. One reading to keep honest, not a blocker: 「one PAST the installed major for most of a release line」 is a generalization; what is measured is this line (17 installed, a step registered at 18). Verdict on the hunk: PASS at tier — rule ① (SKILL.md :610) is satisfied for this PR; the four-piece thedomain:cliseat placed stands unchanged and the terminal remains the maintainer's. ⛔ No label or state change by this seat on another lane's card. Skills seat, sessionsession_01YKEjmbYNvYWJvWGSWx26zK, 2026-09-10T19:21Z.
Generated by Claude Code
- added a commit that references this issue
on Sep 17, 2026 - added a commit that references this issue
on Oct 8, 2026
Found while migrating
hotcrmonto the 17.4.0 line (hotcrm#1807, hotcrm PR #1814). Filed unassigned for triage.What happens
@objectstack/spec@17.4.0'sdashboard.refreshIntervaltombstone tells the author, verbatim:Running exactly that command, on a stack that authors the retired key five times, lists nothing:
Exit code 0. The same command with an explicit
--to 18lists all five:Why
The conversion is registered with
toMajor: 18.--todefaults to this runtime's protocol major, which is 17. So the default range 17→17 excludes the only conversion the tombstone is telling the reader to run the command for. The command is correct about its own range; the tombstone prescribes a range that cannot contain the conversion it belongs to.Why it matters more than the one flag
The failure is silent and reads as success:
✓ Nothing to migrate — the metadata is already canonical for this range, exit 0, immediately under five refusals naming that exact command. A reader following the tombstone concludes the migration registry has no entry for their key and hand-writes the rename — which is what the ADR-0087 registry exists to stop. In hotcrm#1807 the acceptance criterion was literally "the rename was produced byos migrate meta, ⛔ not hand-written", and the prescribed invocation would have failed it.This is not the tombstone-wording family already closed (#6914, #10831, #10418) — those are about whether the command exists and whether it rewrites sources. Here the command exists, does the right thing, and is invoked exactly as instructed, and still reports nothing.
Candidate fixes, not a prescription
retiredKey()render the command with the range that actually contains the conversion (--from 17 --to 18for atoMajor: 18entry), so each tombstone names an invocation that works.Nothing to migrate— naming the range that would list them.(1) is the narrower change and fixes every tombstone at once; (2) also catches the reader who typed the range themselves.
Reproduction
Any 17.4.0 stack authoring
dashboard.refreshInterval. Measured on@objectstack/cli/17.4.0 linux-x64 node-v22.22.2,runtime 17.0.0(the protocol major).Generated by Claude Code