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:
MissionStatusis placed insidestatus/(Mission Management domain) per the ADR design — consistent with the four-module domain model. - Commit atomicity gate:
MissionStatus.save()routes throughBookkeepingTransaction— 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]
| WP | Issue | Description | Depends on |
|---|---|---|---|
| WP01 | #1674 | Three ADRs + glossary entries | — |
| WP02 | #1672 | e2e parity ratchet test (main-CWD vs lane-CWD) | WP01 |
| WP03 | #1664 | status/ boundary test + fix ~245 bypass imports | WP02 |
| WP04 | #1667 | MissionStatus aggregate + agent/status.py migration | WP02 |
| WP05 | #1663 | MissionRun → Mission back-reference on snapshot + ref | WP01 |
| WP06 | #1673 | Route residue surfaces through resolve_action_context | WP02, 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.md — resolve_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.statusmay import from anyspecify_cli.status.*submodule". - Pass B (AST scanner): grep-confirms zero
from specify_cli.status.<submodule>imports insrc/outsidestatus/(exemptingcoordination/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.pyquery-mode: replace independentfeature_dirderivation withresolve_action_context(...).workflow.pyfix-mode: routerepo_root/target_branchthroughresolve_action_context.- Delete all unreachable path-builder helpers after routing.
Complexity Tracking
No charter gate violations. No complexity exceptions required.