Implementation companion to phase4-pipeline-review.md. Reflects
the approved architecture decisions and what shipped in src/veritas/pipeline/.
Date: 2026-06-23.
The pipeline orchestrates the components from Phases 0–3 into one lifecycle. It adds no new
judgement — it routes, escalates, proposes, and rolls up, with the BudgetGuard as the only
throttle. Everything is replay-backed and deterministic for tests.
Dataset → Parser → RuleEngine → RoutingDecision → EscalationRouter → LLMJudge → RemediationProposal → EventOutcome
| # | Decision | Where |
|---|---|---|
| 1 | Rules FAIL → QUARANTINE, no LLM spend | DefaultRoutingPolicy → RouteAction.QUARANTINE; runner skips the router |
| 2 | Rules REVIEW → escalate to LLM | policy → ESCALATE with escalation_checks |
| 3 | Rules CLEAN → 20% sampling (configurable) | DefaultRoutingPolicy(clean_sample_rate=…); ACCEPT otherwise |
| 4 | Escalate only the uncertain check | TieredEscalationRouter: per-check Haiku→Sonnet, never re-runs the event |
| 5 | Proposal only, never auto-apply | HeuristicRemediator → RemediationProposal(auto_applicable=False) |
| 6 | Bounded async, configurable, shared budget, no scheduler | PipelineRunner.run windowed pool + shared BudgetGuard |
Note vs the design review: the doc anticipated an AUTO_REMEDIABLE terminal state. Decision 5
(never auto-apply) makes that misleading, so FinalStatus is just CLEAN / QUARANTINED / REVIEW,
with a RemediationProposal attached whenever any check failed.
clean_sample_rate selects events by a stable hash of event_id
(sha256(event_id)[:8] / 0xFFFFFFFF < rate). The same event always lands the same way, so the
20% sample is idempotent across re-runs — a precondition for resumability and for the storage
upsert (Phase 5). No random anywhere.
TieredEscalationRouter holds, per check, a CheckJudges(primary, escalation). For each check in
the decision: run primary (cheap tier, e.g. Haiku); if it returns uncertain and an escalation
judge exists, re-run only that check on the expensive tier (Sonnet) and treat that verdict as
authoritative. EscalationResult.escalated_checks records which escalated; cost_usd includes the
intermediate (escalated-away) call even though only the authoritative verdict is kept. Any judge
implementing the LLMJudge protocol slots in — so ReplayJudge makes the whole router offline.
Mirrors README §5 precedence, and respects rule uncertainty even when the LLM passes:
parse_error→QUARANTINED.- rule
QUARANTINED→QUARANTINED(no LLM ran). - any LLM
FAILwithconfidence ≥ llm_fail_min_confidence→QUARANTINED(+ proposal). - rule
REVIEW, orbudget_exhausted, or a stageerror→REVIEW. - any LLM non-
PASS(uncertain / low-conf fail) →REVIEW. - otherwise →
CLEAN.
So a rule-REVIEW event whose semantic judge passes still finalizes as REVIEW — the rule's
uncertainty is a different dimension and is not silently overridden.
- One shared
BudgetGuard. The router callsensure_available()before every judge call; onBudgetExceededErrorit stops, setsbudget_exhausted=True, and returns partial results → the runner finalizes those events toREVIEW. The run completes and degrades, never crashes. - The runner wraps escalation per event (
try/except): one bad event setserrorand routes toREVIEWrather than killing the stream. ParseErrors in the input stream becomeQUARANTINEDoutcomes (structural).
PipelineRunner.run(items) is a windowed async pool: it keeps at most max_concurrency tasks in
flight (bounding both concurrency and live task count), yielding outcomes in completion order
(events are independent). No external scheduler; the BudgetGuard is the cost throttle.
RoutingDecision, EscalationResult, RemediationProposal, EventOutcome (Pydantic, extra=forbid)
and the RoutingPolicy / EscalationRouter / Remediator / VerdictSink / PipelineTraceSink
Protocols. Every stage consumes and returns these or earlier-phase types (ResolvedEvent,
RuleReport, Verdict). PipelineRunner takes all collaborators by injection.
- RuleEngine (rules/) — the gate;
RuleReport.statusdrives routing. - LLMJudge (judges/) — primary/escalation judges;
ReplayJudgefor offline runs. - PromptRegistry (prompt_registry/) —
build_default_escalation_routerconstructsAnthropicJudges from the registry (semantic/source on Haiku+Sonnet, entity on Sonnet). - BudgetGuard (llm_gateway/) — shared throttle across the run.
VerdictSink (storage upsert by (event_id, check, prompt_version)) and PipelineTraceSink
(one trace row per outcome) are optional injected Protocols with no-op-by-absence behavior — the
runner calls them only when provided. Storage, monitoring, and the dashboard land in Phase 5+ with
no rework to the runner.
Routing (quarantine/review/accept/escalate + deterministic sampling), tiered escalation (uncertain→escalate, confident→no escalation, budget-exhausted degrade), remediation (proposal for fail, none otherwise), and runner end-to-end (clean-pass, high-conf-fail→quarantine +proposal, rule-quarantine skips LLM, rule-review persists despite LLM pass, accept path, budget-exhausted→review, bounded-concurrency stream, parse-error handling). All replay-backed.