Record quality gate audit decisions

This commit is contained in:
2026-05-15 16:21:19 +02:00
parent a4b39b3cb7
commit 1913793658
3 changed files with 62 additions and 3 deletions

View File

@@ -235,6 +235,16 @@ class RegistryService:
candidate_source = "deterministic"
candidates = normalize_candidate_drafts(candidates)
self.store.replace_candidate_graph(repository_id, completed_run.id, candidates)
gate_outcomes = evaluate_candidate_graph_quality(
self.store.get_candidate_graph(repository_id, completed_run.id)
)
if gate_outcomes:
self.store.create_review_decision(
repository_id,
completed_run.id,
action="quality_gate_evaluation",
notes=self._quality_gate_review_notes(gate_outcomes),
)
if "llm" in candidate_source:
log_operation(
"llm_extraction_used",
@@ -282,6 +292,27 @@ class RegistryService:
facts=facts,
)
def _quality_gate_review_notes(
self,
gate_outcomes,
) -> str:
criteria = ", ".join(
sorted({outcome.criterion_id for outcome in gate_outcomes})
)
outcome_counts: dict[str, int] = {}
for outcome in gate_outcomes:
outcome_counts[outcome.outcome] = outcome_counts.get(outcome.outcome, 0) + 1
outcomes = ", ".join(
f"{name}:{count}" for name, count in sorted(outcome_counts.items())
)
return (
f"criteria_version={active_quality_criteria_version()}; "
f"criteria={criteria}; outcomes={outcomes}; "
f"quality_gate_outcomes={len(gate_outcomes)}; "
"rationale=Deterministic quality gates flagged candidates for "
"review without approving registry truth."
)
def _generate_candidates(
self,
repository: Repository,

View File

@@ -138,3 +138,29 @@ def test_legacy_trusted_auto_approval_skips_quality_gate_blocked_capability(tmp_
assert safe is False
assert "quality gates require review" in reason
assert "RREG-QC-002" in reason
def test_analysis_records_deterministic_gate_review_decision(tmp_path):
source = tmp_path / "provider-repo"
source.mkdir()
(source / "README.md").write_text("# Provider Repo\n", encoding="utf-8")
(source / "providers.py").write_text(
"provider_registry = {'openrouter': OpenRouterAdapter}\n",
encoding="utf-8",
)
store = RegistryStore(tmp_path / "registry.sqlite3")
store.initialize()
service = RegistryService(store, ingestion=GitIngestionService(tmp_path / "checkouts"))
repository = service.register_repository(name="Provider Repo", url=str(source))
summary = service.analyze_repository(repository.id, use_llm_assistance=False)
decisions = service.list_review_decisions(repository.id, summary.analysis_run.id)
gate_decision = next(
decision for decision in decisions if decision.action == "quality_gate_evaluation"
)
assert gate_decision.reviewer_type == "deterministic-gate"
assert "RREG-QC-002" in gate_decision.criterion_ids
assert gate_decision.criteria_version == "repo-scoping-quality-criteria/v1"
assert "without approving registry truth" in gate_decision.rationale
assert service.ability_map(repository.id).abilities == []

View File

@@ -175,7 +175,7 @@ validated for rationale, criteria IDs, and evidence refs before being applied.
```task
id: RREG-WP-0014-T05
status: in_progress
status: done
priority: high
state_hub_task_id: "0d12559a-831e-40ff-bf82-85f45b763f07"
```
@@ -193,11 +193,13 @@ Acceptance criteria:
edits/relinks".
- Existing decisions remain readable through a migration or compatibility view.
Implementation note 2026-05-15: started the audit-trail compatibility view by
Implementation note 2026-05-15: added the audit-trail compatibility view by
enriching listed review decisions with derived reviewer type, reviewer identity,
policy version, criteria version, rationale, criterion IDs, evidence refs,
accepted-after-edits marker, and decision kind. Existing stored decisions still
use the same table and remain readable.
use the same table and remain readable. Deterministic gate evaluations now
record aggregate `quality_gate_evaluation` review decisions with criteria IDs,
outcome counts, criteria version, and a rationale that no approval was applied.
## T06: Add Human Override And Criteria Refinement Flow