generated from coulomb/repo-seed
session-memory Phase 2: evidence-bar + bloat guard (T04)
gating.py: two-tier evidence bar (OQ5) — promote floor (frequency/sessions/ cost_impact) plus a stricter distribution-eligibility floor that sets a promoted pattern to approved+distribution_ready vs provisional. Wired into review() so thin approvals land provisional. bloat_warnings flags duplicate and near-duplicate (same signal-type+locus) candidates (OQ6). [curate]/ [curate.gate] knobs in config.toml. 6 new tests; suite 64/64 green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,7 @@ from datetime import datetime, timezone
|
||||
from typing import Callable, Optional
|
||||
|
||||
from .catalog import Catalog
|
||||
from .gating import GateConfig, evaluate
|
||||
from .schema import Provenance, Resolution, Scope, SolutionPattern
|
||||
|
||||
APPROVE = "approve"
|
||||
@@ -46,8 +47,13 @@ def evidence_fingerprint(candidate: dict) -> str:
|
||||
return hashlib.sha1(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest()
|
||||
|
||||
|
||||
def candidate_to_pattern(candidate: dict) -> SolutionPattern:
|
||||
"""Build a (provisional) Solution Pattern from a detect candidate."""
|
||||
def candidate_to_pattern(candidate: dict, *, status: str = "provisional",
|
||||
distribution_ready: bool = False) -> SolutionPattern:
|
||||
"""Build a Solution Pattern from a detect candidate.
|
||||
|
||||
``status``/``distribution_ready`` come from the evidence gate (T04); they
|
||||
default to a provisional, non-distribution-ready pattern when ungated.
|
||||
"""
|
||||
src = candidate["key"]
|
||||
flavors = list(candidate.get("flavors", []))
|
||||
hints = {f: {"target": _DEFAULT_TARGET.get(f, ""), "note": "TODO: refine rendering"}
|
||||
@@ -62,7 +68,8 @@ def candidate_to_pattern(candidate: dict) -> SolutionPattern:
|
||||
scope=Scope(flavors=flavors, repos=list(candidate.get("repos", []))),
|
||||
provenance=Provenance(source_key=src, evidence=dict(candidate), promoted_at=_now()),
|
||||
rendering_hints=hints,
|
||||
status="provisional",
|
||||
status=status,
|
||||
distribution_ready=distribution_ready,
|
||||
)
|
||||
|
||||
|
||||
@@ -112,8 +119,14 @@ class ReviewResult:
|
||||
|
||||
|
||||
def review(candidates: list[dict], decide: Decider, catalog: Catalog,
|
||||
log: ReviewLog) -> ReviewResult:
|
||||
"""Run each candidate through ``decide``; promote approvals into ``catalog``."""
|
||||
log: ReviewLog, gate: Optional[GateConfig] = None) -> ReviewResult:
|
||||
"""Run each candidate through ``decide``; promote approvals into ``catalog``.
|
||||
|
||||
When a ``gate`` (T04 evidence bar) is supplied, the promoted pattern's
|
||||
``status``/``distribution_ready`` are set from the gate evaluation, so an
|
||||
approved-but-thin candidate lands as ``provisional`` rather than
|
||||
distribution-ready.
|
||||
"""
|
||||
result = ReviewResult()
|
||||
for cand in candidates:
|
||||
key = cand["key"]
|
||||
@@ -125,7 +138,11 @@ def review(candidates: list[dict], decide: Decider, catalog: Catalog,
|
||||
result.deferred.append(key)
|
||||
continue # not a final decision — leave for a later pass
|
||||
if action == APPROVE:
|
||||
cat_action = catalog.upsert(candidate_to_pattern(cand))
|
||||
g = evaluate(cand, gate) if gate is not None else None
|
||||
pattern = (candidate_to_pattern(cand, status=g.status,
|
||||
distribution_ready=g.distribution_ready)
|
||||
if g is not None else candidate_to_pattern(cand))
|
||||
cat_action = catalog.upsert(pattern)
|
||||
result.approved.append((key, cat_action))
|
||||
elif action == REJECT:
|
||||
result.rejected.append(key)
|
||||
|
||||
Reference in New Issue
Block a user