Data Model — Partition-Aware Implement-Claim Precondition

This is a control-flow correctness fix; there is no persisted schema. The "model" is the ref-resolution decision the precondition applies per artifact kind.

Entities / Value Objects

ArtifactKind partition (authority: mission_runtime/artifacts.py)

PartitionKindsLives on
PRIMARYspec.md, plan.md, data-model.md, research.md, checklists, tasks.md, tasks/WP*.md, lanes.json, meta.json, retrospectiveprimary/target branch (every topology)
COORDstatus.events.jsonl, status.json, acceptance-matrix, issue-matrix, analysis-reportcoordination branch (coord / lanes-with-coord)

Classified by kind_for_mission_file(path, mission_slug=...) / is_primary_artifact_kind(kind). Consumed read-only — not redefined here.

Precondition comparison ref (the decision this mission corrects)

For each candidate file in the working tree, the precondition asks: "compare its bytes against which committed ref to decide if it still needs a commit?" Resolved per path by resolve_precondition_ref(repo_rel_path, coord_branch_for_filter), which returns the coord branch only for is_coordination_artifact_residue_path(path) and "HEAD" for everything else (fail-safe toward primary).

Candidate pathcoord_branch_for_filterResolved ref
spec.md / plan.md / tasks.md / lanes.json (PRIMARY)any"HEAD"
meta.json (kind → None; not a residue path)any"HEAD"
status.events.jsonl / status.json / matrices / analysis (COORD residue)coord branchcoord branch
any pathNone (flat/primary)"HEAD"

Before (buggy): all files → single collapsed coord_branch_for_filter; PRIMARY artifacts (and meta.json) absent on the coord branch → false "changed". After (fixed): per-path resolution via resolve_precondition_ref. The meta.json → None classification (it is in the self-bookkeeping allowlist, not the kind map) is why the None-safe residue predicate is used instead of is_primary_artifact_kind(kind_for_mission_file(path)), which would misroute it to coord and trip mypy --strict.

State Transition — the claim precondition

claim(WP##)
  └─ _ensure_planning_artifacts_committed_git         (implement.py:494)
        └─ resolve_planning_artifact_staging          (implement_cores.py:454)
              ├─ _files_changed_vs_ref(files, ref)     (:406)   ← ref now per-kind
              ├─ _committed_meta_mapping(ref)          (:249)   ← ref now per-kind
              └─ _drop_runtime_frontmatter_only_wp(ref)(:387)   ← ref now per-kind
        ⇒ staging set EMPTY for already-committed PRIMARY artifacts ⇒ claim PROCEEDS
        (was: non-empty ⇒ "Planning artifacts not committed" printed at implement.py:319,
         then raise typer.Exit(1) at implement.py:342 on the auto_commit=False path)

Write-side (WP02, FR-003): when files_to_commit is non-empty (a genuinely-dirty PRIMARY artifact), _commit_planning_artifacts_transaction (implement.py) partitions the set by is_coordination_artifact_residue_path and commits PRIMARY → primary/target ref, COORD → coord ref (two transactions), so a PRIMARY artifact never lands on coord.

Boundary guard (unchanged): status-event commit target (_resolve_claim_commit_target) continues to resolve to the coordination ref.

Invariants

the coordination branch. (FR-001, FR-003)

commit to the coordination ref under coord/lanes-with-coord topology. (NFR-002, C-001)

(mission_runtime/artifacts.py); no consumer redefines it. (NFR-004)

still detected (the fix corrects the ref, it does not skip the check).

  • INV-1: PRIMARY planning artifacts are never diffed against, nor committed to,
  • INV-2: COORD-owned status/matrix/analysis files continue to diff against and
  • INV-3: Mission topology derivation is invariant across this change. (NFR-001)
  • INV-4: Exactly one artifact-kind→partition authority exists
  • INV-5: A PRIMARY artifact with genuine uncommitted working-tree changes is