Research: Residue-safe migration sweep and fabricated-identity audit
Grounding came from two squads: the alignment and scope squad, and the post-specify adversarial review. Both read and probed main @ 3d6754b8, and their probes ran with .venv/bin/python -I in temporary git repositories.
R-01 Successor selection on existing projects
- Decision: a successor's
detect()returns False when its predecessor is settled. "Settled" uses the same view selection uses: asuccessrecord in the predecessor's partition (registry.migration_settled), or pending settlement by first ledger adoption (adoption_settled_ids). - Rationale: a new migration id is unrecorded, so ledger-diff selects it on every ledger-era project. Without the gate, every project would re-run the whole sweep. A ledger-reading
detect()has precedent inm_4_0_0rc6_charter_pack_cutover._is_first_applicationandm_3_1_1_charter_rename. Using the selection view keepsupgrade --dry-runand the live run in agreement. The dry run writes no adoption backfill, so a raw ledger read would preview a re-sweep the live run never does. - Alternatives considered: recording a no-op
success(misreports "applied"); editing the adoption backfill (frozen, NFR-001); no gate at all (re-sweeps every project).
R-02 Successor placement
- Decision: each successor goes immediately after its predecessor in
order_manifest.txt, with the sametarget_versionand a# tie:annotation. - Rationale: later steps read what the predecessor wrote.
3.0.0_canonical_context(viamigration/runner._discover_features) and3.2.0a4_normalize_mission_lifecycleneed 2.0.6's baselinemeta.json.4.0.0rc8_retire_stale_mission_template_forksexpects 2.0.6's runtime-asset move.test_migration_order_manifest.pyaccepts ties. The #6589 supersession test requires only that the successor comes after the superseded id. - Alternatives considered: a 4.0.0rc8 slot at the end (breaks the 3.0.0 and 3.2.0a4 inputs for pre-2.0.6 projects).
R-03 How each successor reuses the frozen module
- Decision:
- 0.9.0 and 0.9.1: subclass the shipped class and override the per-folder predicates (
_is_legacy_format/_has_remaining_lane_dirsand the walkers that call them), so every writer stays in the frozen module. - 2.0.6 and 3.1.1: the per-folder walk is a module-level function, so their successors re-implement
detect()/apply()over a residue-filtered walk. 2.0.6 calls the frozen helpers (_migrate_runtime_assets,_cleanup_legacy_worktree_assets,_has_legacy_worktree_assets,_feature_requires_repair,_repair_feature,check_stale_legacy_assets). 3.1.1 calls_needs_normalisationand keeps its own snapshot write. - Rationale: importing a shipped module does not change its bytes, and
m_3_2_0a4_safe_globalize_commandsalready imports from a shipped module. The decorator is cached, so a subclass import does not register the parent twice. - Risk: 3.1.1's write lives inline in
apply(). Its successor's write is a new write site, so checktest_status_events_writes_gate.py,test_mutation_ownership_routing.py,test_generated_writer.pyandtest_byte_identity_mapping.py, and add a rationale entry only if a gate fires.
R-04 Git-aware mode
- Decision: git-aware mode applies when git can run and
tracked_mission_paths(repo_root)is non-empty. AGitCommandErrorwithnot_run, a non-work-tree, an ignoredkitty-specs/, or an untracked project nested in a parent repo all fall back to the file-presence rule. - Rationale: operator ruling (2026-10-25). The adversarial review confirmed that a nested untracked project and an ignored
kitty-specs/both yield an empty tracked set while_git_prefixsucceeds, so "git can run" alone is not enough. - Accepted cost (operator ruling): in git-aware mode, committed or uncommitted pre-identity folders with only
tasks/andtasks.mdbecome residue. The notice gives a working remedy.
R-05 Reproduction shapes still reachable after the narrow fix
- Finding: with only the rule and 2.0.6 filtered, two shapes still change under a real upgrade.
3.1.1rewrites a straystatus.json, and0.9.0movestasks/doing/WP01-x.md. This is why the operator extended the scope to supersede 0.9.0, 0.9.1 and 3.1.1.
R-06 Worktrees
- Finding:
_worktrees_to_upgrade(upgrade/runner.py:128-159, #5457) drops every integrating worktree (lane, coordination,kitty/mission-, detached HEAD). Only non-integrating.worktrees/are upgraded, and each readsMigrationLedger(worktree). The 0.9.x migrations also walk.worktrees/*/kitty-specsthemselves; the subclass override covers those folders, withrepo_rootset to the worktree.
R-07 Doctor finding signals
- Signal 1 is an allow-list (
meta.json,status.json,status.events.jsonl,decisions/,*.lock), and it needsmeta.jsonto be git-tracked. A deny-list reading would have matched the real Missionissue-6164-remove-hosted-from-core. - Signal 2 compares slug-derived fields from a shared helper. Blank means
"",nullor absent; a numeric prefix matches as either"007"or7(after 3.2.0a4 normalizationmission_numberis an int). - Signal 3 uses
mission_runtime.identity.mid8_from_slug(Crockford). It does not useresolve_mid8, which always returns the declared prefix. Of the 644 slugs, 76 carry no mid8 and can never match. - Corpus: signal 2 alone matches 5 folders, signal 3 alone matches 1, and all three together match 0.
R-08 Notice collapse
- Decision (DM 01M5SVE8QC28KRAZM25PR2N4PT): steps emit the same
RESIDUE_NOTICE, and the runner collapses repeats per folder per run.
R-09 Known residuals
- C-006: a pre-ledger project with the predecessor recorded
failedorskippedabove its version gets the successor settled by adoption, so the retry is lost. - #6673: a Mission whose tracked
meta.jsonis missing on disk gets a fresh identity (not residue; out of scope). - The doctor's signal 1 misses a fabricated folder that also kept a stray
tasks/.
Supply chain
No dependency is added, upgraded or removed; DIRECTIVE_051 controls are not triggered.