bench(learn): injection first slice -- compiled AI classifier, out-of-repo evaluation (decision-engine 6.2, 6.6)

6.2: the 724 static examples' 616 distinct excerpts embedded with openai/text-embedding-3-small via
OpenRouter, 198,819 tokens, $0.0040 metered (ceiling $1).

6.6, family injection only, memory = every family (at most 2 contrast examples per prompt), input
profile v1: smoke fixed k = 5 (repeat prefix-cache hit share 0.96); one compile (program c1237a49,
proposed instruction 5, balanced Brier 0.128 on 13 C_val candidates vs 0.199 baseline); outer grouped
5-fold, 110 out-of-repository candidates in 29 groups: ADVISORY recall 0.90 [0.82, 0.97], precision
0.54 [0.44, 0.65], AUC 0.74; BLOCK not offered (ECE 0.064 > 0.05; no fold reached a Wilson lower bound
of 0.95); canary re-ask agreement 0.83. $0.91 metered at list price; DeepSeek balance $18.20 -> $17.90.

Instrument fixes found on the way, each with a test:
- memory build: pair sides join the record stored under their harvest unit pin (--units), and only the
  built profile's records are read (static and plane shared a key; 85 -> 724 examples);
- retrieval: stage 1 per side (family / contrast) and at most 2 contrast examples, never padded;
- program: the JSON answer contract is part of the signature, appended to proposed instructions
  (DeepSeek JSON mode refused a prompt without "json");
- compile: candidate_families for a one-family compile; compile.toml k = 5 from the smoke rule.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
This commit is contained in:
Marius Ciepluch
2026-10-01 20:40:41 +02:00
co-authored by Claude Opus 5.5
parent 68294e9db3
commit 7b17eacd44
13 changed files with 692 additions and 46 deletions
+6 -3
View File
@@ -25,6 +25,7 @@ def add_commands(learn_sub: Any) -> None:
build.add_argument("--cache", type=Path, help="clones and checkouts (default ~/.cache/openultrasast)")
build.add_argument("--memory", help="the store (default OUSAST_MEMORY, then results_root()/memory)")
build.add_argument("--profile", choices=("static", "plane"), default="static")
build.add_argument("--units", type=Path, help="the harvest units.json: the pin each pair side's record is stored under")
build.add_argument("--dry-run", action="store_true", help="count, write nothing")
embed = memory_sub.add_parser("embed", help="embed the stored excerpts (OpenRouter, metered; cached by excerpt sha)")
embed.add_argument("--memory", help="the store (default OUSAST_MEMORY, then results_root()/memory)")
@@ -209,19 +210,21 @@ def _memory_embed(args: argparse.Namespace) -> int:
def _memory_build(args: argparse.Namespace) -> int:
"""Exit 2 on a refused label (``origin: user``, a source outside the list) or a source with >10% unread excerpts."""
from ..plane.memory import open_store
from .examples import ExampleBuildError, SideReader, build_examples, read_jsonl, records_by_key
from .examples import ExampleBuildError, SideReader, build_examples, pair_pins, read_jsonl, records_by_key
from .labels import DEFAULT_CACHE, DEFAULT_SOURCES, load_sources
sources = load_sources(args.sources or DEFAULT_SOURCES)
labels = read_jsonl(args.labels)
store = None if args.dry_run else open_store(args.memory)
rows = [row for path in args.features for row in read_jsonl(path)]
rows = [row for path in args.features for row in read_jsonl(path) if row.get("profile", args.profile) == args.profile]
if store is not None:
rows += [r.row for r in store.rows(kind="features")]
# one profile only: the static and plane records of a candidate share a key, and the other would overwrite it
rows += [r.row for r in store.rows(kind="features") if r.row.get("profile") == args.profile]
reader = SideReader(args.root, args.cache or DEFAULT_CACHE, sources, labels)
try:
build = build_examples(
labels, records_by_key(rows), reader, sources=sources, store=store, profile=args.profile, license_of=reader.license,
pins=pair_pins(json.loads(args.units.read_text(encoding="utf-8"))) if args.units is not None else None,
) # fmt: skip
except ExampleBuildError as exc:
print(f"learn memory build: {exc}", file=sys.stderr)
+6 -3
View File
@@ -354,12 +354,14 @@ def compile_program(
vectors: Mapping[str, Sequence[float]] | None = None,
identities: Callable[[Example], Iterable[str]] | None = None,
families: Sequence[str] = (),
candidate_families: Sequence[str] = (),
store: MemoryStore | None = None,
created: str = "",
code_commit: str = "",
) -> CompileResult:
"""Compile one profile's program from ``memory`` (module docstring). Deterministic given the memory, the spec and
the response cache."""
the response cache. ``candidate_families`` limits the bootstrap and validation candidates to those families (a
one-family slice); retrieval still draws on every family of the compile split, for contrast examples."""
seed = spec.seed
pool = [e for e in memory if e.profile == profile]
split: CompileSplit = compile_split(pool, fraction=float(spec["split"].get("compile_fraction", 0.25)),
@@ -368,8 +370,9 @@ def compile_program(
assert_disjoint(split, evaluation)
compile_memory = [e for e in pool if e.group in split.groups]
fold = Fold("compile", "compile", frozenset(), phase="compile")
boot = _order([e for e in compile_memory if e.group in split.boot], seed, "boot")[: int(spec["bootstrap"].get("max_candidates", 150))]
val = _order([e for e in compile_memory if e.group in split.val], seed, "val")[: int(spec["search"].get("val_candidates", 60))]
asked = [e for e in compile_memory if not candidate_families or e.family in candidate_families]
boot = _order([e for e in asked if e.group in split.boot], seed, "boot")[: int(spec["bootstrap"].get("max_candidates", 150))]
val = _order([e for e in asked if e.group in split.val], seed, "val")[: int(spec["search"].get("val_candidates", 60))]
if not boot or not val:
raise CompileError(f"compile split too small: {len(boot)} bootstrap and {len(val)} validation candidates")
retrieval, classify = spec["retrieval"], spec["classify"]
+3 -1
View File
@@ -29,7 +29,9 @@ val_candidates = 60
[classify]
model = "deepseek-flash"
k = 1 # k and lam are A/B variants (design section 6), not searched here
k = 5 # fixed by the smoke run before any compile spend (task 6.6): samples 2..5 hit the
# provider prefix cache for 0.96 of their prompt tokens (>= 0.8), 2026-10-01. k and lam
# remain A/B variants (design section 6), not searched here
max_output_tokens = 300
inputs = "v1" # the input profile: the engine instrument is withheld from prompts and retrieval (2026-10-01)
+15 -4
View File
@@ -22,7 +22,9 @@ Memory examples carry **no rationale** (code, signals, roles, family and label o
``fix_function`` (a different handler of the same snapshot).
Pair labels have no commit pin (the corpus holds excerpts); their envelope pin is :func:`label_pin`, a stable
40-hex digest of the pair and its side, so the vulnerable and the fixed side stay distinct rows.
40-hex digest of the pair and its side, so the vulnerable and the fixed side stay distinct rows. The harvest keyed a
pair side's feature record by its unit's pin instead (the blob sha1 of the side's excerpt, ``plane/harvest.py``):
:func:`pair_pins` reads that mapping from the harvest ``units.json`` and the build uses it when given.
"""
from __future__ import annotations
@@ -62,6 +64,11 @@ def label_pin(label: Mapping[str, Any]) -> str:
return hashlib.sha1(subject.encode("utf-8")).hexdigest() # noqa: S324 -- a name, not a security digest
def pair_pins(units: Mapping[str, Mapping[str, Any]]) -> dict[tuple[str, str], str]:
"""Harvest ``units.json`` -> ``(case reference, side) -> pin`` for the pair units (the pin their records carry)."""
return {(str(u["ref"]), str(u["side"])): str(u["pin"]) for u in units.values() if u.get("source") == "pairs"}
def record_key(repo: str, pin: str, candidate: str, family: str, unit: str) -> tuple[str, str, str, str, str]:
return repo_key(repo), pin, candidate, family, unit
@@ -127,9 +134,11 @@ def build_examples(
license_of: Callable[[Mapping[str, Any]], str] | None = None,
function_of: Callable[[Mapping[str, Any], str], str | None] | None = None,
bounds: Bounds = EXAMPLE,
pins: Mapping[tuple[str, str], str] | None = None,
) -> MemoryBuild:
"""Example rows for the verified labels that have a feature record (``records`` by :func:`record_key`) and an
excerpt; with ``store`` the excerpt blobs and the rows are written. Delta units append the base->head diff."""
excerpt; with ``store`` the excerpt blobs and the rows are written. Delta units append the base->head diff.
``pins`` (:func:`pair_pins`) gives a pair side the pin its harvest record was stored under."""
rows = [_as_dict(label) for label in labels]
allowed = {s.id for s in sources.sources} | {d for d, kind in DERIVED_SOURCES.items() if any(s.kind == kind for s in sources.sources)}
for row in rows:
@@ -145,7 +154,9 @@ def build_examples(
continue
count = build.sources.setdefault(str(label["source"]), SourceCount())
count.labels += 1
pin = label_pin(label)
side = (str(label.get("source_ref")), str(label.get("pin_role") or "vulnerable"))
pin = (pins or {}).get(side) if label.get("source") == "pairs" else None
pin = pin or label_pin(label)
candidate, family, unit = str(label["candidate"]), str(label["family"]), str(label["unit"])
record = records.get(record_key(str(label["repo"]), pin, candidate, family, unit))
if record is None or record.get("profile") != profile:
@@ -307,5 +318,5 @@ def records_by_key(rows: Iterable[Mapping[str, Any]]) -> dict[tuple[str, str, st
__all__ = [
"EXAMPLE_KIND", "NO_EXCERPT_LIMIT", "Example", "ExampleBuildError", "MemoryBuild", "SideReader", "build_examples",
"label_pin", "label_set_digest", "load_examples", "read_jsonl", "record_key", "records_by_key",
"label_pin", "label_set_digest", "load_examples", "pair_pins", "read_jsonl", "record_key", "records_by_key",
] # fmt: skip
+14 -7
View File
@@ -87,16 +87,21 @@ SIGNATURE = Signature(
)
)
BASELINE_INSTRUCTION = (
"You decide whether a function contains an exploitable security vulnerability of the given family.\n"
"You see the function's code (numbered lines), the signals static instruments emitted for it, and the roles\n"
"inferred for its calls. Signals are evidence, not verdicts: rules over-report, and a missing instrument is not a\n"
"negative. Similar labelled functions from other repositories follow as examples; reason about this code.\n"
# The answer contract is part of the signature, not of the searched instruction: a proposed instruction that leaves it
# out still gets it (``render_prefix``), so every prompt asks for JSON (DeepSeek's JSON mode refuses a prompt that
# never says "json") and every answer can parse.
ANSWER_FORMAT = (
'Answer with one JSON object: {"verdict": "vulnerable" | "not_vulnerable" | "unsure", "family": <family or\n'
'"none">, "confidence": <probability in [0, 1] that your verdict is right>, "rationale": <at most 3 sentences>,\n'
'"cited_lines": [<line numbers of the code that support the verdict>]}. Cite at least one line. Say "unsure"\n'
"when the code does not decide it."
)
BASELINE_INSTRUCTION = (
"You decide whether a function contains an exploitable security vulnerability of the given family.\n"
"You see the function's code (numbered lines), the signals static instruments emitted for it, and the roles\n"
"inferred for its calls. Signals are evidence, not verdicts: rules over-report, and a missing instrument is not a\n"
"negative. Similar labelled functions from other repositories follow as examples; reason about this code.\n" + ANSWER_FORMAT
)
# --- rendering ---------------------------------------------------------------------------------------------------------
@@ -232,7 +237,8 @@ ExcerptText = Callable[[str], str | None]
def render_prefix(spec: ProgramSpec, demos: Sequence[Demo], examples: Mapping[str, Example], excerpt_text: ExcerptText) -> str:
"""The instruction and the demonstrations: identical for every candidate of one program and fold."""
parts = [spec.instruction.strip()]
instruction = spec.instruction.strip()
parts = [instruction if ANSWER_FORMAT in instruction else f"{instruction}\n{ANSWER_FORMAT}"]
for number, demo in enumerate(demos, 1):
example = examples[demo.example_id]
code = excerpt_text(demo.excerpt_sha) or ""
@@ -481,7 +487,8 @@ class Program:
__all__ = [
"BASELINE_INSTRUCTION", "PARSE_FAILED", "SIGNATURE", "UNSURE", "VERDICTS", "Answer", "Caller", "Candidate", "Decision", "Demo",
"ANSWER_FORMAT", "BASELINE_INSTRUCTION", "PARSE_FAILED", "SIGNATURE", "UNSURE", "VERDICTS", "Answer", "Caller", "Candidate",
"Decision", "Demo",
"Program", "ProgramSpec", "Prompt", "ReplayMiss", "aggregate", "classify", "label_word", "line_numbers",
"params_digest", "parse_answer", "render", "render_prefix", "render_roles", "render_signals", "request_key",
] # fmt: skip
+21 -24
View File
@@ -11,12 +11,13 @@ profile, computed per instrument block so that missing is explicit, never impute
- ``D`` is the uniform mean over the profile's blocks; prior features are dropped with ``priors = "off"``, and the blocks
of instruments the input profile withholds (``inputs``, default ``v1``: the engine) are left out entirely.
Ties break by ``sha256(example id + seed)``; the first ``n1 = 40`` go to stage 2, a cosine re-rank of the code
Ties break by ``sha256(example id + seed)``; the first ``n1 = 40`` of the candidate's family and the first ``n1``
of the other families go to stage 2, a cosine re-rank of the code
embeddings, ``r = lam (1 - D) + (1 - lam) cos``. Without the target's vector the whole retrieval is
``signals_only``; an example without a vector scores ``cos = 1 - D`` (its signal similarity, so it keeps its
stage-1 standing). The **balance rule** then takes ``k_ret = 6``: at most ``k/2`` per label, at most 2 per
repository group, at most 2 from other families while the candidate's family has examples left; ``short`` when
the pool cannot fill it.
repository group, at most 2 contrast examples from other families whenever the pool holds the candidate's family
(never padded with more); ``short`` when the pool cannot fill it.
**The boundary.** :func:`eligible` is the only way an example enters a prompt -- retrieved examples and compiled
demonstrations alike: not the target's group, not a group the fold evaluates, not the held-out source, not the
@@ -43,6 +44,7 @@ LAM = 0.5
NEAR_DUPLICATE_COS = 0.98
UNBOUNDED_CAP = 10.0
PER_GROUP = 2
CONTRAST = 2 # contrast examples from other families, at most, while the pool holds the target's family
class BoundaryViolation(AssertionError):
@@ -153,35 +155,26 @@ def _tie(example_id: str, seed: int) -> str:
def balance(ranked: Sequence[Example], family: str, k: int = K_RET) -> tuple[list[Example], str]:
"""Take up to ``k`` in rank order: <= k/2 per label, <= 2 per group, <= 2 other families while the target's family
has examples left; then, if short, fill from other families under the label and group caps."""
per_label, other_cap = k // 2, min(2, k)
"""Take up to ``k`` in rank order: <= k/2 per label, <= 2 per group, and at most 2 contrast examples from other
families whenever the pool holds any example of the target's family (maintainer, 2026-10-01: "allow 2 contrast").
A pool without the target's family takes from the others under the label and group caps. ``short`` when the
pool cannot fill ``k`` or a label minimum: what exists is taken, never padded with more contrast."""
per_label = k // 2
other_cap = min(CONTRAST, k) if any(e.family == family for e in ranked) else k
taken: list[Example] = []
labels: dict[int, int] = {}
groups: dict[str, int] = {}
def fits(example: Example) -> bool:
return labels.get(example.label, 0) < per_label and groups.get(example.group, 0) < PER_GROUP
def take(example: Example) -> None:
taken.append(example)
labels[example.label] = labels.get(example.label, 0) + 1
groups[example.group] = groups.get(example.group, 0) + 1
others = 0
for example in ranked:
if len(taken) == k:
break
if example.family != family and others >= other_cap:
continue
if fits(example):
if labels.get(example.label, 0) < per_label and groups.get(example.group, 0) < PER_GROUP:
others += example.family != family
take(example)
for example in ranked:
if len(taken) == k:
break
if example.id not in {e.id for e in taken} and fits(example):
take(example)
taken.append(example)
labels[example.label] = labels.get(example.label, 0) + 1
groups[example.group] = groups.get(example.group, 0) + 1
pool_labels = {e.label for e in ranked}
short = len(taken) < k or any(labels.get(lab, 0) < min(2, per_label) for lab in (0, 1) if lab in pool_labels)
return taken, "short" if short else "ok"
@@ -202,10 +195,14 @@ def retrieve(
) -> Retrieval:
"""The ``k`` balanced examples for ``target`` from the eligible part of ``pool`` (module docstring)."""
candidates = [e for e in pool if e.profile == target.profile and eligible(e, target, fold)]
scored = sorted(
ranked_all = sorted(
((gower(target.x, target.instruments, e.x, e.instruments, target.profile, priors=priors, inputs=inputs), e) for e in candidates),
key=lambda pair: (pair[0], _tie(pair[1].id, seed)),
)[:n1]
)
# stage 1 per side: the n1 nearest of the target's family and the n1 nearest contrast examples, so a family that is a
# minority of the memory still reaches stage 2 (the balance rule then takes at most 2 contrast examples)
own = [pair for pair in ranked_all if pair[1].family == target.family][:n1]
scored = own + [pair for pair in ranked_all if pair[1].family != target.family][:n1]
mode = "signals+embeddings" if target.vector is not None and vectors else "signals_only"
near = without = 0
reranked: list[tuple[float, str, Example]] = []