- variant_search.search_tree skips a malformed store row with a variants_store_row_invalid degradation instead of aborting the scan; a hit on a demoted or flowless overlay record is never merged (test). - seed.pair_lessons is the one shape derivation shared by the exporter and leave-one-out; labels pass line and sink together; pairs without a labeled function are reported as no_labeled_function. - config parses [variants].max_mechanisms with the shared int helper. - regress.candidate.hotspot_from_finding is the one zero-score hotspot helper for forced inventory and variant findings. - design synced (SearchResult, hits_to_findings summaries, candidate.py). Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01T7XD7x5KonMdmGRkp63EQG
14 KiB
Design Document
Overview
Purpose. Turn trusted pairs into mechanism records, search scanned trees for structural variants, measure the corpus's own detection rate leave-one-out, and give the improve loop a recall-raising lever.
Users. Maintainers export and measure. AppSec engineers get variant findings that name the known mechanism and its known fix. The improve loop admits or retracts mechanisms under the existing gate.
Impact. semantic/mechanisms.py gains additive fields and a second writer. New semantic/variants.py (shapes and matching) and semantic/loo.py (leave-one-out). MAP runs variant search after overlay. improve gains the mechanisms lever. Reports show mechanism ids. No disposition, gate, or sandbox change.
Goals
- One mechanism record per distinct shape, seeded from
seededandreviewedpairs. - Variant findings on real trees at
suspicion, merged with overlay flows when present. - Leave-one-out recall, silence, and Youden per slice, profile, and mechanism.
mechanismslever under the existing accept gate and per-profile holdout.
Non-Goals
- LLM-authored shapes, regex growth, disposition or ladder changes, inter-file flow, embedding changes, new gates.
Boundary Commitments
This Spec Owns
- Shape derivation and matching (
variants.py). - Additive mechanism fields, corpus writer, dedupe (
mechanisms.py). - Variant proposer in MAP and manifest counters.
- Leave-one-out runner and artifact (
loo.py,pairs --loo). mechanismslever, its validator rules, proposer, journal fields.- Report rendering of mechanism ids.
Out of Boundary
OverlayRecordfields beyond an optionalmechanism_id; dispositions; evidence rungs.- Catalog schema, scorer rules, harvest (
pair-corpus-honesty). - Taint, facts, walker; call graph; hunter.
- Rule and policy levers; ledger format.
Allowed Dependencies
FileIR,parse_file,SemanticFacts/load_facts(read),adjudicateoutput,MechanismStore,PairCase/load_pair_catalog/select_slice, pair scorer matching helpers (read),run_roundgate,EvolveValidator, journal, reports.
Revalidation Triggers
- Variant findings stamped above
suspicionwithout overlay or sandbox agreement. - Seeding from
advisoryortitlepairs. - Free-form shape text accepted by the validator.
- Leave-one-out used as a gate.
Architecture
flowchart TB
PAIRS[trusted pairs: seeded | reviewed] --> EXPORT[derive_shape per pair]
EXPORT --> STORE[mechanisms.jsonl origin=corpus]
STORE --> VS[variant search in MAP]
IR[FileIR of scanned tree] --> VS
FACTS[facts: sources] --> VS
VS --> VF[variant findings: suspicion, mechanism_id]
OV[overlay records] --> MERGE[merge when same call site has a flow]
VF --> MERGE
MERGE --> CAND[regress candidates after promotions]
PAIRS --> LOO[leave-one-out runner]
STORE -. temp store per held-out pair .-> LOO
LOO --> ART[loo.json + roadmap table]
LOO --> PROP[improve proposer: admit/retract]
PROP --> GATE[accept gate + per-profile holdout]
GATE --> STORE
Key decisions
- Shapes are closed structures derived from
FileIR, never text patterns. - Variant search proposes at
suspicion; overlay or sandbox raises. - Leave-one-out is the corpus's detection rate and is reported only.
- The lever edits record ids from the exporter; validator rejects anything else.
Technology Stack
| Layer | Choice | Role | Notes |
|---|---|---|---|
| Shapes | stdlib dataclasses over FileIR |
derive, match | no new parser |
| Store | existing JSONL + cache | corpus and sandbox records | additive fields |
| Search | MAP stage | proposer | file budget as rank |
| LOO | temp dirs + existing pair materialization | measurement | artifact only |
| Improve | existing lever machinery | admit/retract | same gate |
File Structure Plan
src/openultrasast/
├── semantic/
│ ├── mechanisms.py # Mechanism += origin, review_tier, pairs, shape, guard; append_from_pair; dedupe_by_shape
│ ├── variants.py # Shape, GuardKind, derive_shape(vuln_ir, fixed_ir, label, facts) -> Shape|None; match_shapes(file_ir, shapes, facts) -> list[VariantHit]
│ ├── variant_search.py # search_tree, hits_to_findings, SearchResult (needs store/overlay/findings; variants.py stays free of them)
│ ├── seed.py # export_mechanisms(cases, store) -> ExportReport (maintainer command `mechanisms export`)
│ └── loo.py # evaluate_loo(cases, *, store_factory) -> LooResult; payload
├── findings.py # origin "variant" via tags/finding_id prefix "variant:" (no schema change)
├── cli.py # MAP: variant search after overlay; manifest counters; `pairs --loo`; `mechanisms export --slice`
├── improve/validator.py# MechanismEdit lever "mechanisms"
├── improve/evolve.py # proposer from loo misses/leaks; gate unchanged
└── reports.py # mechanism id, guard, provenance on findings
tests/
├── test_variants.py, test_loo.py, test_mechanism_export.py, test_improve.py (lever)
Modified Files
semantic/overlay.py—OverlayRecord.mechanism_id: str | None = None(additive).regress/candidate.py— exposeshotspot_from_finding; variant findings enter the ranked candidate slice as zero-score hotspots fromcli.py(after every scored promotion, inside the cap, through the safety check).config.py—[variants] enabled=true, max_mechanisms=500.
System Flows
Export
flowchart LR
A[load trusted pairs] --> B[parse vuln + fixed excerpts]
B --> C[find labeled sink call site]
C --> D[source positions via binds]
B --> E[guard kind: statements only on fixed side]
D --> F[Shape]
E --> F
F --> G[dedupe by shape; append with pairs provenance]
Variant search in MAP
For each parsed file within the MAP budget: for each call site whose trailing callee name equals a shape's sink name and whose arity matches, check the shape's source positions: the argument carries a fact source, a function parameter, or a container read (bind whose value is a subscript or .get(). On match emit a variant:<mechanism_id>:<path>:<line> finding at suspicion. If an overlay record exists at that path and line with a flow, set its mechanism_id and do not emit a separate finding.
Leave-one-out
For each trusted pair P in the slice: temp store from all trusted pairs except P; materialize P's sides; run variant search on both; detection if a variant hit lies inside P's labeled function and names P's mechanism id or matches P's sink; leak if any hit on the fixed side. Aggregate per slice, profile, mechanism; record which mechanism found P.
Requirements Traceability
| Requirement | Summary | Components | Flows |
|---|---|---|---|
| 1.1–1.5 | seed from trusted pairs, dedupe, origin corpus, maintainer command | Export, Store | export |
| 2.1–2.5 | structural shapes, closed guard kinds, no text, extra-absent degradation | Shape | export |
| 3.1–3.6 | proposer at suspicion, merge, candidates, budget, absent from quick | VariantSearch | MAP |
| 4.1–4.4 | leave-one-out per slice/profile/mechanism, artifact, not a gate | LOO | leave-one-out |
| 5.1–5.5 | lever, validator, proposer, gate unchanged, journal | Lever | improve |
| 6.1–6.3 | reports, manifest counters, suspicion label | reports | — |
| 7.1–7.3 | offline tests, gates unchanged, extra-free green | tests | — |
Components and Interfaces
| Component | Domain | Intent | Req | Dependencies | Contracts |
|---|---|---|---|---|---|
| Shape | semantic | derive and compare shapes | 2 | FileIR, facts | Service |
| Export | maintainer | pairs → records | 1 | PairCase, Shape, Store | Batch |
| Store | semantic | additive fields, dedupe | 1.3, 1.4 | JSONL | State |
| VariantSearch | MAP | propose at suspicion | 3 | Shape, Store, overlay | Service |
| LOO | maintainer | detection rate of the corpus | 4 | Export, VariantSearch, pair rules | Batch |
| Lever | improve | admit/retract records | 5 | validator, gate | Batch |
Shape
GUARD_KINDS = ("null_test", "bounds_test", "allowlist_test", "auth_check", "parameterized_call", "type_change", "none")
SOURCE_KINDS = ("fact_source", "parameter", "container_read")
@dataclass(frozen=True)
class Shape:
language: str
sink_name: str # trailing callee segment
arity: int
source_positions: tuple[int, ...]
source_kinds: tuple[str, ...]
guard: str # GUARD_KINDS
mechanism: str # closed vocabulary id from the pair label
def key(self) -> str: ... # stable, text-free
def derive_shape(vuln: FileIR, fixed: FileIR, *, function: str, sink: str | None, line: int | None, mechanism: str, facts: SemanticFacts, vuln_text: str = "", fixed_text: str = "", cwe: str | None = None) -> Shape | None: ...
# texts: guard statements are neither calls nor binds in FileIR, so the guard kind is classified from the labeled
# function's lines present only on the fixed side (closed set, none of the text enters the shape); cwe: when a label
# names no sink and no line, the fact-sink call in the function whose CWE matches is the labeled call.
def match_shapes(ir: FileIR, shapes: Sequence[Shape], facts: SemanticFacts, *, mechanism_ids: Mapping[str, str]) -> list[VariantHit]: ...
Invariants: shapes carry no path, line, or literal beyond identifiers; derive_shape returns None when the labeled sink call site is not found.
Store additions
Mechanism gains origin: str = "sandbox", review_tier: str = "", pairs: tuple[str, ...] = (), shape: dict[str, object] | None = None, guard: str = "none". append_from_pair(store, shape, *, summary, cwe, pair, provenance, tier) dedupes on shape.key() and extends pairs. Old rows load with defaults.
VariantSearch
@dataclass(frozen=True)
class VariantHit:
path: str
line: int
mechanism_id: str
sink_name: str
source_kind: str
def search_tree(root: Path, targets: Sequence[FileTarget], store: MechanismStore, facts: SemanticFacts, *, max_mechanisms: int) -> SearchResult: ... # hits, mechanisms_searched, files_searched, degradations
def hits_to_findings(hits, records: Sequence[OverlayRecord], summaries: Mapping[str, tuple[str, tuple[str, ...], str]]) -> tuple[list[StaticFinding], list[OverlayRecord]]: ... # merge with overlay flows
Finding: id variant:<mechanism_id>:<path>:<line>, evidence suspicion, tags ["variant", "mechanism:<id>"], rationale names the mechanism summary and pair provenance.
LOO
@dataclass(frozen=True)
class LooOutcome:
pair: str
detected: bool
silent: bool
found_by: tuple[str, ...] # mechanism ids (and their pairs) that hit inside the labeled function
def evaluate_loo(cases: Sequence[PairCase], *, facts: SemanticFacts) -> LooResult: ... # per slice/profile/mechanism metrics
Only seeded | reviewed pairs seed; every trusted pair is held out once. known_limit pairs are excluded.
Lever
MechanismEdit(lever="mechanisms", action="admit"|"retract", mechanism_id, source="loo"|"export", rationale). Validator: id must exist in the exporter's candidate set; shape guard in GUARD_KINDS; no free text fields beyond rationale. Proposer: admit candidates whose leave-one-out hit recovers a currently missed holdout pair in some profile; retract admitted records that leak on holdout fixed sides. Gate unchanged; revert restores mechanisms.jsonl byte for byte.
Data Models
Store row additions: origin, review_tier, pairs, shape, guard. Manifest: "variants": {"mechanisms_searched": n, "files_searched": m, "findings": k, "merged_into_overlay": j}. LOO artifact loo.json: per_slice, per_profile, per_mechanism, outcomes[] with found_by.
Error Handling
| Condition | Response |
|---|---|
| Extra absent for a language | shape derivation and search skipped for that language; degradation variants_language_unsupported |
| Labeled sink not found in vuln IR | pair skipped from export with a reason in the export report |
Store row lacks shape (sandbox origin) |
not used for search; still used for prove ordering |
| Admission leaks on holdout | round rejected, store reverted |
Testing Strategy
- Unit: derive_shape on a Python pair (request →
os.system, fix adds allowlist) yields sinksystem, arity 1, source position 0, guardallowlist_test; match_shapes finds a variant in another file and ignores constant arguments; shape has no path or literal;<anon>sinks resolve to enclosing named function for LOO scoring. - Integration: export over the sast slice writes deduped rows with
origin=corpus; variant search on the split-sink Python fixture emits avariantfinding merged into the overlay coverage record; LOO on a three-pair toy slice reports which pair taught which. - Improve: admitting a record that recovers a holdout pair is accepted; one that leaks is rejected with byte-identical store.
- Gates: detection, map, local pair gates unchanged; extra-free green with semantic tests skipping.
Security Considerations
Shapes carry no code text; store rows are data. Variant findings never execute anything; sandbox candidates follow the existing safety check.
Performance
Search is O(call sites × shapes) per file with a name index on sink_name; bounded by MAP budget and max_mechanisms. LOO parses each excerpt once and reuses IR.
Migration Strategy
- Store fields and exporter; export the sast and vibe-py trusted pairs; record counts.
- Variant search behind
[variants].enabled(default true in standard); manifest counters. - LOO command and roadmap table.
- Lever and proposer.