Data Model — Coord-Authority Gate Hardening (01KW4T2F)

Phase 1. The "entities" here are GATE entities — the static-analysis vocabularies, census sets, and fixtures the hardening operates over. No runtime persisted state, no schema migrations. All sets are defined in tests/architectural/ (the arm) and src/mission_runtime/artifacts.py (the partition), and all fixtures in tests/integration/coord_topology_fixture.py.


1. Read-func families (the call-shape arm vocabulary)

The two kind-read shapes the call-shape arm gates, each bounded to its own scan scope (SC-005 — never red-CI on out-of-scope strangers).

FamilyRead funcsScan scope (current)Scan scope (after FR-002/FR-005)
IDENTITYresolve_mission_identity, get_mission_typecli/commands/ + agent_utils/status.py+ merge/ + lanes/ + core/worktree_topology.py (FR-002) + src/runtime/next/ (FR-005)
LANES.JSONread_lanes_json, require_lanes_jsonmerge/ + lanes/ + core/worktree_topology.py+ src/runtime/next/ (FR-005)

Defined: _IDENTITY_READ_FUNCS / _LANES_READ_FUNCS (test_gate_read_literal_ban.py); scan dirs _IDENTITY_SCAN_DIRS / _IDENTITY_SCAN_FILES / _LANES_SCAN_DIRS / _LANES_SCAN_FILES (test_coord_read_residuals_closeout.py).


2. Coord-aware resolver set (the divergence source)

A dir bound from one of these selects the STATUS-only -coord husk under coord topology (no meta.json/lanes.json since #2106). A kind-read off such a dir without a primary fold is the violation.

_COORD_AWARE_CALLSHAPE_RESOLVERS = {        # CURRENT — 3 names
    "resolve_feature_dir_for_mission",
    "candidate_feature_dir_for_mission",
    "resolve_feature_dir_for_slug",
}

FR-001 widening (REQUIRED — root cause of the FR-001 hollowness). This 3-name set is narrower than the read-arm's _TOPOLOGY_ROUTED_READ_RESOLVERS (5 names — the 3 above plus _find_feature_directory and resolve_handle_to_read_path). The named one-hop residual _run_documentation_wiringsetup_plan binds feature_dir from _resolve_setup_plan_feature_dir_find_feature_directory, which this 3-name set does NOT recognize as coord-aware → the one-hop check would fire on no live caller. FR-001 MUST widen the call-shape coord-aware set to align with _TOPOLOGY_ROUTED_READ_RESOLVERS (catalog _resolve_setup_plan_feature_dir and/or _find_feature_directory) so the residual's binding is recognized. The one-hop check also needs a module-scoped caller index (the per-function callshape_violations(func, *, read_funcs) signature gains caller/module context). Re-run the NFR-004 census across the widened surface.

3. Primary-fold seam set (the sanctioned shape)

A dir bound from / built by one of these lands on the durable PRIMARY kitty-specs/<slug>-<mid8>/ home for every topology — NEVER flagged.

_PRIMARY_FOLD_CALLSHAPE_FUNCS = {
    "_canonicalize_primary_read_handle",
    "primary_feature_dir_for_mission",
    "resolve_planning_read_dir",
}

4. Sanctioned primary attributes / folds (FR-008)

When a kind-read's first arg is an ast.Attribute, it is sanctioned (never flagged) iff it is a primary-attr:

Sanctioned attributeWhy
.target_feature_dirthe primary-surface field on the run/context object
a field bound from a primary foldalready folded onto PRIMARY by the caller

Any other coord-bearing attribute (e.g. run.feature_dir) is flagged. Self-mutation anchor: resolve_mission_identity(run.feature_dir) in executor → RED.


5. Named shrink-only census + sanctioned-exclusion set (FR-003)

Every in-scope param-/attribute-fed kind-read is either routed or recorded in a NAMED shrink-only census, mirroring _CALLSHAPE_KNOWN_RESIDUALS (currently frozenset()) and _DIR_READ_KNOWN_RESIDUALS.

Census schema (per entry): "<rel_path>::<qualname>" → tracked residual with a tracker reference. Shrink-only semantics:

  • A NEW in-scope flag not in the census → RED (cannot hide behind the known set).
  • A pinned residual that no longer flags (fixed) → RED (remove the stale pin; ratchet stays tight).

Sanctioned, never-flagged (NOT census entries — true exclusions):

ExclusionKindWhy never flagged
require_lanes_jsonleaf primitiveMUST take a dir; it IS the by-kind resolver leaf (C-006)
_mission_identity_payloadpayload/builder helperbuilds a payload from an already-resolved dir, not a read-routing decision
read_events STATUS-leg reads inside _STATUS_BEARING_MODULES (lanes/recovery.py, merge/executor.py)STATUS legC-007 binding: a "self-resolve → PRIMARY" remediation here would move a STATUS leg to PRIMARY (C-001/#2155 re-opener)

The exclusion is READ-FUNC-SCOPED, not a blanket module exclusion (paula/renata post-plan). _STATUS_BEARING_MODULES pairs with _STATUS_READ_FUNCS = {"read_events"} — only the read_events STATUS legs in recovery.py/executor.py are excluded. Identity/lanes reads in the same modules remain IN-SCOPE and flaggable — in particular an injected resolve_mission_identity(run.feature_dir) off a coord-aware dir in merge/executor.py (SC-006 / FR-008) MUST be caught. Excluding the whole module would let the SC-006 executor residual escape (FR-008 directly requires it be flagged). _STATUS_BEARING_MODULES = ("src/specify_cli/lanes/recovery.py", "src/specify_cli/merge/executor.py"); the read_events STATUS leg stays coord-aware (the NFR-001 secondary cross-check already guards this — do not regress it).


6. PRIMARY/STATUS partition + the per-kind rationale map (FR-006)

The live partition (src/mission_runtime/artifacts.py)

MissionArtifactKind (14 members). The two frozensets partition it exactly once (exhaustive + disjoint — asserted today):

PartitionMembers
_PRIMARY_ARTIFACT_KINDSSPEC, DATA_MODEL, RESEARCH, CHECKLIST, FINALIZED_EXECUTION_PLAN, TASKS_INDEX, WORK_PACKAGE_TASK, LANE_STATE, PRIMARY_METADATA, RETROSPECTIVE
_PLACEMENT_ARTIFACT_KINDS (COORD)ACCEPTANCE_MATRIX, ISSUE_MATRIX, STATUS_STATE, ANALYSIS_REPORT

The net-new machine-read rationale map

PARTITION_RATIONALE: dict[MissionArtifactKind, tuple[partition, rationale, load_bearing_consumer]]
  • One entry per enum member (missing entry → RED).
  • The map's derived PRIMARY/STATUS split MUST == the live frozensets (re-home a kind without editing its rationale → RED → SC-003).
  • A parametrized anti-mutant runs across ALL load-bearing kinds (not just SPEC): forcing each kind into the opposite partition makes its placement-ref assertion go RED.

Keyed on enum members — content-anchored, allowlist-free, zero line-pins (CT7 exemplar, C-004 / NFR-005).


7. The divergent coord-topology fixture (tests/integration/coord_topology_fixture.py)

The behavioral backstop reused for SC-001/SC-002/FR-007. No resolver is patched — real git + filesystem state.

FixtureHusk shapeUsed by
coord_topology_missionPRIMARY: meta.json (topology=coord, coordination_branch), tasks/WP01.md, lanes.json, DECOY status.events.jsonl. Coord husk: status.events.jsonl ONLY (no tasks/, no lanes.json, no meta.json).SC-002 (preview revert-fails), FR-007(a) production-shaped STATUS-only husk
coord_topology_mission_sentinel_metaas above + husk meta.json PRESENT-but-WRONG (SENTINEL_HUSK_MISSION_ID, SENTINEL_HUSK_MISSION_TYPE=research) — wrong-leg read returns a SILENT-WRONG domain value rather than raisingidentity revert-fails proofs
flat_topology_missionsingle-branch neutralityneutrality checks
NEW (owned by IC-C / FR-007): tasks/-present-non-legacy husk varianta coord husk carrying a post-3.0 tasks/ (WP .md, no planned/doing/... lane subdirs)FR-007(b) — exercises the LEGACY_LANE_DIRS/.md branch → still no-op

Single-consumer rule (paula post-plan correction — there is NO fixture contention). IC-B's SC-002 uses the existing coord_topology_mission fixture (its STATUS-only husk already lacks tasks/); IC-B does not need the tasks/-present-non-legacy variant. The new variant is consumed only by IC-C (FR-007(b)), so IC-C owns and adds it. The earlier "extend ONCE in IC-B, IC-C consumes read-only" contention resolution is removed: with a single consumer there is no contention to resolve.

Helpers: assert_reads_primary, assert_status_from_coord, assert_both_legs (existing, reused).


8. CT7 content-anchor primitive (consumed, not modified)

tests/architectural/_ratchet_keys.composite_key(source, lineno) -> (qualname, token_line) — the drift-proof anchor that survives +1 line drift (qualname + string/comment-stripped token line). Every new/extended gate keys off composite_key, never file.py:NNN (NFR-001).