mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 03:49:26 +00:00
Feature engineering is the centre: a feature table with switchable columns
is the contract between producers (source.facts_for, semantic facts, entry
points, diff hunks; lizard and Semgrep only after their evaluations), the
ablation runner and the hunter program. DSPy runs the program (ReAct over a
HuntSignature; GEPA and MIPROv2 propose), our rule accepts on held-out
folds. The pilot stays held out; the hook, its client and the core install
are untouched. Requirements approved by the maintainer ('4 first').
Co-Authored-By: Claude Fable 5.1 <[email protected]>
9.9 KiB
9.9 KiB
Research and design decisions: hunter-learning-loop
Summary
- Feature:
hunter-learning-loop - Discovery scope: Extension of the learn harness and the valid benchmark; one new runtime dependency (DSPy).
- Maintainer direction, 2026-10-13: "the core we need to evolve is feature engineering ... that is the deciding factor here"; "labels for me are a pandas task"; DSPy for the hunter program first, statistics libraries second, no OpenAI SDK on its own ("unless dspy pulls it in"; it does, through litellm); lizard against its alternatives is worth pursuing; labels and folds are a pandas and scikit-learn task; Semgrep OSS is a good idea. Each of those is a feature producer or a feature consumer, and the feature table decides between them.
- Key findings:
- The pilot (benchmarks/measurements/2026-10-12-valid-benchmark-pilot) measured the hunter at 5 to 6 of 12 and 54 false alarms per 100 pushes after the classifier; 250 of 343 "vulnerable" suspicions on ordinary pushes sit more than 10 lines from the diff. The hunter never sees the diff (01-flowcharts/U6-hunter-client.md).
- The harness already has the pieces the loop needs: labels (
learn/labels.py), repository folds and leak audit (learn/folds.py,learn/leaks.py), paired experiments with cluster bootstrap and McNemar (learn/experiments.py), the cached caller (learn/program.py), and the runner that joins hunter output to frozen changes (benchmarks/valid/hunter_arm.py). What it lacks is a feature table and a program runtime that can be optimised rather than hand-edited. source.facts_for(P1, commit062a7b7) is the first feature producer: language, enclosing function, span, callers, entry points, from the repository only.
Research log
DSPy as the program runtime
- Context: requirements R3 to R6 need a signature with switchable inputs, demonstrations, instruction proposals, a scorer-driven compile, and metered, replayable model calls.
- Sources: local install,
dspy==3.4.1into the research venv (uv pip install --dry-run dspy: 42 packages, among themlitellm 1.105,openai 2.54,gepa 0.1.4,diskcache,json_repair,tenacity,pydantic); API introspection of the installed package (dspy.LM(model, cache=True, num_retries=3, ...),dspy.ReAct(signature, tools, max_iters=20),dspy.Predict,dspy.SignaturewithInputField/OutputField,dspy.Evaluate(devset, metric, num_threads),dspy.BootstrapFewShot,dspy.MIPROv2(metric, auto=...),dspy.GEPA(metric, auto, reflection_lm, candidate_selection_strategy="pareto"),dspy.track_usage(),dspy.Module.save(path)). - Findings:
dspy.LMreaches DeepSeek and OpenRouter through litellm with an OpenAI-compatible base URL and the key from the environment; the project's model iddeepseek-flashis not in litellm's price table, so prices are registered from our table (model/endpoint.py:55). DSPy caches responses on disk (diskcache) keyed by the request; a run's cache directory is settable, so a run can be replayed from its own directory.dspy.ReActis the tool-calling loop the hunter already is (read_file, grep_repo, find_refs). GEPA is the optimiser the learning-harness brief described (per-instance Pareto archive, reflective edits); MIPROv2 is the instruction and demo search. Neither applies our acceptance rule; that stays outside DSPy (R6). - Implications: the hunter program is written as a DSPy module from the start; the classifier
(
learn/program.py) stays as is for this spec (R7.5). The hook's urllib client is untouched: the hook has no LLM tier yet, and the core install stays PyYAML-only; DSPy is a research extra.
The feature table (labels and features as a pandas task)
- Context: R1, R3, R4 need one row per candidate location with named feature columns, a label, provenance, and the ability to switch columns off per ablation arm.
- Findings:
learn/features.pybuilds feature records as nested dicts and parts;learn/labels.py(979 lines) joins sources by hand;benchmarks/unseen/score.pyjoins records by id. All of it is group-by, merge and dedup work. pandas and scikit-learn (grouped folds, calibration, metrics) are what the maintainer expects here and are pulled by none of the current extras. - Implications: the loop's contract is a DataFrame (persisted as JSONL or parquet) with a fixed column schema; producers add columns, ablation arms drop columns, the program renders a row into its prompt. Existing dict-based builders are consumed, not rewritten, in this spec; migrating labels to pandas is part of the statistics item, not this one.
Feature producers and the tools under evaluation
- Context: the annotation model (R3) lists the facts; the maintainer wants the producers chosen by what gives the best features.
- Candidates:
source.facts_for(ours; P1), the semantic overlay facts (roles, def-use, obligations;semantic/),mapping.analyze_entry_points, lizard (function spans, nesting, cyclomatic complexity; pure Python), universal-ctags (spans across many languages; a binary), Semgrep OSS or Opengrep (pattern and taint findings as features; also a candidate to replace the quick-rule engine, a separate decision). - Status: a measured comparison of lizard against our two engines on the pair corpus is running
(
~/ousast-results/fossil/span-eval/REPORT.md); a licensing and capability brief for Semgrep CE and Opengrep is being written (~/ousast-results/fossil/semgrep-brief.md). Both land as producers behind the same contract; the ablation decides whether their columns stay. - Research needed: Semgrep registry rule licence terms for an open-source tool.
Training data apart from the pilot
- Context: R2. The pilot (252 changes, 12 positives) is held out.
- Findings:
benchmarks/valid/catalog.pyandverify.pyproduce fix-site positives from OSV advisories in about 12 accepted per 32 advisories; the used-set guard (benchmarks/unseen/*) excludes training-only sources.learn/folds.pygroups by repository;learn/leaks.pyaudits per-signal separation. - Implications: about 100 advisories give about 40 positives; negatives at 20 per positive from the same repositories; folds by repository; the pilot is never read by the loop.
Architecture pattern evaluation
| Option | Description | Strengths | Risks | Notes |
|---|---|---|---|---|
A. Extend learn/program.py with a hunter signature |
one harness, our caller and cache | no new dependency | the compile loop has no train-vs-holdout rule, no tool loop, no optimiser beyond demos+instructions; everything the maintainer asked for (statistical mutation of prompts) is to build | rejected |
B. New hunt/ package on DSPy, feature table as the contract, harness reused for labels, folds, leaks, paired analysis |
the optimisers, the tool loop, caching and usage come with the library; our code is the feature producers, the table, the acceptance rule, the scoring | one heavy research dependency; two caches (DSPy's and Caller's) until the classifier migrates |
selected | |
| C. Rewrite the classifier onto DSPy too | one runtime | large rewrite of a parked line | later, if B earns it |
Design decisions
Decision: DSPy runs the hunter program; the acceptance rule stays ours
- Alternatives: A above; a hand-written optimiser.
- Selected:
dspy.ReActover aHuntSignaturewhose inputs are the rendered feature row plus the changed hunks;dspy.GEPAorMIPROv2propose candidates;dspy.Evaluatescores them on compile folds with our metric; the candidate is accepted only by our rule on held-out folds (R6), implemented outside DSPy. - Trade-offs: DSPy's cache is request-keyed, not content-addressed; a run pins its cache directory and copies it
with the run record. Cost comes from
dspy.track_usage()with our prices registered in litellm.
Decision: the feature table is the contract between producers, ablation and the program
- Selected: one row per (change, path, line) candidate with columns
feature.<producer>.<name>,label,provenance.*,fold; producers are functions(root, change, path, line) -> dict; an ablation arm is a column mask recorded in the manifest; the program sees only the unmasked columns, rendered in a fixed order. - Trade-offs: a table per run is larger than nested records; it is also what pandas, scipy and the maintainer read.
Decision: producers are a fixed list, switched by name, no registry
source.facts_for,semanticfacts (roles, def-use, obligations),mappingentry points,diffhunks; lizard and Semgrep/Opengrep join the list only after their evaluation reports.
Decision: objective and metric
- Search stage: recall over verified fix sites (a suspicion within the scorer's tolerance of the fix site with the right family) and suspicions per ordinary push; the DSPy metric returns the per-change score and, for GEPA, a text feedback naming the missed site or the off-diff suspicions. No threshold is tuned (R4.7); the curve is reported (R4.8).
Risks and mitigations
- DSPy or litellm upgrades change behaviour: pin
dspy==3.4.*, record versions in every run artifact. - A producer reads a label by accident: producers take the repository and a location only; a test feeds a repository with a planted advisory id and asserts no feature carries it (R3.7).
- Train-on-test: the pilot path is denied to the loop by construction (the runner refuses the pilot manifest digest), and every artifact lists the data digests it read (R2.4).
- Cost: budgets per run (R9.1); DSPy cache on;
--replay-onlyreruns at no cost.
References
- DSPy 3.4.1 (installed; https://dspy.ai), GEPA (https://github.com/gepa-ai/gepa), litellm pricing registration.
- PATHFINDER-2026-10-13/01-flowcharts/U6-hunter-client.md, U7-learn-memory-tasks.md; 02-duplication-report.md D1, D4.
- benchmarks/measurements/2026-10-12-valid-benchmark-pilot/NOTES.md (numbers and the filter sweep).