Data Model — Landing-Pass Campsite Follow-ups

This is a test-infrastructure + CLI-internals mission; it introduces no persistent domain entities. The only "model" changes are the small structural elements below.

SG-1 — ShardGroup.default_fallback (new optional field)

hash-bucket shard for any under-root file not present in file_assignment, instead of None.

win; the fallback only fires for unlisted under-root files; the group's shard union still covers the full root universe (GC-1 union invariant).

row is unaffected unless it also opts in.

  • Where: tests/_shard_registry.py (the ShardGroup dataclass).
  • Type: bool, default False.
  • Meaning: when True, shard_for(path) returns a deterministic
  • Invariant: explicit file_assignment entries are checked first and always
  • Consumers: the arch row (tests/_arch_shard_map.py) opts in; the next

LN-1 — Lane.UNINITIALIZED (new canonical member)

src/specify_cli/status/lane_reader.py.

it from get_wp_lane() instead of the bare string LEGACY_UNINITIALIZED_SENTINEL, so the loader's return type is a pure Lane.

distinct. get_wp_lane returns the sentinel when a WP is absent from the snapshot (empty event log, or WP not present); GENESIS is a WP that is seeded but has no explicit lane (lane_reader.py:72 default). Collapsing them onto GENESIS regresses worktree_topology.py:81 (unseeded→"planned") and merge/done_bookkeeping.py (done-detection). Verified in code.

== "uninitialized" equality (via StrEnum) and any serialized form remain behavior-identical. UNINITIALIZED is a non-display, non-transitionable read sentinel (like GENESIS). Full, verified blast radius (post-tasks squad):

allowed_targets() -> frozenset() (EMPTY)** and register it in _STATE_MAP/_FACTORY_ALIASES — NOT an alias to GenesisState. Aliasing to genesis (targets {PLANNED, CANCELED}) would inject 2 edges into the import-time ALLOWED_TRANSITIONS build (transitions.py:43-48) → the ==29 count test fails AND the sentinel becomes transitionable. An unmapped member crashes transitions.py:44 at import.

through a canonical non-display-lane authority): reducer.py:134,166, wp_metadata.py:385, tasks_status_view.py:163, and lifecycle.py:119 (the last has no genesis filter today — it already emits "genesis": 0).

requires every non-genesis member in CANONICAL_LANES. UNINITIALIZED is exempted like genesis (non-display) — update the parity exemption, do NOT add it to the tuple.

tests/specify_cli/cli/commands/agent/, tests/integration/, tests/test_dashboard/ derive "all non-genesis lanes" or a hardcoded roster and must also exclude/extend for UNINITIALIZED.

merge/done_bookkeeping.py:151 both rely on Lane("uninitialized") RAISING today; once it is a member the except goes dead — preserve the GENESIS-fallback / force-done=False contract explicitly.

annotation → dict[str, Lane] (verify workspace/context.py:497,513 does not surface a new diagnostic). Requires behavior tests on the unseeded path — the regression is type-invisible because consumers already str(...)-coerce.

  • Where: src/specify_cli/status/models.py (add member), consumed via
  • Change: add a NEW Lane.UNINITIALIZED = "uninitialized" member and return
  • Why a new member, NOT reuse of Lane.GENESIS: the two are semantically
  • Invariant: the StrEnum value stays "uninitialized", so existing
  • FSM (wp_state.py): add a **dedicated UninitializedState with
  • Display filters must exclude UNINITIALIZED at five sites (route
  • CANONICAL_LANES parity (status_lanes.py): test_parity.py:812
  • ~12 lane-roster tests across tests/status/, tests/specify_cli/status/,
  • Consumer contract (WP06): coordination/status_transition.py:557 and
  • Effect: removes the Lane | str union at its source; get_all_wp_lanes

RR-1 — Remediation registry

ALL_REMEDIATION_TEXTS collection is the canonical set. Both _REMEDIATION_HINTS (field → remedy) and _build_remediation_lines() (trigger → bullet) reference the constants.

(the full set), not just the dict; every referenced command resolves under --help. Rendered output is byte-identical to today.

  • Where: src/specify_cli/sync/preflight.py.
  • Change: all remediation sentences become named module constants; a single
  • Invariant: the command-name validation guard scans ALL_REMEDIATION_TEXTS

No API contracts

No external route, request/response body, auth header, websocket, sync payload, or tracker control-plane semantics change → contracts/ is intentionally empty (recorded in plan.md).