From 1913793658ae110a3240692b8744daec906dfc12 Mon Sep 17 00:00:00 2001 From: tegwick Date: Fri, 15 May 2026 16:21:19 +0200 Subject: [PATCH] Record quality gate audit decisions --- src/repo_registry/core/service.py | 31 +++++++++++++++++++ tests/test_quality_gates.py | 26 ++++++++++++++++ ...-0014-agentic-characteristic-acceptance.md | 8 +++-- 3 files changed, 62 insertions(+), 3 deletions(-) diff --git a/src/repo_registry/core/service.py b/src/repo_registry/core/service.py index 4de935d..efa65ee 100644 --- a/src/repo_registry/core/service.py +++ b/src/repo_registry/core/service.py @@ -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, diff --git a/tests/test_quality_gates.py b/tests/test_quality_gates.py index a14198b..f6b5586 100644 --- a/tests/test_quality_gates.py +++ b/tests/test_quality_gates.py @@ -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 == [] diff --git a/workplans/RREG-WP-0014-agentic-characteristic-acceptance.md b/workplans/RREG-WP-0014-agentic-characteristic-acceptance.md index 4b9c10d..1e8e3fe 100644 --- a/workplans/RREG-WP-0014-agentic-characteristic-acceptance.md +++ b/workplans/RREG-WP-0014-agentic-characteristic-acceptance.md @@ -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