Files
Open_Ultra_SAST/docs/decision-engine.md
T
Marius CiepluchandClaude Fable 5.1 6b12546531 docs: where we stand -- capability, approach, ax integration, token economics, open items
One page, docs/where-we-stand.md, composed with the COBESY composition method (governing idea,
SCQA opener, MECE sections): the M4 gates and the state against them, the detection approach step
by step, ax on the server, token economics before and after, and the open items. Every figure is
quoted from a committed record whose path stands next to it.

decision-engine.md, evaluation.md and token-ergonomics.md link to the page where they repeated its
content (detail tables kept); decision-engine.md's Experiments section now records exp-002's
result instead of saying it has not run. Linked from the nav, the index and the README.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
2026-10-02 19:18:54 +02:00

9.4 KiB

The learned decision engine (in development)

Status 2026-10-02: in development, not adopted. No scan, pre-push check or report uses it; every user-facing path is today's deterministic one until a compiled program is adopted. The design is the learned-decision-engine specification (maintainer tooling); the code is in src/openultrasast/learn/.

Why

Every detection gain before the engine was a hand edit tuned on the cases that exposed it, and engine tuning measured on population v1 did not transfer to v2. The reasoning, and the step-by-step chain that replaced hand edits, are on Where we stand.

Design

  • Signals, not verdicts. Every instrument (quick rules, the Joern engine, model sink and role classification, the plane's verify passes and agreement, repository facts such as callers) contributes a feature record per candidate (path, function, family), including an explicit state when it did not run or failed. None decides alone.
  • Labels from ground truth. ousast learn labels derives labels only from sources on a fail-closed allow-list (learn/sources.toml): the vulnerable and fixed sides of pairs, the declared sites and fix ranges of spent populations, benign deltas and recorded adjudications. "Rejected by the plane" is never a negative. Reserved populations (v3) are not on the list.
  • An AI classifier with local memory, compiled in house in the DSPy style. The program is an instruction, a few demonstrations and up to six retrieved labelled examples, then the candidate's code excerpt and signals; the model (deepseek-flash) answers vulnerable / not vulnerable / unsure with a rationale, sampled k times, giving a score s. Compilation bootstraps demonstrations and searches instructions on a compile split, scored by a pre-registered balanced Brier score (learn/compile.toml).
  • Retrieval from the memory store: nearest neighbours by Gower distance on the signal profile, re-ranked by cosine similarity of code embeddings (OpenAI text-embedding-3-small through OpenRouter, cached by excerpt hash), under a label/family/repository balance rule. An evaluation-boundary filter keeps a candidate's own repository group out of what it is shown.
  • Repository-grouped evaluation. The compile split (25% of repository groups) is never evaluated. The rest is evaluated by outer grouped 5-fold cross-validation: the Platt calibration map is cross-fitted on the other folds and the operating points are chosen on the other folds (nested). One compile per profile, not one per fold. Intervals are 95% repository-cluster bootstrap.
  • Two operating points. BLOCK is offered only when calibration holds (slope CI contains 1, intercept CI contains 0, ECE <= 0.05) and a fold reaches the required precision; otherwise the engine is ADVISORY only. An unsure answer can never BLOCK.

First measured slice (2026-10-01)

Record: benchmarks/measurements/2026-10-01-decision-engine-injection-slice/record.json (counts only). Family injection only; memory of 724 static examples from all families; input profile v1 with the engine signals and function length withheld (a leak audit found them separating the sides of a pair by construction: 2026-10-01-decision-engine-leak-audit/); framework priors off.

Measure Value (95% repository-cluster CI)
Candidates evaluated 110 in 29 repository groups, 58 positive
ADVISORY recall 0.90 (0.82 - 0.97)
ADVISORY precision 0.54 (0.44 - 0.65)
AUC of the score s 0.74 (0.65 - 0.85)
Majority "vulnerable" verdict precision 0.86 (0.72 - 1.0), recall 0.43 (0.29 - 0.62)
BLOCK not offered: ECE 0.064 > 0.05, and every fold reported precision_unreachable
Compiled program balanced Brier 0.128 on the validation split vs 0.199 for the baseline instruction
Canary agreement on re-run 0.83 (30 candidates)
Spend $0.91 metered at list price for DeepSeek, $0.0040 for embeddings; $0.0032 per evaluated candidate

Second slice: six families (2026-10-02)

Record: benchmarks/measurements/2026-10-02-decision-engine-slice-2/record.json (counts only). After the advisory-fix harvest the memory holds 904 static examples (724 before). One program was compiled per evaluable family (injection recompiled, since the memory changed) and evaluated on that family's own candidates, out of repository as above: compile split never evaluated, outer grouped 5-fold, calibration cross-fitted, operating points nested, 95% repository-cluster bootstrap intervals (2,000 resamples); input profile v1, priors off, k = 5.

Family Candidates (groups; positive) Pooled AUC of s (95% CI) Within-pair AUC (paired groups) ADVISORY recall / precision Canary agreement
access control 37 (22; 23) 0.96 (0.90 - 1.0) 0.93 (14) 0.91 / 0.91 0.83
deserialization 46 (30; 30) 0.90 (0.82 - 0.97) 0.81 (16) 0.83 / 0.76 0.93
injection 110 (29; 58) 0.77 (0.67 - 0.88) 0.72 (22) 0.88 / 0.57 0.77
output encoding 44 (27; 29) 0.91 (0.82 - 0.97) 0.82 (15) 0.86 / 0.81 0.90
path 35 (21; 21) 0.89 (0.79 - 0.97) 0.83 (14) 0.86 / 0.69 0.90
untrusted destination 62 (37; 38) 0.85 (0.76 - 0.94) 0.72 (18) 0.84 / 0.73 0.87
  • BLOCK is offered for no family. Calibration holds only for output encoding (ECE 0.045), and there no fold reaches the required precision (precision_unreachable: no fold reaches a Wilson lower bound of 0.95). For the other five families calibration does not hold (ECE 0.071 to 0.119).
  • The pooled AUC is the more favourable number. Every family's pool includes candidates that carry only a positive label, with no fixed-side counterpart (7 to 19 per family; there are no single-label negatives), so the pooled AUC is inflated by positives that never had to be told apart from their own fix. The within-pair AUC over the paired groups, 0.72 to 0.93, is the stricter reading (paired_groups_check in the record).
  • Re-run agreement (canary, 30 candidates per family) is below 0.9 for three families: injection 0.77, access control 0.83, untrusted destination 0.87.
  • One memory-size point for injection (paired: the same program, candidates and folds, with a retrieval memory of 724 vs 904 examples): AUC 0.76 vs 0.77, within the intervals. No injection example was added, so the new examples reach an injection prompt only as contrast.
  • Spend: $2.88 metered at list price for DeepSeek under an $8 ceiling, $0.0009 for embeddings; $0.0027 to $0.0032 per evaluated candidate. The access-control evaluation replayed from the response cache at $0 with identical predictions.
  • An instrument fix is recorded: compiled demonstrations ignored the near-duplicate rule (cosine >= 0.98) until the pre-call boundary assertion stopped the untrusted-destination evaluation; demonstrations are now swapped or dropped like retrieved examples, and the stopped part was replayed after the fix.

Not adopted. What is still ahead is listed once, on Where we stand.

Experiments

Changes to prompts, models, pass counts, features or sampling are compared by controlled experiments from a pre-registered manifest (plane/experiments/<id>.yaml), so nothing is adopted on one run's number. Two are registered. exp-001 (known callers) has not run. exp-002 (a retrieval ensemble against today's temperature sampling) ran on 160 paired candidates in 56 repository groups and was rejected: within-pair AUC 0.815 against 0.741 and $0.0021 against $0.0051 per candidate, with re-run agreement 0.875 against 0.906 and an interval spanning zero (benchmarks/experiments/exp-002-retrieval-ensemble/result.json). The reading is on Where we stand.

Commands

ousast learn is a maintainer command group over the engine's data. Paid steps take --budget-usd; --replay-only answers every call from the response cache at no cost.

Command Does
ousast learn labels labels from the allow-listed sources (host only); --snapshot writes the counts-only record
ousast learn memory build label + feature record + excerpt -> example rows (no model)
ousast learn memory embed embed the stored excerpts (OpenRouter, metered, cached)
ousast learn compile compile the program on the compile split
ousast learn evaluate --program ID outer-fold evaluation, cross-fitted calibration, nested operating points
ousast learn curve --program ID the metric over memory size on a fixed candidate subset
ousast learn audit-leaks how well each signal alone separates the sides of a pair (no model)
ousast learn experiment register|units|run|analyse MANIFEST A/B experiments from a pre-registered manifest (plane/experiments/<id>.yaml): record its digest, freeze the units (no model), run the arms paired (arm A may replay from the cache at $0), analyse (repository bootstrap, exact McNemar, two looks, the adoption verdict)

The memory store is the plane's (OUSAST_MEMORY; see ops/ax/README.md). The harvests that produced the plane signals ran as plane Runs generated by ousast plane harvest (benchmarks/measurements/2026-10-01-decision-engine-harvest/record.json and, for the advisory-fix pairs, 2026-10-02-decision-engine-harvest-advisory/record.json).