Implementation Plan: Evict WP runtime state into the event log
Branch: mission-prep/2684-wp-runtime-state-eviction | Date: 2026-07-19 | Spec: spec.md Input: Feature specification from kitty-specs/wp-runtime-state-eviction-01KXWN13/spec.md
Summary
Evict all runtime-mutable WP state (shell_pid(+baseline), subtask completion, ## Activity Log notes, tracker_refs, agent/assignee, review-cycle fields) out of tasks/WP##.md and the tasks.md subtask surface into the canonical append-only event log, via one generic InnerStateChanged off-axis event folded into the reduced snapshot (design of record: ADR 2026-07-19-1). The WP files become static design-intent only, which stabilises the dossier content hash (AC-5), makes subtask completion event-sourced (the merged P0 red test flips green), and lets claim-liveness resolve from the snapshot. Additionally fixes the false-force provenance bug (FR-015). Executed as a brownfield migration under the strict backfill → verify → reader cutover → writer cutover → delete fallbacks ordering, with extreme-campsiting removal of the dead tails the eviction creates.
Technical Context
Language/Version: Python 3.12 Primary Dependencies: typer (CLI), pydantic (models), internal specify_cli.status (event log / reducer / FSM), specify_cli.coordination (transactional writes), specify_cli.migration (frontmatter stripper), specify_cli.doctrine Storage: append-only status.events.jsonl (SSOT for runtime state) + the reduced snapshot; git-backed WP markdown carries static design-intent only after cutover Testing: pytest (pytest.ini marker registry). New coverage: an architectural invariant test (#2093), a proof-of-drive lifecycle hash test (AC-5), a persisted-force command-layer test (FR-015) plus re-pointed existing force tests, a migration idempotency + fail-closed-verify test, and the already-merged regression test_issue_2684_subtask_completion_event_sourced.py. Markers: regression, integration, git_repo, architectural, unit Target Platform: Linux/macOS CLI (spec-kitty-cli) Project Type: single (Python CLI library under src/specify_cli/) Performance Goals: the reducer fold is O(events) with no additional re-reduction pass (NFR-005 — asserted structurally, not by wall-clock) Constraints: strict migration ordering incl. the symmetric-window closure (C-001); typed WPInnerStateDelta, never a free dict (C-002); every emit site resolves destination_ref from stored topology, never Path.cwd() (C-003 / #2647); the 9-lane FSM 27-pair matrix is not modified (C-004); no field is dual-homed static+dynamic (#2093) Scale/Scope: ~12 source modules across status/, cli/commands/agent/, migration/, review/, merge/, frontmatter.py, plus a backfill migration over the live mission corpus
Charter Check
GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.
Charter policy headline: "Single canonical authority — every rule, surface, and identity has exactly one source." This mission is a direct expression of that policy: runtime state gets one authority (the event log), static intent keeps one authority (the frontmatter). PASS — no violation; the mission strengthens the invariant, enforced by the FR-013 refactor-stable architectural test. Post-design re-check: the only transient two-authority state is the bounded C-001 migration window, which the revised decomposition minimises via per-field atomic switch and tears down under IC-07 with the no-dual-home assertion. PASS.
Project Structure
Documentation (this mission)
kitty-specs/wp-runtime-state-eviction-01KXWN13/
├── plan.md · research.md · data-model.md · quickstart.md · contracts/ · tasks.md (Phase 2, not here)
Source Code (repository root)
src/specify_cli/
├── status/
│ ├── models.py # StatusEvent + policy_metadata; NEW InnerStateChanged + WPInnerStateDelta
│ ├── store.py # read_events / is_non_lane_event / from_dict — surface annotations to reduce()
│ ├── reducer.py # branch on kind; preserve slots; claim-transition policy_metadata fold
│ ├── wp_state.py # sanctioned non-transition annotate() path (no matrix change)
│ ├── transitions.py # validate_transition (unchanged matrix)
│ └── emit.py # _infer_subtasks_complete → snapshot; emit sites resolve topology target
├── cli/commands/agent/
│ ├── tasks_transition_core.py # _guard_subtasks → snapshot; build_transition_plan force fix (FR-015)
│ ├── tasks_shared.py # _check_unchecked_subtasks (reader re-point)
│ ├── tasks_move_task.py # cut god-write + tails (_mt_persist_tracker_refs, _mt_*uncheck*, _mt_clear_rollback_claim_markers)
│ ├── tasks_mark_status.py # emit subtask InnerStateChanged (stop writing tasks.md checkbox)
│ ├── tasks_materialization.py # review_artifact_override_* write half + coord mirror collapse
│ ├── implement.py # :1730 shell_pid writer (this mission OWNS the restructuring, FR-014)
│ └── workflow_executor.py # :695,:1370 shell_pid writers
├── migration/strip_frontmatter.py # MUTABLE_FIELDS extend + backfill engine
├── review/artifacts.py # review_artifact_override_* READ half (FR-009 both-halves)
├── merge/done_bookkeeping.py # legacy done-evidence fallback (delete after backfill)
├── frontmatter.py # write_shell_pid_claim* + __all__ (delete tail); add_history_entry + __all__ (delete)
├── core/stale_detection.py # :402-403 claim-liveness reader re-point
├── task_utils/support.py # WorkPackage.{shell_pid,agent,assignee} reader re-point
├── status/wp_metadata.py # WPMetadata coercion; inert-field cleanup (deferred IC-08)
└── orchestrator_api/commands.py # :1563 external Activity-Log writer (cross-package; own concern)
tests/
├── regression/ (test_issue_2684_*; force-provenance persisted) · architectural/ (#2093 invariant;
│ no_dead_symbols reconcile) · integration/ (proof-of-drive hash; migration idempotency + fail-closed)
│ · unit/ (fold ordering/preserve; delta merge rules)
Structure Decision: Single Python CLI project; in-place edits to src/specify_cli/ + a net-new backfill migration + the test surfaces above.
Complexity Tracking
No Charter Check violations — the mission reduces complexity (removes a split-brain). Table omitted.
Decomposition strategy (post-plan squad adjudication)
C-001 mandates per-field atomicity (a field's reader and writer must switch together, or dual-write during a bounded window). The squad flagged that a horizontal reader-phase/writer-phase split is orthogonal to that safety axis. Decision: per-field vertical slices with an atomic reader↔writer switch are the primary strategy — no transient dual-authority window — with dual-write allowed only where a field cannot switch atomically, explicitly bounded and torn down in IC-07 (charter single-authority; close-by-construction). The field-eviction concerns below are therefore vertical (each carries its reader re-point, authority-activation gated on that field's verified backfill, writer cut, and created-orphan deletes). Lane sizing: 2–3 lanes — this mission is critical-path-bound (IC-01 foundation, the field verticals, IC-07 closeout), not width-bound; sustained parallel width ≈ 2.
Implementation Concern Map
> Concerns are architectural areas, not work packages. /spec-kitty.tasks translates these into > WPs. Sequencing is expressed as concern dependencies to honour the brownfield migration contract.
IC-01 — Off-axis event class + reducer fold foundation
and correctly folded by the reducer, with the snapshot slot set that every field vertical reads.
from_dict), status/reducer.py (_wp_state_from_event), status/wp_state.py (annotate path).
untouched slots; wire discriminator must not collide with the event_type skip; event-kind partition fold (not at-interleave); an annotation must never be reduced as a lane transition (arch test). Snapshot slot set is {shell_pid, shell_pid_created_at, subtasks, notes, tracker_refs, agent, assignee, review} — includes agent/assignee and a review slot (squad HIGH: the authority table asserted these with no carrier). The planned→claimed transition MUST extract shell_pid/shell_pid_created_at/agent from its policy_metadata into the snapshot slots (squad HIGH: FR-004's claim path was otherwise unowned — the reducer reads policy_metadata nowhere today).
- Purpose: The single generic
InnerStateChangedevent + typedWPInnerStateDelta, made visible to - Relevant requirements: FR-001, FR-002, FR-004 (fold half), C-002, C-004; NFR-005.
- Affected surfaces:
status/models.py,status/store.py(read_events/is_non_lane_event/ - Sequencing/depends-on: none (foundation).
- Risks: reducer today rebuilds the per-WP dict (carries only
force_count) → must preserve
IC-02 — Force-provenance fix (FR-015) — LAND FIRST
persisted-event layer. Sequenced first so the field verticals rebase onto corrected force logic instead of racing it on shared files.
tasks_move_task.py::_mt_emit_transitions, status/emit.py.
tasks_transition_core.py (with IC-04b), tasks_move_task.py + status/emit.py (with IC-04a/b/d).
the enumerated existing plan-level tests (not delete); assert the persisted StatusEvent.force via the real move-task path with a genuine-force positive control.
- Purpose: Stop stamping false
forceon evidence-gated review-rejection backward edges; pin at the - Relevant requirements: FR-015; SC-007.
- Affected surfaces:
tasks_transition_core.py:218-219(build_transition_plan), - Sequencing/depends-on: none. Code-collision edges (land first to avoid races):
- Risks: ask the FSM (
validate_transitionlegal force-free), not a frozen edge list; re-point
IC-03 — Migration engine: per-field backfill + fail-closed verify
field vertical's authority activation on verified parity.
STATIC_FIELDS, retire progress), net-new backfill/seed-event module, read-back verify.
verify is fail-closed and aborts before any field's reader authority activates; honest timestamp-clamp bound; zero-reader verification precedes deletions.
- Purpose: Seed existing missions' frontmatter/checkbox state into the log idempotently; gate each
- Relevant requirements: FR-010 (field-moves), FR-011; SC-006, NFR-002.
- Affected surfaces:
migration/strip_frontmatter.py(extendMUTABLE_FIELDS, movehistoryout of - Sequencing/depends-on: IC-01.
- Risks: deterministic namespaced ULIDs ordering after the annotated transition at equal
at;
IC-04 — Per-field eviction verticals (fan out to WPs by field)
Each vertical = reader re-point (behind flag) → authority activation gated on this field's IC-03 verify → writer cut → created-orphan deletes, atomic per field (C-001). FR-004/006/007/008/014; SC-001, SC-005, SC-008.
WorkPackage.shell_pid; writers implement.py:1730 (owns the FR-014 restructuring), workflow_executor.py:695,:1370, tasks_move_task.py:1770; tails: frontmatter.py:: write_shell_pid_claim* (:357-408) + __all__:448-449 + orphaned tests (test_shell_pid_claim_baseline.py, test_implement_runtime_frontmatter_claim.py, test_tasks_move_task_authority_staging.py), and _mt_clear_rollback_claim_markers (tasks_move_task.py:1730, sole caller is the god-write — a same-cut orphan, moved here from IC-08).
emit via mark-status; cut the god-write _mt_persist_wp_file + the _mt_uncheck trio. Makes the merged red test green.
WP_FIELD_ORDER** (no #2093 dual-home); delete _mt_persist_tracker_refs (:1707-1727).
append_activity_log writers plus the divergent frontmatter seam task_metadata_validation.py:146-165 (an eviction target, not optional cleanup).
InnerStateChanged; re-point WorkPackage.{agent,assignee} + WPMetadata.
(ACL boundary); carve as its own WP with SC-008 (#2647 topology-resolution, not Path.cwd()) as its dedicated acceptance evidence.
inbound gate from PR #2766 — #2766 rebases onto this mission's writer cutover** (C-006), not the reverse.
- IC-04a
shell_pid(+baseline, +agentclaim) — readersstale_detection.py:402-403, - IC-04b
subtasks— gate re-source_guard_subtasks+_infer_subtasks_completeto snapshot; - IC-04c
tracker_refs— union delta (map-requirements,move-task); **strike from - IC-04d
notes/Activity Log — append delta from the full write-seam census (≥7): the six - IC-04e
agent/assignee—agentrides the claimpolicy_metadata(IC-01);assigneevia - orchestrator_api sub-concern —
orchestrator_api/commands.py:1563is a cross-package writer - Sequencing/depends-on: IC-01, IC-02 (landed first), and each field's IC-03 verify.
- Risks: every new emit site resolves
destination_reffrom stored topology (C-003/SC-008); **no
IC-05 — Review-cycle eviction (both halves)
the merge gate; land the review snapshot slot.
mirror collapse), review/artifacts.py (read half), the merge-gate override recognition.
a second read path = #2093 violation) and IC-03 (fallback deletes gated on backfill).
read authority is the reduced snapshot, not a parallel event read.
- Purpose: Event-source
review_artifact_override_*as a matched write+read pair without breaking - Relevant requirements: FR-009.
- Affected surfaces:
tasks_materialization.py(write +_persist_review_artifact_override_in_coord - Sequencing/depends-on: IC-01 (needs the
reviewsnapshot slot + fold — squad HIGH: otherwise - Risks: write-only eviction silently breaks override recognition → false merge blocks; the review
IC-06 — history[] / progress deprecation (single owner)
frontmatter.py::add_history_entry (:176,:347) + __all__:445; test_no_dead_symbols.py:282 allowlist entry; zero-reader verification for both history[] and progress.
next dead symbol).
- Purpose: One deprecation unit end to end — no half-owned dead writer.
- Relevant requirements: FR-010.
- Affected surfaces:
migration/strip_frontmatter.py(field-move + retireprogress); - Sequencing/depends-on: IC-03 (after backfill/zero-reader verify).
- Risks: delete the symbol and its
__all__/allowlist entry together (no stub that masks the
IC-07 — Delete legacy fallbacks + land invariants (closeout)
workflow_cores.py:340-348 + done_bookkeeping.py:104-105 fallback deletion (gated on backfill), the AC-5 stable-hash guard, the #2093 refactor-stable architectural test (incl. no-field-in-both-columns).
lifecycle (proof-of-drive).
- Purpose: Remove the frontmatter fallbacks and any dual-write shim; land the enforcing guards.
- Relevant requirements: FR-009 (deletes), FR-013; NFR-001, AC-5.
- Affected surfaces: the FR-005 fallback-flag removal, any C-001 dual-write shim teardown,
- Sequencing/depends-on: IC-04 (all field verticals) + IC-05 + IC-06.
- Risks: no inert fallback / no dual-write shim left behind; hash guard covers the full driven
IC-08 — Deferred post-cutover reduction (migration-gated reader surfaces only)
migration-gated reader/coercion surfaces (the created-orphan writer helpers moved to IC-04).
(:270), WP_FIELD_ORDER cosmetic slots.
it grows past a bounded reduction, split into a separate post-cutover mission (not this one).
- Purpose: Remove code that is only inert after the corpus migration — narrowed to genuinely
- Relevant requirements: (campsiting, no new FR).
- Affected surfaces:
wp_metadata.pyinert fields +coerce_shell_pid(:363) +reviewer_shell_pid - Sequencing/depends-on: IC-07 (migration complete).
- Risks: pulling these forward would clobber legacy on-disk WPs mid-migration — do NOT reorder; if
Post-plan adversarial review (2026-07-19)
Four profile-loaded lenses (paula-patterns, planner-priti, architect-alphonso, randy-reducer) reviewed this plan; verdicts ranged revise / conditional-go / approve-with-fixes. Convergent amendments folded in above:
slices, atomic switch** (Decomposition-strategy section); dual-write only as a bounded IC-07-torn-down fallback.
on its IC-03 fail-closed verify (no reader authority before verify).
orchestrator_api sub-concern.
code-collision edges drawn.
planned→claimed policy_metadata→snapshot fold made explicit in IC-01.
parallel read path).
add_history_entry + __all__ + allowlist assigned to IC-06; write_shell_pid_claim* __all__ + orphaned tests assigned to IC-04a; activity-log census corrected to ≥7 seams (IC-04d).
- Decomposition axis (paula CRITICAL + planner + architect) → committed to **per-field vertical
- Structural verify gate (planner + paula HIGH) → each field vertical's authority activation depends
- IC-04 over-bundle (planner + paula HIGH) → pre-split into per-field verticals IC-04a–e + the
- Land IC-06→now IC-02 first (planner HIGH + paula) → force-fix sequenced first with its
- Carrier gaps (architect HIGH×2) →
agent/assignee+reviewsnapshot slots added; the - Review read authority (paula HIGH) → IC-05 depends on IC-01 for a
reviewsnapshot slot (no - Campsiting tails (randy HIGH/MEDIUM) →
_mt_clear_rollback_claim_markersmoved to IC-04a; - PR #2766 direction (planner) → no inbound gate; it rebases onto us (IC-04 risks).
- Lane sizing (planner) → 2–3 lanes; critical-path-bound.
Contract/data-model fidelity fixes (emit-force table labelled illustrative-not-implementation; claim policy_metadata fold + agent/assignee/review slots) are applied in data-model.md and contracts/. IC-08's deferral was affirmed correct by both randy and paula (no over-reach into the clobber window).