Implementation Plan: Execution-State Domain Remediation — #1619 Strangler Fig

Branch: main | Date: 2026-06-03 | Spec: spec.md Input: kitty-specs/execution-state-domain-remediation-01KT6HVH/spec.md

Summary

Six linked issues (#1663, #1664, #1667, #1672, #1673, #1674) implement the Strangler Fig sequence to fix CWD-dependent execution-context re-derivation across ~40 spec-kitty command surfaces. Implementation follows a strict dependency order: ADRs gate all code; an e2e parity ratchet gates all Strangler steps; the MissionStatus aggregate and status/ boundary enforcement run after the ratchet; the MissionRun → Mission back-reference can run in parallel; and resolve_action_context hardening closes out the sequence after the aggregate lands.

Technical Context

Language/Version: Python 3.11+ Primary Dependencies: Pydantic (MissionRunSnapshot, BaseModel), dataclasses (stdlib, MissionStatus frozen dataclass), pathlib (stdlib), pytest, mypy, ruff Storage: Filesystem — kitty-specs/<slug>/status.events.jsonl, .kittify/runtime/feature-runs.json, .kittify/runtime/<run_id>/state.json Testing: pytest — new tests in tests/architectural/ (boundary enforcement, e2e ratchet) and existing tests/integration/ patterns Target Platform: Python CLI (Linux/macOS), same as existing spec-kitty runtime Project Type: Single Python project under src/ Performance Goals: Architectural boundary scan ≤10 s wall-clock in CI (NFR-005) Constraints: No new external dependencies; BookkeepingTransaction internals unchanged (C-004); MissionRunStartedPayload (spec_kitty_events) out of scope (C-005); ADRs must land before any implementation PR (C-001) Scale/Scope: ~40 bypass surfaces to route; ~245 direct submodule imports to fix; 6 in-engine snapshot-copy sites to update; 2 schema classes to extend; 3 ADR files to author

Charter Check

Charter context loaded (compact mode). No charter violations identified:

  • Complexity gate: Architectural refactoring within existing domain boundaries — no new bounded contexts, no new external services.
  • Dependency gate: No new external PyPI packages. Pydantic and dataclasses are already present.
  • Boundary gate: MissionStatus is placed inside status/ (Mission Management domain) per the ADR design — consistent with the four-module domain model.
  • Commit atomicity gate: MissionStatus.save() routes through BookkeepingTransaction — the existing atomicity mechanism is preserved, not bypassed.

Post-Phase 1 re-check: no new gaps identified after design artifact generation.

Project Structure

Documentation (this feature)

kitty-specs/execution-state-domain-remediation-01KT6HVH/
├── plan.md              ← This file
├── research.md          ← Phase 0 output
├── data-model.md        ← Phase 1 output
├── checklists/
│   └── requirements.md
└── tasks.md             ← Phase 2 output (generated by /spec-kitty.tasks)

Source Code (affected paths)

architecture/3.x/adr/
├── 2026-06-03-1-execution-state-domain-model.md          ← WP01 (new)
├── 2026-06-03-2-executioncontext-owner-and-committarget.md ← WP01 (new)
└── 2026-06-03-3-effector-actor-model.md                  ← WP01 (new)

src/specify_cli/
├── status/
│   ├── __init__.py                    ← WP03 (promote symbols) + WP04 (export MissionStatus)
│   ├── aggregate.py                   ← WP04 (new: MissionStatus + ActiveWPStatus)
│   └── [all submodules]               ← WP03 (rename private where needed)
├── core/
│   └── execution_context.py           ← WP06 (no structural change; surfaces route through it)
├── cli/commands/agent/
│   ├── status.py                      ← WP04 (migrate to MissionStatus.load() + .claim())
│   └── workflow.py                    ← WP06 (route fix-mode through resolve_action_context)

src/runtime/next/
├── _internal_runtime/
│   ├── schema.py                      ← WP05 (add optional mission_id, mission_slug to MissionRunSnapshot)
│   └── engine.py                      ← WP05 (extend MissionRunRef, plumb 6 copy sites)
└── runtime_bridge.py                  ← WP05 (feature-runs.json write) + WP06 (query-mode routing)

src/specify_cli/glossary/              ← WP01 (GovernanceContext/ExecutionContext/InfraContext/Effector entries)

tests/architectural/
├── test_status_module_boundary.py     ← WP03 (new: ~245 bypass import enforcement)
└── test_execution_context_parity.py   ← WP02 (new: e2e CWD parity ratchet)

tests/integration/                     ← WP02 (supporting fixtures if needed)

Implementation Sequence

Work packages follow a strict dependency order. The ratchet (WP02) is the primary gate.

WP01 (ADRs + glossary)
  └── WP02 (e2e ratchet)
        ├── WP03 (status/ boundary)        [parallel with WP04, WP05]
        ├── WP04 (MissionStatus aggregate) [parallel with WP03, WP05]
        │     └── WP06 (ExecutionContext hardening)
        └── WP05 (MissionRun back-reference) [parallel with WP03, WP04]
WPIssueDescriptionDepends on
WP01#1674Three ADRs + glossary entries
WP02#1672e2e parity ratchet test (main-CWD vs lane-CWD)WP01
WP03#1664status/ boundary test + fix ~245 bypass importsWP02
WP04#1667MissionStatus aggregate + agent/status.py migrationWP02
WP05#1663MissionRun → Mission back-reference on snapshot + refWP01
WP06#1673Route residue surfaces through resolve_action_contextWP02, WP04

Phase 0: Research

See research.md for full findings. Key resolutions:

1. _resolve_mission_ulid availability: confirmed at src/runtime/next/runtime_bridge.py:95 — callable at start_mission_run time to populate mission_id on the new snapshot fields. 2. Snapshot-copy sites: confirmed ~6 sites in engine.py (lines 282, 346, 368, 393, 448 + MissionRunRef construction at 207/227) — all must carry mission_id and mission_slug through. 3. status/__init__.py public API: ~35 symbols already exported — MissionStatus and ActiveWPStatus will be added to __all__ after creation. 4. Architectural test pattern: tests/architectural/test_shared_package_boundary.py uses both pytestarch and an AST scanner — the new status/ boundary test follows the same dual-mechanism pattern. 5. e2e ratchet location: tests/architectural/test_execution_context_parity.py — follows the "architectural" classification since it validates an architectural invariant (CWD-invariance), not a feature behavior. 6. coordination/status_transition.py exemption: confirmed as internal domain plumbing; boundary test must mark it as an internal caller, not a violation.

Phase 1: Design & Contracts

See data-model.md for full type designs.

WP01 — ADR Authoring Plan

Each ADR follows the format in architecture/3.x/adr/2026-04-25-1-shared-package-boundary.md (Status / Context / Decision / Consequences sections). Three files:

1. 2026-06-03-1-execution-state-domain-model.md — four-bounded-module domain model; Mission Management owns status; context is per-domain. 2. 2026-06-03-2-executioncontext-owner-and-committarget.mdresolve_action_context is the canonical OHS entry point; CommitTarget atomicity; Strangler Fig via Option C→B. 3. 2026-06-03-3-effector-actor-model.md — Effector named in docs only; materialization trigger; low-layer placement when materialized.

WP02 — e2e Ratchet Design

The test fixture: 1. Creates an in-memory (tmp_path) mission with two work packages using existing test helpers. 2. Runs next → implement WP01 → move-task WP01 in_progress → review WP01 → agent status WP01 from the main checkout CWD. 3. Repeats the move-task → review → status sub-sequence from the lane worktree CWD. 4. Asserts that the lane state and status JSON output are byte-for-byte identical between both invocations.

Marked @pytest.mark.integration and registered in the CI path filter for src/specify_cli/core/execution_context.py and src/specify_cli/status/.

WP03 — Boundary Enforcement Strategy

Two-pass approach matching test_shared_package_boundary.py:

  • Pass A (pytestarch): declares the structural rule "no module outside specify_cli.status may import from any specify_cli.status.* submodule".
  • Pass B (AST scanner): grep-confirms zero from specify_cli.status.<submodule> imports in src/ outside status/ (exempting coordination/status_transition.py).
  • Injection test: inserts a synthetic bypass import into a tmp file and proves the scanner catches it.

Symbol promotion decision: symbols in __all__ that need external access → promoted to status/__init__.py; symbols with no external consumers → renamed _<name>.

WP04 — MissionStatus Aggregate

New file: src/specify_cli/status/aggregate.py. Contains ActiveWPStatus and MissionStatus. MissionStatus.load() calls the existing topology resolver (missions/_resolve_planning_branch.py, workspace/root_resolver.py) to determine legacy vs coordination. Internal calls route through the existing emit_status_transition and BookkeepingTransaction.acquire() paths — no new persistence mechanism.

Migration of agent/status.py: replace the two-line raw path construction with:

ms = MissionStatus.load(repo_root=main_repo_root, mission_slug=mission_slug)
wp_status = ms.claim(wp_id)

WP05 — MissionRun Back-Reference

Pydantic Optional[str] = None fields added to MissionRunSnapshot and MissionRunRef. No migration of on-disk files needed — Pydantic's default handles missing fields gracefully. The previously write-only inputs["mission_slug"] at engine.py:216 is removed as dead code (replaced by the new field on the snapshot).

WP06 — Residue Surface Routing

Starting investigation command (run after WP04 lands):

grep -rn 'kitty-specs.*mission_slug\|main_repo_root.*kitty\|feature_dir.*slug' \
  src/specify_cli --include='*.py' | grep -v 'status/' | grep -v 'core/execution_context'

Primary targets:

  • runtime_bridge.py query-mode: replace independent feature_dir derivation with resolve_action_context(...).
  • workflow.py fix-mode: route repo_root / target_branch through resolve_action_context.
  • Delete all unreachable path-builder helpers after routing.

Complexity Tracking

No charter gate violations. No complexity exceptions required.