Phase 0 Research: WP runtime-state eviction
All open design questions were resolved before planning — through the seven HiC decisions (brief §0), the ADR of record, and a four-lens post-spec adversarial squad (2026-07-19). This file records the decisions in Decision / Rationale / Alternatives form; there are no outstanding [NEEDS CLARIFICATION] markers.
D1 — Off-axis mutation mechanism
validate_transition, folded by the reducer (Option A, realised as a single event).
PID, subtask marks, notes); reusable; typed delta avoids a new split-brain.
resume false-stale window; (C) FSM self-edges — redefines the "transition changes lane" invariant. Both rejected (ADR §7).
- Decision: One generic
InnerStateChangedevent carrying a typedWPInnerStateDelta, bypassing - Rationale: Only option that makes the log a true SSOT for all three off-axis mutations (resume
- Alternatives: (B) fold onto transitions +
policy_metadata— complete only forshell_pid,
D2 — Reducer fold semantics
slots (per-field independence); annotations skip the lane assignment and apply the typed delta; ordering is an event-kind partition (annotations in a post-transition pass), not an at-interleave.
carrying forward only force_count, so a later transition would erase the runtime slots; and at an equal at a deterministic seed ULID vs a time ULID would order arbitrarily.
- Decision: The reducer branches on event kind; lane transitions preserve untouched runtime
- Rationale: Squad-CRITICAL —
reducer.py::_wp_state_from_eventtoday rebuilds the per-WP dict - Alternatives: naive "fold after by timestamp" (rejected — clobbers slots non-deterministically).
D3 — Annotation visibility on the read path
is_non_lane_event surface annotations to reduce(); assert (architectural test) an annotation is never reduced as a lane transition.
StatusEvent.from_dict hard-requires from_lane/to_lane; without reconciliation the annotation is structurally invisible or fails the whole event read.
- Decision: Define a wire/envelope discriminator + a distinct read path so
store.read_events/ - Rationale: Squad-HIGH — today
is_non_lane_eventskips anyevent_type-bearing event and
D4 — Migration ordering & integrity
deterministic namespaced ULIDs ordering after the annotated transition at equal at; symmetric-window closure (atomic reader/writer switch or dual-write per field).
first reader ignoring a fresh frontmatter write). Verify must abort before cutover on parity mismatch.
reader-before-writer window open).
- Decision:
backfill → verify(fail-closed) → reader cutover → writer cutover → delete fallbacks; - Rationale: Closes both the B3 clobber window and the squad-HIGH second-polarity window (snapshot-
- Alternatives: writer-first (rejected — B3 clobber); single-polarity ordering (rejected — leaves the
D5 — tracker_refs classification
WP_FIELD_ORDER so it is not dual-homed.
violation. HiC Decision 5.
- Decision: Evict (runtime, event-sourced union delta) AND strike from the static schema /
- Rationale: It is runtime-written (FR-011/map-requirements); dual-homing would be the exact #2093
- Alternatives: author-immutable (rejected — removes FR-011 runtime append, guts map-requirements).
D6 — review_artifact_override_* scope
(review/artifacts.py) + merge-gate recognition — collapsing the coord-mirror duplication.
blocks. HiC Decision 5 keeps it in-scope; the fix is both-halves, not defer.
- Decision: Evict as a matched pair — write (
tasks_materialization.py) AND read - Rationale: Squad blocker — write-only eviction silently breaks override recognition → false merge
D7 — Force-provenance (FR-015)
edge-specific evidence), not a hard-coded edge list; assert the persisted StatusEvent.force.
but a hard-coded list rots if the matrix changes, and evidence is edge-specific (reason vs review_ref vs structured review_result).
- Decision: Suppress
emit_forceby asking the FSM (validate_transitionlegal force-free with - Rationale: Squad empirically proved the five edges ARE exactly the force-free-legal backward set;
- Alternatives: frozen five-edge list (accepted-but-fragile — use FSM instead).
D8 — Reducer performance gate
wall-clock threshold.
assertion is deterministic and non-flaky.
- Decision: Assert the fold is O(events) with no extra re-reduction pass (structural), not a
- Rationale: A
<5%wall-time delta on ~500 events is inside measurement noise; a complexity
Squad empirical findings carried into design (2026-07-19)
dormant-mask hypothesis was falsified; no over-reach, no missed edge.
tests it must re-point.
- All ~18 code anchors CONFIRMED, zero drift (debugger lens) — the spec's file:line map is current.
- FR-015's five edges are the provably exact force-free-legal backward set — the
for_review→* - SC-003 red test is red-by-execution; SC-007 is double-pinned by the existing plan-level force
- Reducer carries no subtask state today — FR-002/FR-003 are genuinely net-new.
- Subtask-parser consolidation is already complete (no residual duplicate parser to campsite).
Brownfield discipline (standing procedures applied)
anchors; add proof-of-drive lifecycle characterisation before the writer cutover.
strictly behind the FR-011 backfill; do not reorder.
- Characterisation before change: the merged red test + the existing force tests are the behavioural
- Never break legacy on-disk WPs: the fallback deletes (
workflow_cores/done_bookkeeping) are gated - Reader-before-writer with a verified backfill; fail-closed verify.