Data Model: Merge Retention Policy (#3131)

Entity: Retention policy (persisted in meta.json)

FieldTypeDefaultMeaning
retain_branchesbool (JSON boolean)absent = no policyWhen true, lane + mission branches are retained after merge unless an explicit CLI delete override is given.
retain_worktreesbool (JSON boolean)absent = no policyWhen true, lane worktrees (and, coupled, the coordination worktree) are retained after merge unless an explicit CLI remove override is given.

(mission_metadata.py) as flat optional fields; validate_meta preserves them.

field ONLY when its value is true. No migration; legacy missions omit both.

load_meta_fail_closedNone on absent file, MissionMetaReadError on corrupt (aborts the merge).

boolean ("", 0, "true", "false", list, object) is treated as ambiguous → resolves to retain + warn; it is NEVER bool()-coerced. JSON null is the exception: meta.get() cannot distinguish null from an absent field, and null means "no value set", so retain_branches: null resolves as absent → default (delete/remove), preserving INV-3. (WP01 decision, review-ratified.)

  • Home: mission meta.json, primary partition. Added to MissionMetaOptional
  • Write path: mission create (via create_mission_core kwargs) writes a
  • Read path: read_retention_from_meta(primary_meta_dir) over
  • Validation / fail-closed: a genuinely-present value that is not a JSON

Value object: Effective cleanup decision (runtime, not persisted)

Produced by resolve_merge_retention(...):

FieldTypeMeaning
delete_branchboolResolved: whether lane/mission branches are deleted.
remove_worktreeboolResolved: whether lane worktrees are removed.
teardown_coordinationboolCoupled coord decision = delete_branch AND remove_worktree (tear coord topology only when both).
branch_sourcestr ∈ {cli, meta, default}Provenance of delete_branch.
worktree_sourcestr ∈ {cli, meta, default}Provenance of remove_worktree.
warningslist[str]Operator-visible messages (retention-honored, malformed-value).
override_noticeslist[str]Recorded notices when an explicit CLI delete overrode a mission retention policy.

Resolution rule (per resource)

explicit_flag is not None  → effective = explicit_flag ; source = "cli"
                             (if explicit deletes AND meta retains → append override_notice)
explicit_flag is None:
    meta says retain (true)         → effective = keep   ; source = "meta" ; append warning
    meta malformed (non-bool)       → effective = keep   ; source = "meta" ; append warning (malformed)
    meta absent / false             → effective = default(delete/remove) ; source = "default"

delete_branch is the negation of "retain branches"; remove_worktree is the negation of "retain worktrees". teardown_coordination = delete_branch AND remove_worktree.

State transitions (post-merge cleanup, gated by the decision)

stateDiagram-v2
    [*] --> Merged: lane consolidation succeeds (unchanged)
    Merged --> LaneWorktrees: remove_worktree?  (lane worktrees)
    LaneWorktrees --> LaneBranches: delete_branch?  (LANE branches only)
    LaneBranches --> CoordTopology: teardown_coordination? (delete_branch AND remove_worktree)
    CoordTopology --> ScratchCleanup: always (cleanup_merge_workspace, ungated)
    ScratchCleanup --> [*]
    note right of CoordTopology
      COORD topology only. Coupled unit:
      coord/mission branch delete + marker-flatten
      + coord-worktree destroy happen together or
      not at all (atomic — preserves #3086).
      Non-coord mission_branch stays on delete_branch.
    end note
    note right of ScratchCleanup
      Retention never gates this (C-006).
    end note

Topology-awareness (INV-2 detail). For a coord mission the coordination branch is lanes_manifest.mission_branch; its deletion, the coordination_branch marker-flatten, and the coord-worktree teardown form ONE atomic unit gated by teardown_coordination. For a non-coord (single_branch/lanes) mission there is no coordination branch/marker/worktree, so the mission-branch deletion stays under delete_branch exactly as today (no behavior change). This both preserves the #3086 flatten-atomicity and fixes the pre-existing --keep-worktree-on-coord husk.

Invariants

coordination worktree of a retaining mission is deleted without either an explicit recorded override or an emitted warning — across success AND abort.

are always in a mutually consistent state (all retained or all torn down).

exactly as before this change.

→ retain) never resolves to delete.

  • INV-1 (no silent deletion): no lane/mission branch, lane worktree, or
  • INV-2 (coord consistency): the coordination branch, worktree, and marker
  • INV-3 (byte-identical default): a mission with neither field behaves
  • INV-4 (fail-closed): any ambiguity (corrupt meta → abort; malformed value