Files
Open_Ultra_SAST/.kiro/specs/corpus-seeded-mechanisms/design.md
T
Marius CiepluchandClaude Fable 5.1 8f444051f2 refactor(corpus-seeded-mechanisms): apply review round 2 suggestions
- 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
2026-09-05 14:55:00 +02:00

14 KiB
Raw Blame History

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 seeded and reviewed pairs.
  • 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.
  • mechanisms lever 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).
  • mechanisms lever, its validator rules, proposer, journal fields.
  • Report rendering of mechanism ids.

Out of Boundary

  • OverlayRecord fields beyond an optional mechanism_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), adjudicate output, MechanismStore, PairCase/load_pair_catalog/select_slice, pair scorer matching helpers (read), run_round gate, EvolveValidator, journal, reports.

Revalidation Triggers

  • Variant findings stamped above suspicion without overlay or sandbox agreement.
  • Seeding from advisory or title pairs.
  • 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 — exposes hotspot_from_finding; variant findings enter the ranked candidate slice as zero-score hotspots from cli.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 sink system, arity 1, source position 0, guard allowlist_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 a variant finding 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

  1. Store fields and exporter; export the sast and vibe-py trusted pairs; record counts.
  2. Variant search behind [variants].enabled (default true in standard); manifest counters.
  3. LOO command and roadmap table.
  4. Lever and proposer.