Files
breakpilot-compliance/backend-compliance/compliance/services/checkers/router.py
T
Benjamin Admin 3e3644f83d feat(checkers): platform router + Haiku sufficiency tier; cookie is first consumer
Generalise "Embedding finds, Claude decides" into the shared Pruefer-Library:
- router.route_and_check dispatches control -> sensor_classification -> Checker.
- build_spec reads sensor_classification (CONTENT/LLM -> judge=haiku, the
  validated sufficiency tier; the Qwen-first cascade is disproven for sufficiency).
- LLMChecker gains a Haiku-direct tier (reuses the validated deep_check prompt).
- Cookie Layer-3 now routes through route_and_check instead of bespoke code, so
  cookie is the first real router consumer -- proves the architecture end-to-end.

Reproduces the validated result via the shared path: FN 159->14, recall
0.13->0.92, precision 0.89 (vs bespoke 12/0.93/0.90 -- within Haiku noise).
Tests: 10/10 (router dispatch + build_spec + haiku tier + cookie rewire).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-22 17:37:48 +02:00

69 lines
2.5 KiB
Python

"""Prüfer-Router — method-agnostischer Dispatch.
control → sensor_classification (verification_method + decision_method) → Checker.
Ein neues Modul liefert nur ControlSpecs; der Router wählt den Prüfer. Damit wird
der „Embedding findet, Claude entscheidet"-Pfad EIN gemeinsamer CONTENT/LLM-Prüfer
statt Cookie-Sonderlogik. Nicht-gebaute Prüfer (PLAYWRIGHT/AUDIT/SCANNER/REGEX-
FIELD) → present=None (fail-safe: Aufrufer behält sein deterministisches Ergebnis).
"""
from __future__ import annotations
from typing import Any, Optional
from .base import CheckResult, ControlSpec, DecisionMethod, DocContext
from .embedding_checker import EmbeddingChecker
from .llm_checker import LLMChecker
from .reference_checker import ReferenceChecker
_LLM = LLMChecker()
_EMB = EmbeddingChecker()
_REF = ReferenceChecker()
# decision_method → Checker. Fehlende Mechanismen bewusst None (noch nicht gebaut).
_BY_DECISION: dict[str, Any] = {
DecisionMethod.LLM: _LLM,
DecisionMethod.EMBEDDING: _EMB,
DecisionMethod.LINK_RESOLVER: _REF,
}
async def route_and_check(ctrl: ControlSpec, doc: DocContext) -> CheckResult:
checker = _BY_DECISION.get((ctrl.decision_method or "").upper())
if checker is None:
return CheckResult(present=None,
source=f"no_checker:{ctrl.decision_method}")
return await checker.check(ctrl, doc)
def build_spec(
control_id: str,
sensor_classification: Optional[dict[str, Any]],
*,
label: str = "",
criteria: Optional[list] = None,
question: str = "",
patterns: Optional[list[str]] = None,
embed_threshold: Optional[float] = None,
) -> ControlSpec:
"""Baut ein ControlSpec aus der GESPEICHERTEN sensor_classification
(canonical_controls.generation_metadata.sensor_classification) + den
Control-Kriterien. CONTENT/LLM → judge='haiku' (validierter Sufficiency-
Judge; Default für Sufficiency lt. Entscheidung 2026-06-22)."""
sc = sensor_classification or {}
vm = (sc.get("verification_method") or "").upper()
dm = (sc.get("decision_method") or "").upper()
extra: dict[str, Any] = {}
if vm == "CONTENT" and dm == "LLM":
extra["judge"] = "haiku"
return ControlSpec(
control_id=control_id,
verification_method=vm,
decision_method=dm,
label=label,
paraphrases=[str(c) for c in (criteria or []) if c],
question=question,
patterns=patterns or [],
embed_threshold=embed_threshold,
extra=extra,
)