Task 8.3 (Req 10.1-10.5). - pairs.py: catalog rows with vendored = false carry their harvest recipe and no excerpts; evaluate_catalog harvests them into OPENULTRASAST_PAIR_CACHE (default ~/.cache/openultrasast/pairs) only when `pairs --pointers` or OPENULTRASAST_PAIRS_NETWORK=1 allows the network, otherwise records pointer_pair_skipped (a failed fetch is pointer_pair_fetch_failed with detail); a warm cache never scores without the switch. select_vendored for tests and gates. - harvest.py: materialize_pointer harvests into the cache without the license requirement (nothing is redistributed); redaction applies. - catalog_gen.py: emits pointer rows with recipe fields; training manifests skip them. - pair_gate and improve/evolve.evaluate_profiles score vendored pairs only; cli `pairs --pointers`; .gitignore covers the cache path. - tests: loader, skip/degradation, cache hit, fetch failure, warm cache with network off, profile gate exclusion, harvest and generator paths; conftest keeps every test offline whatever the shell exports. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01T7XD7x5KonMdmGRkp63EQG
30 KiB
Implementation Plan
-
1. Foundation: label schema and vocabulary
-
1.1 Add the closed mechanism vocabulary as data
- A versioned TOML next to the catalogs lists every mechanism id with a one-line definition.
- Catalog load rejects an expected row whose mechanism is not in the vocabulary.
- Observable: loading a catalog with an unknown mechanism fails with the pair name; the vocabulary file lists the v1 set from the design.
- Requirements: 2.1, 2.4
- Boundary: Vocabulary, LabelSchema
-
1.2 Extend pair and expected fields with provenance, split, known_limit, function
- Pair rows accept
provenancefrom the closed set,splitof train or holdout, and an optionalknown_limitreason. - Expected rows accept
function; rows withsink = "unknown"and no function and no rule id are rejected. - Defaults: sast is synthetic, local/github/vfc are human, split is train.
- Observable: a unit test loads a row with each new field and rejects an unknown provenance value and a sink-unknown row without function or rule id.
- Requirements: 2.2, 2.3, 2.4, 2.5, 2.6
- Boundary: LabelSchema
- Depends: 1.1
- Pair rows accept
-
2. Core: honest scorer
-
2.1 Count coverage with a source as detection and as leak
- Overlay pair scoring treats a coverage record with a non-empty source the same as a promote on both the vulnerable and the fixed side.
- Outcome records which detection kinds contributed.
- Observable: the owasp-java-sqli vulnerable side counts as detected via coverage; a coverage record on a fixed tree under silent policy counts as a leak.
- Requirements: 1.1
- Boundary: Scorer
-
2.2 Match by function and flag weak labels
OverlayRecordgains an optionalfunctionfilled from the enclosing function's line range; default stays None.- When an expected row names a function, a record matches only inside that function; CWE equality alone never matches; rows without function, sink, or rule id are counted as weak labels.
- Observable: a promotion outside the named function does not match; a CWE-only row is reported under weak_labels and produces no match.
- Requirements: 1.2, 1.3
- Boundary: Scorer
- Depends: 1.2
-
2.3 Report side-by-side scorers and loss counters for every overlay slice
- Every overlay slice gets
overlayandinventoryscorers, not only vfc. - Payload reports parse_failed files and unadjudicated proposals per side and per slice, plus known_limit and achievable metrics.
- Observable:
pairs --slice sast --jsoncontains both scorers and a loss block; known-limit pairs are listed and excluded from achievable metrics. - Requirements: 1.4, 1.5, 2.3, 4.3
- Boundary: Scorer
- Depends: 2.1, 2.2
- Every overlay slice gets
-
2.4 Migrate existing vfc rows and record the new baseline
generate_catalog.pyemits function, mechanism, provenance, and split from recipes; every existing recipe gains a mechanism and half the pairs are declared holdout.- Roadmap records sast and vfc numbers before and after the scorer change.
- Observable: no vfc expected row is a weak label; the catalog loads under the new validation; the roadmap has a before/after table.
- Requirements: 2.6, 1.6
- Boundary: LabelSchema
- Depends: 2.3
-
2.5 Add the hunter scorer for overlay slices
- With a hunter model configured, each pair function is run through the tool hunter and scored with the same detection and leak rules; results appear as
scorers[slice].hunter; without a model the scorer is skipped with a degradation. - Default tests use a scripted client; no network.
- Observable:
pairs --slice sast --jsonwith a scripted client shows ahunterscorer; without a model the payload recordshunter_model_unavailable. - Requirements: 1.7
- Boundary: Scorer
- Depends: 2.3
- With a hunter model configured, each pair function is run through the tool hunter and scored with the same detection and leak rules; results appear as
-
3. Core: harvest library (parallel with 2)
-
3.1 (P) Anchor name extraction at the declarator
- Comments and string literals are masked before the name search; the anchor line must not be inside a mask and must be followed by a parameter list and a body before a semicolon.
- Existing vfc harvest tests still pass through a thin shim.
- Observable: a fixture whose doc comment mentions
Curl_follow()above the definition extracts from the definition line; existing prototype-skip test still passes. - Requirements: 3.1, 3.6
- Boundary: HarvestLibrary
-
3.2 (P) Add line-range, hunk, and Python block modes
- A recipe may give a line range, or a fix commit whose first changed hunk selects the enclosing function on both sides; Python functions are cut by indentation.
- Observable: each mode round-trips a local string fixture; no network in tests.
- Requirements: 3.2, 3.3, 3.4, 3.6
- Boundary: HarvestLibrary
- Depends: 3.1
-
3.3 Re-harvest the curl pairs
- All curl recipes are re-run with the anchored extractor; excerpts start at the declarator.
- Pairs that still fail to parse because of preprocessor-split control flow are kept and show up in the loss counters, not removed.
- Observable: no vfc excerpt has a mid-comment first code line; the loss block for vfc reports the remaining parse failures.
- Requirements: 3.5
- Boundary: SliceCatalogs
- Depends: 3.1, 2.3
-
4. Core: sast hygiene (parallel with 3)
-
4.1 (P) Restore the Juliet strcpy buffer declarations and mark the Java hash pair
- The Juliet CWE-121 vuln and fixed excerpts include the differing buffer declarations from the full case file.
- The OWASP Java hash pair is declared known_limit with reason cross_artifact, or vendors the property file.
- Observable: the two Juliet bodies differ; owasp-java-hash appears under known_limit; sast achievable count is 10 or 11 accordingly.
- Requirements: 4.1, 4.2, 4.3
- Boundary: SliceCatalogs
-
5. Integration: new slices
-
5.1 Add the vibe-py slice from Real-Vuln-Benchmark (escalated: the "at least one agent provenance" observable cannot be met without vendoring unlicensed LLM-generated repos; see Implementation Notes)
- Recipes carry pinned commit, file, line range, function, primary and acceptable CWEs, authorship, and vulnerable or trap flag; harvest uses line-range mode; traps become fixed-side twins; provenance from authorship.
- README states the educational and CTF caveat and the license.
- Observable: the slice loads offline with tens of pairs, at least one agent and one human provenance, and scores with per-profile metrics.
- Requirements: 5.1, 5.4, 5.5
- Boundary: SliceCatalogs
- Depends: 3.2, 1.2
-
5.2 (P) Add the vfc-js slice from SecBench.js fix commits
- Recipes point at upstream package repositories with parent and fix commit, file, and sink line; harvest uses hunk mode; command injection, path traversal, and code injection first; prototype pollution and ReDoS rows carry their mechanism and may be known_limit.
- No SecBench.js test files are vendored; each pair records the upstream package license.
- Observable: the slice loads offline with tens of pairs and JavaScript or TypeScript files; no file from the SecBench.js repository is present.
- Requirements: 5.2, 5.4, 5.5
- Boundary: SliceCatalogs
- Depends: 3.2, 1.2
-
5.3 (P) Add the agent-vfc slice from agent-trailer security fixes (re-scoped by Req 9: rows load at tier
titleand never gate; task closes when 8.1 lands and the slice scores)- Recipes record repo, parent, commit, file, the agent trailer or marker, mechanism, and reviewer; harvest uses hunk mode; provenance is agent.
- README carries the review checklist: real vulnerability, single mechanism, no secrets, license stated.
- Observable: the slice loads offline with tens of reviewed pairs in JavaScript, TypeScript, or Python and scores under the agent profile.
- Requirements: 5.3, 5.4, 5.5
- Boundary: SliceCatalogs
- Depends: 3.2, 1.2
-
5.4 Point datasets.toml at the researched corpora and document slices
- Rows for Real-Vuln-Benchmark, SecBench.js, AIDev, BaxBench, SecurityEval, CodeSecEval, CWEval, CyberSecEval with license and caveats; CyberSecEval is marked rule-defined, not recall ground truth.
- Pair README lists the new slices and the profile and mechanism views.
- Observable: dataset test sees the new names; README mentions each slice.
- Requirements: 5.6
- Depends: 5.1, 5.2, 5.3
-
6. Integration: provenance and profile gate
-
6.1 (P) Compute a deterministic provenance fingerprint at scan time
- Signals from agent configuration paths, generation markers, and git trailers over a bounded commit sample; agent at 85 percent trailer share, mixed on any signal, human otherwise; never synthetic.
- Manifest records the value and the signal list; no model call.
- Observable: an empty tree is human with no signals; a tree with AGENTS.md is mixed; a synthetic git log of Claude trailers is agent; git absence is recorded as a signal.
- Requirements: 6.1, 6.2, 6.3, 6.4, 7.5
- Boundary: ProvenanceFingerprint
-
6.2 Add per-profile and per-mechanism metrics and a profile filter
- Payload gains per_profile and per_mechanism; the pairs command accepts a profile filter.
- Observable:
pairs --slice all --jsoncontains both maps;--profile agentevaluates only agent pairs. - Requirements: 7.1
- Boundary: Scorer
- Depends: 2.3, 5.4
-
6.3 Reject improve-loop changes that regress any profile on holdout
- After the existing four clauses pass, holdout pairs are evaluated per profile; a drop in pair-correct or Youden beyond tolerance for a profile with at least the minimum pairs rejects with a profile reason; smaller profiles are reported only; at most four profiles.
- Observable: a test change that improves synthetic and regresses agent holdout is rejected with
profile_regression:agent; a profile under the minimum appears in the journal and does not gate. - Requirements: 7.2, 7.3, 7.4
- Boundary: ProfileGate
- Depends: 6.2
-
7. Validation
-
7.1 Prove offline load, CLI, and unchanged gates
- Default catalog load sees the three new slices and every file on disk; each slice command exits zero with per-profile metrics; new names are absent from the stage-1 fixture list; detection gate, map gate, and local pair gate outputs are unchanged; extra-free suite is green with overlay assertions skipping.
- Observable: extra-free pytest green; gate entrypoints produce the same verdicts as before this spec.
- Requirements: 8.1, 8.2, 8.3, 8.4, 1.6
- Depends: 5.4, 6.3
-
7.2 Close real-world-vfc-slice as superseded
- Its spec.json records superseded_by; its brief carries a closure note; the roadmap lists this spec and the two engine specs in dependency order.
- Observable: spec status for real-world-vfc-slice shows closed and superseded; roadmap has the new order.
- Requirements: 8.3
- Depends: 7.1
-
8. Amendment: review tiers and pointer slices
-
8.1 Add review tiers to the label schema and the payload
review_tieron every pair with per-slice defaults;per_tiermetrics in the payload;titlerows load and replace the candidates file;reviewedrequires a reviewer name.- Observable: agent-vfc rows load with tier
title;pairs --jsonreportsper_tier; areviewedrow without a reviewer is rejected by the loader. - Requirements: 9.1, 9.2, 9.4, 9.5
- Boundary: LabelSchema, Scorer
-
8.2 Gate the improve loop on seeded and reviewed tiers only
- The per-profile holdout clause filters pair cases to
seeded | reviewedbefore scoring;advisoryandtitlepairs never gate. - Observable: a regression confined to
titlepairs does not reject a round; the same regression onreviewedpairs does. - Requirements: 9.3
- Boundary: ProfileGate
- Depends: 8.1
- The per-profile holdout clause filters pair cases to
-
8.3 Score non-vendored pointer pairs through a local cache
- Catalog rows with
vendored = falsecarry the recipe; evaluation harvests both sides into the cache whenOPENULTRASAST_PAIRS_NETWORK=1(orpairs --pointers), otherwise skips with a degradation; the cache is git-ignored; tests and gates select vendored pairs only. - Observable: a pointer row evaluates to the same outcome shape as a vendored row when network is on, is skipped with
pointer_pair_skippedwhen off, and no cache file appears under the repository. - Requirements: 10.1, 10.2, 10.3, 10.4, 10.5
- Boundary: Scorer, HarvestLibrary
- Depends: 8.1
- Catalog rows with
-
8.4 Add the LLM-generated Real-Vuln repositories and unlicensed agent fixes as pointer pairs
- vibe-py gains the 40 LLM-generated repositories as
agent,seeded,vendored = falserows built bybuild_recipes.py --pointers; agent-vfc gains the license-less candidates astitle,vendored = falserows. - Observable:
pairs --slice vibe-py --pointers --jsonscores both provenances; the default suite and gates are unchanged. - Requirements: 10.6, 5.1
- Boundary: SliceCatalogs
- Depends: 8.3
- vibe-py gains the 40 LLM-generated repositories as
Implementation Notes
-
Review round 1 (kiro-review, 2026-09-04) REJECTED groups 1, 2, 3, 5, 6; group 4 APPROVED. Remediated in round 2: mypy/ruff clean;
tool_hunter.pyreverted (HarnessX-preferring hunter client moved toreachability-flow-modelas a task); weak labels never match on the inventory-fallback path; exact/alias sink matching restored; loader requires a named function on vfc-style slices;catalog_gen.derive_functionignores provenance headers and never emits<anon>; vfc-js rebuilt without duplicate/misattributed fixes; vendored Google API key redacted and a code-safe redaction pass added toharvest.write_excerpt; improve CLI fails loud on a missing pair catalog; profile tolerance compares pass rates; known-limit pairs emit no improve signals. RED evidence for the remediation tests was captured before the fixes (5 + 1 failing tests). -
Review round 2 (2026-09-05) APPROVED groups 1, 3, 4, 6; REJECTED groups 2 and 5. Remediated RED-first (4 failing tests captured, then green):
function_atskips<anon>as well as<module>; the scorer matches a labeled function by line-range containment from the vulnerable side's parse (_in_named_function), name equality only when no range exists; the loader rejects reserved words as function labels;catalog_genderives labels from the declarator on the range's start line (chained and property arrows covered), drops parse-failed excerpts instead of regex-labeling them, and--pruneremoves excerpt directories that are neither cataloged nor candidates (vfc-js: 16 pairs, 17 dirs incl. one candidate-less prune); the agent-vfc test asserts the empty-until-reviewed invariant explicitly. -
Review round 3 (2026-09-05) REJECTED groups 2 and 5 again; per protocol a fresh kiro-debug agent produced the fix plan (ROOT_CAUSE: scorer ranges used raw parser names while labels used declarator names; labels followed formatting hunks; no sink on vfc-js rows). Remediated RED-first (8 failing tests captured, then green): one shared helper
src/openultrasast/semantic/functions.py(declarator_name,named_function_ranges,function_at,spans_named) used byoverlay._flows_for,pairs._overlay_scan,pairs._with_functions, andcatalog_gen; bare-parameter arrows and callback callees are never names;_in_named_functionis containment-only and an unresolvable label is counted asunresolved_function_labelsloss with a!unresolvedmiss marker; hunter matching uses containment too; harvest recordsupstream_startin every excerpt header (extract_pair_with_lines);catalog_gen.derive_functionmapssink_locationthroughupstream_start(±10 lines, CWE-family sink tokens, regex.exec(excluded), falls back to the unique function owning a sink token, then to the mapped line itself for wrapper sinks, and only then to the first non-whitespace hunk; vfc-js re-harvested with headers, regenerated with--prune(17 pairs, 17 dirs),sinkemitted on 13/17 rows. -
Measured after review round 4 remediation (2026-09-05): sast 4/11 (achievable 4/10), Youden +0.273, unresolved 0; vibe-py 13/35, Youden +0.371 (inventory 7), unresolved 0, parse-failed 0; vfc-js 5/17, Youden +0.235, unresolved 0; vfc 0/176, Youden -0.131, parse-failed files 36, unresolved labels 23 after round 5: 18 pairs parse-failed on both sides (36 files, walker gap for overlay-ir-completeness); 3 pairs whose
.infilespreprocess_repositorynever lists as targets (openssl-cve-2022-1292andopenssl-cve-2022-2068-hash_dirPerlc_rehash.in,openssl-cve-2026-31790-rsasve_generateC templatersa_kem.c.in; the Perlsubrule is dead on the scorer path until.infiles are targeted, accepted as visible loss); 2 pairs whose multi-line declarators the walker leaves<anon>and the one-line regex cannot rescue (chromium-cve-2022-2162,openssl-cve-2025-69421: walker gaps). No unresolved label remains inside the Scorer boundary. -
Review round 4 (2026-09-05) REJECTED groups 2 and 5 on two defects, both fixed RED-first (4 failing tests captured): the shared helper now demotes undeclared parser names to
<anon>only for JavaScript/TypeScript (bare-parameter arrows), so C/Java/Python keep their parser names when the return type sits on the previous line;declarator_nameknows Perlsuband never names a statement (return foo(,print(,synchronized ();catalog_genimports the package at module top and exits loud without it,_rangesno longer swallows exceptions, and--prunerefuses to run when no recipe derived a label; the Python block extractor decides indentation on masked lines so triple-quoted strings never end a block (vfapi-main-put-userre-harvested, now parses). -
Review round 5 (2026-09-05) APPROVED group 5; REJECTED group 2 on one defect, fixed RED-first (2 failing tests captured, then green):
declarator_namereturned C++ qualified names (FileReaderLoader::ArrayBufferResult) that no unqualified catalog label matches, so both C++ vfc pairs were unresolvable and had been misattributed to the walker; the helper now returns the unqualified tail for the method form,chromium-cve-2019-5786resolves (vfc unresolved 24 -> 23), sast/vibe-py/vfc-js numbers unchanged, and the measured note above carries the corrected per-cause breakdown. -
Review round 6 (2026-09-05) APPROVED group 2 (verify-completion:
pytest -q468 passed / 4 skipped, extra-free 455 / 17 skipped, ruff/mypy clean, three gates PASS, reviewer reproduced RED by deleting the fix line and re-measured all four slices). Feature-level verdict stays REJECTED for completeness only: groups 7 and 8 open, two escalations undecided. Reviewer suggestions carried to the roadmap, not blocking: a multi-line-aware declarator rescue infunctions.pywould name the two<anon>C/C++ ranges without touchingcst.py; the inventory-fallback miss strings lack the!unresolvedsuffix the overlay path attaches (counter is correct). -
Task 8.1 (2026-09-05, RED first: 6 failing tests captured —
REVIEW_TIERS/select_tiermissing,reviewedwithout reviewer accepted, noper_tierpayload, agent-vfc absent from the default catalog, CLI payload lackedper_tier; then green):PairCase.review_tier/reviewer,PairOutcome.review_tier,PairEvalResult.per_tier,select_tier,GATING_TIERS; loader derives the tier from the row, else fromreviewer(pending -> title, a name -> reviewed), else the slice default, and rejects unknown tiers andreviewedwithout a reviewer;catalog_genstampsreview_tier(andreviewer) on every generated row,titlerows entercatalog.toml,catalog-candidates.tomlis retired (deleted on regenerate); local rows carryreviewer = "Marius Ciepluch"(fixture author per git history). Task 7.1 evidence: default catalog loads 277 pairs across local/github/sast/vfc/vibe-py/vfc-js/agent-vfc with every file on disk and no name on the stage-1LANGUAGE_MANIFESTS;pairs --slice {vibe-py,vfc-js,agent-vfc} --jsonexit 0 withper_tier,per_profile,per_mechanism; detection/map/local-pair gate outputs byte-identical to commit0116f9b(pre-spec); extra-free suite 459 passed / 17 skipped; full suite 472 passed / 4 skipped; each of the six task commits is green in isolation (worktree bisect: 413 -> 430 -> 430 -> 430 -> 459 -> 465 -> 468 passed, ruff clean). Measured agent-vfc at tiertitle: 0/29, Youden +0.000 ({'typescript': 18, 'python': 6, 'javascript': 5}; no row in any language yields a finding on either side: the excerpts carry no rule-known sink (CORS defaults, missing guards, prototype pollution: title-guessed mechanisms, several wrong on inspection, e.g.dnf-plugin-anyrepo-config-26d19ahas no path join); inventory rules do run on.tsfiles, but the overlay plane reportslanguage_unsupportedfor TypeScript (18 rows) and counts them neither as parse-failed nor as a separate loss — mechanisms {'source_reaches_sink': 7, 'permissive_default': 6, 'path_join_user_input': 3, 'prototype_pollution': 7, 'missing_auth_guard': 1, 'identity_from_request_body': 5}), unresolved labels 1. This is the honest title-tier baseline the engine specs measure against, not a scoring defect. Task 5.3 closes with 8.1 as re-scoped: the slice loads and scores; human review promotes rows toreviewed. -
Review round 7 (2026-09-05) APPROVED tasks 7.1 and 7.2; REJECTED 8.1 on documentation only (REVIEW.md still described the candidates file; READMEs claimed the tier gate filter before 8.2 existed; TS zeros misattributed). Remediated: REVIEW.md/READMEs rewritten to tier semantics, note corrected,
catalog_gen._review_tierprecedence aligned with the loader (explicit tier wins),training/manifest.jsonlrows carryreview_tier, design §LabelSchema candidates sentence superseded, roadmap agent-vfc line updated. Deferred to the engine specs: alanguage_unsupportedloss counter (reviewer suggestion). -
Task 8.2 (2026-09-05, RED first:
test_profile_gate_ignores_title_and_advisory_tiersfailed withreason='profile_regression:agent'on five title-tier pairs; then green):evolve.evaluate_profilesfilters holdout pairs toGATING_TIERSviaselect_tierbefore scoring, soadvisory/titlepairs never gate and a regression onreviewedpairs still rejects the round. -
Review round 8 (2026-09-05) APPROVED 8.1 and 8.2 (verify-completion:
pytest -q473 passed / 4 skipped, extra-free 460 / 17, ruff/mypy clean, three gates PASS; reviewer reproduced RED by removing the tier filter and checked the default holdout gate now covershumanseeded pairs only, with the 16 title-tier agent rows excluded). Task 5.3 closed with 8.1 as re-scoped. Applied suggestion: the agent-vfc tier test acceptsreviewedrows so promoting a row does not break it. Follow-up for corpus-seeded-mechanisms: report holdout profiles excluded by tier (profiles_ungated) so an operator sees whyagent/syntheticdo not gate. -
Task 8.3 (2026-09-05, RED first: 6 failing tests captured —
pointer_recipe/select_vendoredmissing, loaderKeyError: 'vuln'on avendored = falserow (x2),pairs --pointersunknown flag (SystemExit: 2),validate_recipe(require_license=)unknown,catalog_genskipped pointer recipes as missing excerpts; then green):PairCase.vendored/recipe,pair_cache_dir(OPENULTRASAST_PAIR_CACHE, default~/.cache/openultrasast/pairs),network_allowed(--pointersper run orOPENULTRASAST_PAIRS_NETWORK=1),select_vendored,_pointer_ready(cached -> score; network ->harvest.materialize_pointervia the shared library; elsepointer_pair_skipped; a failed fetch ispointer_pair_fetch_failed),evaluate_catalog(pointers=);harvest.validate_recipe(require_license=False)andmaterialize_pointer(cache only, redaction applies);catalog_genemitsvendored = falserows with recipe fields and no excerpts, training manifest skips them;pair_gatescores vendored local pairs only;.gitignorecovers the cache path. Smoke with real network on two Real-Vuln LLM-repo recipes into a scratchpad cache: network off -> 0 outcomes, 2 xpointer_pair_skipped; network on -> both harvested to<cache>/vibe-py/<name>/{vuln,fixed}.pyand scored (silent fix on both, vuln side undetected, unresolved 0); nothing written under the repository. Suite 479 passed / 4 skipped, extra-free 466 / 17, ruff/mypy clean, three gates PASS. -
Review round 9 (2026-09-05) REJECTED 8.3 on two findings, both fixed RED-first (2 failing tests captured: warm cache + network off scored the pointer pair;
evaluate_profileskept anagentpointer pair):_pointer_readyconsultsnetwork_allowedbefore the cache, so a warm cache only spares the fetch under--pointers/env and never turns a default run into a pointer run;evolve.evaluate_profilesappliesselect_vendoredbefore the tier filter (design amendment: "tests and gates callselect_vendored"; ProfileGate is the gate the design names, so the one-line filter is in boundary). Suggestions applied: fetch failures carry adetail; the harvest module load is memoized; pointer excerpt headers getfunction/mechanismfrom the expected row;tests/conftest.pyautouse fixture clearsOPENULTRASAST_PAIRS_NETWORKand points the cache at a temp dir for every test; full-catalog on-disk assertions useselect_vendored. -
Review round 10 (2026-09-05) APPROVED 8.3 (verify-completion:
pytest -q481 passed / 4 skipped; extra-free 468 / 17 inside a no-network namespace; ruff/format/mypy clean; three gates PASS; reviewer reproduced both RED tests by mutation, the warm-cache/network-off skip on the real cache, and that the default run creates no cache and touches no network). Non-blocking: top-level README/docs mention--pointersonly in benchmarks/pairs/README.md (ride with 8.4); the cwd-relative harvest library path follows the DEFAULT_CATALOG convention. -
ESCALATION (task 5.1 observable): vibe-py has
humanprovenance only because the 40 LLM-generated Real-Vuln repos have no license (Req 5.4 wins). Spec owner decision needed: accept human-only vibe-py, or obtain a license statement upstream. -
ESCALATION (Req 5.3): agent-vfc rows are unreviewed. They are NOT in the loaded catalog;
benchmarks/pairs/agent-vfc/catalog-candidates.tomlandREVIEW.mdlist them for a human reviewer. Task 5.3 stays open until reviewed. -
2026-09-04 execution evidence (manual mode, main context): full suite
uv run pytest440 passed / 4 skipped;python -m openultrasast.gatePASS;map_gatePASS;pair_gatePASS (local 3/3);ousast --helpboots. Validation commands come from.github/workflows/ci.yml. -
Scorer (2.1–2.3): coverage-with-source counts both ways; function-scoped match; CWE-only rows are
weak_label;scorers[slice]has overlay+inventory for every overlay slice,hunterwith a client; payload gainsper_profile,per_mechanism,achievable,known_limit,loss,degradations.OverlayRecord.functionis the one additive overlay field;_flows_forparses once and keeps function ranges. -
2.4 baseline after the scorer change is in the roadmap (sast 4/11 → achievable 4/10; vfc 0/176 under function matching). Regex findings get an enclosing function from a parse so function-labeled rows can match the inventory column.
-
2.5 hunter scorer:
pairs --hunter [--hunter-model]; client resolution prefers HarnessX (resolve_hunter_client(prefer_harnessx=...), best-effort adapter, offline-untested) then any OpenAI-compatible endpoint viaOPENROUTER_API_KEY+OPENROUTER_BASE_URL(DeepSeek documented in.env.example). Live run not executed in this session; scripted-client test covers the scorer. -
3.x harvest: shared
benchmarks/pairs/harvest.py(comment-blindname,line_range,hunkwith next-function and whole-small-file fallbacks,enclosingwithfix_path/fix_line),vfc/harvest.pyis a shim;catalog_gen.py --sliceregenerates catalogs and derivesfunctionfor hunk-harvested pairs from the excerpt diff. 3.3: curl re-harvested; parse failures fell from 50 files to 36 (remaining are#if-split control flow and similar grammar limits, visible inloss.parse_failed_files; engine tolerance belongs tooverlay-ir-completeness). -
5.1 vibe-py: 35 pairs from the 15 licensed human-authored Real-Vuln repos. The 40 LLM-generated repos (
kolega-ai-dev/vc-*) have no license file and were NOT vendored (Req 5.4);build_recipes.py --allow-unlicensedexists for local inspection only. So the slice currently hashumanprovenance only; agent provenance comes fromagent-vfc. -
5.2 vfc-js: 15 pairs from upstream fix commits after dedupe by fix commit and module/repo attribution check (SecBench.js pointer only; 10 unlicensed upstreams, 3 duplicate fixes, 8 misattributed modules skipped; 2 excerpts without a named function not cataloged).
sink_locationis kept as metadata, not as the expected sink. -
5.3 agent-vfc: 29 candidate pairs in
catalog-candidates.toml(not loaded), allreviewer = "pending";REVIEW.mdis the review sheet. Mechanism/CWE were assigned from commit titles bybuild_recipes.pyand must be confirmed by a human. 631 candidates were searched; 202 skipped for missing license; the loadedcatalog.tomlhas 0 rows until review. -
6.1 provenance:
src/openultrasast/provenance.py, manifestprovenanceblock, stageprovenancein_run_scan. -
6.3 profile gate:
run_round(pair_cases=..., profile_tolerance, min_holdout_pairs); CLIimprove --pair-catalog/--no-pair-gate/--profile-tolerance/--min-holdout-pairs; default on with the full catalog holdout split (slower rounds). -
Measured after round-2 remediation (2026-09-04): sast 4/11 (achievable 4/10), Youden +0.273; vibe-py 13/35 pair-correct, Youden +0.371 (overlay) vs inventory 7/35, 0 weak labels; vfc-js 0/15, Youden −0.200 (2 parse-failed files, 0 weak labels; the earlier 5/32 was inflated by first-function labels and duplicate rows); agent-vfc 0 loaded rows, 29 candidates awaiting review.
-
Do not touch
taint.py,facts.py,cst.py, orir.py. Parser ERROR-node tolerance belongs tooverlay-ir-completeness; this spec only makes the loss visible. -
Expect vfc recall to drop when function-scoped matching lands. Record it; do not soften the matcher.
-
BaxBench generation is deferred until deep mode can run its exploit harness; provenance and mechanism fields are designed so that slice slots in later.