Task 1.1 (Req 2.1-2.4). Spec approved by the maintainer in session. - semantic/variants.py: Shape (language, sink trailing name, arity, source positions and kinds, closed guard kind, mechanism id) with a stable text-free key; derive_shape finds the labeled sink call site in the vulnerable FileIR, traces argument identifiers through binds to a fact source, a parameter or a container read, and classifies the guard the fixed side added from the labeled function's added statements against a closed set (none when unknown). No path, line or literal enters a shape; a constant-only call teaches nothing. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01T7XD7x5KonMdmGRkp63EQG
8.2 KiB
Implementation Plan
-
1. Foundation: shapes and store fields
-
1.1 Define structural shapes and derive them from a pair
Shapewith language, sink name, arity, source positions and kinds, closed guard kind, mechanism id;derive_shapefinds the labeled sink call site in the vulnerableFileIR, traces source positions through binds to a fact source, parameter, or container read, and classifies the guard from statements present only on the fixed side.- Shapes contain no path, line, or literal beyond identifiers.
- Observable: a Python pair (request input into
os.system, fix adds an allowlist) yields sinksystem, arity 1, source position 0, guardallowlist_test; an unlabeled sink returns None; the shape key is stable across runs. - Requirements: 2.1, 2.2, 2.3, 2.4
- Boundary: Shape
-
1.2 Add additive mechanism fields and a corpus writer with dedupe
Mechanismgains origin, review_tier, pairs, shape, guard with defaults;append_from_pairdedupes by shape key and extends provenance; old JSONL rows load unchanged.- Observable: appending the same shape from two pairs yields one row listing both pairs; a pre-existing sandbox row loads with
origin = "sandbox". - Requirements: 1.3, 1.4
- Boundary: Store
- Depends: 1.1
-
2. Core: export and search
-
2.1 Export mechanisms from trusted pairs as a maintainer command
- Only
seededandreviewed, non-known-limit, parse-ok pairs seed; the command runs offline over the vendored catalog and reports skipped pairs with reasons; extra-absent languages are skipped with a degradation. - Observable:
ousast mechanisms export --slice sastwrites corpus rows and reports the skippedtitle/advisory/known-limit pairs. - Requirements: 1.1, 1.2, 1.5, 2.5
- Boundary: Export
- Depends: 1.2
- Only
-
2.2 Search a tree for variants and merge with overlay flows
- For each parsed file within the MAP budget, call sites matching a shape's sink name, arity, and source positions (fact source, parameter, container read) produce
variantfindings atsuspicionwith the mechanism id; when the overlay has a flow at the same call site, its record gainsmechanism_idand no duplicate finding is emitted. - Observable: the split-sink Python fixture yields a variant finding merged into the overlay coverage record; a constant-argument call does not match;
quickemits no variants. - Requirements: 3.1, 3.2, 3.3, 3.6
- Boundary: VariantSearch
- Depends: 2.1
- For each parsed file within the MAP budget, call sites matching a shape's sink name, arity, and source positions (fact source, parameter, container read) produce
-
2.3 Make variant findings sandbox-eligible and count them in the manifest
- Variant findings enter the candidate set after overlay promotions under the existing safety check and caps; the manifest records mechanisms searched, files searched, findings, and merges.
- Observable: with the fake sandbox a variant finding runs; the manifest
variantsblock is present on standard scans and absent on quick. - Requirements: 3.4, 3.5, 6.2
- Depends: 2.2
-
3. Measurement: leave-one-out
-
3.1 Evaluate the corpus leave-one-out
- For each trusted pair, seed a temporary store from the others, search both excerpts, and score detection and silence with the pair rules; report per slice, profile, mechanism, and per pair which mechanism found it; write
loo.json. - Observable:
ousast pairs --slice vibe-py --loo --jsonreports recall, silence, Youden per slice/profile/mechanism andfound_byper pair; the run is offline. - Requirements: 4.1, 4.2, 4.3, 4.4
- Boundary: LOO
- Depends: 2.2
- For each trusted pair, seed a temporary store from the others, search both excerpts, and score detection and silence with the pair rules; report per slice, profile, mechanism, and per pair which mechanism found it; write
-
3.2 Record the leave-one-out baseline
- Roadmap gains a leave-one-out table per slice next to the pair baselines; not a gate.
- Observable: roadmap table present; gates unchanged.
- Requirements: 4.4, 7.2
- Depends: 3.1
-
4. Integration: improve lever
-
4.1 Add the mechanisms lever with a closed validator
MechanismEditadmit/retract by record id from the exporter's candidate set; validator rejects free-form shapes and unknown guard kinds; journal records pair provenance and metrics.- Observable: a free-form edit is rejected; an admit edit of an exported record passes validation and appears in the journal.
- Requirements: 5.1, 5.2, 5.5
- Boundary: Lever
- Depends: 2.1
-
4.2 Propose admissions from leave-one-out misses and retractions from leaks under the existing gate
- Admit candidates that recover a currently missed holdout pair; retract admitted records that leak on holdout fixed sides; existing accept gate and per-profile clause decide; rejection reverts the store byte for byte.
- Observable: a scripted candidate that recovers a holdout pair is accepted and persisted; one that leaks on a fixed side is rejected and the store is unchanged.
- Requirements: 5.3, 5.4
- Depends: 4.1, 3.1
-
5. Integration: reports
-
5.1 Show mechanism id, known fix guard, and provenance on findings
- Markdown and SARIF show mechanism summary, guard shape, and pair provenance for findings carrying a mechanism id; variant findings are labeled suspicions unless raised.
- Observable: a standard scan report on the split-sink fixture names the mechanism and its guard; SARIF properties carry the id.
- Requirements: 6.1, 6.3
- Depends: 2.2
-
6. Validation
-
6.1 Prove offline tests and unchanged gates
- Unit and integration tests run without network, model, or sandbox; semantic-marked tests skip without the extra; detection, map, and local pair gates unchanged; extra-free suite green.
- Observable: both pytest matrices green; gate entrypoints unchanged.
- Requirements: 7.1, 7.2, 7.3
- Depends: 2.3, 3.1, 4.2, 5.1
Implementation Notes
- Spec approved by the maintainer in session 2026-09-05 ("continue with corpus-seeded-mechanisms"); approvals recorded in spec.json.
- Task 1.1 (2026-09-05, RED first: 5 failing tests in
tests/test_variants.py,ModuleNotFoundError: openultrasast.semantic.variants; then green):semantic/variants.pywithShape(language, sink_name, arity, source_positions, source_kinds, guard, mechanism;key()/to_dict()/from_dict()text-free), closedGUARD_KINDS/SOURCE_KINDS,derive_shape(vuln_ir, fixed_ir, *, function, sink, line, mechanism, facts, vuln_text, fixed_text). Deviation from the design signature: the two excerpt texts are passed in because guard statements (if x not in ALLOWED,if len(x) > n) are not call sites or binds inFileIR; the guard kind is classified from the lines of the labeled function that exist only on the fixed side, closed set,nonewhen unknown; nothing of that text enters the shape. Source kinds trace argument identifiers through binds (fact-source pattern ->fact_source; subscript/.get(/request container ->container_read; parameter ->parameter), preferring fact_source, then parameter, then container_read. A constant-only sink call or a missing function/call site yields None. - Task 1.2 (2026-09-05, RED first: 3 failing tests in
tests/test_mechanism_export.py—Mechanismlackedorigin,append_from_pairmissing; then green): additiveMechanism.origin/review_tier/pairs/shape/guardwith defaults so sandbox rows load unchanged;MechanismStore.load()folds the append-only log by id (latest row wins) so a re-seeded shape carries the extendedpairs;append_from_pairuses a deterministic idcorpus:<sha1(shape.key())[:16]>so the improve lever can name records across machines;corpus_mechanisms()selects searchable rows. Existingorder_promotionstests unchanged and green. - Review round 1 (2026-09-05) APPROVED 1.1 and 1.2 (reviewer re-simulated RED, ran six mutations, verified the import boundary and that sandbox rows load unchanged; isolated suite 493 / 480 green, statics and gates clean). Suggestions carried into group 2: trim
_CONTAINER_READto the design's subscript/.get(definition so no source vocabulary lives outside facts; intersect line and sink in_labeled_call;append_from_pairkeeps the highest tier and accumulates provenance tags when a shape is re-seeded; sync design §Shape to the implemented signature. - Seeding reads
PairCase.review_tierfrompair-corpus-honestytask 8.1; until it lands, sast and vibe-py rows default toadvisory/seededper that design and only vibe-py seeds. - Guard classification is heuristic and closed;
noneis allowed and reported, never used to demote.