Phase 0 Research — Plan-Phase Decisions
The pre-spec investigation lives in research/pre-spec-research.md (five profile-loaded streams, file:line-anchored). This file records the plan-phase decisions that resolve every open fork before /spec-kitty.tasks. There are zero [NEEDS CLARIFICATION] markers.
D-PLAN-1 — Collapse-first ordering dissolves FR-001's commit-atomicity
(write-partition flip). The write-partition flip lands after every verdict reader has moved to the event authority.
is physically written (IC-04) cannot affect the approval guard — the flip becomes non-safety-critical. This meets FR-001's guarantee (no partial-order fail-open) by ordering, not by a large write+reader single commit. It is also the only order that satisfies the two blocker findings (authority must be populated before readers flip; the un-migrated cohort must be backfilled first).
harder-to-review, and still unsafe without the backfill; (b) write-first then migrate readers — the exact fail-open the research proved (§5.C).
post-plan squad should confirm the ordering closes the fail-open window with no residual.
- Decision: Sequence IC-01 → IC-02 (backfill+durability) → IC-03 (reader collapse) → IC-04
- Rationale: Once no consumer reads the
.mdfor a verdict (IC-03), moving where the prose.md - Alternatives rejected: (a) atomic write+reader mega-commit (spec FR-001 literal reading) — larger,
- Spec relationship: refines FR-001's mechanism (ordering) while preserving its guarantee. The
D-PLAN-2 — Verdict-provenance backfill: idempotency + cohort sizing
verdict into status.events.jsonl via emit_status_transition, keyed so a re-run is a no-op (idempotent on (mission, wp, verdict, cycle)). The provenance gate parses for "terminal .md verdict with no event review_result slot" and blocks IC-03's reader deletion until zero.
missions before running, so the migration is sized against data (operator asked to confirm scope; confirmed in-scope).
- Decision: A new
migration/verdict_provenance_backfill.pyreduces each existing terminal.md - Rationale: FR-012; closes the arch-lens blocker (event log not retroactive;
reducer.py:198). - Open measurement (do in IC-02, not blocking planning): count the real cohort across in-repo
D-PLAN-3 — Vocabulary bridge home + guard
function artifact_verdict → event_verdict over {approved, rejected, arbiter_override, approved_after_orchestrator_fix} → {approved, changes_requested} and its inverse for prose/render. An architectural test forbids any other module from spelling the rejected↔changes_requested equivalence inline (today inline in 9 modules).
- Decision: One canonical surface beside
status/models.py(e.g.verdict_vocab.py), a total - Rationale: FR-005; prevents the bridge from becoming a new drift surface (paula/renata).
D-PLAN-4 — FR-004 enforcement: derived ratchet AND census (belt + suspenders)
_READER_AUTHORITY_ROOTS, add a review-cycle-*.md-glob detector arm, add verdict to tracked fields, add a synthetic-poison non-vacuity test) and rely on the all-src/ verdict-seam census as the enumeration ratchet. Two independent checks, because the census caught what test_2093 structurally could not (renata's load-bearing finding).
- Decision: Extend
test_2093_authority_invariant.py(addagent_utils/review/post_mergeto - Rationale: FR-004/SC-002; a single check was proven insufficient.
D-PLAN-5 — Prose-only artifact: no verdict field (structural single-authority)
so it physically cannot be re-read as a verdict source. A census-backed check asserts the written artifact carries no field the census classifies as a verdict.
persisted dual-store. Removing the field makes single-authority structural, not disciplinary.
parses feedback_source as an artifact) is re-expressed to check prose identity without a verdict read-back, or retired if the provenance concern is fully covered by the event authority — resolved during IC-04 with its own test.
- Decision: The best-effort
.mdis written without averdict:frontmatter field (SC-007), - Rationale: Finding 3 — demoting the commit to best-effort without removing the field leaves a
- Consequence:
_guard_feedback_source_provenance(the #990/#2996 duplicate-feedback guard, which
D-PLAN-6 — Review-cycle merge driver relaxes under D3
conflict-marker driver downgrades to non-aborting (union/last-writer on prose) rather than Exit(1). Retiring it entirely is the fallback if no prose-merge is needed.
- Decision: With the
.mdnon-authoritative and unread, thespec-kitty-review-cyclefail-closed - Rationale: FR-014; a cosmetic render divergence must not block a squash.
D-PLAN-7 — #2804 fixed at the write surface (#2404)
driver-registration-before-squash guarantee is defense-in-depth. A write-side check asserts no PRIMARY acceptance-matrix is authored under a coordination topology.
(paula). Operator confirmed #2404 in scope.
- Decision:
acceptwrites the acceptance-matrix to the COORD surface (no PRIMARY husk); the - Rationale: FR-009; merely winning-at-merge is timing-dependent and leaves the add/add source
D-PLAN-8 — Merge gate goes pure-event
_artifact_dirs_for_wp + the _resolve_terminal_verdict_conflict artifact leg are retired (census retirement rows), not repointed.
Depends on IC-02 (authority populated).
- Decision:
find_rejected_review_artifact_conflictsreads only the event authority; - Rationale: FR-013; the blocker contradiction (FR-001 "repoint" vs FR-003 "retire the consumer").
Phase 0 Research — Post-Plan Squad Folds (D-PLAN-9..16)
The post-plan adversarial squad (4 lenses, live-code-verified) confirmed the collapse-first ordering but broke the plan's parallelism/sizing claims and found two correctness bugs in the backfill and the SC-007 mechanism. Folded decisions:
D-PLAN-9 — All verdict readers die in IC-03 (arch Finding A)
Two verdict readers the original plan left for IC-04 — the approval-write probe (tasks_verdict_persistence.py:531-568, calls latest_review_artifact_verdict) and the arbiter reader (arbiter.py:461) — are frontmatter verdict reads. Pull the entire review/artifacts.py verdict-parser family retirement (latest_review_artifact_verdict, rejected_review_artifact_for_terminal_lane, ReviewCycleArtifact.latest) and the probe repoint into IC-03, so every verdict reader dies in one atomic wave. This restores the dissolution: IC-04 becomes a pure schema+placement change with no verdict reader left.
D-PLAN-10 — Backfill uses append_events_atomic_verified, not emit_status_transition (arch Finding B)
emit_status_transition derives from_lane from the WP's current lane and runs validate_transition (FSM matrix) — it cannot replay a historical in_review→… edge onto a settled WP. Use append_events_atomic_verified with a hand-constructed event (the repo's own precedent, backfill_runtime_state.py:1507). The event's at MUST be the historical verdict timestamp (from the .md/git record), never now(), or a late-stamped rejection sorts last and resurrects over a real later approval. Define "terminal verdict" = the latest review-cycle-N.md for the WP; handle a .md rejection superseded by a later lane-only approval. Idempotency key includes temporal identity. Red-first: a WP with a historical .md rejection followed by a later approval reduces to approved after backfill.
D-PLAN-11 — Durability demote lands with the reader flip, not before (auth Finding F2)
IC-02 adds emit_status_transition as an authoritative durability write but keeps the .md commit hard-error. The demote-to-best-effort lands in IC-03's PR (same PR as the reader flip). C-008 amended: the .md durability demote never precedes the reader flip. Otherwise a best-effort render failure diverges the event log and the .md while readers still read the .md.
D-PLAN-12 — SC-007 is a schema change + a new check (auth Finding F1; renata Finding 4)
verdict is a required ReviewCycleArtifact field (artifacts.py:148), emitted by to_dict, hard-validated by from_dict:214. IC-04 removes the field from the dataclass + to_dict + the from_dict/validate_review_artifact validation — a schema change. The "census-backed check" does not and cannot exist (the census classifies functions, not serialized fields); IC-04 adds a new serialized-artifact assertion (parse the written .md, assert no verdict key). Sequence: the verdict-parser family retirement (IC-03) lands before the field removal (IC-04), else from_dict breaks. Enumerate the test_review_durability_matrix.py re-pins the field removal triggers.
D-PLAN-13 — Durability anchors re-pointed to the event log (renata Findings 1, 2)
test_review_durability_matrix.py's SC-003 anchor asserts on .md files + a clean git status — the exact .md-commit property the mission retires; post-demote it reds. *Re-point SC-003 to count durable event records (read_events/reducer slots). NFR-004's "exactly one authoritative call" verifier counts commit_artifact (the demoted .md commit) — re-point to count the emit_status_transition append == 1 per verdict; the artifact_calls == 1 assertion becomes a best-effort observation. These pins are rewritten*, not "greened as-is."
D-PLAN-14 — Vocabulary bridge is display-only; overrides never synthesize a verdict (renata Finding 3)
The reducer deliberately does not overwrite review_result for an arbiter override (it clears the gate over a standing changes_requested; the ReviewOverride record carries provenance, reducer.py:244-252) — the #3044 separation. Scope the bridge to {approved, rejected} for review_result emission. arbiter_override / approved_after_orchestrator_fix are not verdict-bridge inputs to a review_result event; they resolve via ReviewOverride/orchestrator-fix records, or the bridge output is display-only and forbidden from feeding an emitted review_result. Negative test: an arbiter_override must not produce an approved review_result event while its ReviewOverride slot carries the provenance. The .from_dict census gap's real site is models.py:570 (not _runtime_repair_delta, which uses a direct ctor already matched).
D-PLAN-15 — Provenance interlock is a TEST, and a NEW predicate (arch Finding C)
The reconcile doctor reports live_coord_pre_adr_primary_record — a location class, not the FR-012 provenance predicate ("terminal .md verdict + no event slot"). Add the new provenance predicate as an explicit IC-02 deliverable, distinct from the location class; FR-007's location gate and FR-012's provenance gate are two separately-named findings. The "blocks reader deletion" interlock is enforced by the SC-008 hermetic red-first test (seed a pre-event .md-only rejection → backfill → delete readers → assert approval still refused), not a runtime block. Make the SC-008 pin a hard dependency edge from IC-02 to the first IC-03 commit.
D-PLAN-16 — IC-06 splits; IC-04's physical flip is largely subsumed (planner; renata Finding 5)
(the actual PRIMARY-husk producer — suppress the PRIMARY scaffold under coord topology) + m_3_2_6_* migrations + a write-side check that greps every write_acceptance_matrix call site. Genuinely touches no IC-01..05 file.
registration + .md→.json seed drift. Shares merge_driver.py/init.py with IC-04's FR-014 driver relax → same lane, not parallel. Add test_merge_reconciliation_class_guard.py, verdict_seam_IC04.yaml, test_review_cycle_merge_driver.py to the IC-04/06b surface set.
classifier, ADR 2026-08-03-1); test_analysis_report_rehome:232 pins the physical on-disk write to PRIMARY and is green. Once the collapse (IC-03) removes every verdict reader, the .md physical location no longer affects any verdict decision. IC-04's real deliverables are the schema change (D-PLAN-12), the census resolver retirements, the driver relax (FR-014), and FR-011 — not a redundant physical move. Whether to additionally relocate the physical write to COORD for prose-consistency (re-pinning test:232) is a narrow /tasks decision, not a mission guarantee. Reconcile FR-007 wording: _review_cycle_wp_dir is status: retire in the census, so the fallback is relocated into the canonical placement resolver, not "preserved verbatim."
- IC-06a (parallel-safe):
merge/executor.pyaccept→COORD +mission_finalize._scaffold_acceptance_matrix_if_lane_based:1315 - IC-06b (serial with IC-04):
merge_driver.pymatrix drivers +init.py.gitattributes - IC-04 physical flip is largely subsumed: the review-cycle commit is already COORD (per-file
Corrected shared-file serialization set (C-008)
{verdict_seam_census.yaml, verdict_seam_IC01.yaml} serialize IC-01/IC-02/IC-02b/IC-03/IC-04; {merge_driver.py, init.py} serialize IC-04/IC-06b. Only IC-06a is a genuinely parallel lane. Re-baseline the file estimate to ~35 (named-only floor was 20-24). Every census row is mapped to an owning IC before /tasks; orchestrator_api/commands.py::_parse_review_result_json is ruled in/out explicitly (it parses injected JSON, not frontmatter → likely stays, but carries inline vocab → swept by IC-02b).
D-PLAN-17 — #3219 canonical flatten primitive (operator-added, post-plan)
Fold in #3219: extract flatten_coordination_metadata(feature_dir) (all three mutations — del coordination_branch + pop topology + flattened=True — in one load→mutate→write_meta(validate=False)), converging merge/executor.py (#3218), _coordination_doctor.py:816-826, and mission_type.py::_flatten_discarded_mission. Correct the mission close --discard partial-flatten latent bug (it never pops topology → a discarded coord mission can still hit CoordinationBranchDeleted). Import the topology/flattened key constants from backfill_topology.py (their semantic owner). Non-vacuous single-source arch-guard so the 5th re-inline reds (this is the 4th touch: #2069→#2120→#2614→#3086/#3218).
the same canonical-source-unification pattern the mission embodies, on a sibling field-set. Folded per operator instruction as IC-07.
the base; IC-07 converges the executor call site #3218 introduces. If #3218 is not yet on main at rebase time, IC-07 waits. Shares merge/executor.py with IC-06a → serialize/same lane.
(phase 11) runs before cleanup (phase 12), so a --push merge lands the flatten bookkeeping commit local-only; origin/target keep the stale coordination_branch.
- Domain note: this is coordination-metadata SSOT, adjacent to (not part of) the verdict seam —
- Dependency: assumes PR #3218 (the #3086 hotfix, handed to the parallel session) has landed on
- Also verify (#3218 landing-review residual): the
--pushorigin-divergence —_phase_push