mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 03:49:26 +00:00
propose.py: one change per round (instruction, demonstrations or one mask), examples built from compile folds only with a provenance record of the change ids used, a registry for GEPA, MIPROv2 and BootstrapFewShot (MIPROv2 without the data-aware proposer so labels are never serialised), compile-fold scoring with Wilson and the repository-paired bootstrap, and a compile-only history the next round may read. accept.py: the rule as independent clauses (both folds non-negative with one strictly positive, per-family regression budget on held-out recall, tie tolerance, no acceptance without an interval, instruction cap, one change only), the comparison recorded in the candidate artifact's configurations, rejections logged without held-out numbers. 48 fixture tests. Known gap for live rounds: the optimisers and propose._score call the program directly, so a tool-using program must be scored through the runner with its tools bound per checkout (6.1). Implemented by Codex (gpt-6-astra), gated here. Co-Authored-By: Claude Fable 5.1 <[email protected]>
181 lines
7.8 KiB
Python
181 lines
7.8 KiB
Python
"""Accept a proposal only when separate compile and held-out comparisons support it."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import math
|
|
from collections.abc import Mapping
|
|
from dataclasses import asdict, dataclass, is_dataclass
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from ..stats import Estimate
|
|
from .artifact import RunArtifact
|
|
from .propose import Candidate, _append, _write, changed_surfaces, proposer_record
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Rule:
|
|
"""The acceptance rule fixed before measurement.
|
|
|
|
Regression budgets are signed recall differences: -0.02 permits a loss of
|
|
two percentage points. The 'default' entry applies to unlisted families;
|
|
when omitted it is zero. The held-out tie band includes both endpoints.
|
|
"""
|
|
|
|
tolerance: float
|
|
regression_budget: Mapping[str, float]
|
|
instruction_cap: int
|
|
version: str = "accept-v1"
|
|
|
|
def __post_init__(self) -> None:
|
|
"""Reject malformed or unsupported rules before using any measurements."""
|
|
if self.version != "accept-v1":
|
|
raise ValueError("unsupported acceptance rule version")
|
|
if not math.isfinite(self.tolerance) or self.tolerance < 0:
|
|
raise ValueError("tolerance must be finite and non-negative")
|
|
if type(self.instruction_cap) is not int or self.instruction_cap < 0:
|
|
raise ValueError("instruction cap must be a non-negative integer")
|
|
if any(not math.isfinite(v) or not -1 <= v <= 0 for v in self.regression_budget.values()):
|
|
raise ValueError("regression budgets must be signed differences between -1 and zero")
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class FoldResult:
|
|
"""Paired candidate-minus-current differences for one scope and one family.
|
|
|
|
Use family='pooled' for the aggregate and scope='compile' or 'holdout'. Each
|
|
metric accepts stats.Estimate or a record containing diff, ci, units and
|
|
groups. Missing intervals remain explicit so decide can reject them.
|
|
"""
|
|
|
|
scope: str
|
|
family: str
|
|
recall: Estimate | Mapping[str, Any]
|
|
suspicions_per_push: Estimate | Mapping[str, Any]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Decision:
|
|
"""The verdict, each failed clause, and the full private comparison."""
|
|
|
|
accepted: bool
|
|
reasons: list[str]
|
|
comparison: dict[str, Any]
|
|
|
|
def record(self, out_dir: Path | str, *, artifact: RunArtifact | None = None) -> None:
|
|
"""Save comparison.json and optionally attach it to the candidate's artifact.
|
|
|
|
The existing configurations field carries the full comparison. Call
|
|
artifact.write afterwards to persist its new content address.
|
|
"""
|
|
if artifact is not None and artifact.program_id != self.comparison["candidate_id"]:
|
|
raise ValueError("comparison belongs to a different program artifact")
|
|
_write(Path(out_dir) / "comparison.json", self.comparison)
|
|
if artifact is not None:
|
|
record = {"comparison": self.comparison}
|
|
if record not in artifact.configurations:
|
|
artifact.configurations.append(record)
|
|
|
|
|
|
def _record(value: Any) -> dict[str, Any]:
|
|
"""Convert dataclass estimates and ordinary mappings to JSON records."""
|
|
return asdict(value) if is_dataclass(value) else dict(value)
|
|
|
|
|
|
def _valid_number(value: Any) -> bool:
|
|
"""A missing or non-finite point estimate cannot support acceptance."""
|
|
return isinstance(value, int | float) and not isinstance(value, bool) and math.isfinite(value)
|
|
|
|
|
|
def _interval_present(estimate: Mapping[str, Any]) -> bool:
|
|
"""Require an ordered finite interval and real unit and group counts."""
|
|
ci = estimate.get("ci")
|
|
return (
|
|
isinstance(ci, tuple | list)
|
|
and len(ci) == 2
|
|
and all(_valid_number(v) for v in ci)
|
|
and ci[0] <= ci[1]
|
|
and _valid_number(estimate.get("diff"))
|
|
and type(estimate.get("units")) is int
|
|
and type(estimate.get("groups")) is int
|
|
and 0 < estimate["groups"] <= estimate["units"]
|
|
)
|
|
|
|
|
|
def decide(
|
|
current_id: str, candidate: Candidate, compile_results: Mapping[str, FoldResult], holdout_results: Mapping[str, FoldResult], rule: Rule
|
|
) -> Decision:
|
|
"""Apply every clause independently and save the comparison beside the proposal.
|
|
|
|
A zero compile difference and positive held-out difference may accept. A
|
|
positive compile difference with a held-out tie rejects. Confidence
|
|
intervals must exist; they need not exclude zero under this rule.
|
|
"""
|
|
rule.__post_init__()
|
|
reasons = []
|
|
if candidate.base_program_id != current_id:
|
|
reasons.append("current_program_mismatch")
|
|
try:
|
|
surfaces = changed_surfaces(candidate.base_state, candidate.state, candidate.base_mask, candidate.mask)
|
|
if surfaces != {candidate.kind} or set(candidate.change) != {candidate.kind}:
|
|
reasons.append("multiple_or_invalid_changes")
|
|
except ValueError:
|
|
reasons.append("multiple_or_invalid_changes")
|
|
instructions = [state.get("signature", {}).get("instructions", "") for state in candidate.state.values()]
|
|
if any(not isinstance(text, str) or len(text) > rule.instruction_cap for text in instructions):
|
|
reasons.append("instruction_cap")
|
|
compiled, heldout = {}, {}
|
|
for scope, source, target in (("compile", compile_results, compiled), ("holdout", holdout_results, heldout)):
|
|
for family, result in source.items():
|
|
record = _record(result)
|
|
if record.get("scope") != scope or record.get("family") != family:
|
|
reasons.append(f"invalid_scope:{scope}:{family}")
|
|
target[family] = record
|
|
for metric in ("recall", "suspicions_per_push"):
|
|
estimate = _record(record.get(metric, {}))
|
|
record[metric] = estimate
|
|
if not _interval_present(estimate):
|
|
reasons.append(f"missing_interval:{scope}:{family}:{metric}")
|
|
if "pooled" not in source:
|
|
reasons.append(f"missing_pooled:{scope}")
|
|
if set(compiled) != set(heldout):
|
|
reasons.append("missing_family_comparison")
|
|
compile_diff = compiled.get("pooled", {}).get("recall", {}).get("diff")
|
|
holdout_diff = heldout.get("pooled", {}).get("recall", {}).get("diff")
|
|
if _valid_number(compile_diff) and compile_diff < 0:
|
|
reasons.append("compile_recall_negative")
|
|
if _valid_number(holdout_diff) and holdout_diff < 0:
|
|
reasons.append("holdout_recall_negative")
|
|
if not any(_valid_number(value) and value > 0 for value in (compile_diff, holdout_diff)):
|
|
reasons.append("no_positive_recall_difference")
|
|
if _valid_number(holdout_diff) and abs(holdout_diff) <= rule.tolerance:
|
|
reasons.append("holdout_tie")
|
|
for family, result in heldout.items():
|
|
if family == "pooled":
|
|
continue
|
|
diff = result["recall"].get("diff")
|
|
floor = rule.regression_budget.get(family, rule.regression_budget.get("default", 0))
|
|
if _valid_number(diff) and diff < floor:
|
|
reasons.append(f"family_regression:{family}")
|
|
comparison = {
|
|
"current_id": current_id,
|
|
"candidate_id": candidate.program_id,
|
|
"compile_results": compiled,
|
|
"holdout_results": heldout,
|
|
"rule": asdict(rule),
|
|
"accepted": not reasons,
|
|
"reasons": reasons,
|
|
}
|
|
decision = Decision(not reasons, reasons, comparison)
|
|
decision.record(candidate.out_dir)
|
|
return decision
|
|
|
|
|
|
def record_rejection(out_dir: Path | str, candidate: Candidate, decision: Decision) -> None:
|
|
"""Append the compile score and reason codes, never the private comparison."""
|
|
if decision.accepted:
|
|
raise ValueError("cannot record an accepted candidate as rejected")
|
|
if decision.comparison["candidate_id"] != candidate.program_id:
|
|
raise ValueError("decision belongs to a different candidate")
|
|
_append(Path(out_dir) / "rejected.jsonl", {**proposer_record(candidate), "reasons": decision.reasons})
|