From d105d2f84a11abc58938dcf34599f0e604b1442b Mon Sep 17 00:00:00 2001 From: Marius Ciepluch <11855163+norandom@users.noreply.github.com> Date: Mon, 21 Sep 2026 13:45:00 +0200 Subject: [PATCH] fix(scan): carry the partition in both census passes, and PHP completes 120 of 127 The scoping fix looked applied and was not. The same census gap is raised in TWO passes, arbitration and evidence, and only the first carried the partition it came from, so an unscoped copy kept demoting every question in the repository. One key in one dict. What found it was the record, not the code: the census now prints each outcome reason rather than only its status, and three separate causes had been looking identical as a bare count of unresolved. Measured on Paid Memberships Pro at 25 regions: questions completed goes from 0 to 120 of 127, with its 19 findings unchanged. The seven still unresolved are the JavaScript partition own questions, which is correct, because the four missing files are its files and its answers are the ones that could be wrong. Still open, and recorded rather than implied: the push policy _blocking_degradations exempts declared exclusions but does not yet respect a partition, and a post-hoc diagnostic copy carries no language, so a JavaScript gap would still block admission of a PHP finding. 1,419 tests pass, eighteen skipped. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo --- ...2026-09-21-partition-gap-blast-radius.json | 20 +++++++++++++++++-- benchmarks/push/family_census.py | 3 +++ src/openultrasast/model/scan.py | 5 ++++- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/benchmarks/measurements/2026-09-21-partition-gap-blast-radius.json b/benchmarks/measurements/2026-09-21-partition-gap-blast-radius.json index 81e40a0..31c7908 100644 --- a/benchmarks/measurements/2026-09-21-partition-gap-blast-radius.json +++ b/benchmarks/measurements/2026-09-21-partition-gap-blast-radius.json @@ -22,7 +22,8 @@ "reading": "The JAVASCRIPT partition expected 36 files and its graph holds 32. The four absent ones are a webpack build output, a minified third-party library, a build config and one source file. The PHP partition is intact. So a plugin's bundled select2.min.js costs the entire PHP analysis its admissibility: 4,425 PHP regions demoted by four JavaScript files.", "why_the_gap_is_right_and_its_blast_radius_is_not": "A file expected but missing from a graph could hold a sanitizer, so answers drawn from THAT partition may be wrong and demoting them is conservative. A missing JavaScript file cannot make a PHP answer wrong. This is the same over-broad blast radius as part one and the third instance of one principle in this thread: a gap should reach the questions it can actually affect.", "fix_direction": "Scope a partition's census gap to that partition's questions. The language is known where the gap is raised, and path-to-language is already computed and used to dispatch every request, so the data exists; it is not threaded to the demotion.", - "second_order": "Three of the four files are build output or minified vendored code. The JavaScript vendored list holds only node_modules/, so a minified library under js/ is treated as first-party. Excluding build artefacts would remove this instance without addressing the blast radius." + "second_order": "Three of the four files are build output or minified vendored code. The JavaScript vendored list holds only node_modules/, so a minified library under js/ is treated as first-party. Excluding build artefacts would remove this instance without addressing the blast radius.", + "superseded_by": "part_two_closed" }, "measured_after_part_one": { "questions_completed": 0, @@ -39,5 +40,20 @@ "limits": [ "One multi-language subject. A single-language PHP repository would isolate part one's effect and has not been measured.", "Part one is verified by argument and by the test suite, not yet by a subject that completes because of it." - ] + ], + "part_two_closed": { + "change": "A census gap now carries the partition it was raised for, and an integrity gap reaches only that partition's questions. The same gap is raised in TWO passes, arbitration and evidence, and carrying the language in one but not the other left an unscoped copy that demoted everything: the fix looked applied and was not, until the record printed each outcome's reason rather than only its status.", + "measured_on_pmpro": { + "questions_completed_before": 0, + "questions_completed_after": 120, + "questions_total": 127, + "findings_unchanged": 19, + "outcome_reason": { + "completed:query_answered_and_arbitrated": 120, + "unresolved:graph_incomplete": 7 + }, + "the_seven_still_unresolved": "the JavaScript partition's own questions, which is correct: the four missing files are its files, so its answers are the ones that could be wrong" + }, + "still_open": "The push policy's _blocking_degradations exempts declared exclusions but does not yet respect a partition, and a post-hoc diagnostic copy carries no language. A JavaScript partition's gap would still block admission of a PHP finding." + } } diff --git a/benchmarks/push/family_census.py b/benchmarks/push/family_census.py index 8bbfa79..9f0d184 100644 --- a/benchmarks/push/family_census.py +++ b/benchmarks/push/family_census.py @@ -94,6 +94,9 @@ def main() -> int: # Why the outcomes say what they say, counted rather than inferred: a family can find something while # every one of its questions is recorded unresolved, and the two numbers then disagree in public. "outcome_status": dict(collections.Counter(q.status for q in result.question_outcomes)), + # WHY an outcome is not completed. Counting only the statuses left three separate causes looking + # identical, and each one had to be found by reading code rather than by reading the record. + "outcome_reason": dict(collections.Counter(f"{q.status}:{q.reason}" for q in result.question_outcomes)), } asked = collections.Counter(q.identity.family for q in result.question_outcomes) diff --git a/src/openultrasast/model/scan.py b/src/openultrasast/model/scan.py index 09a5cea..a22d5d5 100644 --- a/src/openultrasast/model/scan.py +++ b/src/openultrasast/model/scan.py @@ -812,7 +812,10 @@ def _evidence_pass( { "stage": "model", "reason": "cpg_empty" if "methods" in entry and methods <= 0 else entry.get("census_failure", "cpg_empty"), - **{k: entry[k] for k in ("methods", "files", "missing_file_names") if k in entry}, + # The partition travels here too. This pass raises the same gap as the arbitration pass, + # and carrying the language in one place and not the other left an unscoped copy that + # demoted every question in the repository -- the scoping fix looked applied and was not. + **{k: entry[k] for k in ("methods", "files", "missing_file_names", "census_language") if k in entry}, } ) if shards > 1: