Two reviewer-reliability tracks, both deterministic and unit-verified (no model/network).
§B-§5 — static_check stage seam. The StaticAnalysisRunner + StaticFindingsRecordedEvent +
reviewer-context filter already existed; what was missing was a stage that runs the tool and
emits the event. Added:
- StaticCheckStageExecutor: reads stage metadata (static_tool/static_argv), runs the configured
command via StaticAnalysisRunner, returns a StaticFindingsRecordedEvent. No-op (empty findings)
when no runner is wired or no command is configured — safe to carry unconfigured.
- A deterministic-stage seam in DefaultSessionOrchestrator.enterStage: any stage with
metadata["stage_type"] == "static_check" is run by the executor instead of the LLM subagent,
then advances on its (unconditional) exit edge.
- TomlWorkflowLoader: stage_type/static_tool/static_argv fields → StageConfig.metadata.
- role_pipeline.toml: a static_check stage between implementer and reviewer (no-op until
static_argv + a CommandRunner are set; activation is live-QA-gated, see StaticAnalysisRunner doc).
§B-§6 — critique-outcome producer. The CritiqueFinding type + CriticCalibrationProjection existed
but nothing fed them. Added:
- CritiqueFindingsRecordedEvent (+ CritiqueVerdict): the producing side — a critic's findings +
verdict for one review iteration, carrying modelHash for per-model calibration.
- CritiqueOutcomeCorrelator: pure loop-resolution logic deciding UPHELD (fixed between rounds) /
DISMISSED (persisted into an approved final) / INCONCLUSIVE (open at a non-approved terminal),
per critic (role + modelHash) and per finding id.
- A hook in completeWorkflow/failWorkflow that correlates recorded findings into
CritiqueOutcomeCorrelatedEvents at loop resolution — no-op when none recorded, idempotent.
(LLM-side finding emission stays a separate model-gated activation.)
Tests: StaticCheckStageExecutorTest (4), StaticCheckStageTest integration (1, fake runner →
event + transition), CritiqueOutcomeCorrelatorTest (8), CritiqueCalibrationWiringTest integration
(1, seeded findings → outcomes at completion), updated RolePipelineWorkflowTest. Full suites for
core:events/kernel/critique, infrastructure:workflow, testing:integration green; detekt clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The reviewer prompt told the model to judge "the analysis requirements," but
the reviewer stage only needed [patch, impl_plan] — it never actually received
the analysis artifact, so the acceptance criteria it was meant to check against
were never in its context. A reliability gate checking against criteria it
can't see is theater.
- reviewer now needs `analysis`, so the diff is judged against concrete,
pre-stated acceptance criteria (§5 narrow question) rather than whole files
against taste; the §3 minItems enforcement guarantees those criteria are
non-empty.
- reviewer.md operationalizes §5's two principles: evaluate the diff against
each requirement (findings must map to a requirement/plan-step/defect), and
don't re-report what deterministic tools already catch (compiler/detekt/tests
— trust verification, point at failures, don't re-derive them).
This is the workflow-level realization of §5. The full static-first
infrastructure (running static tools as a pipeline step and mechanically
excluding their findings from reviewer context) needs a static-findings event
representation and is deferred. §6 critic calibration needs runtime history
before it can say anything (spec ordering #5).
Local analysts hallucinate file paths the same as planners. A stage can now
opt in with `ground_references = true`: after it produces its brief, every
workspace-relative file path the brief named is checked for existence, and a
path that doesn't exist fails the stage (retryable) with the misses fed back
("Reference only files you have read") so the model re-grounds.
Grounding probes the filesystem directly rather than the repo map — the map
is recency-ranked and depth/extension-limited, not a complete existence
oracle, so "absent from the map" can't be a hard failure. Existence is a
nondeterministic observation, so the result is recorded as a
BriefGroundingCheckedEvent (invariant #9); replay reads it back and never
re-probes.
- BriefGroundingCheckedEvent + GroundingReference, registered in eventModule
- BriefReferenceExtractor: pure, deterministic pull of relative file-path
references from a brief artifact JSON (needs a '/' and a dotted extension;
skips modules, bare names, URLs, absolute/parent paths — so coarse
"subsystem" mentions the brief is allowed to make aren't false-flagged)
- SessionOrchestrator.groundBriefReferences on the post-verifyProduces path,
gated by stage metadata; uses the existing worldProbe
- TomlWorkflowLoader `ground_references` → metadata; role_pipeline analyst
opts in
Scoped to file references with a '/' + extension (not bare basenames, which
a depth-limited probe can't resolve), so a flagged miss is high-confidence.
The symbol-grounding half of §3 is left for a real symbol index.
Scope creep — an implementer writing files it never declared — is the
dominant local-model failure that compiler/tests don't catch. A stage can
now declare a `writes` manifest (workspace-relative globs); a FILE_WRITE
that resolves inside the workspace but outside the declared set is raised
as PATH_OUTSIDE_MANIFEST → BLOCK, so the model gets the violation as tool
feedback and retries within scope (the §2 bounded-retry signal).
Implemented as a plane-2 rule beside PathContainmentRule (same effect-based
dispatch on FILE_WRITE, same symlink-safe WorldProbe resolution, same
recorded observations so replay reads them back — invariant #9). An empty
manifest is unrestricted; out-of-workspace targets stay PathContainmentRule's
concern, so the two rules don't double-flag.
- StageConfig.writeManifest + TomlWorkflowLoader `writes` parsing
- ToolCallAssessmentInput.writeManifest, threaded from the active stage via
SessionOrchestrator.runPlane2Assessment
- new ManifestContainmentRule, registered in the server assessor
- shared candidatePathStrings extracted so both path rules judge the same
target set
- role_pipeline.toml documents the option on the implementer stage
The dedicated ManifestViolationEvent the spec names is not added: the
violation is already recorded replayably in ToolCallAssessedEvent (issue +
observations + BLOCK) and surfaced in the TUI rationale band. A first-class
event is worth adding only once reconciliation consumes it.