# Requirements: Autonomous Self-Healing

This file is the explicit capability and coverage contract for the project.

## Active

### R001 — Idle Halt Detection
- Class: failure-visibility
- Status: active
- Description: The autonomous loop must detect when it is in a `stop` state that has persisted beyond a configurable time threshold.
- Why it matters: Prevents the loop from sitting idle without the operator knowing.
- Source: spec
- Primary owning slice: M003/S01
- Supporting slices: none
- Validation: unmapped
- Notes: Requires a watchdog timer in `auto/loop.js`.

### R002 — Multi-Channel Notification
- Class: failure-visibility
- Status: active
- Description: Persistent and transient notifications must fire when a halt is detected.
- Why it matters: Ensures the operator sees the "stuck" signal across different surfaces (TUI, terminal, push).
- Source: spec
- Primary owning slice: M003/S02
- Supporting slices: none
- Validation: unmapped
- Notes: Should use `ctx.ui.notify` and a durable log like `.sf/notifications.jsonl`.

### R003 — Halt Self-Feedback
- Class: quality-attribute
- Status: active
- Description: Every autonomous halt must produce a structured self-feedback entry capturing the stuck state and reason.
- Why it matters: Provides a durable audit trail and allows for future "triage" units to address the cause.
- Source: spec
- Primary owning slice: M003/S03
- Supporting slices: none
- Validation: unmapped
- Notes: Filed with severity `high` if blocking.

### R004 — Auto-Remediation Dispatch
- Class: differentiator
- Status: active
- Description: When a milestone is stuck on `needs-attention`, SF should autonomously dispatch a remediation unit if a clear plan exists.
- Why it matters: Reduces human intervention for common validation failures.
- Source: spec
- Primary owning slice: M003/S04
- Supporting slices: none
- Validation: unmapped
- Notes: Leverages existing `replan-slice` or a new `remediation-slice`.

### R005 — Auto-Defer Confidence Policy
- Class: constraint
- Status: active
- Description: High-confidence findings that match specific categories can be auto-deferred to unblock completion.
- Why it matters: Prevents trivial findings from stopping the pipeline.
- Source: spec
- Primary owning slice: M003/S05
- Supporting slices: none
- Validation: unmapped
- Notes: Requires a threshold check (e.g., confidence < 0.3).

### R006 — Fail-Open Safety
- Class: quality-attribute
- Status: active
- Description: Failure of the self-heal logic itself must not crash the autonomous loop or worsen the halt.
- Why it matters: System robustness.
- Source: spec
- Primary owning slice: M003/S06
- Supporting slices: none
- Validation: unmapped
- Notes: Standard try/catch protection.

### R007 — Knowledge/Graph Artifact Formalization
- Class: constraint
- Status: active
- Description: `knowledge` and `graph` must be declared in `ARTIFACT_KEYS` in `unit-context-manifest.js` alongside their existing `computed` registrations in `UNIT_MANIFESTS`.
- Why it matters: Without formal registration, manifests that declare `knowledge` and `graph` as `computed` entries are structurally unreliable — the artifact registry doesn't know these keys exist, making the system incomplete and future tooling harder to build.
- Source: spec
- Primary owning slice: M005/S01
- Supporting slices: none
- Validation: unmapped
- Notes: The manifests already declare them as `computed`; this formalizes the registry entry.

### R008 — Remaining Builder Migration to composeUnitContext v2
- Class: core-capability
- Status: active
- Description: All 7 unmigrated unit-type builders (`execute-task`, `complete-slice`, `discuss-milestone`, `discuss-project`, `discuss-requirements`, `research-project`, `rewrite-docs`) must be wired through `composeUnitContext` v2 with proper `computed` knowledge/graph entries.
- Why it matters: The migration eliminates imperative string manipulation and positions SF for the Phase 4 pipeline-variants feature. Fragile sentinel-string searches (e.g., `body.lastIndexOf("### Task Summary:")`) are replaced by structured computed entries.
- Source: spec
- Primary owning slice: M005/S01
- Supporting slices: M005/S02
- Validation: unmapped
- Notes: Phase 2 shipped 15/26 types migrated. This completes the remaining 7.

### R009 — Builder Ordering Safety Tests
- Class: quality-attribute
- Status: active
- Description: Position-assertion and equivalence tests must cover all migrated builders to guard against silent ordering degradation when manifests are changed.
- Why it matters: Without tests, manifest reordering or new computed entries silently change prompt output — a regression only visible in production LLM calls.
- Source: spec
- Primary owning slice: M005/S01
- Supporting slices: M005/S03
- Validation: unmapped
- Notes: Extends existing `auto-prompts-phase3.test.mjs` which covers plan-milestone, replan-slice, validate-milestone, research-slice.

### R010 — Dead Code Removal
- Class: quality-attribute
- Status: active
- Description: `prompt-cache-optimizer.js` must be removed — `optimizeForCaching()`, `estimateCacheSavings()`, and `computeCacheHitRate()` have zero importers.
- Why it matters: Dead code is maintenance burden; the actual caching logic lives in `prompt-ordering.js` (which is wired).
- Source: spec
- Primary owning slice: M005/S02
- Supporting slices: none
- Validation: unmapped
- Notes: `reorderForCaching` in `prompt-ordering.js` is the live implementation.

### R011 — Defective-Complete Milestone Detection
- Class: failure-visibility
- Status: active
- Description: When a milestone reaches `status: complete` (all slices done) but is missing a required PDD field — specifically a non-empty `vision` or a written `M{id}-SUMMARY.md` — the doctor must emit a structured, machine-actionable signal that downstream remediation can consume. The detection already exists as the `db_milestone_missing_vision` and `all_slices_done_missing_milestone_summary` issue kinds in `doctor-engine-checks.js`; this requirement extends them with a self-feedback emission (kind: `legacy-milestone:no-vision` / `:no-summary`) carrying `occurredIn.milestone` so the inline-fixer can route remediation.
- Why it matters: Today these issues are report-only — zero downstream consumers (`grep db_milestone_missing_vision` finds only the emitter). M001-6377a4 (2026-05-16) is in exactly this state and has deadlocked the autonomous loop: doctor ERROR gates dispatch, but no path exists to repair the milestone, so the operator must manually patch state. As ADR-0000 enforcement spreads, more legacy milestones will surface this gap.
- Source: spec
- Primary owning slice: unmapped
- Supporting slices: none
- Validation: unmapped
- Notes: Pairs with R012. Reference self-feedback `sf-mp8a3kzm-iqbxkl` for full context. Detection must remain idempotent (one open self-feedback entry per defective milestone, deduped via existing rollup logic).

### R012 — Vision-Fill Recovery Dispatch
- Class: differentiator
- Status: active
- Description: When R011's signal fires for a defective-complete milestone, SF must autonomously dispatch a recovery unit that (a) reads the milestone's completed slice goals, demos, and roadmap context, (b) synthesizes the missing `vision` and 8 PDD fields via LLM, (c) writes the result to the DB through the standard writer, and (d) routes back to `completing-milestone` so the existing deterministic SUMMARY renderer (`tools/complete-milestone.js:33-93`) and purpose-coherence-gate run against the filled content. The unit must be content-fill only — it does not mutate slice contracts or run any tasks.
- Why it matters: This closes the chicken-and-egg deadlock: `plan-milestone` refuses to plan without a vision, `solver-purpose-gate` pauses on missing PDD fields, and `buildRegistryAndFindActive` (`state-db.js:139-175`) skips status=complete milestones — so no current path can author vision content. A scoped recovery unit lets autonomous self-heal legacy and structurally-defective milestones through the normal verification chain instead of forcing operator intervention. Mirrors R004's auto-remediation-dispatch pattern but for content defects rather than validation defects.
- Source: spec
- Primary owning slice: unmapped
- Supporting slices: none
- Validation: unmapped
- Notes: Requires (1) new prompt template `prompts/fill-milestone-vision.md`, (2) new dispatchable unit wired in `auto-dispatch.js` + `state-transition-matrix.js`, (3) an exception in `buildRegistryAndFindActive` for one-shot `status=complete && vision=""` repair, (4) inline-fixer handler that converts the R011 self-feedback entry into a dispatch. Must satisfy R006 (fail-open) — recovery-unit failure halts with notification, never crashes the loop.

### R013 — Unified Dispatch v2: `inline` Scope for `full` Isolation
- Class: core-capability
- Status: active
- Description: Implement the `inline` scope row of `UNIFIED_DISPATCH_V2_PLAN.md`'s parameter matrix (line 152: `full | managed | inline | single`) so the autonomous loop can execute units in-process without spawning a subprocess/worktree. A new `src/resources/extensions/sf/dispatch-layer.js` exposes `DispatchLayer.dispatch(opts)` per the plan's API spec (lines 51-138). When `scope: 'inline'` and `isolation: 'full'`, the unit's executor runs in the calling process against the project DB directly — no `child_process.spawn`, no session-status-io files, no worktree.
- Why it matters: The current spawn-based path silently fails on `validate-milestone` and likely other unit types (self-feedback `sf-mp8bhp5s-cmgt8d`, critical, blocking) — worker session IDs are issued and tracked in `.sf/runtime/units/*.json` but the worker never writes its session JSONL and `recoveryAttempts` stays at 0 across runaway-final-warning phases. Universal across providers (kimi-k2.6 and minimax both produce 0 tool calls with heartbeats only). Adding an inline path naturally retires this whole class of bug for units that don't need worktree isolation. Also reduces process-start latency and removes the file-based-IPC pressure point that has accumulated multiple historical issues.
- Source: spec
- Primary owning slice: unmapped
- Supporting slices: none
- Validation: unmapped
- Notes: Aligned with `docs/plans/UNIFIED_DISPATCH_V2_PLAN.md` (Qwen Plan, 2026-05-08). Scope of R013 is the **minimum slice** of that plan: just `full + managed + inline + single`. Other rows of the matrix (parallel/debate/chain inline, slice/milestone scope with worktrees) are out of scope for R013 and stay on their current implementations. Resolves `sf-mp8bhp5s-cmgt8d` and likely the 56+ historical `runaway-loop:idle-halt` entries on M005.

### R014 — Inline Worker Bootstrap Without Spawned `sf` CLI
- Class: core-capability
- Status: active
- Description: Extract the unit-execution code path that `sf headless autonomous` currently invokes after spawn into a callable function (`runUnitInline(unitType, unitId, ctx)`) usable from the same process. UOK kernel calls it directly when dispatching with `scope: 'inline'`. Must respect the single-writer invariant on `.sf/sf.db` (`sf-db.js`); the in-process call shares the kernel's existing WAL connection rather than opening a new one.
- Why it matters: Today the unit executor is reachable only via subprocess argv parsing in the headless CLI surface. Without this extraction, R013's inline scope cannot wire a real executor — the dispatcher would have nothing to call. This is the prerequisite for R013.
- Source: spec
- Primary owning slice: unmapped
- Supporting slices: none
- Validation: unmapped
- Notes: Reuses existing unit-context-manifest, prompt builders, and tool registries. The only change is execution surface: function call instead of process boundary. Session JSONL is still written for audit but to a path keyed off the in-process session ID, not a worker subprocess.

### R015 — Spawn-Failure Loud Failure (Defensive)
- Class: failure-visibility
- Status: active
- Description: Until R013/R014 land for every unit type, the existing spawn path must fail loudly. If a dispatched worker fails to write its session JSONL within a configurable timeout (default 30s) AND has zero `progressCount`, the runtime must (a) transition the unit to `status: failed`, (b) capture any stderr from the spawn into `lineage.events`, (c) emit a doctor-visible signal, and (d) trigger the retry path up to `maxRetries`. Today the runaway watchdog only fires a warning and never retries — `recoveryAttempts` stays at 0.
- Why it matters: Even after inline scope retires the spawn path for the common cases, spawn-based dispatch will persist for milestone/slice-scope workers and parallel modes. Silent failure is the worst possible behavior — operator sees a "running" unit that's a ghost. This requirement keeps the spawn path observable for as long as it exists.
- Source: spec
- Primary owning slice: M010/S04
- Supporting slices: none
- Validation: unmapped
- Notes: Touches the runaway-recovery / unit-ownership / parallel-orchestrator surfaces. Distinct from R013 — R013 removes the bug for inline scope; R015 contains the bug for non-inline scope.

### R016 — MessageBus Inbox Coherence Invariant
- Class: core-capability
- Status: active
- Description: After `_busDispatch(envelope)` resolves, the target agent's `_inbox` must see the new message on the next `refresh()` call. The current implementation can have the swarm-dispatch bus and the agent's inbox bus as separate instances with independent SQLite read caches, causing the just-dispatched message to be invisible (root cause of the chronic prompt-never-sent class — sf-mp8g4rcd-w01tkh, sf-mp8c0arc-vgw8io, and the 56+ runaway-loop:idle-halt entries on M005). The bounded-retry workaround at `agent-runner.js:174-241` papers over this; R016 fixes the underlying coherence.
- Why it matters: The autonomous loop's reliability rests on dispatched messages reaching their consumer. Silent message loss is the worst-possible failure: heartbeats keep firing while the LLM is never called, operator sees "progress" that doesn't exist, recoveryAttempts stays at 0. Without coherence, every dispatch is best-effort.
- Source: spec
- Primary owning slice: M012/S01
- Supporting slices: M012/S02, M012/S03
- Validation: unmapped
- Notes: Two viable fixes — (a) single shared MessageBus singleton across SF kernel + agent inboxes, (b) sync barrier in `_busDispatch` that returns only after the message is durably visible. M012/S01 documents the tradeoffs. Resolves sf-mp8g4rcd-w01tkh family.

### R017 — Tool-Failure Model Demotion
- Class: differentiator
- Status: active
- Description: When a model fails 2+ consecutive tool calls on a unit, SF must proactively switch to the next-best model in the fallback chain and soft-demote the failing model for future units of the same type. Today `agent-loop.ts:368` only stops after 3 consecutive all-failure turns (Schema overload cap), wasting budget. This requirement wires tool-call failures into the existing provider-failover infrastructure (commit `53259aebf` established the pattern at the provider-error layer).
- Why it matters: Provider failures get sane failover; tool failures don't — yet they're equally indicative that the current model isn't fit for the work. Wasting budget on a model that consistently fails tool calls reduces autonomous-loop reliability and increases cost.
- Source: spec (originated from kimi-for-coding triage-decider audit of sf-mp8zxh3f-weyalx, 2026-05-17)
- Primary owning slice: M013/S02
- Supporting slices: M013/S01, M013/S03, M013/S04
- Validation: unmapped
- Notes: 6-step plan per the triage-decider analysis: counter → switch on threshold → routing penalty → exhaustion → visibility → test. Soft-demotion store: module-level Map with 30-min TTL (simplest coherent store; migrate to routing_feedback table later if cross-session persistence needed).

### R018 — Roadmap Coherence
- Class: quality-attribute
- Status: active
- Description: PROJECT.md / ROADMAP.md (when present) and the DB-backed milestone/slice state must not drift. Doctor must detect mismatches (e.g. PROJECT.md says S03 [ ] unchecked but DB says complete) and surface them as structured issues. A new `sf reconcile` command must audit drift (read-only by default) and apply fixes (with --apply) interactively. Section-aware PROJECT.md writer preserves handwritten prose elsewhere in the file.
- Why it matters: Drift between DB and human-facing roadmap docs was the #1 trust issue during the 2026-05-17 dogfood session — the DB said "all complete" while PROJECT.md showed 4 unchecked slices, undermining every claim of milestone delivery. Reconciliation must be deterministic, safe, and discoverable.
- Source: spec (originated from the dogfood session reality check)
- Primary owning slice: M014/S01
- Supporting slices: M014/S02, M014/S03, M014/S04
- Validation: unmapped
- Notes: Includes ghost-milestone detection (e.g. M009-w9xoug empty stub, legacy M001/M002 disk directories with no DB row). Reconcile must not lose handwritten content — section-aware writer is the safety net.

### R019 — Operator Recovery Commands
- Class: quality-attribute
- Status: active
- Description: First-class CLI commands for hygiene operations that the 2026-05-17 dogfood session had to perform manually via `node -e` and `rm`: `sf triage clear/status/resolve`, `sf milestone forget`, `sf inline-fix reset`, `sf lock check/release`, `sf reset --soft`. Each command has --help docs, refuses destructive operations without explicit confirmation, and tests cover both happy and conflict paths.
- Why it matters: When SF gets stuck on stale state (zombie inline-fix dispatch, orphan lock from crashed sf, accumulated self-feedback queue), the operator currently needs intimate code knowledge to unstick it. R019 makes recovery a one-line command per failure mode.
- Source: spec (originated from the dogfood session manual interventions)
- Primary owning slice: M015/S01
- Supporting slices: M015/S02, M015/S03, M015/S04, M015/S05
- Validation: unmapped
- Notes: Indirectly serves R006 (Fail-Open Safety) — when self-heal fails, the operator has a tighter feedback loop. operator-recovery.md doc page is the discoverable surface.

### R020 — Inline-Scope Equivalence Proof
- Class: quality-attribute
- Status: active
- Description: M010's inline-scope dispatch must produce output structurally equivalent to the spawn-based path for the same unit invocation. A regression test takes a fixture unit (e.g. validate-milestone on a known milestone), dispatches it both ways (inline + spawn), and asserts the resulting artifacts (SUMMARY.md, VALIDATION.md, session JSONL message contents excluding timestamps and session IDs) match.
- Why it matters: M010 is a fundamental dispatch refactor; without an equivalence proof, regressions could silently degrade prompt quality or tool-call patterns. The test is the safety net that lets us land inline scope confidently.
- Source: spec
- Primary owning slice: M010/S06
- Supporting slices: none
- Validation: unmapped
- Notes: Equivalence is *structural*, not byte-exact — timestamps, IDs, and provider-specific metadata are normalized.

### R021 — Self-Feedback Triage Automation Quality
- Class: quality-attribute
- Status: active
- Description: The kimi-for-coding triage-decider produced excellent structured triage decisions in 8 minutes (2026-05-17, sf-mp8zxh3f-weyalx). This requirement formalizes the quality bar: every triage decision must include (a) verification against current code with file:line citations, (b) outcome (fix / wontfix / dup / stale / promoted), (c) sibling-entry awareness (don't refile resolved patterns), (d) proposed approach with concrete acceptance criteria and a file list. Triage that doesn't meet this bar is rejected and re-dispatched.
- Why it matters: Self-feedback triage is the gate between "we found a bug" and "we know how to fix it." Low-quality triage wastes the queue (filing duplicates, wishful claims, missing context); high-quality triage compounds into reusable fix recipes.
- Source: spec
- Primary owning slice: unmapped (would belong to a future "M016 Triage Quality" milestone, or could extend M015)
- Supporting slices: none
- Validation: unmapped
- Notes: The kimi-for-coding triage decision template (verify → outcome → reason → proposed_approach with numbered AC1-N steps) is the de facto contract.

### R022 — Web Dashboard Reflects Autonomous-Loop State
- Class: failure-visibility
- Status: active
- Description: The Next.js web UI at `web/` must reflect current autonomous-loop state in real time: active milestone, current unit, recent dispatches, unresolved self-feedback count, locked/unlocked, last heartbeat. Operator should not need to run `sf status` in a terminal to know SF's state.
- Why it matters: A working dashboard is the difference between "SF runs autonomously" (operator can ignore it) and "SF runs autonomously but only if I babysit it." Web is the natural surface for that situational awareness.
- Source: spec
- Primary owning slice: unmapped (future "M017 Web Dashboard Coverage")
- Supporting slices: none
- Validation: unmapped
- Notes: Web builds and runs (verified 2026-05-17, `npm start` returns HTTP 200 in 268ms). Routes exist for sessions, settings, projects. Coverage of autonomous-loop state specifically is what this requirement addresses.

### R023 — Documentation Generation From DB State
- Class: quality-attribute
- Status: active
- Description: PROJECT.md, ROADMAP.md, REQUIREMENTS.md, and per-milestone CONTEXT.md / SUMMARY.md should be derivable from DB state via deterministic generators that preserve handwritten sections. Operator runs `sf docs sync` to write the canonical form; conflicts surface for resolution.
- Why it matters: Human-facing docs decay as the DB state evolves; this drift was the central symptom that the user caught in this session ("must be a lot more"). Deterministic generation closes the loop.
- Source: spec
- Primary owning slice: unmapped (future "M018 Docs Sync") — partially overlaps M014/S04 (section-aware PROJECT.md writer)
- Supporting slices: none
- Validation: unmapped
- Notes: Consider this the natural extension of M014 — instead of just reconciling PROJECT.md, generate all doc surfaces from DB.

### R024 — Test-Backed Milestone Completion
- Class: quality-attribute
- Status: active
- Description: A milestone moves to status=complete only when a CI-suitable test demonstrates the milestone's promise. Manual marking via DB UPDATE is forbidden in production (development hotfixes excepted with documented reason). The `complete-milestone` unit must verify the operational evidence section of VALIDATION.md before promoting.
- Why it matters: This dogfood session marked M005 and M006 complete via manual DB writes after handwriting validation docs. Honest delivery requires automated proof, not operator assertion.
- Source: spec
- Primary owning slice: unmapped (extends M010/S06 or future M019)
- Supporting slices: none
- Validation: unmapped
- Notes: Test-execution evidence (test command + pass count + duration) belongs in VALIDATION.md's Operational verification class. complete-milestone must enforce.

### R025 — Real-Cost Budget Accountability
- Class: constraint
- Status: active
- Description: Every autonomous-loop run reports actual LLM cost (token count × provider price) per unit type, slice, and milestone. Operator sets a budget ceiling per milestone; SF aborts and reports if exceeded. Existing `Cost: $0.0000` in `sf status` must populate from real provider responses, not stay at zero.
- Why it matters: Autonomous loops have unbounded cost if not gated. Budget visibility is essential to trust SF in production. Currently cost reporting is a stub.
- Source: spec
- Primary owning slice: unmapped (future "M020 Cost Accountability")
- Supporting slices: none
- Validation: unmapped
- Notes: Builds on existing `headless-usage.ts`. Real-time pricing data per model is the missing piece — needs a price registry.

### R026 — Slice-Level Real-Time Progress Streaming
- Class: failure-visibility
- Status: active
- Description: While a slice is executing, operators (CLI + web) must see real-time progress: current task, last 3 tool calls, file changes count, token spend so far. Updates push at ≤1Hz; no polling required.
- Why it matters: Long-running slices (e.g. research-milestone with 25-iteration solver loops) are opaque today — operators can only check `sf status` periodically and see heartbeats. Real-time progress builds trust + enables early intervention when work goes off-track.
- Source: spec
- Primary owning slice: unmapped (future "M017 Web Dashboard Coverage")
- Supporting slices: none
- Validation: unmapped
- Notes: Web event stream already exists (`/api/session/events`); needs slice-aware aggregation layer.

### R027 — Project Import/Export Contract
- Class: differentiator
- Status: active
- Description: `sf export <path>` produces a portable archive (DB + key planning docs + skill catalog + git ref) that `sf import <path>` can restore on a clean machine. Round-trip: same project state, same milestone progress, same self-feedback ledger.
- Why it matters: Without portable project state, SF projects are locked to a specific machine. Export/import enables team handoff, backup, and reproducible dogfood runs.
- Source: spec
- Primary owning slice: unmapped (future "M021 Project Portability")
- Supporting slices: none
- Validation: unmapped
- Notes: Must exclude transient state (locks, in-flight unit runtime, ephemeral debug logs). Inclusion list pinned in code, not derived heuristically.

### R028 — Multi-Repo Federation
- Class: differentiator
- Status: active
- Description: A single SF project can span multiple git repos with a federation contract: dispatch units cross-repo, share memories/decisions, coordinate via a parent project DB. Builds on ADR-019/020 (gRPC + internal-wire architecture).
- Why it matters: Real product work usually spans multiple repos (frontend + backend + infra). Today SF is single-repo only; multi-repo unlocks the full purpose-to-software promise.
- Source: spec
- Primary owning slice: unmapped (future "M022 Multi-Repo Federation")
- Supporting slices: none
- Validation: unmapped
- Notes: ADRs already pre-design the wire format. This requirement schedules the actual implementation.

### R029 — Skill Marketplace / Discovery
- Class: differentiator
- Status: active
- Description: Skills (Claude Code skills format) are discoverable from a registry, installable via `sf skill install <ref>`, and verified at install time. SF's own units can declare required skills declaratively in their manifest.
- Why it matters: Skill reuse is the natural extensibility surface (per ADR-016 Charm-AI stack adoption). Without a marketplace, skills stay siloed per project.
- Source: spec
- Primary owning slice: unmapped (future "M023 Skills Marketplace")
- Supporting slices: none
- Validation: unmapped
- Notes: Existing `skill-catalog.js` is the foundation. Needs remote-registry support + signature verification.

### R030 — Privacy & Data Residency
- Class: constraint
- Status: active
- Description: SF must support pluggable LLM providers with per-project policy (e.g. EU-only providers, no third-party calls, on-prem only). Privacy-policy violations fail closed: dispatch refuses before sending the prompt.
- Why it matters: Enterprise adoption requires data-residency guarantees. Today the routing layer can fall back to providers that may violate policy; there's no explicit gate.
- Source: spec
- Primary owning slice: unmapped (future "M024 Privacy & Residency")
- Supporting slices: none
- Validation: unmapped
- Notes: Builds on `model-router.js` scoring. Add policy filter that runs BEFORE scoring eligible models.

### R031 — ADR Enforcement in CI
- Class: quality-attribute
- Status: active
- Description: ADRs declare invariants (e.g. ADR-0000: every milestone has a vision; ADR-019: SF DB is single-writer; ADR-016: pi-tui stays in core until parity). CI runs an `sf adr check` that asserts every ADR's invariants hold against current code/state. Violations fail the build.
- Why it matters: ADRs decay without enforcement. We've already seen drift (M001-6377a4 shipped with empty vision despite ADR-0000). Enforcement closes the loop.
- Source: spec
- Primary owning slice: unmapped (future "M025 ADR Enforcement")
- Supporting slices: none
- Validation: unmapped
- Notes: Each ADR needs a verifier function. Doctor checks are the natural extension point.

### R032 — Idempotent Retry Semantics
- Class: quality-attribute
- Status: active
- Description: Every unit dispatch is idempotent: re-dispatching a unit with the same milestone/slice/task id produces equivalent results without duplicating side-effects (e.g. doesn't create a second SUMMARY.md). Retries on transient failures (provider timeout, network) are safe.
- Why it matters: Without idempotency, retries can corrupt state — duplicated checkpoints, double-counted costs, etc. The current dispatcher relies on convention; this requirement makes it contractual + tested.
- Source: spec
- Primary owning slice: unmapped (cross-cutting; could extend M010/S04 or future M026)
- Supporting slices: none
- Validation: unmapped
- Notes: Dispatch needs a unit-completion fence: once a unit's primary artifact (e.g. SUMMARY.md) exists with the canonical content, retry becomes a no-op.

### R033 — Deterministic Plan Replayability
- Class: quality-attribute
- Status: active
- Description: Given the same inputs (REQUIREMENTS.md, PROJECT.md, code state) and the same model, plan-milestone must produce structurally equivalent slice plans across runs. Non-determinism is bounded to LLM stochasticity within accepted variance.
- Why it matters: Reproducibility is a precondition for dogfood debugging. Today running plan-milestone twice can produce wildly different slices, making it hard to isolate "did the plan change because of code or because of LLM noise."
- Source: spec
- Primary owning slice: unmapped (future "M027 Plan Determinism")
- Supporting slices: none
- Validation: unmapped
- Notes: Likely needs seed propagation to providers that support it + canonicalization of plan output.

### R034 — Performance Budget per Unit Type
- Class: quality-attribute
- Status: active
- Description: Each unit type has a documented performance budget: max wall time, max LLM token spend, max tool-call count. Runtime emits violations as self-feedback. Operator dashboard shows budget vs. actual per unit.
- Why it matters: Performance regressions creep in without measurement (e.g. plan-milestone took 75s longer this week — why?). Budgets surface drift before it becomes pain.
- Source: spec
- Primary owning slice: unmapped (future "M028 Performance Budgets")
- Supporting slices: none
- Validation: unmapped
- Notes: Existing unit-runtime tracking has the raw timing data. This requirement adds the budget contract + alerting.

### R035 — Operator Override / Steering
- Class: differentiator
- Status: active
- Description: Operator can steer an in-progress unit by sending a sideband message (`sf steer <unitId> "focus on X first"`) that's injected into the next agent turn without aborting the unit. Steering is logged and surfaces in the unit's SUMMARY.
- Why it matters: Real autonomy isn't all-or-nothing — operators often want to nudge a unit without killing it. Steering is the natural surface for that.
- Source: spec
- Primary owning slice: unmapped (future "M029 Operator Steering")
- Supporting slices: none
- Validation: unmapped
- Notes: web/ already has a `/api/steer` route — the wire is partially there. Needs unit-side integration that respects the steering message in the next prompt-build cycle.

## Purpose-Driver Requirements

The next group enforces ADR-0000's contract: **purpose is the driver**, not work-output. Every dispatched unit, every emitted artifact, every spent dollar must trace back to a stated purpose — or refuse to ship.

### R036 — Purpose Trace on Every Artifact
- Class: core-capability
- Status: active
- Description: Every SF-produced artifact (SUMMARY.md, ASSESSMENT.md, RESEARCH.md, CONTEXT.md, code commits authored by SF units) carries a `purposeAnchor` field that resolves to a specific milestone/slice vision. Doctor refuses artifacts without a purpose anchor; CI rejects SF-authored commits whose anchor doesn't trace to an active purpose.
- Why it matters: Without traceable anchors, artifacts accumulate noise — research notes for cancelled work, plans for slice scopes that drifted. ADR-0000 says SF is a *purpose-to-software compiler*; the corollary is that work without purpose is waste.
- Source: spec (anchors to ADR-0000)
- Primary owning slice: unmapped (future "M030 Purpose Trace Enforcement")
- Supporting slices: none
- Validation: unmapped
- Notes: Self-feedback entries already have a `purposeAnchor` field — this requirement generalizes the pattern across all artifact types.

### R037 — Purpose-Decay Detection in Long-Running Slices
- Class: failure-visibility
- Status: active
- Description: When a slice runs longer than its estimate OR consumes more than 2x its budget OR makes more than its expected tool-call count, SF must re-validate that the in-progress work still traces to the slice's purpose. If decay is detected (work is now off-purpose), the autonomous-solver pauses for operator review instead of pushing on.
- Why it matters: Long autonomous runs are particularly susceptible to scope drift — the LLM finds an interesting tangent, follows it, and ends up shipping something that wasn't the slice's stated promise. Purpose-decay detection catches this.
- Source: spec (anchors to ADR-0000)
- Primary owning slice: unmapped (future "M030 Purpose Trace Enforcement")
- Supporting slices: none
- Validation: unmapped
- Notes: Adversarial-review pattern — every N tool calls, evaluate "does this action still serve the stated purpose?" Bounded LLM eval, not per-tool-call.

### R038 — Purpose-First Operator UI
- Class: differentiator
- Status: active
- Description: The web dashboard and `sf status` lead every view with the **purpose** of the current work (milestone vision, slice goal), not the technical activity (which file is being read). Purpose is the headline; activity is the supporting detail. Same applies to historical views — every completed milestone shows "Purpose served: ..." prominently.
- Why it matters: ADR-0000 frames SF as a purpose-driven compiler; if the operator UI surfaces activity above purpose, the framing leaks. Purpose-first surfaces keep the doctrine load-bearing.
- Source: spec (anchors to ADR-0000)
- Primary owning slice: unmapped (future "M017 Web Dashboard Coverage", connects to R022)
- Supporting slices: none
- Validation: unmapped
- Notes: Implementation hint: web/components needs a `PurposeBanner` component that loads from current milestone/slice/task context and renders prominently.

### R039 — Purpose-to-Evidence Audit Trail
- Class: quality-attribute
- Status: active
- Description: For each milestone, an audit report exists that maps every piece of evidence (test result, file change, ADR reference, slice SUMMARY) to a specific clause of the milestone's vision. Operator runs `sf audit M006` and sees: "Vision clause 1: covered by S01 evidence X, S02 evidence Y. Vision clause 4: PARTIAL — no evidence found." Validate-milestone uses this audit to score verdict.
- Why it matters: Currently milestone validation is a free-form LLM review. An explicit purpose-to-evidence map makes the validation deterministic and human-auditable — and gives the validate-milestone unit a clear contract.
- Source: spec (anchors to ADR-0000)
- Primary owning slice: unmapped (future "M030 Purpose Trace Enforcement")
- Supporting slices: none
- Validation: unmapped
- Notes: Vision text needs to be decomposed into discrete clauses before mapping. Manual annotation initially; LLM-assisted decomposition over time.

### R040 — Purpose-Driven Cost Allocation
- Class: constraint
- Status: active
- Description: Every dollar of LLM spend is attributed to a specific purpose (milestone/slice/task vision). Cost reports group by purpose, not by unit type or model — answering "what did this $50 of spend serve?" rather than "how much went to validate-milestone?". Operators can set per-purpose budgets and see runaway-spend on a specific purpose before it compounds.
- Why it matters: Per-unit-type cost is the wrong lens. ADR-0000 frames work as purpose-driven; cost accounting should follow that lens or it disconnects spend from value. R025 (cost accountability) plus R040 (purpose attribution) together make budget visibility actionable.
- Source: spec (anchors to ADR-0000)
- Primary owning slice: unmapped (future "M020 Cost Accountability" + cross-cuts M030)
- Supporting slices: none
- Validation: unmapped
- Notes: Pairs with R025. The grouping dimension is what changes — same raw cost data, lens shifts from technical surface to purpose surface.

## Traceability

| ID | Class | Status | Primary owner | Supporting | Proof |
|---|---|---|---|---|---|
| R001 | failure-visibility | active | M003/S01 | none | unmapped |
| R002 | failure-visibility | active | M003/S02 | none | unmapped |
| R003 | quality-attribute | active | M003/S03 | none | unmapped |
| R004 | differentiator | active | M003/S04 | none | unmapped |
| R005 | constraint | active | M003/S05 | none | unmapped |
| R006 | quality-attribute | active | M003/S06 | none | unmapped |
| R007 | constraint | active | M005/S01 | none | unmapped |
| R008 | core-capability | active | M005/S01 | M005/S02 | unmapped |
| R009 | quality-attribute | active | M005/S01 | M005/S03 | unmapped |
| R010 | quality-attribute | active | M005/S02 | none | unmapped |
| R011 | failure-visibility | active | M011/S01 | none | unmapped |
| R012 | differentiator | active | M011/S02 | M011/S03, M011/S04 | unmapped |
| R013 | core-capability | active | M010/S02 | M010/S03, M010/S05 | unmapped |
| R014 | core-capability | active | M010/S01 | none | unmapped |
| R015 | failure-visibility | active | M010/S04 | none | unmapped |
| R016 | core-capability | active | M012/S01 | M012/S02, M012/S03 | unmapped |
| R017 | differentiator | active | M013/S02 | M013/S01, M013/S03, M013/S04 | unmapped |
| R018 | quality-attribute | active | M014/S01 | M014/S02, M014/S03, M014/S04 | unmapped |
| R019 | quality-attribute | active | M015/S01 | M015/S02, M015/S03, M015/S04, M015/S05 | unmapped |
| R020 | quality-attribute | active | M010/S06 | none | unmapped |
| R021 | quality-attribute | active | M016/S01 | M016/S02, M016/S03 | unmapped |
| R022 | failure-visibility | active | M017/S03 | M017/S01 | unmapped |
| R023 | quality-attribute | active | M018/S02 | M018/S01, M018/S03 | unmapped |
| R024 | quality-attribute | active | M019/S02 | M019/S01, M019/S03 | unmapped |
| R025 | constraint | active | M020/S02 | M020/S01, M020/S03, M020/S04 | unmapped |
| R026 | failure-visibility | active | M017/S02 | M017/S01 | unmapped |
| R027 | differentiator | active | M021/S01 | M021/S02 | unmapped |
| R028 | differentiator | active | M022/S03 | M022/S01, M022/S02, M022/S04 | unmapped |
| R029 | differentiator | active | M023/S02 | M023/S01, M023/S03 | unmapped |
| R030 | constraint | active | M024/S02 | M024/S01 | unmapped |
| R031 | quality-attribute | active | M025/S02 | M025/S01 | unmapped |
| R032 | quality-attribute | active | M026/S02 | M026/S01 | unmapped |
| R033 | quality-attribute | active | M027/S02 | M027/S01 | unmapped |
| R034 | quality-attribute | active | M028/S02 | M028/S01 | unmapped |
| R035 | differentiator | active | M029/S01 | M029/S02 | unmapped |
| R036 | core-capability | active | M030/S01 | none | unmapped |
| R037 | failure-visibility | active | M030/S02 | none | unmapped |
| R038 | differentiator | active | M030/S03 | M017/S02 | unmapped |
| R039 | quality-attribute | active | M030/S04 | none | unmapped |
| R040 | constraint | active | M030/S05 | M020/S04 | unmapped |
| R041 | differentiator | active | M031/S02 | M031/S01, M031/S05 | unmapped |
| R042 | differentiator | active | M031/S01 | M031/S03 | unmapped |
| R043 | quality-attribute | active | M031/S03 | none | unmapped |
| R044 | differentiator | active | M031/S04 | none | unmapped |
| R045 | differentiator | active | M032/S02 | M032/S01 | unmapped |
| R046 | differentiator | active | M033/S02 | M033/S01, M033/S03 | unmapped |
| R047 | quality-attribute | active | M034/S02 | M034/S01, M034/S03, M034/S04 | unmapped |
| R048 | core-capability | active | M035/S02 | M035/S01, M035/S03, M035/S04 | unmapped |
| R049 | differentiator | active | unmapped (M036 future) | none | unmapped |
| R050 | quality-attribute | active | unmapped (M037 future) | none | partial — variant-fallback shipped in benchmark-coverage.js |
| R051 | failure-visibility | active | M038/S01 | none | unmapped |
| R052 | failure-visibility | active | M038/S02 | none | unmapped |
| R053 | failure-visibility | active | M038/S03 | none | unmapped |
| R054 | failure-visibility | active | M038/S04 | none | unmapped |
| R055 | differentiator | active | M038/S05 | none | unmapped |
| R056 | core-capability | active | M038 | M038/S01-S05 | unmapped |
| R057 | differentiator | active | M039/S01 | M039/S02, M039/S03, M039/S04, M039/S05 | unmapped |
| R058 | core-capability | active | M040/S01 | M040/S02, M040/S03, M040/S04 | unmapped |
| R059 | core-capability | active | M041 | M041/S01-S06 | unmapped |
| R060 | quality-attribute | active | M042/S01 | M042/S02, M042/S03, M042/S04 | unmapped |
| R061 | core-capability | active | M043/S02 | M043/S01, M043/S03, M043/S04 | unmapped |
| R062 | quality-attribute | active | M044/S01 | M044/S02, M044/S03, M044/S04 | unmapped |
| R063 | differentiator | active | M045/S01 | M045/S02, M045/S03 | unmapped |
| R064 | quality-attribute | active | M046/S01 | M046/S02 | unmapped |
| R065 | quality-attribute | active | M047/S01 | M047/S02 | unmapped |

## Coverage Summary

- Active requirements: 65
- Mapped to slices: **63**
- Validated: 0
- Unmapped active requirements: **2** (R049 — multi-provider parallel routing; R050 — auto-benchmark uncovered models)
- Owning milestones: M003 (R001-R006), M005 (R007-R010), M010 (R013-R015, R020), M011 (R011-R012), M012 (R016), M013 (R017), M014 (R018), M015 (R019), M016-M030 (R021-R040), M031 (R041-R044), M032 (R045), M033 (R046), M034 (R047), M035 (R048), [pending] M036-M037 (R049-R050), M038 (R051-R056 Wiggums Detector), M039 (R057 System Lane), M040 (R058 DB-via-Bus), M041 (R059 Typed Vocabulary), M042 (R060 Backup+Restore), M043 (R061 Schema Migration), M044 (R062 Secrets Lifecycle), M045 (R063 Rate Budget), M046 (R064 Cache Policy), M047 (R065 Deprecation)

## Purpose Anchor

ADR-0000 declares SF a **purpose-to-software compiler**. R036–R040 codify that doctrine at the artifact level (every output carries a purpose trace). R048 codifies it as an **unbroken chain**: purpose → R → M → S → T → code, validated at every link. R041–R044 close the recursive loop: SF generates its own milestones from R's and grows the R-set from research, so the capability contract compounds with delivery. R047 walks the chain bottom-up; bootstrap walks it top-down. Together: purpose is not a doctrinal framing on top of work — it is the **organizing principle the entire system enforces, generates, and validates**.

### R041 — SF Bootstrap From Requirements
- Class: differentiator
- Status: active
- Description: Given a REQUIREMENTS.md with active R-entries that aren't yet owned by any milestone, SF must autonomously generate appropriate milestones (with vision + slice scaffolding) to host them. The bootstrap unit reads unmapped R's, groups them by theme, drafts milestones with vision statements anchored to R-purposes, and emits them for operator review (with --auto-accept for fully unattended bootstrap).
- Why it matters: Closes the purpose-to-software loop end-to-end. Today an operator must hand-draft milestones (as done this session for M010-M030); R041 makes "write a requirement, get a milestone" the autonomous default. SF becomes self-bootstrapping from the capability contract.
- Source: spec (anchors to ADR-0000)
- Primary owning slice: unmapped (future "M031 R-to-Milestone Bootstrap")
- Supporting slices: none
- Validation: unmapped
- Notes: This is the recursive case of purpose-to-software: SF builds SF from the capability contract. Each new R-cluster becomes a milestone the autonomous loop can plan and deliver.

### R042 — Deep Research Per Requirement Cluster
- Class: differentiator
- Status: active
- Description: When R041 generates milestones from R-entries, each cluster gets a deep research pass BEFORE the milestone is drafted: comparable-systems lookup (DeepWiki, Context7, web search), prior-art analysis, existing-codebase scan for partial implementations, related ADR/decisions review. Research output feeds the milestone vision + initial slice scaffolding. Target 8-12 web searches per cluster (deep mode) plus full codebase scout + relevant package docs. No half-researched milestone drafts.
- Why it matters: R041 without deep research produces shallow milestones that miss prior art, duplicate existing work, or under-specify slice scope. Deep research is the difference between SF as a Markov-chain planner vs. SF as a purpose-to-software compiler that genuinely understands the problem space. Builds on the research prompt tuning landed in M005 (target 8-12 web searches, mandatory Comparable Systems section).
- Source: spec (anchors to ADR-0000)
- Primary owning slice: unmapped (future "M031 R-to-Milestone Bootstrap")
- Supporting slices: none
- Validation: unmapped
- Notes: Reuses the existing research-milestone prompt template + scout swarm pattern. Adds an R-cluster preamble that lists unmapped R-entries + their themes. Output: a research artifact per cluster that the milestone-draft unit consumes.

### R043 — Iterative Refinement Loop For Generated Milestones
- Class: quality-attribute
- Status: active
- Description: Generated milestones (from R041) enter a refinement loop: draft → adversarial review (architect, partner, combatant lenses) → revision → operator review checkpoint → finalize. Each iteration must improve on a defined quality metric (clarity, scope coherence, R-coverage completeness, evidence-of-research-depth). Refinement stops when quality threshold is met OR after N=3 iterations (operator escalation).
- Why it matters: First-draft milestones are usually wrong — scope creep, missing risks, under-specified DoD. Iterative refinement closes the gap to a milestone the autonomous loop can actually deliver. The 3-lens adversarial review pattern is borrowed from the existing roadmap-meeting unit.
- Source: spec
- Primary owning slice: unmapped (future "M031 R-to-Milestone Bootstrap")
- Supporting slices: none
- Validation: unmapped
- Notes: Quality metric thresholds: clarity (slice goals are 1-2 sentences each), scope coherence (no slice depends on out-of-milestone work), R-coverage (every cluster R has a slice owning it), research-depth (≥1 comparable system documented per cluster).

### R044 — Auto-Promote Research Findings To Requirements
- Class: differentiator
- Status: active
- Description: When R042's deep research surfaces capability gaps, anti-patterns to avoid, or differentiator opportunities NOT yet in REQUIREMENTS.md, the research unit must auto-promote them to candidate R-entries. Candidates surface for operator review before becoming active. Closes the loop: research drives requirements, requirements drive milestones, milestones drive code.
- Why it matters: Today research findings are advisory — they inform the current slice but don't update the capability contract. R044 makes research actively grow the requirement set so SF's understanding of what the project should do compounds over time.
- Source: spec
- Primary owning slice: unmapped (future "M031 R-to-Milestone Bootstrap")
- Supporting slices: none
- Validation: unmapped
- Notes: Surface via `.sf/CANDIDATE-REQUIREMENTS.md` — operator promotes to active by moving entries into REQUIREMENTS.md. Doctor surfaces unreviewed candidates.


### R045 — Active Requirement Expansion By SF
- Class: differentiator
- Status: active
- Description: Beyond R044's candidate-promotion, SF can directly write new R-entries to REQUIREMENTS.md when (a) the gap is structurally obvious (e.g. doctor detects an invariant violation no R covers), (b) the gap matches an ADR that doesn't yet have a measurable enforcement R, or (c) a self-feedback entry of kind `requirement:missing-coverage` has been triaged with outcome=add-r. Auto-written R-entries are flagged with `source: sf-auto` and require operator review within N days before being promoted to load-bearing.
- Why it matters: Requirements naturally evolve as the system reveals new failure modes (this session alone surfaced R016 through R045 from dogfood findings). Making R-expansion a first-class SF capability — not just an operator chore — keeps the capability contract live with reality. Pairs with R044 (research-driven candidates) and closes the loop where SF improves its own contract.
- Source: spec (anchors to ADR-0000)
- Primary owning slice: unmapped (future "M031 R-to-Milestone Bootstrap" or sibling "M032 R-Auto-Expansion")
- Supporting slices: none
- Validation: unmapped
- Notes: Operator-review gate prevents runaway R-bloat. The "source: sf-auto" tag is queryable so we can audit how much of the contract is operator-authored vs SF-suggested over time.

### R046 — Autonomous Loop Parallel Dispatch
- Class: differentiator
- Status: active
- Description: The autonomous loop dispatches at most one unit at a time today. R046 enables N-way parallel dispatch for non-conflicting units: e.g. plan-slice on different slices of the same milestone can run concurrently; research-slice across multiple milestones can fan out. The existing parallel-orchestrator handles milestone-scope parallel work; R046 extends the principle into the autonomous loop's iteration loop with a file-conflict DAG that determines safe concurrency.
- Why it matters: Single-threaded autonomous dispatch is the dominant time cost (each iteration takes 5-6 min for real LLM units). 4-way parallelism cuts wall-time roughly 4x for milestones with independent slices. Compounds with the M010 inline-scope work because inline units are cheaper to fan out (no worktree spawn).
- Source: spec
- Primary owning slice: unmapped (future "M033 Autonomous Parallel Dispatch")
- Supporting slices: none
- Validation: unmapped
- Notes: Builds on existing parallel-orchestrator + slice-parallel-orchestrator. New layer: a scheduler that maps "ready slices" (deps met) to available concurrency slots, respecting the file-conflict DAG.

### R047 — Per-Requirement Fulfillment Validation
- Class: quality-attribute
- Status: active
- Description: Beyond test-backed milestone completion (R024), introduce a separate validation pass that walks every active R-entry and asserts it is *actually* fulfilled by current code/tests/artifacts — not just that its owning milestone is marked complete. The pass produces a per-R verdict (covered / partial / missing) with concrete evidence (test name + pass, file:line, artifact path). `sf validate-requirements [--fix]` runs the pass; CI fails on missing-coverage for active R's. Symmetric validation for milestones: `sf validate-milestone <id>` walks the milestone's vision clauses and asserts each is backed by R-coverage + evidence (per R039).
- Why it matters: Marking a milestone "complete" in the DB is cheap; honestly fulfilling its requirements is hard. R047 closes the loop — every R has an evidence trail, every milestone's vision has R-coverage. Without this, "delivered" is operator assertion, not provable fact. The dogfood session this requirement was written in explicitly exposed cases where milestones were DB-complete but PROJECT.md showed unchecked slices (drift R018 catches; R047 catches missing R-fulfillment).
- Source: spec (responds to dogfood evidence 2026-05-17 — operator caught manual completion claims that lacked per-R evidence)
- Primary owning slice: unmapped (future "M034 Per-R Validation Pass")
- Supporting slices: none
- Validation: unmapped
- Notes: Builds on R024 (test-backed completion) and R039 (purpose-to-evidence audit). The validation contract per R-class:
  - failure-visibility R's: there's a doctor check OR test that asserts the signal fires
  - core-capability R's: there's a unit/integration test that exercises the capability
  - quality-attribute R's: there's a regression test guarding the property
  - differentiator R's: there's user-facing evidence (CLI cmd, web feature, etc.)
  - constraint R's: there's a CI gate that fails violations

### R048 — Unbroken Purpose Chain From R → M → S → T → Code
- Class: core-capability
- Status: active
- Description: Every requirement (R) declares its purpose anchor (which slice of ADR-0000's compiler contract it serves). Every milestone (M) declares which R's it owns + why those R's serve a coherent purpose cluster. Every slice (S) traces to specific R-acceptance-criteria within its milestone. Every task (T) traces to specific S success-criteria. Every code artifact traces to a task. Validation (R047) walks the chain bottom-up; bootstrap (R041) generates the chain top-down. Doctor refuses any link missing its anchor.
- Why it matters: This is the operational form of ADR-0000. "Purpose-to-software compiler" only holds if purpose propagates *as a verifiable property* through every layer. Without R048, purpose is documentation that decays; with R048, purpose is contract that's enforced at every dispatch and every artifact write. The chain is what makes SF *compositional*: you can read up from any commit to its task → slice → milestone → R → purpose, and read down from any purpose to the commits that fulfilled it.
- Source: spec (anchors directly to ADR-0000; supersedes R036–R040 as the integrating contract)
- Primary owning slice: unmapped (future "M030 Purpose-Driver Enforcement Family" gets extended to also enforce the chain integrity, or new "M035 Chain Integrity")
- Supporting slices: none
- Validation: unmapped
- Notes:
  - **R-level**: every R-entry's `Purpose:` line names a specific ADR-0000 clause it serves
  - **M-level**: every milestone vision opens with "Serves purpose-cluster: <name>. Owns: <R-list>. The purpose these R's jointly serve is <one sentence>."
  - **S-level**: every slice goal opens with "Advances R<X>'s acceptance criterion <N>: ..."
  - **T-level**: every task plan opens with "Implements S<x>'s success-criterion <N> via concrete artifact <path>."
  - **Code-level**: every SF-authored commit message footer carries `purposeAnchor: <R-id>` (cross-cuts R036)
  - **Doctor**: a new `purpose-chain-integrity` check walks the chain and refuses any link missing its anchor (cross-cuts R031 ADR enforcement)

### R049 — Multi-Provider Parallel Routing
- Class: differentiator
- Status: active
- Description: Across-unit parallel dispatch (R046) routes different concurrent units to different LLM providers based on (a) provider per-minute quota, (b) model specialty (kimi-for-coding for code-heavy units, minimax for synthesis, gemini for long-context research), (c) cost optimization (cheaper models for low-stakes units), (d) failover (avoid the provider that just rate-limited). The scheduler reads model-router config + live quota state and picks per-unit. Different models work simultaneously on different slices, spreading load across providers.
- Why it matters: Single-provider parallel maxes out at that provider's per-minute quota; multi-provider parallel scales N× as we add providers. Combined with R046, this is what makes the 2-4 week horizon collapse to days for embarrassingly parallel work (e.g. plan-slice across 20 independent slices).
- Source: spec
- Primary owning slice: unmapped (future "M036 Multi-Provider Parallel Routing")
- Supporting slices: none
- Validation: unmapped
- Notes: Builds on existing model-router.js scoring + R017's tool-failure demotion + R046's autonomous parallel dispatch. The new piece is the scheduler-level multi-model assignment per dispatch slot.

### R050 — Model Benchmark Coverage
- Class: quality-attribute
- Status: active
- Description: Every model in the SF model-router registry must have a benchmark score (static or measured). Today (~/.sf/benchmark-coverage.json snapshot 2026-05-17): 57 of 154 models covered (37%). Notable uncovered: kimi-coding/kimi-for-coding (actively used by triage), mistral-medium-* family, gemini-2.5-flash-lite, gemini-3.1-flash-lite. Routing decisions (R017 demotion, R046 parallel scheduling, R049 multi-provider) are only as good as the scores they read; missing scores produce inconsistent routing.
- Why it matters: Model selection compounds across thousands of dispatches in a 2-4 week autonomous run. Picking the wrong model wastes cost + time + produces lower quality work. Benchmark coverage is the foundation for routing intelligence.
- Source: spec
- Primary owning slice: unmapped (future "M037 Model Benchmark Coverage")
- Supporting slices: none
- Validation: unmapped
- Notes: Two paths to scores: (a) bulk-import published scores from MMLU/HumanEval/SWE-bench for known models, (b) live-measure via SF's eval suite for unknown models (existing `.sf/evals/autonomous-solver/` framework). Doctor surfaces uncovered models; scheduler treats uncovered as "use cautiously, not for high-stakes units."

### R051 — Same-Unit Dispatch-Loop Detection (Ralph Wiggum Safety Net)
- Class: failure-visibility
- Status: active
- Description: When the dispatcher resolves the same `{unitType, unitId}` more than N=3 times in a single autonomous session without that unit's outcome changing the dispatcher's decision, SF must detect the loop, pause the autoLoop, file a self-feedback entry of kind `dispatch:degenerate-loop` with the unit details, and surface to operator. Default: stop dispatching that unit; advance to the next-best alternative.
- Why it matters: 2026-05-17 dogfood: SF dispatched `reassess-roadmap M010/S03` 70+ times because `checkNeedsReassessment` had a suffix-mismatch bug (`ASSESS` vs `ASSESSMENT.md`). Each dispatch took 2-7 minutes; cumulatively hours of compute burned on the same no-op task. The Ralph-Wiggum-obvious failure mode — "I keep doing the same thing and nothing changes" — needs to be a first-class detector, not require operator hand-debugging.
- Source: spec (responds to dogfood evidence 2026-05-17)
- Primary owning slice: unmapped (future "M038 Dispatch Loop Safety")
- Supporting slices: none
- Validation: unmapped
- Notes: Implementation: per-session counter of `dispatch-resolve` decisions keyed by `${unitType}:${unitId}`. When count > 3 in <30min wall AND no measurable state change between dispatches (e.g., milestone status, slice status, artifact set unchanged), trigger the safety net. Doctor surfaces the loop as a project-level issue.


### R052 — Zero-Progress Runtime Unit Detector
- Class: failure-visibility
- Status: active
- Description: Watch active .sf/runtime/units/*.json files. When any unit sits at progressCount:0 for >5min wall AND has lastHeartbeatAt within that window (alive but stuck), surface as kind=dispatch:zero-progress-stall self-feedback. This catches the "heartbeating ghost" pattern witnessed multiple times this dogfood session.
- Why it matters: Heartbeats-alive-but-zero-progress is invisible to the standard runaway-guard because runaway tracks tool-call growth, not unit-state growth. This detector closes that gap.
- Source: spec (responds to 2026-05-17 dogfood evidence)
- Primary owning slice: M038/S02
- Supporting slices: none
- Validation: unmapped
- Notes: 5-minute threshold tunable per unit type (research-slice naturally takes longer than complete-milestone).

### R053 — Repeated Self-Feedback Kind/Target Detector
- Class: failure-visibility
- Status: active
- Description: Group unresolved self-feedback entries by {kind, occurredIn.milestone, occurredIn.slice}. When a group has >5 entries within 24h, the same error is being filed repeatedly without resolution — surface as kind=feedback:repeated-failure and trigger triage escalation. This catches the "56+ runaway-loop:idle-halt entries on M005" pattern that nobody acted on for days.
- Why it matters: The self-feedback queue is a write-only audit if nobody reads it. Detecting clustering signals "this is a real recurring bug, not noise" — and routes the entry to humans/triage with high signal.
- Source: spec (responds to 2026-05-17 dogfood evidence — 56+ idle-halt entries accumulated)
- Primary owning slice: M038/S03
- Supporting slices: none
- Validation: unmapped
- Notes: Rollup logic must respect existing maybeRecordRepeatedFailureRollup in doctor.js; this detector extends, not replaces.

### R054 — Artifact Predicate Flap Detector
- Class: failure-visibility
- Status: active
- Description: Watch key artifact predicates (file exists, vision non-empty, slice status, M*-SUMMARY.md presence). When any predicate flaps true/false between consecutive dispatches of the same unit, the unit is undoing its own work — surface as kind=dispatch:artifact-flap.
- Why it matters: A unit that creates an artifact, the next iteration deletes it, the next iteration recreates it is in a self-destructive loop. Today this is invisible until operator notices via mtimes. The detector makes flap-loops first-class.
- Source: spec
- Primary owning slice: M038/S04
- Supporting slices: none
- Validation: unmapped
- Notes: Predicates checked per unit type. Initial set: SUMMARY.md / ASSESSMENT.md / PLAN.md exists, slice status, milestone vision non-empty.

### R055 — Stale-Lock Auto-Recovery
- Class: differentiator
- Status: active
- Description: Detect .sf/sf.lock held by no live PID (the holder PID is dead). Auto-fix: rm the lock + log + file self-feedback kind=lock:stale-recovered. Also detect .sf/runtime/self-feedback-inline-fix.json with dispatchedAt older than 30min and no in-flight dispatcher work — auto-clear. Race-safe: verify the holder is genuinely dead via /proc check before removing.
- Why it matters: This session repeatedly hit "Another autonomous mode session (PID X) appears to be running. Stop it with kill X" — but PID X was always already dead. Stale-lock auto-recovery removes the manual cleanup chore + prevents crashed sf from blocking the next watchdog cycle.
- Source: spec (responds to 2026-05-17 dogfood evidence — multiple stale-lock incidents)
- Primary owning slice: M038/S05
- Supporting slices: none
- Validation: unmapped
- Notes: The script watchdog path has been removed. R055 moves recovery into SF core so any sf invocation benefits without a separate watchdog loop.

### R056 — Wiggums Detector Periodic Orchestrator
- Class: core-capability
- Status: active
- Description: A single periodic loop in auto-timers.js that runs every 30s and evaluates all 5 Wiggums questions (R051, R052, R053, R054, R055). Detector results aggregate into a wiggums-state.json that the operator dashboard reads. Single orchestrator pattern means: add new detectors by adding a question function; no per-detector lifecycle code.
- Why it matters: Without a single orchestrator, each detector becomes its own scheduled task with its own lifecycle bugs (the watchdog uses the simple "loop forever" pattern but inside SF that's not idiomatic). One orchestrator = one place where ALL stuck-pattern detection lives. Future Wiggums questions (R057+) just plug in.
- Source: spec
- Primary owning slice: M038 (cross-cuts all S01-S05)
- Supporting slices: M038/S01, M038/S02, M038/S03, M038/S04, M038/S05
- Validation: unmapped
- Notes: Pairs with the dashboard surface (R022, R026). Wiggums state is part of the autonomous-loop status snapshot.

### R057 — System Lane (concurrent non-unit work)
- Class: differentiator
- Status: active
- Description: Introduce a dedicated **system lane** that runs concurrent with the unit-execution lane, dispatching non-unit work (sleeptime memory consolidation, self-feedback triage, memory extraction, doctor audits, log compaction, reflection-corpus assembly, catalog refreshes) in parallel with active unit work. System-lane tasks write to isolated surfaces (`memory_*` tables, doctor-history.jsonl, .sf/reflection/, .sf/logs/, model catalogs) that don't conflict with active-unit artifacts (slice files, SUMMARY/ASSESSMENT, code edits). Lanes become the unifying primitive: today's autoLoop is one "unit lane"; R057 adds the system lane; R046 will add multi-unit lanes (parallel slice dispatch); R049 maps each lane to a different LLM provider.
- Why it matters: A typical 6min unit dispatch leaves CPU mostly idle waiting on LLM responses + tool execution. System-lane work (often <30s tasks) currently queues behind unit completion, accumulating into multi-minute "between-unit" stalls. Running them in the system lane reclaims that latency and shrinks the 2-4 week horizon. Pairs with R046 (multi-unit lanes) but is independent — system lane is non-unit work; R046 is more unit lanes.
- Source: spec (responds to operator observation 2026-05-17 — "could system work on other things at same time as executing in tree with non-colliding memory logs etc" → operator's coined name: "system lane")
- Primary owning slice: unmapped (future "M039 System Lane")
- Supporting slices: none
- Validation: unmapped
- Notes: System-lane inventory: (1) sleeptime consolidation (currently auto/loop.js drainSleeptimeConsolidationQueue between units), (2) self-feedback triage drain, (3) memory-extract for completed units (already async post-unit but currently blocks next dispatch), (4) doctor periodic audits, (5) log/journal compaction (NEW — no current implementation), (6) reflection-corpus assembly, (7) gemini-catalog + openai-codex-catalog refresh (currently only at session start). Single-writer DB invariant met by routing all DB writes through sf-db.js serial queue. Cross-lane conflicts (e.g. memory-extract reading a unit's session file while the next unit starts) resolved by lane tasks operating on already-sealed inputs. The **lane** primitive unifies R046 (multi-unit lanes), R049 (per-lane model routing), and R057 (system lane) into one architectural model.

### R058 — DB Writes Routed Via MessageBus (single-writer actor)
- Class: core-capability
- Status: active
- Description: All project-DB writes flow through a single MessageBus-mediated **db-writer actor** that owns the only WAL connection. Lanes (unit lane, system lane, multi-unit lanes per R046) send write-request messages; the actor serializes + applies them. Reads stay direct (multi-reader concurrency works). Crash isolation: lane crash mid-write doesn't corrupt the DB because the message is either fully delivered or not — the actor's transaction is atomic. Builds on existing uok/message-bus.js + serves the lane primitive (R046, R049, R057).
- Why it matters: With concurrent lanes (R057 system lane today, R046 multi-unit tomorrow), naïve direct writes hit SQLITE_BUSY contention or worse (WAL corruption from mishandled retries). DB-via-bus enforces the single-writer invariant *operationally*, not just by convention. Also enables: queue-depth backpressure visibility, write-replay on crash recovery, and is the natural substrate for multi-repo federation (R028 / ADR-019/ADR-020) where cross-process DB sharing already needs message-based access.
- Source: spec (responds to operator observation 2026-05-17 — "db via bus so we dont crash it?")
- Primary owning slice: unmapped (future "M040 DB-via-Bus")
- Supporting slices: none
- Validation: unmapped
- Notes:
  - Writer actor lives in same process today (in-memory MessageBus), can become a separate process in federation mode (ADR-020 gRPC wire)
  - sf-db.js becomes the message-producer; actual SQLite UPDATE/INSERT/DELETE happens only inside the writer
  - Reads bypass the bus (multi-reader is safe via WAL)
  - Backpressure: queue depth >N triggers self-feedback (writer overwhelmed → operator visibility)
  - Test contract: 1000 concurrent write requests from N lanes produce N total rows, no errors, no SQLITE_BUSY
  - Migration is gradual: move writers one table at a time (slices first, then milestones, then memory_*, etc.)

### R059 — Typed Entity Vocabulary (R/A/D/M/S/T/F/G/K/P/E)
- Class: core-capability
- Status: active
- Description: Pin a consistent typed-prefix vocabulary so every cross-referenced entity in SF has a stable ID with a clear type. Today only R/M/S/T are typed; ADRs, Decisions, Findings (self-feedback), Gates, Knowledge facts, Patterns, and Evidence artifacts are referenced inconsistently. The chain integrity contract (R048) and per-R validation (R047) both require typed cross-references to function honestly.

  | Prefix | Type | Source of truth | Migration |
  |---|---|---|---|
  | **R** | Requirement | .sf/REQUIREMENTS.md | already typed (R001-R058+) |
  | **A** | ADR | docs/adr/NNNN-name.md | rename to A0000-name.md, IDs `A0000` etc. |
  | **D** | Decision | .sf/DECISIONS.md | add D-IDs to each entry |
  | **M** | Milestone | DB milestones table | already typed (M001-M040+) |
  | **S** | Slice | DB slices table (per-M) | already typed |
  | **T** | Task | DB tasks table (per-S) | already typed |
  | **F** | Finding | DB self_feedback table (kind=*) | add F-IDs alongside legacy sf-{ts}-{rand} for human reference |
  | **G** | Gate | DB quality_gates table | add G-IDs |
  | **K** | Knowledge | KNOWLEDGE.md + memory_* tables | add K-IDs for cross-reference-worthy facts |
  | **P** | Pattern | docs/dev/sf-ace-patterns.md and equivalents | add P-IDs to pattern entries |
  | **E** | Evidence | per-slice SUMMARY.md / ASSESSMENT.md / UAT.md | already addressable by path; E-IDs optional |

- Why it matters: Without consistent typed IDs, cross-references are by free-text path. R048's unbroken purpose chain (R→M→S→T→Code) only works if every link is addressable. R024 (test-backed completion) needs to reference G-IDs. R039 (purpose-to-evidence audit) needs E-IDs. A doctor check that asserts "every active R has a covering G or E" needs all types to have stable IDs.
- Source: spec (responds to operator question 2026-05-17 — "should we have a for adrs and d for decisions? any other type we should have?")
- Primary owning slice: unmapped (future "M041 Typed Vocabulary Migration")
- Supporting slices: none
- Validation: unmapped
- Notes:
  - Migration is mechanical: rename ADR files, add ID frontmatter to DECISIONS.md entries, backfill DB tables with id columns.
  - Doctor check: every typed reference resolves (cross-link integrity).
  - Lint: SF-authored docs/code must use typed IDs for cross-references (e.g. "per A0000" not "per ADR-0000" or "per the purpose ADR").


### R060 — DB Backup + Restore
- Class: quality-attribute
- Status: active
- Description: Scheduled .sf/sf.db atomic snapshots with documented restore procedure + CI drill that proves snapshots are restorable. Default cadence: daily + on every milestone-complete. Snapshots exclude transient state (locks, in-flight runtime). `sf restore <snapshot>` command swaps in a snapshot atomically. Point-in-time recovery via WAL replay against most recent snapshot.
- Why it matters: Today a WAL corruption or accidental DELETE wipes the entire roadmap. 2-4 week unattended runs accumulate state continuously — no backup = single point of failure. Restore drill in CI is essential because untested backups don't work.
- Source: spec
- Primary owning slice: M042/S01
- Supporting slices: M042/S02, M042/S03, M042/S04
- Validation: unmapped
- Notes: Backup inclusion list pinned (db + planning .md files); excludes locks/runtime. PITR via WAL replay needs the WAL to be preserved alongside snapshots.

### R061 — Schema Migration Runner
- Class: core-capability
- Status: active
- Description: Versioned, idempotent, transactional, downgradable DB schema migrations via migrations/NNNN-name.{up,down}.sql files. Runner verifies signatures, applies in BEGIN/COMMIT, rolls back on any failure. Downgrade path supported for last 3 versions. Doctor flags unrun migrations; sf headless auto-applies on startup if behind.
- Why it matters: Schema evolves ad-hoc today (schema_version table exists but no formal runner). A half-applied migration during a 2-4 week unattended run = silent corruption. Transactional + downgradable runner makes schema changes safe.
- Source: spec
- Primary owning slice: M043/S02
- Supporting slices: M043/S01, M043/S03, M043/S04
- Validation: unmapped
- Notes: Signature (SHA-256) prevents tampered migrations from being silently applied. Downgrade window of 3 versions is conservative; longer windows accumulate complexity.

### R062 — Secrets Lifecycle (rotation + audit + leak detection)
- Class: quality-attribute
- Status: active
- Description: Storage already solved (auth.json + vault + env, 3-layer resolver). R062 narrows to lifecycle: per-provider rotation cadence with reminder, per-LLM-call audit log {lane, unit, provider, key-fingerprint, ts}, runtime leak detection (scan SF-authored artifacts for secret patterns), and lane-scoped credentials (R057 system lane can use different / cheaper / read-only key set than unit lane).
- Why it matters: Auth storage exists but operations don't. Without rotation reminders, keys live forever and risk accumulates. Without per-call audit, attributing a breach to a specific lane/unit is impossible. Without leak detection, a misbehaving LLM could write a key into a SUMMARY.md and it'd silently leak. Lane-scoped credentials make system lane cheaper + less powerful, matching its read-mostly workload.
- Source: spec (operator clarified 2026-05-17 — auth storage exists in auth.json, R062 narrows to lifecycle)
- Primary owning slice: M044/S01
- Supporting slices: M044/S02, M044/S03, M044/S04
- Validation: unmapped
- Notes: Audit log at .sf/audit-llm.jsonl. Leak detection runs as post-unit hook + system-lane periodic scan. Per-lane credentials configured via preferences.

### R063 — Cross-Provider Rate Budget
- Class: differentiator
- Status: active
- Description: Central token-bucket per provider as a MessageBus actor, shared across all lanes (R046 unit lanes + R057 system lane). Lanes request capacity before dispatching LLM calls. 429 responses feed the bucket → exponential backoff until provider window resets. `sf status --quota` shows live state.
- Why it matters: Bursty parallel lanes without central rate limiting will exhaust provider quotas simultaneously — wasting LLM spend on 429-retries and degrading throughput. Bucket actor enforces shared-state coordination across lanes. Combined with R049 (per-lane model routing), bucket exhaustion on one provider triggers routing to another.
- Source: spec
- Primary owning slice: M045/S01
- Supporting slices: M045/S02, M045/S03
- Validation: unmapped
- Notes: Bucket capacity reads from existing provider-quota.json. Sits naturally on the MessageBus alongside the db-writer actor (R058).

### R064 — Prompt Cache Policy
- Class: quality-attribute
- Status: active
- Description: Cross-session prompt cache reuse for stable prefixes (system prompts, ARTIFACT_KEYS blocks, etc.). Cache hit rate per unit type recorded in unit_metrics. TTL + eviction documented + enforced. Cost-savings from cache hits reported in sf cost.
- Why it matters: Today prompt cache fires per-call without cross-session reuse — every sf headless cold-start re-pays the cache cost. Stable-prefix hashing across sessions could cut LLM spend on common prompt prefixes by 50%+. Cost optimization, not blocking.
- Source: spec
- Primary owning slice: M046/S01
- Supporting slices: M046/S02
- Validation: unmapped
- Notes: Existing prompt-ordering.js handles the cache_control:ephemeral hints; R064 adds the cross-session reuse strategy.

### R065 — Deprecation Lifecycle
- Class: quality-attribute
- Status: active
- Description: R/M/S/A/D/G/K/P entries can have status='deprecated' + sunset_date + reason. Doctor flags entries past sunset that haven't been removed. Cross-link integrity check warns when an active entry depends on a deprecated one.
- Why it matters: Today cancelled/skipped is binary; no graceful sunset path. Without formal deprecation, replaced patterns (e.g. legacy v1 builders after v2 migration) linger forever. Sunset dates force cleanup discipline.
- Source: spec
- Primary owning slice: M047/S01
- Supporting slices: M047/S02
- Validation: unmapped
- Notes: Pairs with R059 typed vocabulary — sunset is a property of any typed entity.

### R066 — Autonomous Regression Quarantine
- Class: capability
- Status: active
- Description: Before SF adopts a runtime change it generated itself (autonomous slice landing into its own source tree), run a smoke loop against a fixture milestone and compare to a "last-green" dispatch ledger. On crash-loop signature, disable the new path via feature flag or roll back the generated change with a self-feedback finding. No promoted artifact runs in the live loop until the smoke fixture passes.
- Why it matters: 2026-05-17 DEFAULT_STALE_TIMEOUT_MS regression — SF autonomously delivered M010/S04/T01 work that introduced a missing export, then crash-looped its own watchdog for ~2 hours with operator unaware. R051-R056 Wiggums covers stuck patterns at the unit level, but a source-level autonomous-loop bug crashes before any unit predicate or progress counter fires. This is the "SF shouldn't break SF" capability. Without it, every new foundation bug from autonomous delivery can strand SF unrecoverable.
- Source: codex-adversarial-review-2026-05-17
- Primary owning slice: M048/S01
- Supporting slices: M048/S02, M048/S03, M048/S04
- Validation: unmapped
- Notes: Sibling to R051-R056 (Wiggums detects symptoms at runtime); R066 prevents the regression from running at all. Required for safe autonomous M010-level work.

### R067 — Lane Conflict Contract
- Class: capability
- Status: active
- Description: Before any Lane class or LaneScheduler ships, the scheduler must have a concrete written contract specifying: queue eligibility predicate, lock granularity (per-file vs per-table vs per-resource), ownership/handoff model, sealed-input markers (immutable snapshot of dependencies at task start), idempotency rule (re-dispatch must be safe), retry/backoff on conflict, degraded-mode behavior when conflict is detected, and the explicit fallback path. The contract must be testable as a set of assertions that any scheduler implementation passes.
- Why it matters: R057 (system lane) and R046 (multi-unit lanes) currently claim concurrency-safety by hand-waving "isolated surfaces" — but under real autonomous workload, system lane work can race the next unit over runtime/session-derived inputs or DB-backed memory while the unit lane believes it has a stable view. Without a written contract, the lane primitive is a foot-gun layered on top of an already-fragile dispatch.
- Source: codex-adversarial-review-2026-05-17
- Primary owning slice: M033/S00 (new prerequisite slice for the contract spec)
- Supporting slices: M039/S00 (same contract applied to system lane)
- Validation: unmapped
- Notes: Comparison must include the simpler alternative — a serial unit-queue scheduler with per-file locks — to validate that lanes are the right primitive vs premature parallelism. R067 blocks M033 + M039.

### R068 — Always-on per-repo supervisor
- Class: capability
- Status: active
- Description: SF has one real operator server surface: the web/Next.js process, with `sf-server` owning server-side registry/lifecycle support. Repositories do **not** each get their own web server. The server package auto-syncs the web swarm registry from configured scan roots; each repo execution boundary remains its own `sf autonomous` worker with crash-restart + crash-loop backoff. The first Linux implementation target is a user-level systemd unit per repo. `packages/daemon`/`sf-server` is the lifecycle and registry adapter, not a shared multi-repo workflow owner or custom always-on JSON-RPC brain. Each repo's execution stays self-contained.
- Why it matters: Operator wants one web surface for 24/7 visibility without manually-tended watchdogs. Per-repo supervised workers deliver always-on execution with smallest blast radius (one bad repo cannot take down others), standard ops tooling (`systemctl status`, `journalctl`), and no new cross-repo workflow semantics.
- Source: user-direction-2026-05-17, reshaped by codex-adversarial-review-2026-05-17
- Primary owning slice: M053/S10
- Validation: unmapped
- Notes: Was originally specified as a custom `sf serve` JSON-RPC daemon hosting multiple swarms. Codex review flagged the multi-swarm-in-one-daemon design as collapsing blast radius and quietly reintroducing federation; reshaped to one web server plus server-owned registry sync, per-repo supervised worker processes, and read-model projection. Script watchdog/meta-supervisor entrypoints are removed. M028 federation remains the only place for cross-repo coordination.

### R069 — Multi-Swarm Isolation [CANCELLED]
- Class: capability
- Status: cancelled
- Description: [CANCELLED 2026-05-17] Was specified for a custom daemon hosting N swarms. With R068 simplified to per-repo supervision, isolation is automatic — each repo has its own process boundary and `.sf/`. No shared workflow actors, no namespace negotiation. Superseded by R068.
- Source: user-direction-2026-05-17
- Notes: Cross-repo coordination, if ever needed, is M028 federation.

### R070 — Web Multi-Repo Dashboard (read-only status projection)
- Class: capability
- Status: active
- Description: Web `/swarms` route lists all repos from server-maintained `~/.sf/swarms.json` with per-repo *status projection*: active milestone, current unit, last-cycle outcome, queue depth, supervisor-health. `sf-server` auto-refreshes the registry from configured scan roots on startup and interval; operators can force the same refresh with `sf-server --sync-swarms`. State is sourced from a **dedicated, versioned, atomically-written read-model file** at each repo's `.sf/status.projection.json` — written by the repo's own SF process via temp+fsync+rename, schema-validated on read, with a `projectionVersion` field. Web aggregator polls every 5s initially; fs.watch/chokidar can be added after the read model is stable. Drill-in opens the existing per-repo dashboard. **Excluded from this projection** (defer to M028 federation boundary, per codex review 2026-05-17): self-feedback content aggregation, full doctor reports, last-green-ledger details, any cross-repo learnings. Web shows aggregate counts/health flags from the projection; drill-in into a single repo's own dashboard for detail.
- Why it matters: Operator running SF across N repos needs sublinear-attention visibility — one tab to see all swarms at a glance. A dedicated projection file (rather than reading raw mutable `.sf/state.json`/doctor reports) avoids partial-write parsing, schema drift, and cross-repo trust propagation. Per-repo error isolation: a corrupt projection in one repo surfaces as "degraded" status, not a dashboard crash. The projection is one direction (swarm → read-model) and explicitly does NOT cross ADR-012's deferred federation boundary; cross-repo coordination remains M028.
- Source: user-direction-2026-05-17, reshaped by codex-adversarial-review-2026-05-17 (ADR sweep pass)
- Primary owning slice: M053/S11
- Validation: unmapped
- Notes: Atomic-write contract: writers MUST use temp-file + fsync + rename. Readers MUST tolerate ENOENT, schema mismatch, and partial-parse as "degraded/last-known-good" without surfacing partial JSON to the operator. Schema versioned via `projectionVersion: 1` field; readers reject unknown versions cleanly. Provenance: each projection record includes the swarm-id, repo-path, and writer-timestamp. Excludes self-feedback / doctor detail / last-green-ledger aggregation per codex review — those stay per-repo only until M028 federation defines a safe cross-repo channel.

### R071 — A2A primary investment surface [CANCELLED]
- Class: capability
- Status: cancelled
- Description: [CANCELLED 2026-05-17] A2A was not actually a requirement (user direction: "a2a was not requirment just nore i think we have it"). SF-owned A2A code and plan documents were removed from the active tree; the only remaining A2A dependency is transitive inside the Google Gemini CLI provider. Web-first preference for new operator features is an informal convention, not milestone-grade work. If A2A becomes important later, it gets filed as a new R then.
- Source: user-direction-2026-05-17
- Notes: Cancelled-clean. TUI deprioritization remains an informal preference, not a tracked requirement. `sf autonomous` already routes to the non-TUI machine surface internally, so operators should not need to type `headless` for normal autonomous runs; `sf headless ...` remains as a compatibility spelling for explicit machine-surface commands.

### R072 — Status-Completion-Drift Detector (Wiggums extension)
- Class: capability
- Status: active
- Description: Wiggums-family detector for stuck patterns where a task/slice/milestone row's prose fields (`narrative`, `full_summary_md`, `verification_result`) indicate completion but the structured status columns disagree (`status != 'complete'`, `completed_at = NULL`, `task_status != 'done'`). Fires after the same unit-id has been dispatched ≥2 times within a short window with low tool-call counts (≤1 per attempt). Reports the drift as a stuck-pattern self-feedback entry and optionally proposes the canonical-column update.
- Why it matters: Observed 2026-05-17 — M010/S05/T02 ran 4 times across 32 minutes; each attempt did 1 tool call ($0.005–$0.009) because the agent kept reading its own prior narrative saying "already complete". Root cause was schema-drift: completion was recorded in prose fields but not in the canonical status columns the autoLoop reads. R051–R056 Wiggums covers symptoms (repeated dispatch, zero progress) but does not name this specific drift class — the exact case where R066/M048 firewall wants a concrete signature. Without R072, this drift class is invisible until an operator notices.
- Source: user-direction-supervision-2026-05-17
- Primary owning slice: M038/S02 (extension; new sibling slice)
- Supporting slices: M048/S03 (crash-loop classifier consumes R072 as one signal)
- Validation: unmapped
- Notes: Extends R051-R056 Wiggums family. Self-feedback shape: `kind='status-completion-drift'`, `severity='high'`, evidence includes unit-id + retry count + column-vs-prose diff. Pairs with R066 (autonomous regression quarantine). Production implementation should run as part of the periodic doctor sweep (R055-style cadence).

### R073 — Dispatcher honors priority tier
- Class: capability
- Status: active
- Description: Add a `tier` column on milestones (tier INTEGER NOT NULL DEFAULT 5 or similar) and change the autonomous dispatcher's milestone-selection query from `ORDER BY sequence ASC` to `ORDER BY tier ASC, sequence ASC`. PROJECT.md's Priority Tier Order table becomes the source-of-truth projection: each milestone's tier is a typed field readable by both human reviewers and the autonomous planner. When the operator updates the PROJECT.md tier table, a small sync script (or autoLoop sanity check) writes the tier column from the table.
- Why it matters: Observed 2026-05-17 — PROJECT.md Priority Tier Order was added saying M038/M048 are Tier 1, but the canonical DB sequence column was unchanged (M038=seq38, M048=seq48). The dispatcher read sequence ASC and would not pick up Tier 1 milestones for months. SF's stuck-pattern (T02 retry loop) was directly downstream of M038 (Wiggums) being unreachable in the dispatch queue. Without R073, every PROJECT.md tier edit risks the same silent drift between human-readable projection and canonical state.
- Source: user-direction-supervision-2026-05-17
- Primary owning slice: M014/S01
- Supporting slices: M014/S02
- Validation: unmapped
- Notes: Surgical fix already applied 2026-05-17: milestone.sequence rewritten to put M038/M048/M011/etc at seq=1–9. R073 is the proper fix so future tier edits propagate without manual SQL. Implementation: add tier column (default tier=5 for existing milestones), seed Tier 1 = M038/M048/M011, Tier 2 = M030/M034/M019/M025, etc per PROJECT.md table; update dispatcher's ORDER BY. Sync script reads PROJECT.md table on autoLoop startup and reconciles drift back into DB with self-feedback if mismatch detected. Sibling to R018 (project roadmap & state reconciliation, M014).

### R074 — Hard runtime gate on default-on inline-eligible dispatch
- Class: capability
- Status: active
- Description: Until R020 (inline-vs-spawn equivalence proof) AND R066 (autonomous regression quarantine) both pass, the inline dispatch path for inline-eligible unit types (validate-milestone, complete-milestone, reassess-roadmap) MUST refuse default-on routing at runtime. Per-cycle check: if either R020 or R066 is incomplete in the active project, the dispatcher hard-fails any default-on inline route with a clear error, forcing the operator to either complete the gates or set SF_INLINE_DISPATCH=0 explicitly. Operators who explicitly opt in via SF_INLINE_DISPATCH=1 bypass the gate (audited via self-feedback so the bypass is visible).
- Why it matters: Codex adversarial review 2026-05-17 [high] — M010 inline default-on routes validate-milestone/complete-milestone through inline dispatch on EVERY milestone's cycle, including the M038/M048 cycles that come first under the new tier ordering. Sequencing M010 later does not quarantine its risky surface. R020 (equivalence proof) and R066 (regression firewall) are the named safety contract gates; promoting inline to default before they pass means the unproven path runs even during the cycles meant to LAND those gates. A hard runtime gate makes the contradiction non-silent and forces an explicit operator choice.
- Source: codex-adversarial-review-2026-05-17
- Primary owning slice: M048/S05
- Supporting slices: M010/S06
- Validation: unmapped
- Notes: Sibling to R020 (equivalence proof) + R066 (regression quarantine). Implementation hooks the dispatcher check before tryInlineDispatch: query active R020/R066 status; if either incomplete and SF_INLINE_DISPATCH not explicitly "1", throw with structured error. Explicit opt-in records a self-feedback breadcrumb. Test fixtures: R020/R066 incomplete + SF_INLINE_DISPATCH unset rejects default-on; both R020/R066 complete + SF_INLINE_DISPATCH unset allows default-on; SF_INLINE_DISPATCH=1 always allows. Softer fix than reverting the comment+code drift — keeps SF dogfooding inline while making the gate contract executable.

### R075 — Model-Diverse Adversarial Review (autonomous)
- Class: capability
- Status: active
- Description: SF's autonomous loop fires adversarial reviews of its own work using a *different model* than the one that produced the artifact. The model-router picks from the existing catalog (kimi, minimax, deepseek, cogito, gemini, zai, etc.), excluding the worker model and any model in the same vendor family, then prompts it adversarially to challenge the diff or backlog. Findings ingested as self_feedback entries with `kind='adversarial-finding'`, severity, milestone/slice anchor. Cadence: (a) after every slice completion → review the slice diff; (b) after every milestone completion → review the milestone-vs-purpose; (c) on stuck-pattern signal (R051-R056, R072) → targeted review of the stuck unit; (d) weekly tick → ADR-sweep review. Per-cycle budget cap prevents review spending from dominating LLM cost. This is the SF-native version of the operator-facing `/codex:adversarial-review` slash command — codex stays as the operator's tool; SF uses its own model fleet for independence.
- Why it matters: 2026-05-17 dogfood evidence — 4 manual codex adversarial reviews caught 7+ real bugs (A2A truthy gate, inline default-on, T02 status-drift, ADR-020 wire-decision violation, ack-without-deliver, etc.). Every cycle that lands without external eyes codes regressions into the autonomous loop. Model-diversity is the independence mechanism (not protocol coupling): a kimi-built slice reviewed by deepseek-v4 catches blind spots a kimi self-review would miss. Pairs naturally with R066/M048 firewall (high-severity findings trigger quarantine) and the existing self-feedback triage drain. Cost per review (~$0.02-0.10) is well under the cost of one stuck-pattern hour.
- Source: user-direction-supervision-2026-05-17
- Primary owning slice: M039/S06
- Supporting slices: M039/S07, M048/S05
- Validation: unmapped
- Notes: Operator's codex tool stays available unchanged — R075 is the SF-internal capability that runs without operator. Model-router exclusion rule: skip worker model + same vendor family. Self-feedback shape: `kind='adversarial-finding'`, severity from review verdict, occurredIn anchors review target. R066 firewall consumes high-severity findings as a quarantine signal. Budget governance: per-cycle token cap (e.g. 5% of unit token budget) + per-week cap (e.g. 100 reviews) configurable via preferences. System-lane task (R057) so reviews run concurrent with unit execution rather than serial. Sibling to R053 (repeated self-feedback kind detector) — when same finding kind recurs N times, escalates to operator notification.

### R087 — Typed PDD Contracts via engine-types.js
- Class: capability
- Status: active
- Description: Per ADR-0000, the 8 PDD fields (Purpose, Consumer, Contract, FailureBoundary, Evidence, NonGoals, Invariants, Assumptions) and the 7-dimension RunControlPolicy (confidence, risk, reversibility, blastRadius, cost, legal, customerImpact) become TypeScript interfaces in engine-types.js (currently empty stub: export {};). Per ADR-0075, GateResult shape is also exported from engine-types.js. All engine/policy/validator/planner code imports these typed contracts as the canonical leaf-node — turns ADR-0000 prose into compile-time contracts. plan-milestone/plan-slice/plan-task tool handlers validate input against PDDFields; UokGateRunner consumes GateResult; routing policy reads RunControlPolicy.
- Why it matters: ADR-0000 specifies the 8 PDD fields + run-control policy but they exist only in prose. Codex doctrine review 2026-05-17: "PDD eight-field gate not enforced by plan-milestone or plan-slice." R036-R040 (M030) cover data-level anchoring; R087 adds the typed schema fix at the code level. Pairs with R086 (dormant code triage) since engine-types.js IS one of the dormant files M058 wires in.
- Source: codex-doctrine-review-2026-05-17 + supervisor-deep-audit-2026-05-17
- Primary owning slice: M058/S05
- Supporting slices: M030/S01
- Validation: unmapped
- Notes: engine-types.js header explicitly says "All engine/policy interfaces depend on these types." File is purpose-built for this content; stub never filled. M030 R036 purposeAnchor data-level enforcement gets stronger when backed by typed PDDFields. Compile-time at import boundary; runtime at tool input boundary.

### R088 — Pre-Removal Test-Import Safety Gate
- Class: capability
- Status: active
- Description: UokGate per ADR-0075 (type=policy, id=removal-safety) that fires BEFORE any file removal proposed by the autonomous loop or triggered by R086 dormant-code triage. Greps tests/* for static + dynamic-import references; scans file content for external-integration env-var signatures (TWILIO_, ELEVENLABS_, STRIPE_, DISCORD_, SENDGRID_, etc.). Returns outcome=manual-attention with cited test paths or env-var matches when found.
- Why it matters: Operator-supervisor 2026-05-17 removed web/app/api/voice/ that turned out to be Twilio IVR on-call infrastructure with src/tests/integration/web-voice-ivr-contract.test.ts as paired contract test. R086 dormant-code-triage catches after removal; R088 prevents BEFORE. Without R088, autonomous removal repeats the same operator mistake at scale.
- Source: operator-supervisor-mistake-recovered-2026-05-17
- Primary owning slice: M058/S12
- Supporting slices: M048/S05
- Validation: unmapped
- Notes: Sibling to R082 (drift detector UokGate). M048/S05 (smoke-fixture + last-green ledger) catches destructive autonomous changes in general; R088 specifically guards removal-safety.

### R089 — Migrate Voice IVR On-Call Infra to Central Cloud Ops
- Class: capability
- Status: active
- Description: Migrate web/app/api/voice/ + voice/prompt/ routes + src/tests/integration/web-voice-ivr-contract.test.ts (Twilio IVR + ElevenLabs voice synthesis + on-call paging) out of SF to central cloud-ops repo/service. SF is per-project autonomous compiler (ADR-0000); operator-paging infra is org-level. Migrate first, verify cloud-ops endpoint, then remove from SF (gated by R088 pre-removal safety gate).
- Why it matters: Current placement in SF is historical drift. Voice infrastructure does not belong in a per-project compiler codebase. Migration to the right home + removal from SF is the correct fix.
- Source: operator-direction-2026-05-17
- Primary owning slice: M058/S13
- Supporting slices: M058/S14
- Validation: unmapped
- Notes: Files: web/app/api/voice/route.ts, web/app/api/voice/prompt/route.ts, src/tests/integration/web-voice-ivr-contract.test.ts, related env-var docs (TWILIO_, ELEVENLABS_). Removal from SF gated by R089/S13 (migration done) AND R088 (pre-removal safety gate green).

### R090 — Planning-Execute Lane Split
- Class: capability
- Status: active
- Description: Two concurrent lanes in the autonomous dispatcher: PLANNING (discuss/plan/research/reassess for ANY milestone, writes only to .sf/+DB, file-conflict-free) and EXECUTE (execute-task + complete-*, exclusive worktree, N=1). Planning queue stays ahead so execute never waits on plan gating. Subset of R046 (full multi-unit lanes via M033); much smaller blast radius, ships much earlier.
- Why it matters: Today execute waits ~10-20 min for next plan-slice to finish before starting next execute. Splitting lanes ~2x throughput on planning-heavy slices. Safer than R046 full parallel; only planning runs concurrent with execute, never two executes.
- Source: operator-direction-2026-05-17
- Primary owning slice: M059/S01
- Supporting slices: M059/S02, M059/S03
- Validation: unmapped
- Notes: Planning writes bounded to .sf/{sf.db, runtime/, milestones/}; never source. Permission-profile enforces. R082 drift detector watches the boundary. M033/R046 eventually generalizes; R090 is the safe near-term cut.

### R091 — Triage Sidecar
- Class: architecture
- Status: active
- Description: Decouple self-feedback triage from main autonomous loop entry. Today every watchdog cycle runs handleTriage(max=5) at headless.ts before dispatch, gating milestone/slice work behind ~8min triage. Required: triage runs as its own scheduled sidecar (every Nth cycle, time-based, or operator-explicit), NOT as first action of every cycle. Main autonomous loop entry goes directly to plan/research/execute dispatch.
- Why it matters: Operator 2026-05-17 "but triage is mot for milestone wtc". Every cycle today burns ~8min in triage-decider+review-code before touching milestone work; last task completion 09:13Z, 4 cycles since produced planning but zero execute-task progress because each cycle's first 8min are self-feedback triage. Triage is self-feedback drain, not gating for milestone delivery.
- Source: operator-direction-2026-05-17
- Primary owning slice: M058
- Supporting slices:
- Validation: unmapped
- Notes: Near-term mitigation landed 2026-05-17 in src/headless.ts: 30-min time gate (SF_TRIAGE_INTERVAL_MS) + max raised to 25 (SF_TRIAGE_MAX). Full decoupling pending. Pairs with R090 lane split: triage becomes a third lane (or sidecar) outside planning+execute.

### R092 — PDD-Completeness as Routing Signal
- Class: capability
- Status: active
- Description: Use PDD completeness (ADR-0000 8 fields: purpose, consumer, contract, failureBoundary, evidence, nonGoals, invariants, assumptions) as routing signal for execute-task. Fully-populated PDD → LIGHT coding tier (compose-only). Partial → SMART. Missing critical fields → refuse dispatch and emit drift via R082 gate.
- Why it matters: Operator 2026-05-17 "also less thinkimg on code if spec ia good?" — if the spec is well-defined the implementer does not need a deep-reasoning model to figure out what to build. defaultModelModeForWorkMode today returns "smart" regardless of PDD quality; this makes PDD completeness a first-class routing signal. Saves provider capacity, reduces execute-task latency on well-spec'd work.
- Source: operator-direction-2026-05-17
- Primary owning slice: M058
- Supporting slices:
- Validation: unmapped
- Notes: Implementation: extend operating-model.js defaultModelModeForWorkMode to accept ctx with PDD field count. Wire counter at task-fetch time in unit-context-manifest. R087 engine-types.js validatePDDFields(input) is the gate — already landed.

### R093 — Pin Model per Orchestration Agent
- Class: config
- Status: active
- Description: Pin model in agent.yaml for orchestration agents. triage-decider and review-code currently fall through to system default (kimi-coding, coding-specialized, slow for non-coding reasoning). Both are READ-ONLY structured-output reasoning tasks, not coding. Add model: (or modelMode:) field so triage-decider routes to fast structured-output model and review-code routes to strong reasoning model.
- Why it matters: Operator 2026-05-17 "why dont we use modwls for coding an not the orchestrator loop for all". Orchestration agents should not consume coding-tier capacity. Today triage-decider runs 6-8min per call on kimi-coding; review-code likely the same. Proper routing could land in 1-2min, freeing coding capacity for execute-task.
- Source: operator-direction-2026-05-17
- Primary owning slice: M058
- Supporting slices:
- Validation: unmapped
- Notes: Specific model choice deferred to model-learner (feedback_let_learner_demote.md). Just ensure agent.yaml pins NON-coding mode. Other orchestration agents (planner, researcher, synthesizer) audited at same time.

### R094 — Swarm-Role Model Tier Specialization
- Class: capability
- Status: active
- Description: In swarm topology (swarm-dispatch.js, swarm-roles.js, persistent-agent.js), each role gets pinned model tier matched to its job: COORDINATOR/planner/researcher/synthesizer = deep reasoning; REVIEWER/critic/triage = fast structured-output (or strong reasoning for review-code); CODER/executor/repair-worker = light coding tier — receives well-spec'd work from coordinator, just composes. Coordinator owns the thinking; coders execute. Pin via agent.yaml at role-definition time, not per-call.
- Why it matters: Operator 2026-05-17 "if in swarm then coordinator can run coders with less thinking" — generalizes R092 (PDD-completeness) and R093 (per-agent pinning) to swarm topology. Coordinator→coder handoff IS the spec-population step; coders always receive PDD-complete work, can default light without per-call inspection. Frees deep-reasoning capacity for actual reasoning instead of routine code composition.
- Source: operator-direction-2026-05-17
- Primary owning slice: M058
- Supporting slices:
- Validation: unmapped
- Notes: Today swarm-dispatch._busDispatch enqueues but no runner consumes (R084 covers the runner). Wire R094 alongside R084: runner reads agent.model from role-defined yaml at construction. Until then design-only.
