§1 — Ten repositories are still on legacy state, and two of them are wanted back

Measured from the live mounts registry: 12 mounted projects, 2 migratedimas-codex and reckon — and 10 still legacy: L2G, hanney, helix, imas-ambix, imas-efit, imas-standard-names, norma, nova, tessera, vv.

The lead wants imas-ambix and nova migrated first in order to resume work in those repositories. That is the ordering constraint this plan serves; the remaining eight follow behind the same proof.

What this plan is not. Three distinct things share the word migration and conflating them has already cost one wrong sequencing argument. This plan owns only the first.

MigrationWhat it doesOwner
Project stateConverts a project's monolithic index.json into typed per-resource HTML. Moves no files.This plan
LayoutFlattens 149 nested resources into typed roots and rewrites 119 links.sprint-scope-and-surface §3
Frozen-index redundancyThe legacy copy at state/<project>/index.json duplicates a byte-identical digest-named snapshot.Nobody yet — cleanup, not a blocker

A claimed prerequisite that was checked and did not hold, recorded so it is not re-litigated. It was argued that reckon build silently overwrites each migrated repository's frozen index.json, making the marker's integrity claim false on first publish, and that this had to be repaired ahead of everything. It does not: reckon/cli.py:1629 resolves output_state to projection.json whenever the project is distributed, sync carries the same guard at reckon/cli.py:1419, and tests/test_project_state.py:999 is a regression test named test_static_build_writes_projection_without_touching_canonical_index. The guard is structural, not a scheduling accident. This plan proceeds on the lead's direct instruction, not on that argument.

§2 — The robustness gate: field-by-field, before anything live is touched

The bar is the lead's and it is specific: the migration must not drop plan linking or other relational and organisational metadata. Existing evidence does not meet it. The imas-codex run reported 13 resources, source b9fc8216ed3b, composed parity 46b93fe68018 and a byte-identical snapshot — that is composed-state parity, a whole-blob digest comparison. It is not a field-by-field diff of relational metadata, and it must not be cited as though it were.

The evidence can be produced without touching a live target. imas-codex and reckon are already migrated and both retain their frozen pre-migration snapshots. Diffing the post-migration typed resources against those snapshots yields the field-by-field proof on real corpora, at zero risk to imas-ambix or nova.

What must survive, enumerated so the harness cannot quietly check less:

ClassFields
Relationaldepends_on, blocks, informs, evidence_for, verifies, supersedes
Organisationalsprint, milestone, north_star, tier
Sprint membershipevery item, with its why_now and done_when intact
Nested collectionsdecisions — including locked choice, rationale, by, when — plus followups, comments, questions, research

Decision rationales are the field to watch hardest. They are the longest free text in the state and the least likely to be spot-checked, so a truncation or an entity-escaping fault there survives every summary-level comparison. The harness compares them in full rather than by length or digest alone.

Two measured false positives the harness must exclude by name

Both would read as the migration breaking relational metadata. Neither is caused by it, and a naive before/after diff reports both.

Done when a harness diffs both already-migrated projects against their frozen snapshots and asserts every field in the table above is preserved exactly, with the two false positives excluded by explicit name rather than by a loosened comparison, and the run emits a per-field count of compared and matched values so a silent no-op cannot pass as a proof.

§3 — imas-ambix and nova

With the gate green, migrate the lead's two priority repositories. Each is a separate transaction: reckon migrate-fleet resolves the mounts registry at runtime, fixes its path and content hash for the run, snapshots every registered repository, and mutates only those named with repeated --apply-project. Unselected, dirty, detached or otherwise unsafe repositories take an explicit terminal deferred ledger row rather than silent omission.

Each migration is verified with the §2 harness against its own snapshot, not assumed from the fleet command's exit code. The harness was built to run on already-migrated corpora precisely so it can be pointed at a freshly migrated one without modification. A repository whose diff is not clean is rolled back, not explained.

Done when both repositories report format=distributed status=complete, the §2 harness passes against each, roadmap resolves in both, and the resulting state is committed in each repository — with any new wiring finding classified as pre-existing-and-now-visible or as a genuine defect, never left ambiguous.

§4 — The remaining eight

L2G, hanney, helix, imas-efit, imas-standard-names, norma, tessera, vv. Same transaction shape, same per-repository verification, no new mechanism.

None of the ten carries topical grouping this migration could damage, and that was checked rather than assumed. The 73 standard-names documents live in imas-codex — across research/standard-names/, archive/standard-names/ and archive/research/standard-names/, which is the three interleaved numbered sequences the tagging work describes — and imas-codex is already project-state migrated. imas-standard-names carries none of them, despite its name. imas-ambix has no topical subdirectory under a typed root at all, and nova has exactly one, evidence/logs/biot-element-conditioning.

That exposure belongs to the layout migration in any case, which moves files; this one does not. project_state.py contains no document mover — its only os.replace is inside _durable_replace, the atomic-write helper that fsyncs a temporary file, replaces the target and fsyncs the directory entry. Directory grouping is untouched by everything in this plan.

Done when all 12 mounted projects report distributed state and the §2 harness passes against every one of them.

§5 — Born distributed: the change that makes the tools deletable

This is a correction to the premise that the tools are one-shots, and it is the reason §6 cannot simply be executed. The distributed marker is written only by project_state.py and fleet_migration.py. reckon sync never writes it: for any project without a marker it takes the else branch at reckon/cli.py:1419 and seeds a legacy index.json with discovered sprints, milestones and active_sprint_id.

So a newly onboarded repository is born legacy. Deleting migrate-project-state today would strand every future repository in a state with no forward path — the tool would be gone precisely when the next project needed it. migrate-fleet is genuinely one-shot, because its input is the fixed set of ten; the per-project migration is not, until this changes.

Deliverable: reckon sync creates distributed typed state natively for a project that has none, writing the marker as part of that creation, so the legacy seeding path is reachable only for projects that already have a legacy index. A new project never enters the legacy shape at all, which is a stronger guarantee than migrating it promptly afterwards.

This must land before project-onboarding extends the same function, and that is a file-scope constraint as much as a logical one: onboarding adds Pages detection and badge insertion to reckon sync, and two plans editing that function concurrently is exactly the merge hazard the sprint contract exists to prevent.

Done when a test onboards a project with no prior state and asserts the result reports format=distributed with no index.json written, and a second test asserts a project with an existing legacy index is left untouched by sync rather than silently converted.

§6 — Deleting the ladder

Once every project is distributed and nothing can be born legacy, both migrate-fleet and migrate-project-state have no remaining caller and no future input. They are removed, along with their one-shot tests, in the commit that records the fleet complete — the same disposal rule the sprint already locked for the layout migration's scripts, and for the same reason: a one-shot kept for reuse becomes an undocumented tool someone runs later against a corpus it no longer describes.

The receipts stay. The migration markers, the frozen snapshots and this plan's evidence record are the durable artifacts; the code that produced them is not.

Done when the fleet reports 12 of 12 distributed, the commands are gone from the CLI surface, the test suite is green without them, and a fresh reckon sync on a scratch project still produces distributed state — proving the deletion removed a ladder rather than a floor.

§ Decisions

Where does the field-by-field relational-parity evidence come from?

The lead requires proof that the migration does not drop plan linking or other relational metadata BEFORE it touches imas-ambix or nova. The existing imas-codex run proved composed-state parity — a whole-blob digest — which is a weaker claim than the bar asks for.

Locked 2026-08-24 on the sequencing constraint rather than on preference. imas-codex and reckon are already migrated and both retain frozen pre-migration snapshots, so the proof is available on two real corpora at zero risk to either priority repository — and it is available NOW, whereas a dry-run harness on the targets makes the gate depend on the very thing it is gating. The harness is written to run against any project plus its snapshot, so the same code verifies each live migration in §3 without modification. Accepted limit: the evidence covers the corpus shapes those two projects contain, which is why imas-standard-names is sequenced last in §4 as the corpus most likely to exceed them.

In what order does the fleet migrate?

Locked by the lead 2026-08-24: work is waiting to resume in imas-ambix and nova, so they migrate first and their completion is confirmed back before the rest proceed. This also gives the fleet sweep two verified precedents rather than making the first live migration a ten-way commitment. Hardest-first was not chosen despite its diagnostic appeal because it delays exactly the two repositories the lead is blocked on.

What must be true before the migration tools are deleted?

The lead's position is that both tools are one-shot processes that should not recur and can be deleted once the fleet is migrated. That is true of migrate-fleet, whose input is the fixed set of ten. It is not yet true of migrate-project-state.

Locked 2026-08-24, correcting the premise rather than following it. reckon sync never writes the distributed marker; for a project without one it seeds a legacy index.json at cli.py:1419. So a newly onboarded repository is born legacy, and deleting the per-project migration on fleet-completion alone would strand every future repository — the tool would be gone at precisely the moment the next project needed it. Making sync create distributed state natively removes the last future caller, at which point the lead's instinct is correct and both tools go. The deletion is not weakened, only sequenced behind the change that earns it.

§ Followups

Prove relational parity, then migrate the two repositories work is waiting on

The gate comes first and it is not negotiable: a field-by-field diff of both already-migrated projects against their frozen snapshots, covering relations, organisational fields, sprint items and the nested collections — with decision rationales compared in full, and the two measured false positives excluded by name rather than by loosening the comparison. Then imas-ambix and nova, each verified against its own snapshot with the same harness and rolled back rather than explained if the diff is not clean. Then the remaining eight, with imas-standard-names last as the corpus most likely to exceed what the gate proved. sync creating distributed state natively must land before project-onboarding touches the same function, and it is what earns the deletion in §6.

/reckon-ship project-state-fleet-migration