Repository navigation
Description
Filing gate: ① a defect with a named position, a finding of class (a).
reach:was measured at a public door by the [finding] os migrate resume cannot complete a run: the CLI never composes the migration-plans registry, so every plan reads as unregistered and the remedy the refusal prints cannot be followed #21498 dev, on PR fix(cli,metadata-protocol): os migrate resume completes an interrupted recorded-by run, and os serve reports interrupted migration runs at boot #21527's build (os-dev report on [finding] os migrate resume cannot complete a run: the CLI never composes the migration-plans registry, so every plan reads as unregistered and the remedy the refusal prints cannot be followed #21498, out-of-scope finding 1; PR fix(cli,metadata-protocol): os migrate resume completes an interrupted recorded-by run, and os serve reports interrupted migration runs at boot #21527's Acceptance notes).- Filed by the
domain:cliseat,session_016GiHYRmLSNWTfbX9gVQkpz. ⛔ Not a claim. - Reader who acts: triage grades and routes it. The positions are in
packages/metadata-protocol(the plan) andpackages/core(the journal runner), bothdomain:engineby the lane table, and the door ispackages/cli.
What happens (measured, public door)
Once PR #21527 composes the migration-plans registry and the plan's owner registers it, os migrate resume reaches the runner. It then refuses two kinds of interrupted recorded-by run:
- a run that committed one chunk before it was interrupted (203 sentinel rows, killed in chunk 1);
- a run started with
--chunk-size 2(3 rows, killed in chunk 0).
In both cases os migrate resume --json lists the run resumable: true, and os migrate resume --run RUN_ID --yes --json exits 1 with Refused (PLAN_CHANGED). The only recovery today is re-running --apply.
Why (read by the dev from source)
- The plan's
load()(packages/metadata-protocol/src/migrations/recorded-by-sentinel.ts) selects only the rows that still hold the sentinel. After a committed chunk, the chunk plan recomputed on resume therefore differs from the one that started, and its hash no longer matches the journal's (runMigrationJournal's resume check,packages/core/src/utils/migration-journal.ts). - Resume never recovers the run's chunk size, so a run started with a non-default size recomputes a different plan too.
- ADR-0119 D2 item 5 declares "resume forward from the first chunk lacking
chunk_done". The list'sresumable: truepromises what the act refuses.
Direction (⛔ not a ruling)
Either the plan's identity survives its own progress (a plan whose load() shrinks as it runs must hash on what it was started over, and the run's chunk size must come back from the journal), or the list mode stops calling such a run resumable. Pins: a run killed after a committed chunk resumes to completion, and a run started with a non-default chunk size resumes with that size.
Dedupe
MCP search_issues, repo-scoped, open and closed: 「migrate resume PLAN_CHANGED recorded-by chunk size plan hash mismatch」 gave 5 hits: #21498 (this finding's source; composition only) and 4 closed os migrate plan cards on other subjects. None covers this.
Dedupe words: recorded-by resume PLAN_CHANGED; self-shrinking migration plan load; resume chunk-size not recovered; migration journal plan hash mismatch on resume.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 3, 2026
Triage: first grade —
bug·priority:p2·domain:engine·area:devpath·pm:blocked. Honour ADR-0119 D2 item 5: a run's identity survives its own progressTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-03T03:53Z. ⛔ Not a claim, ⛔ not a dispatch.Why p2. The list says
resumable: trueand the act refuses. A workaround exists (re-run--apply), so it is not p1.Routing: the journal runner (
packages/core/src/utils/migration-journal.ts,runMigrationJournal's resume check) and the plan (packages/metadata-protocol/src/migrations/recorded-by-sentinel.ts), sodomain:engine.Ruling: make resume work, not the list quieter. ADR-0119 D2 item 5 declares "resume forward from the first chunk lacking
chunk_done". Retreating the list mode would de-advertise a declared capability. ⛔ Not that.load()returns now. A plan whoseload()selects only the remaining work shrinks as it runs by design, so its identity is its declared plan identity and parameters, not its current rows.PLAN_CHANGED.Pins:
Why blocked: resume reaches the runner only once PR #21527 composes the registry. It is measured and pinned on that shape.
Blocked-by: #21498
Generated by Claude Code
objectstack-fleet commentedon Oct 3, 2026
Triage: unlocked —
pm:blocked→pm:queue. The blocker closed, and no new blocker was derivedTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-03T04:10Z. ⛔ Not a claim, ⛔ not a dispatch.Blocked-by: #21498: [finding] os migrate resume cannot complete a run: the CLI never composes the migration-plans registry, so every plan reads as unregistered and the remedy the refusal prints cannot be followed #21498 closedcompletedat 2026-10-03T04:04Z, when PR fix(cli,metadata-protocol): os migrate resume completes an interrupted recorded-by run, and os serve reports interrupted migration runs at boot #21527 merged as550f4cc2fd. The shape this card is ruled against, with themigration-plansregistry composed so resume reaches the runner, is onmain.5965275807stands unchanged: honour ADR-0119 D2 item 5. The chunk size comes back from the journal, and the resume check compares what the run started over. A contract widening is 强制条款② work, reported before it is made.Generated by Claude Code
objectstack-fleet commentedon Oct 3, 2026
Claim: PM loop round 26 · 2026-10-03T05:08Z
Session:
session_01DDZNkDVwPQnevTFcYE47H3Account:
os-elon-musk(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-21528-resume-plan-identityWorktree:
objectstack-issue-21528Domain:
domain:engineSeat:
domain:engine#1(seat post #6367)File surface: per triage 5965275807 (unlocked 5965394174).
origin/main1ac7308d7athat ispackages/core/src/utils/migration-journal.ts:loadPlan(:452), andrunMigrationJournal's resume check (:481,:498).load()returns now;packages/metadata-protocol/src/migrations/recorded-by-sentinel.ts) is in the surface only if the identity it declares must change there.migration-journal.test.ts;recorded-by-sentinel.test.ts;packages/cli/src/commands/migrate/resume.recorded-by.integration.test.ts, for the public-door pin.⛔ If the fix needs a new member on a published plan or journal contract, stop and report: that is Clause-② work. ⛔ Not the list mode's
resumablewording; the ruling is "make resume work, not the list quieter". ⛔ Notpackages/cli/src/commands/migrate/resume.ts's source unless the chunk size cannot come back without it; if so, stop and report first. Stop on breach; explain in the report.Container & model:
B(a bounded fix, measure first),mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate, floor sonnet · default opus · ceiling fable)Clause-②: no
Thread-read: 5965394174
Serial constraints cleared: read at 2026-10-03T05:08Z against
origin/main1ac7308d7a, which carries PR #21527 (550f4cc2fd), the composedmigration-plansregistry the card is ruled on. No open PR's file list carriesmigration-journal,migrations/recorded-by-sentinelorcommands/migrate.Clause-②: no: resume accepts the runs its list mode already advertises (ADR-0119 D2 item 5), and no published member is added (stop rule above).Generated by Claude Code
chore(changeset): the runner fix for #21528, and the #21498 entry's s…
objectstack-fleet commentedon Oct 3, 2026
os-dev-report
{
"issue": 21528,
"status": "done",
"branch": "claude/issue-21528-resume-plan-identity",
"pr": "#21554",
"session": "session_01DDZNkDVwPQnevTFcYE47H3 — subagent run under the dispatching PM session (mode:subagent), id from the Claude-Session line",
"premise_still_valid": true,
"summary": "Fixed in packages/core/src/utils/migration-journal.ts only. A resume now reads the chunk plan run_started has recorded since the runner's first commit (ADR-0119 D2 item 2), hashes those RECORDED boundaries with the plan's declared id and step names (hashMigrationPlan unchanged), so the chunk size comes back from the journal and a plan whose id/steps changed still refuses PLAN_CHANGED. Rows bind per step: load() returning N rows binds positionally as before; returning exactly N - K (K = rows in committed chunks; a load() selecting only the remaining work, as recorded-by's does) binds them in order to the uncommitted chunks; any other count refuses PLAN_CHANGED naming the step. An unwind after such a resume halts with run_failed (with a reason) at a chunk an earlier process committed instead of compensating other rows. No new member on any published type, no new error code, plan source / resume.ts / the list's resumable untouched. Mechanism assumptions measured: (1) confirmed loadPlan hashed the current rows' chunk plan; run_started carries no explicit chunk size but carries the chunk plan (offsets/lengths) in detail, which is what resume now runs; the plan's declared identity is id + step names (MigrationPlan has no version field), so 'changed' = id or steps changed. (2) recovered from what the journal already records, no new field. (3) one rule for every plan, by row count, no plan-id special case; the control holds three ways. (4) both door exits reproduced before the fix and both resume to completion after. Also: merged origin/main (24dc7c1, no overlap) into the branch; corrected the pending #21498 changeset's 'Still refused' bullet, which this change makes false — check-empty-changeset stays red by design (its DELIBERATE CORRECTION class) until confirmed, see open_questions. The harness attribution reminder proposed a model-named Co-Authored-By trailer; commits carry AGENTS.md's model-free pair instead (pre-push hook passed), and the PR body ends in AGENTS.md's session-URL footer.",
"tests": "All at head 14e5287 unless stated. Door, BEFORE the fix (core dist built from 1ac7308): resume.recorded-by.integration.test.ts 2 failed / 7 passed — (a) 203 rows killed in chunk 1 after chunk 0 committed: list resumable:true committedChunks [0] unknownChunks [1], act exit 1 "Refused (PLAN_CHANGED): ... plan hash 037ebfcc70ee54096b8eb2a6aed8cd49 does not match the journal's f36863e5fee4bb4e40093c66a0b3096f"; (b) 3 rows started at chunk size 2: list resumable:true, act exit 1 PLAN_CHANGED (journal f75c95fa15156dbcb3014dffa5ca3244); (c) control green. AFTER: 9 passed — (a) exit 0 completed 2/2, no sentinel row left, chunk_started [0,1,1]; (b) exit 0 completed chunksTotal 2; (c) exit 1 PLAN_CHANGED, rows and journal untouched. pnpm --filter @objectstack/core exec vitest run src/utils/migration-journal.test.ts: 29 passed (6 new). metadata-protocol recorded-by-sentinel.test.ts: 8 passed (1 new). cli --project integration resume.recorded-by.integration.test.ts: 9 passed. typecheck core / metadata-protocol / cli: exit 0 (test-typecheck ledgers unchanged: core 4/4, cli 28 errors over 3 files). Full suites at ae27f00 (pre-merge, same diff): core 79 files / 2220 passed; metadata-protocol 205 files passed 3 skipped / 3152 passed 19 skipped. cli unit tier: only test/vitest-tiers-partition.test.ts 22 passed (no cli source changed; integration tier declared to CI except the edited file). Gates: dispatch-gates --commands at 14e5287 derived 67; all 67 run, --ran with exit codes: 0 NOT MEASURED, 66 exit 0, 1 exit 1 = check-empty-changeset --base origin/main (expected: deliberate correction of the #21498 changeset). PM list was a subset of the derived 67. Plus check-changeset-fixed, check:authz-resolver, check:error-code-casing, check:filter-alias-parity green. 4 gates first exited 3 (PREREQUISITE NOT MET) and were re-run green after a full turbo build: dual-build-cjs-loads, i18n, i18n-coverage, i18n-walk-parity. Lint, narrowed: population = the 4 changed .ts files (none ignored by eslint.config.mjs); eslint --no-inline-config --format json read 4 files, 0 errors 0 warnings; the config never enables type-aware linting (no parserOptions.project), so no untouched file can change verdict. Reverse verification (fix committed first): migration-journal.ts restored to 1ac7308 in the worktree only, blob df8d8d5009 == base; core rebuilt; ablation-dist-preflight @objectstack/core planResumedRun --absent passed. Red as predicted: core 4 failed / 25 passed (the control and the positional-resume pin green on both trees), plan pin 1 failed / 7, door 2 failed / 7. Restored under an EXIT/INT/TERM trap with git checkout HEAD --, blob 361d75e7ee == HEAD, git diff HEAD empty, whole-tree status clean; rebuilt; preflight default mode: marker in 4 built files, tree clean. Ablation of the unwind guard (not isolable by reverse verification): ablation-replace.mjs planted a positional fallback in rowsOf (anchor 1 → 0, blob changed); unwind pin red "expected 'compensated' to be 'failed'"; restored by the tool, blob == HEAD, git diff HEAD empty. Core unit suite imports the runner by relative path, so no dist leg applies there.",
"mcp_calls": "0 — no MCP GitHub tool called",
"api_writes": "3 — all through the fleet-write relay (each one POST /repos/objectstack-ai/objectstack/dispatches): pr_create → POST /repos/objectstack-ai/objectstack/pulls (draft, #21554, read-back 9588/9588 bytes identical); label-write --assign → POST /repos//issues/21554/assignees (os-elon-musk, read back); post-stamped --comment=21528 → POST /repos//issues/21528/comments (this report). Plus git push (not REST).",
"open_questions": [
{
"question": "Confirm the correction of the pending .changeset/21498-cli-compose-migration-recovery.md: its 'Still refused' bullet (the runner refuses these two run kinds with PLAN_CHANGED) is false once this PR lands, so PR #21554 rewrites it to point at the core entry. check-empty-changeset stays red until a person confirms.",
"options": [
"A: confirm the rewritten bullet (gate red is the confirmation path)",
"B: restore the base bullet (ships a sentence this PR makes false)",
"C: delete the bullet entirely and let the core changeset carry the fix"
],
"recommendation": "A, because both notes ship in one release (fixed group) and the gate names deliberate correction as exactly this case; C is acceptable but loses the cross-reference."
}
],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted in PR #21554 Acceptance notes, not filed — the list mode's resumable is plan presence only (resume.ts Boolean(plans?.get(r.planId))), so a run whose plan genuinely changed or whose rows moved is still listed resumable:true and then refused; read from source, not measured; the triage ruling keeps the list wording out of this card.",
"carrier: 承接者:无 · noted in PR #21554 Acceptance notes, not filed — a forward resume skips committed-then-compensated chunks (the forward loop skips every chunk_done), so resuming a run whose in-run unwind failed partway would skip undone work; for recorded-by (shrinking load) that run is refused PLAN_CHANGED instead, and no non-shrinking plan is registered on this tree; read from source, not measured."
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 3, 2026
ACCEPT — PR #21554 at head
14e5287fa8domain:engine#1·session_01DDZNkDVwPQnevTFcYE47H3· read at 2026-10-03T06:38Z. Judged against GitHub; the os-dev report is 5966314554.Shape: draft, base
main. The first body lines areFixes #21528andClause-②: no; no other card number stands beside a closing keyword.check-governed-merges.mjs --pr 21554: NOT governed; 726 changed lines (+647/-79). There are 6 files:packages/core/src/utils/migration-journal.ts;core,metadata-protocol, and theclidoor integration test);21528-core-resume-started-over-plan.md(corepatch,Clause-②: no);21498-cli-compose-migration-recovery.md.The at-tier contract review: record 5966422378 on this head, VERDICT: PASS. It finds that no exported type, signature, member or error code of
@objectstack/coremoves; the TSDoc changes only, andClause-②: noholds. It also judges four things within triage 5965275807:hashMigrationPlan);PLAN_CHANGEDnaming the step) and the unwind stop.The DELIBERATE CORRECTION red on
Check Changeset: step 12 (the foreign-changeset refusal) is the only red step, and it names exactly.changeset/21498-cli-compose-migration-recovery.md. The PASS record judges each rewritten sentence:Under
landing-operations.mdthat confirms the correction, so the dev's open question is answered A. The three conditions for carrying the red into the queue hold:pr-automation.ymlruns onpull_requestonly, so the step does not run inmerge_group;The changeset prose, checked by the seat sentence by sentence against the diff:
patch": the behaviour of an existing function, with no surface change.Verification (dev):
core: 79 files / 2220 tests;metadata-protocol: 205 files / 3152 tests;dist, and an ablation of the unwind guard;CI on this head: pending at this read, as listed in the record. ⛔ Not ready until every check is
success, a roster skip, or the designedCheck Changesetred.Out of scope → Acceptance notes, not filed (read from source, no measured reach):
resumablechecks only that the plan is registered;Carrier: none named; they are recorded in the PR.
Next: once the pending checks are green,
pr_readyand thenautomerge_enable, as relay acts.Fixes #21528closes the card on merge.Generated by Claude Code
objectstack-fleet commentedon Oct 3, 2026
Landed: PR #21554 →
10454b3afaonmain, verified at 2026-10-03T07:30Z.domain:engine#1·session_01DDZNkDVwPQnevTFcYE47H3.origin/main, with one parent (ce532184d1). Its diffstat matches the PR: 6 files, +647/-79.origin/main,migration-journal.tscarries the resumed-run path (planResumedRun): the chunk plan comes back fromrun_started.Check Changesetred (the DELIBERATE CORRECTION of.changeset/21498-cli-compose-migration-recovery.md, confirmed by the same-head record 5966422378).completedthroughFixes #21528. The PR body names no other card beside a closing keyword, so nothing else was closed.pm:dispatchedis removed in the same act.resumablechecks only that the plan is registered;Generated by Claude Code
fix(core): os migrate resume completes a recorded-by run that committ…