measure(contributor-scan): 7.9 measured portioning completes 281 of 406, up from 153

Same 500-region PHP run on frozen source: completion 281/406 against 153
(hard cap) and 131 (7.8), no portion killed, per-family findings never
lower, declared target still entailed. Records the 7.9 task, its limits,
and corrects the 7.8 reuse note, which was stated backwards.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
This commit is contained in:
Marius Ciepluch
2026-09-23 08:51:43 +02:00
co-authored by Claude Opus 5.5
parent d930f5bc69
commit cda679cb3b
2 changed files with 43 additions and 1 deletions
+12 -1
View File
@@ -1283,8 +1283,19 @@ the ontology describes libraries, frameworks and language constructs, never repo
- Done when a killed taint batch hands back the answers it finished and the retry asks only the rest; portion sizes follow observed cost; the streamed and legacy forms both parse; and the plugin population completes more questions per unit of budget than under 7.7 with no finding lost.
- _Status 2026-09-22:_ implemented; host controls pass for the assembler in both forms and under a kill, for the remainder-only retry, and for the sizer; the joern-gated check passes on the real engine — the taint query streams one line per request, and the comment-sink and literal-destination controls hold under the streamed form. The plugin measurement is running on a frozen export of the committed source.
- _Two things the measurement exposed on the way, both fixed:_ the evidence pass was one unportioned batch protected only by the ceiling — 22,125 requests on a plugin push transaction — and now runs in portions of 4,000; and a first cut of the harvest read a timed-out process's pipe BEFORE killing it, which blocks forever because a pipe whose writer is alive never reaches end-of-file. The suite's own hanging-engine control caught that after twenty minutes of what I mistook for CPU starvation.
- _A stated limit:_ the reuse layer's re-query of `missing` ids calls the backend batch directly and so bypasses sink-weighted portioning. Those re-queries are the small remainder of a cached scan, and streaming still keeps what a killed one finished, so it is recorded here rather than fixed.
- _A stated limit:_ the reuse layer's re-query of `missing` ids calls the backend batch directly and so bypasses sink-weighted portioning. Those re-queries are the small remainder of a cached scan, and streaming still keeps what a killed one finished, so it is recorded here rather than fixed. _Corrected 2026-09-23:_ the limit was stated backwards and was worse. The reuse layer wrapped only `run_batch`, and the portioner asks `run_batch_once`, so every portioned query bypassed the reuse cache — and its source-root normalization — entirely. Fixed under 7.9: both paths go through the cache.
- _End-to-end tuning, 2026-09-23, the honest version._ The first end-to-end run REGRESSED completion (64 of 406 against 7.7's 153) while finding as many defects, and each fix exposed the next: the adaptive sizer read one portion at 953 s because it timed `run_batch`, whose own split-retry folded in, and collapsed; giving the portioner a single-attempt path (`run_batch_once`) fixed the timing but only after it was wired on the PARTITIONED graph too, which every plugin uses and which the first wiring missed; and even then completion sat at 56 of 406 because the portioner iterated a fixed list and dropped the remainders of killed portions, stopping at 1,263 s of a 3,600 s budget. The queue re-asks those remainders. The sizer now adapts both ways in the trace — 268 up to 535 after a cheap portion, halved to 267 to 133 on kills — and re-asks what a kill left. Each step is unit-tested: the assembler in both forms and under a kill, the single-attempt path, coverage-halving, and queue termination. _Final end-to-end 2026-09-23, mixed and told straight._ Against the hard-cap-plus-reserve baseline on the same 500-region PHP run: findings IDENTICAL at 125 either way, completion slightly LOWER under portioning (131 of 406 against 153), wall time higher (2,982 s against 2,338). Portioning does not beat the cap on throughput here; it matches it on what it finds. What it buys is kill-safety -- the maintainer's actual ask -- and reaching the same 125 findings is the evidence that streaming loses nothing the cap kept while no longer discarding a near-complete batch when the ceiling falls mid-answer. Completion did not rise because the requeue re-asks a killed portion's remainder, and on this subject those are the expensive output-encoding questions that keep hitting the ceiling in smaller pieces; telling a transient kill from a genuinely-too-expensive family is the next lever, not more portioning. Neither approach is release-ready regardless: 32-38% completion against a 95% gate, on development data that qualifies nothing. Evidence: `benchmarks/measurements/2026-09-23-portioning-vs-hard-cap.json`.
- _Boundary: cpg backend, contributor-scan scan budget, taint query_
- _Depends: 7.7_
- _Requirements: 4.1, 6.3, 8.1_
- [x] 7.9 Size portions from what each answer cost, and tell a transient kill from an expensive question
- **The next lever named by 7.8, and the trace said it was a different lever.** Re-read on 2026-09-23, the 7.8 run's sizer did not lose completion to expensive families so much as to a death spiral: after two kills the budget fell to 3 sink visits per portion and never recovered. Portions of 3–6 visits finished in 25–30 s, which under the assumed 70 s fixed start read as "no information", so the budget could shrink and never grow — and the rest of the run paid a JVM start per handful of requests.
- The instrument first: the taint query streams each answer's own cost (`ms`), carried as a reserved `__timing__` list through shards, partitions and reuse. The sizer then MEASURES what it used to assume — the fixed start as the wall time the answers do not explain (median over whole portions), and a seconds-per-sink-visit rate per family from that family's own answers. Portions are cut in predicted seconds and grow request by request from the head of the queue.
- A kill names its culprit. Answers stream in request order, so the first unanswered id was running at the kill and every id after it never started: those go straight back to the queue, the culprit is asked alone after everything else, and its family is costed at the lower bound it proved. A request that fails alone after more than a portion's target is `query_too_expensive`, reported apart from `query_failed`.
- Two defects fixed on the way: a scan small enough for one portion took a single attempt and lost whatever a kill cut off (it now goes through the queue); and requests the arbitration reserve held back were reported as `query_failed` although they were never asked (now `query_budget_reserved`).
- _Measured 2026-09-23, same 500-region PHP run, 3,600 s, frozen at `1b28465`:_ **281 of 406 questions completed, against 153 (hard cap) and 131 (7.8)**, in 2,987 s. Findings per family never fell: injection 24→33, output encoding 70→125, path 15→35, untrusted destination 16→31; 224 in total against 125. The declared target `classes/class.memberorder.php:936:getMemberOrderByCode` is found at `model_entailed`. The measured fixed start was 22–24 s throughout. **No portion was killed in the whole run** — every one answered whole — so the gain is the sizing, and the culprit path is covered by host controls but was NOT exercised end to end here.
- _Limits, stated:_ one subject, one budget. The census keeps ten examples per family, so it shows per-family counts did not fall, not that the earlier findings are a strict subset. The extra findings are unadjudicated: output encoding on PHP `echo` is the family most likely to carry false positives, and 224 is a count, not a precision. 99 taint questions were still held for arbitration, and access control, config and prototype were never reached (0 of 39) — the ranker puts them last, and the reserve cuts the tail. Completion is 69%, still short of the 95% M4 gate; this is development data and qualifies nothing. Evidence: `benchmarks/measurements/2026-09-23-measured-portioning.json`.
- _Boundary: cpg backend, taint query, contributor-scan scan budget, push reuse_
- _Depends: 7.8_
- _Requirements: 4.1, 6.3, 8.1_
@@ -0,0 +1,31 @@
{
"schema_version": 1,
"experiment": "measured-portioning-v1",
"date": "2026-09-23",
"task": "contributor-scan 7.9",
"frozen_commit": "1b28465",
"claim_scope": "Portions sized from engine-reported per-answer cost, per family, with a measured fixed start and kill-culprit isolation; one PHP subject (Paid Memberships Pro) at 500 regions, 3600 s, 3 GB container. Same command as the 7.7 and 7.8 runs.",
"hard_cap_7_7": {"completed": 153, "of": 406, "findings": 125, "seconds": 2338.2},
"portioned_7_8": {"completed": 131, "of": 406, "findings": 125, "seconds": 2982.0},
"measured_7_9": {"completed": 281, "of": 406, "findings": 224, "seconds": 2987.2},
"per_family_7_9_vs_7_7": {
"injection": {"done": [49, 93], "found": [24, 33]},
"output_encoding": {"done": [32, 65], "found": [70, 125]},
"path": {"done": [64, 96], "found": [15, 35]},
"untrusted_destination": {"done": [8, 27], "found": [16, 31]},
"access_control": {"done": [0, 0], "found": [0, 0]},
"config_secrets": {"done": [0, 0], "found": [0, 0]},
"prototype": {"done": [0, 0], "found": [0, 0]}
},
"declared_target": {"site": "classes/class.memberorder.php:936:getMemberOrderByCode", "rung": "model_entailed", "found": true},
"instrument_check": "The first portion logged 21 answered in 187 s with a measured fixed start of 23 s; the fixed start stayed at 22-24 s for all 25 portions. The 7.8 constant assumed 70 s.",
"kills": 0,
"reading": "Completion rose from 131-153 to 281 of 406 at the same budget. The 7.8 loss was a sizer death spiral (budget stuck at 3 sink visits because portions under an assumed 70 s start read as uninformative), and measuring the fixed start and per-family rates removed it. No portion was killed, so the gain is attributable to sizing; the culprit-isolation path is unit-tested only.",
"limits": [
"One subject, one language, one budget.",
"The census keeps ten examples per family: per-family finding counts did not fall, but strict containment of the earlier findings is not shown.",
"Findings are unadjudicated; 224 is a count, not a precision. PHP output encoding is the likeliest false-positive source.",
"99 taint questions were held for arbitration and access_control/config_secrets/prototype (39 questions) were never reached.",
"69% completion against a 95% M4 gate; development data that qualifies nothing."
]
}