Data Model: MissionTopology SSOT + structural planning-surface coherence

This is a seam + convergence mission. It introduces ONE new persisted field (topology in meta.json) and ONE new value type (MissionTopology); everything else is the existing surface-resolution concepts re-expressed against the stored topology.

MissionTopology (NEW enum — src/mission_runtime/context.py)

The mission-level shape: the orthogonal coordination × lanes 2×2 grid as one value.

Membercoordination branch?lanes.json?Meaning
SINGLE_BRANCHnonoone branch, no coord, no lanes (a.k.a. "all-on-feature")
LANESnoyeslane worktrees, no coordination branch
COORDyesnoa coordination branch, no lanes
LANES_WITH_COORDyesyeslanes + a coordination branch

dropped is now SINGLE_BRANCH/LANES with a flattened provenance flag (history, not shape).

LANES from SINGLE_BRANCH (both resolve a PRIMARY ref today and are indistinguishable to the per-ref CommitTargetKind).

  • FLATTENED is NOT a member. A mission that was coord and had its coordination_branch
  • The enum is the single place the lanes-vs-coord cross-product is named; it distinguishes

meta.json — NEW topology field (FR-002)

FieldTypeRoleWhen assigned
topologyMissionTopology (str value)the stored, authoritative mission shape — READ, never re-inferred at resolve timeat mission create (core/mission_creation.py); legacy missions via migrate backfill-topology
flattened (provenance)bool/flagrecords that a coord mission was flattened — history only, does not change topologyat flatten time

NOT recompute the shape from coordination_branch is None or a worktree stat.

topology exactly once via the legacy derivation, then reads the stored value. THIS mission's own meta.json is backfilled before any caller reads the field (dogfooding sequencing).

  • Invariant (FR-002/C-004): resolve-time code reads topology from meta.json; it does
  • Backfill (FR-003): until a legacy mission is backfilled, the shell computes-and-persists

resolve_context_for_mission (NEW pure resolver — mission_runtime/resolution.py)

resolve_context_for_mission(mission_id: str, topology: MissionTopology) -> ExecutionContext

I/O — NFR-005). The imperative shell parses/persists meta.json and passes id + topology.

AND the independent runtime_bridge.py:144-211 ladder (_coord_path.exists() ⇒ COORDINATION).

BranchRefFragment (target/coordination/destination ref), WorkspaceFragment (primary/coord/exec), StatusSurfaceFragment (read/write dir), ArtifactPlacementFragment, IdentityFragment.

  • A pure projection over the single construction door build_execution_context (no FS/git
  • Optional input-assertion: fail-closed on a supplied-vs-resolved mismatch.
  • Retires BOTH live derivations (FR-004): _resolve_coordination_branch/resolution.py:705-718
  • Returns the existing ExecutionContext (a.k.a. ActionContext, context.py:177) op-composite:

routes_through_coordination (NEW predicate, FR-005)

routes_through_coordination(target) -> bool   # MissionTopology-derived per-ref projection

mission.py ×2, tasks.py, orchestrator_api, _substantive, artifacts).

(~143 value-literal refs / 41 files) is the behavior-neutral Mission B (C-007).

  • Replaces the 9 .kind is COORDINATION decision-site reads (commit_router ×2, implement,
  • The CommitTargetKind TYPE survives this mission (vestigial constructor field); its eradication

Planning surface (where an artifact lives)

artifacts (spec.md, plan.md, tasks/, meta.json) and for SINGLE_BRANCH/LANES missions.

commit surface for COORD/LANES_WITH_COORD missions (staged at commit-time).

finalize-tasks, status events) resolve their surface through the seam; map-requirements writes and finalize-tasks --validate-only reads WP requirement_refs on the SAME surface.

  • PRIMARY checkoutkitty-specs/<slug>-<mid8>/ — authoritative for planning INPUT
  • COORDINATION worktree.worktrees/<slug>-<mid8>-coord/kitty-specs/<slug>-<mid8>/ — the
  • Invariant (FR-007/FR-008): planning writes (spec-commit, map-requirements,

Read resolution — driven by STORED topology (FR-006), transient states by the probe (C-006)

SituationStored topologyRead resolutionAuthority
coord mission, worktree presentCOORD/LANES_WITH_COORDCOORDstored topology
flattened, stale -coord husk on diskSINGLE_BRANCH/LANESPRIMARYstored topology (FR-006) — the husk is NOT consulted (was the #2062 bug)
no coordSINGLE_BRANCH/LANESPRIMARYstored topology
create-window (coord declared, worktree not yet materialized)COORD/LANES_WITH_COORDPRIMARYthe probe discriminates the transient state (#1718) — NOT the enum (C-006)
coord-deleted (declared branch gone from git)COORD/LANES_WITH_COORDhard-fail CoordinationBranchDeletedthe probe (#1848 data-loss carve-out) — NOT the enum (C-006)

(a disk stat) is NO LONGER the deciding signal. The on-disk husk cannot out-vote the stored shape.

states orthogonal to the 4 cells; they stay discriminated by probe_coord_state with the branch signal — the stored topology does not subsume them.

  • Key shift (FR-006/C-004): the read path resolves from stored topology, so CoordState.MATERIALIZED
  • Carve-out (C-006): create-window (#1718) and coord-deleted (#1848) are transient on-disk×git

Status-event write surface

feature_dir is resolved by the seam at every call site, not passed ad-hoc, so dep-gate/kanban/review-claim reads and move-task writes converge.

  • emit_status_transition(feature_dir=…) (status/emit.py) — Invariant (FR-009):

is_committed collapse (FR-011)

(FR-006/FR-007 via stored topology), the 3-surface OR reduces to a single-surface check on the resolved placement ref. Gated on the FR-010 live convergence proof (NFR-001/C-002).

  • missions/_substantive.is_committed (:317-412) — once the surface is structurally single

Differential equivalence gate (FR-010)

(mission_id, topology) for all 4 cells → assert ExecutionContext fields, zero FS/git fixtures; AND (b) the RETAINED on-disk flattened-stale-coord row × every handle form → PRIMARY. The type(a) is type(b) AND error_code assertion stays unweakened.

  • tests/missions/test_surface_resolution_equivalence.py — extended with (a) a pure cell: