Skip to content

Commit 3c739fb

Browse files
authored
Merge pull request #12 from SharedIntellect/feature/wire-tester-phase3
v0.7.0: Wire TesterCritic into pipeline as Phase 3 (DEC-020)
2 parents e310767 + 53379bb commit 3c739fb

12 files changed

Lines changed: 655 additions & 18 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@ ports/
33
__pycache__/
44
.pytest_cache/
55
.hypothesis/
6+
.DS_Store
7+
quorum-runs/

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<p align="center">
66
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-2ba4c8.svg" alt="MIT License"></a>
77
<img src="https://img.shields.io/badge/platform-OpenClaw-2ba4c8" alt="Platform: OpenClaw">
8-
<img src="https://img.shields.io/badge/status-v0.6.0-2ba4c8" alt="Status: v0.6.0">
8+
<img src="https://img.shields.io/badge/status-v0.7.0-2ba4c8" alt="Status: v0.7.0">
99
<a href="https://clawhub.ai/dacervera/quorum"><img src="https://img.shields.io/badge/ClawHub-dacervera%2Fquorum-2ba4c8" alt="Available on ClawHub"></a>
1010
</p>
1111

critic-status.yaml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# Used by tools/validate-docs.py to detect stale documentation.
33
# Update this file FIRST when shipping a critic or feature.
44

5-
version: "0.6.1"
5+
version: "0.7.0"
66
spec_version: "3.0"
77

88
critics:
@@ -34,10 +34,13 @@ critics:
3434
note: "Activated with --relationships flag; not part of default depth panel"
3535
tester:
3636
status: shipped
37-
callable: false
37+
callable: true
3838
since: "0.6.0"
3939
description: "Finding verification — L1 deterministic + L2 LLM claim checking"
40-
note: "Code and tests shipped; not yet wired into pipeline (not in CRITIC_REGISTRY or depth configs)"
40+
note: "Phase 3 in pipeline. L1 at standard depth, L1+L2 at thorough. Quick: skipped."
41+
depth_configs:
42+
- standard
43+
- thorough
4144
architecture:
4245
status: planned
4346
callable: false

docs/CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,23 @@ All notable changes to Quorum will be documented in this file.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
66

7+
## [0.7.0] — 2026-03-12
8+
9+
### Added
10+
- **TesterCritic wired into pipeline as Phase 3** (DEC-020). The Tester verifies other critics' findings after Phase 1 (single-file critics) and Phase 2 (cross-consistency), before the Aggregator produces the verdict.
11+
- **L1 contradictions auto-excluded**: If a critic cites a file/line that doesn't exist or content that doesn't match, the finding is silently removed from verdict calculation. Preserved in tester output for audit.
12+
- **L2 contradictions annotated**: If the Tester's LLM judges a claim unsupported by evidence, the finding gets a `[L2-CONTRADICTED]` annotation but remains in the verdict. Human decides.
13+
- **Depth-based activation**: Quick = skip, Standard = L1 only (zero API cost), Thorough = L1 + L2.
14+
- **Tester stats in run manifest**: `verified`, `unverified`, `contradicted`, `verification_rate`, `runtime_ms`.
15+
- **Verification section in report.md** when tester results are present: table of verified/unverified/contradicted counts with per-finding exclusion details.
16+
- 12 new integration tests (`test_tester_pipeline_integration.py`). Total: 879 passed, 6 skipped.
17+
18+
### Changed
19+
- `AggregatorAgent.run()` accepts optional `tester_result` parameter. Applies DEC-020 policy before deduplication.
20+
- `AggregatedReport` model gains `tester_result` and `l1_excluded_count` fields.
21+
- Verdict reasoning now includes excluded finding count when tester ran.
22+
- Removed unnecessary `asyncio.CancelledError` isinstance guards (CancelledError is BaseException in Python 3.9+, never caught by `except Exception`).
23+
724
## [0.6.1] — 2026-03-11
825

926
### Changed

reference-implementation/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "quorum-validator"
7-
version = "0.6.1"
7+
version = "0.7.0"
88
description = "Multi-critic quality validation for agents, research, and configurations"
99
authors = [
1010
{name = "Daniel Cervera", email = "daniel@sharedintellect.com"},

reference-implementation/quorum/agents/aggregator.py

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@
2828
CriticResult,
2929
Finding,
3030
Severity,
31+
TesterResult,
3132
Verdict,
3233
VerdictStatus,
34+
VerificationStatus,
3335
)
3436
from quorum.providers.base import BaseProvider
3537

@@ -52,17 +54,26 @@ def __init__(self, provider: BaseProvider, config: QuorumConfig):
5254
self.provider = provider
5355
self.config = config
5456

55-
def run(self, critic_results: list[CriticResult]) -> Verdict:
57+
def run(self, critic_results: list[CriticResult], tester_result: TesterResult | None = None) -> Verdict:
5658
"""
5759
Main entry point.
5860
5961
Args:
6062
critic_results: List of CriticResult from each critic
63+
tester_result: Optional TesterResult from Phase 3 verification
6164
6265
Returns:
6366
Final Verdict with AggregatedReport attached
6467
"""
6568
all_findings = self._collect_findings(critic_results)
69+
70+
# DEC-020: Apply tester results before deduplication
71+
l1_excluded_count = 0
72+
if tester_result is not None:
73+
all_findings, l1_excluded_count = self._apply_tester_results(
74+
all_findings, tester_result,
75+
)
76+
6677
deduped_findings, conflicts_resolved = self._deduplicate(all_findings)
6778
confidence = self._calculate_confidence(critic_results, deduped_findings)
6879

@@ -71,16 +82,72 @@ def run(self, critic_results: list[CriticResult]) -> Verdict:
7182
confidence=confidence,
7283
conflicts_resolved=conflicts_resolved,
7384
critic_results=critic_results,
85+
tester_result=tester_result,
86+
l1_excluded_count=l1_excluded_count,
7487
)
7588

76-
verdict = self._assign_verdict(report)
89+
verdict = self._assign_verdict(report, l1_excluded_count=l1_excluded_count)
7790
logger.info(
7891
"Aggregator: %d findings → %d deduped → verdict=%s (confidence=%.2f)",
7992
len(all_findings), len(deduped_findings), verdict.status.value, confidence,
8093
)
94+
if l1_excluded_count > 0:
95+
logger.info(
96+
"Aggregator: %d finding(s) excluded by Tester (L1 contradicted)",
97+
l1_excluded_count,
98+
)
8199

82100
return verdict
83101

102+
def _apply_tester_results(
103+
self,
104+
findings: list[Finding],
105+
tester_result: TesterResult,
106+
) -> tuple[list[Finding], int]:
107+
"""Apply DEC-020: auto-exclude L1 contradictions, annotate L2 contradictions.
108+
109+
Returns (filtered_findings, l1_excluded_count).
110+
"""
111+
# Build mapping: finding_id → VerificationResult
112+
verification_map = {
113+
vr.original_finding_id: vr
114+
for vr in tester_result.verification_results
115+
}
116+
117+
filtered: list[Finding] = []
118+
l1_excluded = 0
119+
120+
for finding in findings:
121+
vr = verification_map.get(finding.id)
122+
if vr is None:
123+
filtered.append(finding)
124+
continue
125+
126+
if vr.status == VerificationStatus.CONTRADICTED and vr.level == 1:
127+
# L1 CONTRADICTED: auto-exclude from verdict
128+
l1_excluded += 1
129+
logger.debug(
130+
"DEC-020: Excluding finding %s (L1 contradicted): %s",
131+
finding.id, vr.explanation,
132+
)
133+
continue
134+
135+
if vr.status == VerificationStatus.CONTRADICTED and vr.level == 2:
136+
# L2 CONTRADICTED: annotate only, keep in verdict
137+
annotated = finding.model_copy(
138+
update={"description": f"[L2-CONTRADICTED] {finding.description}"}
139+
)
140+
filtered.append(annotated)
141+
logger.debug(
142+
"DEC-020: Annotating finding %s (L2 contradicted): %s",
143+
finding.id, vr.explanation,
144+
)
145+
continue
146+
147+
filtered.append(finding)
148+
149+
return filtered, l1_excluded
150+
84151
def _collect_findings(self, results: list[CriticResult]) -> list[Finding]:
85152
"""Flatten all findings from all critics into a single list."""
86153
findings = []
@@ -168,7 +235,7 @@ def _calculate_confidence(
168235

169236
return round(evaluated_criteria / total_criteria, 3)
170237

171-
def _assign_verdict(self, report: AggregatedReport) -> Verdict:
238+
def _assign_verdict(self, report: AggregatedReport, l1_excluded_count: int = 0) -> Verdict:
172239
"""
173240
Deterministic verdict assignment based on findings severity.
174241
@@ -222,6 +289,9 @@ def _assign_verdict(self, report: AggregatedReport) -> Verdict:
222289
if counts:
223290
reasoning += f" Issues: {', '.join(counts)}."
224291

292+
if l1_excluded_count > 0:
293+
reasoning += f" {l1_excluded_count} finding(s) excluded by Tester (L1 contradicted)."
294+
225295
return Verdict(
226296
status=status,
227297
reasoning=reasoning,

reference-implementation/quorum/configs/quick.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Quick depth profile — fast feedback for low-stakes validation
22
# Critics: correctness + completeness (2 critics, 0 fix loops)
3+
# Tester: skipped at quick depth
34
# Use for: draft review, pre-commit checks, iterative development
45

56
critics:

reference-implementation/quorum/configs/standard.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Standard depth profile — balanced rigor for most work
22
# Critics: correctness + completeness + security (3 critics)
3+
# Tester: L1 (deterministic) verification active — zero API cost
34
# Use for: PR reviews, config changes, research submissions
45

56
critics:

reference-implementation/quorum/configs/thorough.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Thorough depth profile — maximum rigor for critical decisions
22
# Critics: all 4 implemented critics (correctness, completeness, security, code_hygiene)
3+
# Tester: L1 + L2 (LLM claim check) verification active
34
# Use for: production releases, security review, compliance artifacts
45
# Add --relationships for cross-artifact consistency (Phase 2)
56

reference-implementation/quorum/models.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@ class AggregatedReport(BaseModel):
125125
confidence: float = Field(ge=0.0, le=1.0)
126126
conflicts_resolved: int = Field(default=0)
127127
critic_results: list[CriticResult] = Field(default_factory=list)
128+
tester_result: Optional["TesterResult"] = None
129+
l1_excluded_count: int = Field(default=0, description="Findings excluded by Tester L1 contradictions")
128130

129131
@property
130132
def critical_count(self) -> int:

0 commit comments

Comments
 (0)