From 7c1c2d44758e578cd110a265ea6b77cac0631238 Mon Sep 17 00:00:00 2001 From: Marius Ciepluch <11855163+norandom@users.noreply.github.com> Date: Mon, 14 Sep 2026 08:07:06 +0200 Subject: [PATCH] fix(contributor-scan): retain declared Gruntfile and report census gaps honestly --- .../census-repair-verification.md | 76 ++ .../pre-push-runtime-followup.md | 25 + .kiro/specs/contributor-scan/spec.json | 13 +- .kiro/specs/contributor-scan/tasks.md | 9 + .kiro/specs/pre-push-safety-net/spec.json | 8 +- .kiro/steering/safety-net.md | 9 + .../2026-09-14-node-census-repair.json | 793 ++++++++++++++++++ ops/frontend-retention/README.md | 9 + ops/frontend-retention/install.py | 10 +- ops/smoke_partitions.py | 3 + src/openultrasast/cpg/backend.py | 15 +- src/openultrasast/model/partitions.py | 18 +- src/openultrasast/model/scan.py | 24 +- tests/test_frontend_retention.py | 6 + tests/test_model_partitions.py | 19 +- tests/test_model_scan.py | 20 +- tests/test_model_scope.py | 7 +- tests/test_push_policy.py | 9 +- 18 files changed, 1048 insertions(+), 25 deletions(-) create mode 100644 .kiro/specs/contributor-scan/census-repair-verification.md create mode 100644 benchmarks/measurements/2026-09-14-node-census-repair.json diff --git a/.kiro/specs/contributor-scan/census-repair-verification.md b/.kiro/specs/contributor-scan/census-repair-verification.md new file mode 100644 index 0000000..01dfb57 --- /dev/null +++ b/.kiro/specs/contributor-scan/census-repair-verification.md @@ -0,0 +1,76 @@ +# Task 2.19 — measured input retention and census completeness + +The maintainer accepted the proposed contributor-scan follow-up after the pre-push +NO-GO report. The Kiro root-cause-first workflow identified a concrete shared-boundary +repair; no new detector, ranking policy or rollout threshold is introduced. + +## Debug Report +- ROOT_CAUSE: Joern 4.0.625's default filename filter excludes Gruntfile.js. A fresh read-verified NodeGoat input has 44 files/95,411 bytes but its raw graph census contains 43 files, 564 methods and 3,750 calls. The partition adapter substitutes zero counts for this incomplete census, incorrectly producing cpg_empty. +- CATEGORY: CONFIG_GAP +- FIX_PLAN: Retain the original Gruntfile under an explicit versioned frontend policy; preserve actual census counts and named omissions while keeping incomplete questions unresolved. +- VERIFICATION: Exact pinned Node input/census before and after; packaged native PHP/JavaScript partition smoke; malformed/missing census controls; full regression suite and canonical gates. +- NEXT_ACTION: RETRY_TASK +- CONFIDENCE: HIGH +- NOTES: Independent quality evidence, VAmPI context/transitive witness and runtime amortization remain separate blockers. The debug timing is not a representative hook latency profile. + +## Implementation and review + +`JoernBackend.include_build_configs` explicitly sets the frontend environment option, +overriding inherited ambient values. Runtime and graph identity include the option. +The pinned adaptation exempts only the `Gruntfile.js` basename from the known default +filter when enabled. Paths, bytes and graph connectivity are preserved; no configuration +is executed. Existing test retention and vendor exclusion remain intact. + +Partition census rejection now carries actual counts plus a named completeness reason +and the missing filenames when available. Scan scope and outcomes consume that reason +in both evidence and arbitration passes. A nonempty graph is no longer reported empty. +Empty, absent or malformed census payloads produce an explicit unavailable marker, +with no invented observed counts. Review found this latter gap in the first correction; +three failing controls reproduced it before repair. A missing-file marker survives +aggregation with another valid partition; existing sharded/empty graph limits remain. + +RED evidence: `/tmp/owner-retention-red.log` and `owner-retention-off.log` fail the new +explicit policy assertion. Default ON passes; the supported opt-out remains a real input +policy. Census diagnostics: `owner-census-diagnostic-red.log` and `-off.log` fail three +new expectations; ON passes and the temporary switch was removed. Additional empty, +malformed and absent census controls fail before repair (`owner-census-empty-red.log`). +The final focused suite passes 20 tests, including the combined push regression controls. + +The raw Node check after retention finds all 44 files and 579 methods, with no missing +or extra source names. Both runs use exactly 95,411 readable source bytes. Diagnostic +build/census costs remain tens of seconds and cannot qualify the 30-second hook gate. +The before/after checks use packaged images; final partition smoke also exercises the +updated scan completeness path. Existing eligibility becomes stale after runtime/core +identity changes and admits nothing; no old measurement is relabeled as current PASS. + + +Review also checked regression fixtures that intentionally exercise complete graph evidence. +Those backends now provide a named census for the readable files they are given, matching +the production contract. Their ranking, pruning and comparison assertions are unchanged. +A genuinely empty graph retains `cpg_empty`; only unavailable/partial evidence uses the +new explicit census reasons. Counts and missing filenames are carried into the saved +scan degradations. The final focused scan/scope/partition/policy suite passes 74 tests. + +The final offline image's mixed native PHP/JavaScript partition smoke passes, including +original Gruntfile method/file nodes, test directories/suffixes, first-party source byte +receipts and absence of vendor nodes. No language-specific ranking change was made. + +## Status Report +- STATUS: READY_FOR_REVIEW +- TASK: 2.19 +- RED_PHASE_OUTPUT: Explicit policy OFF and census preservation OFF failures recorded above; three additional missing/malformed census failures reproduced before repair. Temporary diagnostic switch removed. +- TESTS_RUN: Final full suite 1321 passed, 9 skipped in 120.96s, exit 0; 74 focused controls pass; ruff/format/mypy/compileall and detector/map/pair gates pass. Final image builds, native mixed partition smoke exits 0, installed builder identity and stale eligibility rejection verified. + +## Review Verdict +- VERDICT: APPROVED +- TASK: 2.19 +- MECHANICAL_RESULTS: Actual diff reviewed; full regression and packaged checks pass. Existing ordinary-scan test backends now supply named readable-file censuses while retaining their original assertions. No new placeholders, secrets, rank heuristics or vendor restoration. +- FINDINGS: Actual empty graphs retain cpg_empty. Nonempty partial graphs retain counts and missing names; absent/empty/malformed census cannot authorize completion or tier-zero pruning. Runtime/cache identity binds the retention option and installed adaptation. Main-context Kiro fallback; no independent review claimed. +- SUMMARY: Targeted shared input/census repair verified without a latency, transitive-detection or rollout claim. + +## Verification Result +- STATUS: VERIFIED +- CLAIM_TYPE: TASK +- CLAIM: Task 2.19 repairs the measured Gruntfile omission and honest census-completeness handling. +- EVIDENCE: `benchmarks/measurements/2026-09-14-node-census-repair.json` preserves all source receipts, raw before/after census, reproducer, build/smoke logs, packaged identity and final test/gate results. Node census 43/44 → 44/44, methods 564 → 579. Final image `898789cb6cc88507a9fa67276fd442ee4ef9e9f1135b333bafda511fb0532b25` passes mixed native PHP/JavaScript smoke, including the original build/test methods and vendor absence. +- GAPS: Representative runtime/coverage gates, VAmPI change context/transitive evidence and independent qualification remain unresolved. Pre-push task 8.3 and rollout remain NO-GO. Old eligibility is stale and enables no capabilities. diff --git a/.kiro/specs/contributor-scan/pre-push-runtime-followup.md b/.kiro/specs/contributor-scan/pre-push-runtime-followup.md index f6f54eb..4a2b08a 100644 --- a/.kiro/specs/contributor-scan/pre-push-runtime-followup.md +++ b/.kiro/specs/contributor-scan/pre-push-runtime-followup.md @@ -64,3 +64,28 @@ boundaries explicit. Do not fabricate transitivity from a raw finding at the tar Revalidate ordinary VAmPI scan, exact question accounting, push regressions and independent qualification after an approved owner fix. This note records the prerequisite; it does not silently approve an upstream implementation or change detector semantics. + +## 2026-09-14 census diagnosis and targeted retention repair + +The maintainer accepted the proposed contributor-scan follow-up with "ok" after +group 8's NO-GO report. This authorizes the targeted shared-boundary fixes and their +validation; independent qualification and rollout still require their existing gates. + +A fresh offline packaged probe copied and reopened all 44 exact NodeGoat first-party +JavaScript inputs (95,411 bytes), built a graph and inspected its raw named census. +Result: 43 files, 564 methods, 3,750 calls; only `Gruntfile.js` was missing. Build took +21.708s and census 14.990s. Tests were retained. The pinned frontend's +`IgnoredFilesRegex` explicitly drops `Gruntfile.js`; the partition adapter then replaced +the partial census with zero counts, producing the misleading `cpg_empty` diagnosis. + +The first repair narrowly extends the existing versioned frontend adaptation to retain +`Gruntfile.js` under explicit `OUSAST_INCLUDE_BUILD_CONFIGS=1`. It changes neither its +path nor bytes and leaves all other defaults, vendor exclusion and test retention +intact. The backend owns the option and binds it to graph/runtime identity, so old +artifacts cannot silently qualify under the new retention policy. It does not execute +the build configuration. This format-level input repair is not NodeGoat-specific scope +selection or a claim that every possible JavaScript configuration file is now supported. +The partial-census correction now preserves actual counts and missing filenames, +with missing/empty/malformed census still preventing completed questions. Runtime amortization and VAmPI context/witness evidence remain open. + +Source: [Joern 4.0.625 AstGenRunner](https://raw.githubusercontent.com/joernio/joern/v4.0.625/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/utils/AstGenRunner.scala). diff --git a/.kiro/specs/contributor-scan/spec.json b/.kiro/specs/contributor-scan/spec.json index 40810dc..d1cb395 100644 --- a/.kiro/specs/contributor-scan/spec.json +++ b/.kiro/specs/contributor-scan/spec.json @@ -1,7 +1,7 @@ { "feature_name": "contributor-scan", "created_at": "2026-09-08T20:00:00Z", - "updated_at": "2026-09-13T09:29:15.806475+00:00", + "updated_at": "2026-09-14T06:06:32.633059+00:00", "language": "en", "phase": "implementation", "approvals": { @@ -46,6 +46,11 @@ "at": "2026-09-13T09:17:01.788332+00:00", "source": "approved", "action": "Approved frontend-retention prerequisite, task 2.18, including reproducible dependency adaptation; resume pre-push-safety-net 3.4 after verification." + }, + { + "at": "2026-09-14T05:58:44.810694+00:00", + "source": "ok", + "action": "Accepted the proposed shared engine/census/context follow-up after group 8 NO-GO. Task 2.19 covers the confirmed missing Gruntfile and honest census-completeness repair; runtime and independent qualification gates remain unchanged." } ], "implementation_verification": { @@ -53,6 +58,12 @@ "status": "VERIFIED", "review": "APPROVED", "record": "frontend-retention-verification.md" + }, + "2.19": { + "status": "VERIFIED", + "review": "APPROVED", + "independent": false, + "record": "census-repair-verification.md" } } } diff --git a/.kiro/specs/contributor-scan/tasks.md b/.kiro/specs/contributor-scan/tasks.md index efe60a5..c0550f8 100644 --- a/.kiro/specs/contributor-scan/tasks.md +++ b/.kiro/specs/contributor-scan/tasks.md @@ -342,6 +342,15 @@ - _Approval:_ maintainer "approved", 2026-09-13; prerequisite for pre-push-safety-net task 3.4. - _Verified:_ independent review APPROVED; 1,141 tests passed, nine skipped, static checks clean; real PHP/JavaScript test nodes retained and disabled-policy control reproduces omission. See `frontend-retention-verification.md`. +- [x] 2.19 Repair the measured Node input census and preserve honest completeness diagnostics + - Retain the declared original `Gruntfile.js` through the versioned frontend adaptation; bind the explicit policy to runtime/cache identity. + - Preserve observed file/method counts and missing names when census is incomplete; missing, empty or malformed census must not complete questions or become a false clean result. + - Verify the exact 44-file NodeGoat input, native PHP/JavaScript partition smoke, and ordinary scan/hook regressions. Do not infer latency or capability admission from retention. + - _Boundary: existing frontend adaptation, partition census and scan completeness integration._ + - _Requirements: contributor-scan 4.3, 5.6, 6.1; pre-push 2.3, 4.3, 8.2._ + - _Approval: maintainer accepted contributor-scan remediation with "ok" on 2026-09-14; no rollout approval._ + - _Verified: 1321 tests passed, 9 skipped; real 44/44 Node census and packaged native PHP/JavaScript smoke. See `census-repair-verification.md`._ + ## Group 3 — Ship it - [ ] 3.1 Contributor output diff --git a/.kiro/specs/pre-push-safety-net/spec.json b/.kiro/specs/pre-push-safety-net/spec.json index ae52b3b..168b5a7 100644 --- a/.kiro/specs/pre-push-safety-net/spec.json +++ b/.kiro/specs/pre-push-safety-net/spec.json @@ -1,7 +1,7 @@ { "feature_name": "pre-push-safety-net", "created_at": "2026-09-12T15:35:00Z", - "updated_at": "2026-09-13T17:38:57.553583+00:00", + "updated_at": "2026-09-14T06:06:32.633405+00:00", "language": "en", "phase": "implementation", "approvals": { @@ -143,6 +143,12 @@ "feature_complete": false, "record": "validation-group-8.md", "capabilities_enabled": 0 + }, + "owner_remediation": { + "task": "contributor-scan 2.19", + "status": "VERIFIED", + "record": "../contributor-scan/census-repair-verification.md", + "effect": "Node 44/44 input census restored; partial/missing census remains explicitly unresolved. Existing eligibility stale; runtime/context/independent qualification remain NO-GO." } } } diff --git a/.kiro/steering/safety-net.md b/.kiro/steering/safety-net.md index 2a3fde3..bcd5b02 100644 --- a/.kiro/steering/safety-net.md +++ b/.kiro/steering/safety-net.md @@ -111,3 +111,12 @@ context/transitive evidence remains unresolved. Independent reviewed Node/PHP/Py populations are missing; libpng arithmetic/bounds remains unsupported. No real capability is enabled. Engine/census/context prerequisites route to contributor-scan before another frozen qualification attempt. See `pre-push-safety-net/validation-group-8.md`. + + +On 2026-09-14, contributor-scan task 2.19 repaired the confirmed Gruntfile frontend +omission (real Node census 43/44 → 44/44) and the misleading empty-graph diagnostic. +Incomplete or unavailable census retains explicit coverage gaps and cannot complete +questions. Native PHP/JavaScript partition smoke and 1321 tests pass. Runtime/core +identity changed, so prior eligibility is stale and remains disabled. This repairs one +shared prerequisite; pre-push 8.3, runtime amortization, VAmPI context/transitive +evidence and independent qualification remain open. No ranker retune or rollout GO. diff --git a/benchmarks/measurements/2026-09-14-node-census-repair.json b/benchmarks/measurements/2026-09-14-node-census-repair.json new file mode 100644 index 0000000..706b665 --- /dev/null +++ b/benchmarks/measurements/2026-09-14-node-census-repair.json @@ -0,0 +1,793 @@ +{ + "schema_version": 1, + "purpose": "Read-verified frontend retention and honest partition census repair; not latency or capability qualification.", + "task": "contributor-scan 2.19", + "verdict": "PASS for targeted repair; rollout NO-GO", + "source_commit": "c5cb68a7084e4ae7dcc60e6a98768720a81841e8", + "before_image": "sha256:4cca6d20c5c9bfdbfa60c4e8fc991e096bc9b2f35096bb92cc92440e99f946d4", + "retention_probe_image": "sha256:4f941a2f38ebb9ea8dd17c7fb5e78df336712f5e2307972d4ab3ef238b801315", + "final_smoke_image": "sha256:898789cb6cc88507a9fa67276fd442ee4ef9e9f1135b333bafda511fb0532b25", + "inputs": [ + { + "path": "Gruntfile.js", + "bytes": 6359, + "sha256": "2e9fd032cbc6a5594d90c76448ce37b98be0f0d3ef7a66e6b4aabbc674293af6" + }, + { + "path": "app/assets/js/chart/chart-data-morris.js", + "bytes": 3718, + "sha256": "a5511ae7a5614f9f2dd89625bce94d6b794a675dae4987259d087a8d2e8e2693" + }, + { + "path": "app/assets/js/tour/redirects-steps.js", + "bytes": 648, + "sha256": "16babf9b88e186cb4e2a7ab896a78494f6d4a88737c903eb1e23e35579ac651b" + }, + { + "path": "app/data/allocations-dao.js", + "bytes": 3928, + "sha256": "c6aebc971ce7f9c8b2a8f0864a500497de25a3ea0318ef94ecd57cadf6e789ea" + }, + { + "path": "app/data/benefits-dao.js", + "bytes": 1240, + "sha256": "0ac30b097e016eeffb10e1c72d9c657153d3ce67e7b3f4756b2cb127d94181d7" + }, + { + "path": "app/data/contributions-dao.js", + "bytes": 2791, + "sha256": "363065e8b3c32f0997ce2dcb17341eb3e40b7f43b00b72c576dfebf73f551c85" + }, + { + "path": "app/data/memos-dao.js", + "bytes": 1107, + "sha256": "6582195bd913c2d60d63e9b519f9508a3319e07e158fd0c6f31dcbc50a80e8cf" + }, + { + "path": "app/data/profile-dao.js", + "bytes": 3506, + "sha256": "80429a5df579867401a59b1cbd9c1d74f88d5548780fbd909e9504876c1968cc" + }, + { + "path": "app/data/research-dao.js", + "bytes": 750, + "sha256": "f1cf267e56080fc94943ad7d83802b4e9db9420687fac49f84dbc0e45dfa88ac" + }, + { + "path": "app/data/user-dao.js", + "bytes": 4078, + "sha256": "577fb140933a50e010a4bbcb74e975f678d7f3a989467e4d113926f35c042e64" + }, + { + "path": "app/routes/allocations.js", + "bytes": 915, + "sha256": "b5b7da1a6b595799cf6cca402756dd9a3f6e2a8b5d712c1989d23b7f01f349a6" + }, + { + "path": "app/routes/benefits.js", + "bytes": 1340, + "sha256": "bc5516b35ba21884915b88b3f810441e616f068d8a0cf756b143185d5aeda2d2" + }, + { + "path": "app/routes/contributions.js", + "bytes": 2485, + "sha256": "196bdeaa22da4cfe8a851b1b932710b2fcedd21f14ed75d70b739ba759a01003" + }, + { + "path": "app/routes/error.js", + "bytes": 281, + "sha256": "452dc61f9f06a31ea4305edb2e573fc940428615841533b59320792fcfb6fb3f" + }, + { + "path": "app/routes/index.js", + "bytes": 3149, + "sha256": "3ab970b2888bc1501515db24a83cc7851ac9c09d00948c652300401515185eb5" + }, + { + "path": "app/routes/memos.js", + "bytes": 824, + "sha256": "c3543f308f24083436e25e3a72210ad31e611db5c93268d8dd0e340095e132c9" + }, + { + "path": "app/routes/profile.js", + "bytes": 3633, + "sha256": "18575bf3d3710906e57e09e6036e40fd3986bdcca436ed0e2a2875b41f0f2aca" + }, + { + "path": "app/routes/research.js", + "bytes": 1071, + "sha256": "e399f3c70436fa160f00835626bf50f88980268fcd143436af97c9d2297c1d92" + }, + { + "path": "app/routes/session.js", + "bytes": 10199, + "sha256": "e53e38b6eea76b026efaacde07f694e7a2eca005de4083fb74f9393d432a5b25" + }, + { + "path": "app/routes/tutorial.js", + "bytes": 637, + "sha256": "502e0b959b37cbc844d15eadd7a78a40b5281155fcf8d532f0e4abdd2fee8eb9" + }, + { + "path": "artifacts/db-reset.js", + "bytes": 4183, + "sha256": "df5d947ef23786683ab0d5f3c5ed5ef3f25ab93ce7610448c6db79b446168aa9" + }, + { + "path": "config/config.js", + "bytes": 487, + "sha256": "b84b1bf634159caffdef50fe826077e60dbde1e381ceb148ff3b869e789b6962" + }, + { + "path": "config/env/all.js", + "bytes": 365, + "sha256": "31a71288f0d830799da9c39e1b1ad10b0e989282bcf9a31bb48f8f618f37f6ad" + }, + { + "path": "config/env/development.js", + "bytes": 636, + "sha256": "1829bc509dcc6a6eb58b0eee49901df530b9cceafa2ed37e855beacec4971f08" + }, + { + "path": "config/env/production.js", + "bytes": 21, + "sha256": "8222b8169ee86f25cdccd84d340340060ae3f0cff55e2ea9d344d7c332733b71" + }, + { + "path": "config/env/test.js", + "bytes": 318, + "sha256": "b48cd5c17fc9639c1c4198fced69b438c2b0c7fe70e80f0113bcd0d86cd583a5" + }, + { + "path": "server.js", + "bytes": 4860, + "sha256": "48ec5e8f9668b230caf6af27f4a4ffbdcd8b702b376a6bbcc951217917c6ea2e" + }, + { + "path": "test/e2e/integration/allocations_spec.js", + "bytes": 1062, + "sha256": "d345f97bc2c3c9e559d98f3e108ab568303c7509dbe707c4cc49f811d5f94db7" + }, + { + "path": "test/e2e/integration/benefits_spec.js", + "bytes": 1228, + "sha256": "e4eb2765b08769e86b140dceee3091a5466da13933400272076b6de693b5cb71" + }, + { + "path": "test/e2e/integration/contributions_spec.js", + "bytes": 1160, + "sha256": "8504f4cbbe578b91f112b7effffb0e43823af9648c5b406ded5a0d65fb4bbde3" + }, + { + "path": "test/e2e/integration/dashboard_spec.js", + "bytes": 933, + "sha256": "96ef5e6d8b75787ac9a44fcd5f918cd3493680e74c70c45b381fe4416fa7f4f8" + }, + { + "path": "test/e2e/integration/general_spec.js", + "bytes": 2044, + "sha256": "5364de581df32c99964f63baa8486fcd32b922c9f327192f3a1e81ca6cd0ceff" + }, + { + "path": "test/e2e/integration/learn_spec.js", + "bytes": 471, + "sha256": "05e6bf11c5e773cd22861dbe18bced293024a7400c3a89b2c0e111bb88d8eaa1" + }, + { + "path": "test/e2e/integration/login_spec.js", + "bytes": 2377, + "sha256": "e1433dfab270ac3f7b32f4ee904d781c2bb2581804177a0cafc488f6c79efb25" + }, + { + "path": "test/e2e/integration/logout_spec.js", + "bytes": 737, + "sha256": "31ebf201e7d899baf8acd1d50c3fc4e42a5636c263982e5e517a1eaf3e35518f" + }, + { + "path": "test/e2e/integration/memos_spec.js", + "bytes": 991, + "sha256": "625a08e3c9f80cf46f3fe81c2acde3dc76d955bde10aa7c68498b6830b7a86b4" + }, + { + "path": "test/e2e/integration/profile_spec.js", + "bytes": 1509, + "sha256": "e22ce888656d18e020ca2775396e000cc899bdac255a1503f9af2c4ef18ffef0" + }, + { + "path": "test/e2e/integration/research_spec.js", + "bytes": 1108, + "sha256": "4e81ed0c2cf42da656217c1e38a35f9257d0a09467ffbe25d9d19930df05ed3a" + }, + { + "path": "test/e2e/integration/signup_spec.js", + "bytes": 1734, + "sha256": "27c56206888e1d664f852090ed943dc746638724743f7d5fb12d35e39136d22f" + }, + { + "path": "test/e2e/integration/tutorial_spec.js", + "bytes": 1819, + "sha256": "2664d921df2c716695df8c216e93b76a9b09376100ee8bc5c3f4354f0ebdb8a6" + }, + { + "path": "test/e2e/plugins/index.js", + "bytes": 788, + "sha256": "1fd870a6602782c27f87cd1aaaae24da269b20408612f718125b4471242f6e83" + }, + { + "path": "test/e2e/support/commands.js", + "bytes": 839, + "sha256": "967bdb0f97ec8277d952de4273c1bf2fcc000b1eb08de533a80e34157f65e499" + }, + { + "path": "test/e2e/support/index.js", + "bytes": 27, + "sha256": "a74243373740a95e3e3691087776e652ba954fcfd96130c4c374279a7310683d" + }, + { + "path": "test/security/profile-test.js", + "bytes": 13055, + "sha256": "17b6d02097d2fdfaebcdae31e70620fe691b5cabbcd99829d7c8d72ac3d4239a" + } + ], + "before": { + "build_seconds": 21.708134463988245, + "census_seconds": 14.989859963068739, + "raw_census": { + "files": "43", + "methods": "564", + "calls": "3750", + "file_names": [ + "app/assets/js/chart/chart-data-morris.js", + "app/assets/js/tour/redirects-steps.js", + "app/data/allocations-dao.js", + "app/data/benefits-dao.js", + "app/data/contributions-dao.js", + "app/data/memos-dao.js", + "app/data/profile-dao.js", + "app/data/research-dao.js", + "app/data/user-dao.js", + "app/routes/allocations.js", + "app/routes/benefits.js", + "app/routes/contributions.js", + "app/routes/error.js", + "app/routes/index.js", + "app/routes/memos.js", + "app/routes/profile.js", + "app/routes/research.js", + "app/routes/session.js", + "app/routes/tutorial.js", + "artifacts/db-reset.js", + "config/config.js", + "config/env/all.js", + "config/env/development.js", + "config/env/production.js", + "config/env/test.js", + "server.js", + "test/e2e/integration/allocations_spec.js", + "test/e2e/integration/benefits_spec.js", + "test/e2e/integration/contributions_spec.js", + "test/e2e/integration/dashboard_spec.js", + "test/e2e/integration/general_spec.js", + "test/e2e/integration/learn_spec.js", + "test/e2e/integration/login_spec.js", + "test/e2e/integration/logout_spec.js", + "test/e2e/integration/memos_spec.js", + "test/e2e/integration/profile_spec.js", + "test/e2e/integration/research_spec.js", + "test/e2e/integration/signup_spec.js", + "test/e2e/integration/tutorial_spec.js", + "test/e2e/plugins/index.js", + "test/e2e/support/commands.js", + "test/e2e/support/index.js", + "test/security/profile-test.js" + ], + "overlays": "base,controlflow,typerel,callgraph,dataflowOss", + "maxHeapMB": "2048" + }, + "missing": [ + "Gruntfile.js" + ], + "extra": [], + "input_paths": 44, + "input_bytes": 95411, + "unparsed": [] + }, + "after_retention": { + "build_seconds": 23.333617498981766, + "census_seconds": 16.393462762003765, + "raw_census": { + "files": "44", + "methods": "579", + "calls": "4134", + "file_names": [ + "Gruntfile.js", + "app/assets/js/chart/chart-data-morris.js", + "app/assets/js/tour/redirects-steps.js", + "app/data/allocations-dao.js", + "app/data/benefits-dao.js", + "app/data/contributions-dao.js", + "app/data/memos-dao.js", + "app/data/profile-dao.js", + "app/data/research-dao.js", + "app/data/user-dao.js", + "app/routes/allocations.js", + "app/routes/benefits.js", + "app/routes/contributions.js", + "app/routes/error.js", + "app/routes/index.js", + "app/routes/memos.js", + "app/routes/profile.js", + "app/routes/research.js", + "app/routes/session.js", + "app/routes/tutorial.js", + "artifacts/db-reset.js", + "config/config.js", + "config/env/all.js", + "config/env/development.js", + "config/env/production.js", + "config/env/test.js", + "server.js", + "test/e2e/integration/allocations_spec.js", + "test/e2e/integration/benefits_spec.js", + "test/e2e/integration/contributions_spec.js", + "test/e2e/integration/dashboard_spec.js", + "test/e2e/integration/general_spec.js", + "test/e2e/integration/learn_spec.js", + "test/e2e/integration/login_spec.js", + "test/e2e/integration/logout_spec.js", + "test/e2e/integration/memos_spec.js", + "test/e2e/integration/profile_spec.js", + "test/e2e/integration/research_spec.js", + "test/e2e/integration/signup_spec.js", + "test/e2e/integration/tutorial_spec.js", + "test/e2e/plugins/index.js", + "test/e2e/support/commands.js", + "test/e2e/support/index.js", + "test/security/profile-test.js" + ], + "overlays": "base,controlflow,typerel,callgraph,dataflowOss", + "maxHeapMB": "2048" + }, + "missing": [], + "extra": [], + "input_paths": 44, + "input_bytes": 95411, + "unparsed": [] + }, + "final_partition_smoke": { + "purpose": "Real mixed PHP/JavaScript graph census and one global ranker budget; no admission or latency claim", + "shared_budget_seconds": 900, + "include_tests": true, + "inputs": { + "Gruntfile.js": { + "bytes": 46, + "sha256": "a144af904affce5e2ddd5befabb58caf3b5fdfbb5ac580210cf408916ebaa809" + }, + "src/api.php": { + "bytes": 49, + "sha256": "47ee82519e90f5ce2c845035f168bc4eaa4b0cc6e46cfc68626b8144e20b8f72" + }, + "server/api.js": { + "bytes": 51, + "sha256": "a9f1b897b9361d0d30250f82aebec987cfd99e66308c9ea889d5b6211e3cad5e" + }, + "browser/client.js": { + "bytes": 41, + "sha256": "42ef299a3c9a53f1ca77f9e4b027c8d6e854f69b7df765bdb833079198513fc4" + }, + "tests/probe.php": { + "bytes": 48, + "sha256": "93c25992f2b709d4f222d88573a004dfb4e65239ce9a3afc2301f629b148c03d" + }, + "tests/probe.js": { + "bytes": 41, + "sha256": "182f7cb37514b7fc6afbba045a9d1b3ca5dc297c35bb98b9f34c18cfd02864f5" + }, + "vendor/library.php": { + "bytes": 55, + "sha256": "dd2aa2514d7c2dffdf4cfd238d7b795c2cd7bb6be086054b0b6d2278bd2c9599" + }, + "node_modules/library.js": { + "bytes": 46, + "sha256": "5e42f2831e2e32d8c19df33ba98fbda879495b74bd6d7e0b6ff45a077674f398" + }, + "unsupported.go": { + "bytes": 36, + "sha256": "ecf7a397251f1fea2e5992bdc3b0581274c20eb83f0ac403f6b7babe7d7b6b5b" + }, + "checks/probe.test.js": { + "bytes": 41, + "sha256": "182f7cb37514b7fc6afbba045a9d1b3ca5dc297c35bb98b9f34c18cfd02864f5" + }, + "checks/probe.spec.js": { + "bytes": 41, + "sha256": "a2677f51273a325b802a4a628ceb54f0bcea533a672c8997c0439d6ea206de37" + }, + "checks/probe.mock.js": { + "bytes": 41, + "sha256": "bc14cd012a0d2c34a3605ce21a009b7fd335bdf248f81aec3462b506929eabd5" + }, + "checks/probe.e2e.js": { + "bytes": 40, + "sha256": "14884e5a7d6c909f30addd8385bb82f6ea3751bb126192052dabe0eedfb42400" + } + }, + "graph_witnesses": [ + { + "frontend": "javascript", + "input_root": "/tmp/ousast-input-w1rlfqae", + "graph_witness": { + "files": [ + "Gruntfile.js", + "browser/client.js", + "checks/probe.e2e.js", + "checks/probe.mock.js", + "checks/probe.spec.js", + "checks/probe.test.js", + "server/api.js", + "tests/probe.js" + ], + "methods": [ + "build_config_marker", + ":program", + "browser_marker", + ":program", + "js_e2e_marker", + ":program", + "js_mock_marker", + ":program", + "js_spec_marker", + ":program", + "js_test_marker", + ":program", + "js_handler", + ":program", + "js_test_marker", + ":program", + ".assignment", + "eval", + ".fieldAccess" + ] + } + }, + { + "frontend": "php", + "input_root": "/tmp/ousast-input-18arqd_p", + "graph_witness": { + "files": [ + "src/api.php", + "tests/probe.php", + "" + ], + "methods": [ + "", + "php_handler", + "", + "php_test_marker", + "eval" + ] + } + } + ], + "result": { + "findings": [ + { + "site": "src/api.php:2:php_handler", + "family": "injection", + "rung": "model_entailed", + "witness": "$req -> eval($req) in src/api.php (line 2, 2 steps)", + "contradiction": "", + "reached_from": 1 + } + ], + "by_rung": { + "suspicion": 0, + "model_corroborated": 0, + "model_entailed": 1, + "execution_confirmed": 0 + }, + "regions_scanned": 1, + "regions_unjudged": 3, + "regions_unasked": 0, + "model_calls": 0, + "cost_usd": 0.0, + "seconds": 113.28, + "build_seconds": 76.28, + "query_seconds": 19.47, + "query_seconds_by_kind": { + "evidence": 17.47, + "taint": 19.47 + }, + "degradations": [ + { + "stage": "model", + "reason": "cross_partition_semantics_unresolved" + }, + { + "stage": "model", + "reason": "frontend_unsupported" + }, + { + "stage": "model", + "reason": "vendor_semantics_unresolved" + }, + { + "stage": "model", + "reason": "regions_truncated", + "examined": 1, + "total": 4 + } + ], + "tiers": [ + [ + "src/api.php", + "php_handler", + "injection", + 4 + ] + ], + "tier_counts": { + "4": 1 + }, + "requests_pruned": 0, + "partitions": [ + { + "language": "go", + "frontend": null, + "paths": [ + "unsupported.go" + ], + "source_bytes": 0, + "status": "unsupported", + "boundaries": [ + "frontend_unsupported" + ], + "runtime": "unspecified", + "excluded_paths": [ + "node_modules/", + "vendor/" + ], + "unshipped_paths": [] + }, + { + "language": "javascript", + "frontend": "javascript", + "paths": [ + "Gruntfile.js", + "browser/client.js", + "checks/probe.e2e.js", + "checks/probe.mock.js", + "checks/probe.spec.js", + "checks/probe.test.js", + "server/api.js", + "tests/probe.js" + ], + "source_bytes": 342, + "status": "built", + "boundaries": [ + "runtime_unspecified" + ], + "runtime": "unspecified", + "excluded_paths": [ + "node_modules/", + "vendor/" + ], + "unshipped_paths": [ + "checks/probe.e2e.js", + "checks/probe.mock.js", + "checks/probe.spec.js", + "checks/probe.test.js", + "tests/probe.js" + ] + }, + { + "language": "php", + "frontend": "php", + "paths": [ + "src/api.php", + "tests/probe.php" + ], + "source_bytes": 97, + "status": "built", + "boundaries": [], + "runtime": "unspecified", + "excluded_paths": [ + "node_modules/", + "vendor/" + ], + "unshipped_paths": [ + "tests/probe.php" + ] + } + ], + "change_context": null, + "scope": { + "ranker_version": "existing-static-evidence-v1", + "ranking_mode": "static", + "population_complete": true, + "selected": [ + { + "identity": { + "unit": "partition-smoke", + "language": "php", + "path": "src/api.php", + "function": "php_handler", + "family": "injection" + }, + "priority": 0.0, + "evidence": [ + "static_rank=10.0", + "normalized_evidence={\"access_declared_public\":true,\"bound_names\":[\"mysqli_prepare\",\"mysqli_stmt_bind_param\",\"pg_execute\",\"pg_prepare\"],\"carried\":false,\"carried_weak\":false,\"entry\":true,\"family_in_repo\":true,\"shipped\":true,\"sinks\":[{\"arg0_literal\":false,\"arity\":1,\"cleansed_on_call\":false,\"code\":\"eval($req)\",\"line\":2,\"method\":\"php_handler\"}],\"source_local\":false,\"source_near\":false}", + "tier=4", + "score=2.5" + ], + "tier": 4, + "score": 2.5 + } + ], + "deferred": [ + { + "identity": { + "unit": "partition-smoke", + "language": "javascript", + "path": "server/api.js", + "function": "js_handler", + "family": "injection" + }, + "reason": "region_budget", + "evidence": [ + "static_rank=9.0", + "evidence_unknown" + ] + }, + { + "identity": { + "unit": "partition-smoke", + "language": "php", + "path": "tests/probe.php", + "function": "php_test_marker", + "family": "injection" + }, + "reason": "region_budget", + "evidence": [ + "static_rank=2.0", + "evidence_unknown" + ] + }, + { + "identity": { + "unit": "partition-smoke", + "language": "javascript", + "path": "tests/probe.js", + "function": "js_test_marker", + "family": "injection" + }, + "reason": "region_budget", + "evidence": [ + "static_rank=1.0", + "evidence_unknown" + ] + } + ], + "unresolved_boundaries": [ + "evidence_unknown:022bab405f3a4daa5ee1cbca6b54c24d052b3060f915b850bc1b592d2233999c", + "evidence_unknown:d867a0ae5d785b179a30fcef9efcdb8304281d53694a51c2da4180ef5069a53d", + "evidence_unknown:66aa438d134c37f7107503949179ce763fecaa08f0a1d00646ee8a5f446d88f7", + "cross_partition_semantics_unresolved", + "frontend_unsupported", + "vendor_semantics_unresolved" + ] + }, + "question_outcomes": [ + { + "identity": { + "unit": "partition-smoke", + "language": "php", + "path": "src/api.php", + "function": "php_handler", + "family": "injection" + }, + "status": "unresolved", + "reason": "graph_incomplete", + "raw_rows_json": "[{\"bound\":\"\",\"bounded\":false,\"inLabeledScope\":true,\"length\":2,\"paths\":1,\"sanitized\":false,\"sink\":\"eval($req)\",\"sinkArg0Literal\":false,\"sinkArity\":1,\"sinkFile\":\"src/api.php\",\"sinkLine\":\"2\",\"sinkMethod\":\"php_handler\",\"source\":\"$req\",\"sourceKind\":\"parameter\"}]" + } + ], + "family_coverage": [ + { + "unit": "partition-smoke", + "language": "javascript", + "family": "injection", + "selected": 0, + "completed": 0, + "unanswered": 0, + "deferred": 2, + "unsupported": 0 + }, + { + "unit": "partition-smoke", + "language": "php", + "family": "injection", + "selected": 1, + "completed": 0, + "unanswered": 1, + "deferred": 1, + "unsupported": 0 + } + ] + } + }, + "final_identity": { + "provenance": { + "repository": "/tmp", + "engine_runtime": "7213365694f836758cb5fe2b05504cbbb735b8c2f0dea5d5dd19546a1f5fd43d", + "engine": "joern-4.0.625", + "facts": "32edb6e1a78f1afb08ac4248cd436be16663e0d90dc1b3aaf948f2ce49a5a8f0", + "graph_layout": "403dda8d804c6109800ab95677f1c9a7c6158b9c83e58a65e2e6baa5132eefa8", + "queries": "7bf090e053331f03d04c9290c8a593d00efd4d390f4b77b8382c2befc47996cc", + "policy": "6fc8e3cf0d3a4f45dd882e37a753e34bc0cd187295cfbd3cbb6fc36ecf9cea20", + "core": "78716c62112f9aafb120f6c0547208f60216a2ef46f689cd4e81f5d63702db82", + "semantics": "2edd13e1582b9a1d5d611b01f9862fafcb8220dcc07838fe42f56e53be00c7f1", + "ranking_mode": "evidence", + "model": "disabled", + "capabilities": "9f0d85961ed49526a3131e71ec3f7e01aeca752da74ba7d74650168853781c9c", + "cache": "cold_no_reuse" + }, + "eligibility_status": "stale", + "capabilities": 0, + "builder_identity_verified": true, + "adaptation": { + "revision": "ousast-frontend-retention-v1", + "builder_sha256": "5a27bac97b4ecd2871a8e1c847dbf2e4b6c506dfa0303ad56c12b74732bd05c9", + "policy": "OUSAST_INCLUDE_TESTS=1; OUSAST_INCLUDE_BUILD_CONFIGS=1 retains Gruntfile.js", + "sources": { + "astgen.tar.gz": [ + "https://codeload.github.com/joernio/astgen-monorepo/tar.gz/refs/tags/javascript-astgen/v3.50.1", + "8d9728dca8eab694a0f07bcd7a1c9a88368cb7bd354fc19c2ee8ca8611ff869a" + ], + "AstGenRunner.scala": [ + "https://raw.githubusercontent.com/joernio/joern/v4.0.625/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/utils/AstGenRunner.scala", + "9963f949360d7e17baed1bd8875b717b2855745cf7260eef8a87d17533a54268" + ], + "bun.zip": [ + "https://github.com/oven-sh/bun/releases/download/bun-v1.4.2/bun-linux-x64.zip", + "36368faef7527875d5ffa52e53cd48021741f2a83eb6208a8dd64068d422a913" + ] + }, + "original_jar_sha256": "49fe488a79cc52b4e1d2e056143ec414f59e0007ed25f054c5fa7ae4aa34dff3", + "adapted_jar_sha256": "8de05769501aaebf0dd6b631b10085e9b09193f6f15a766f8c08e2d80df39cfb", + "astgen_sha256": "1a5dc5c4bb89bd6bb3da75e15ccb6ab69c0d821b206edfdafe041e1740c24bc7", + "compiled_entries": [ + "io/joern/jssrc2cpg/utils/AstGenRunner$$anon$1.class", + "io/joern/jssrc2cpg/utils/AstGenRunner$.class", + "io/joern/jssrc2cpg/utils/AstGenRunner$astGenMetaData$.class", + "io/joern/jssrc2cpg/utils/AstGenRunner.class", + "io/joern/jssrc2cpg/utils/AstGenRunner.tasty" + ] + } + }, + "logs": { + "owner-retention-red.log": "...F [100%]\n=================================== FAILURES ===================================\n_____ test_build_config_retention_is_explicit_and_overrides_ambient_policy _____\n\nmonkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x714808cd0d90>\n\n def test_build_config_retention_is_explicit_and_overrides_ambient_policy(monkeypatch):\n monkeypatch.setenv('OUSAST_INCLUDE_BUILD_CONFIGS', '0')\n> assert JoernBackend()._jvm_env()['OUSAST_INCLUDE_BUILD_CONFIGS'] == '1'\nE AssertionError: assert '0' == '1'\nE \nE - 1\nE + 0\n\ntests/test_frontend_retention.py:36: AssertionError\n=========================== short test summary info ============================\nFAILED tests/test_frontend_retention.py::test_build_config_retention_is_explicit_and_overrides_ambient_policy\n1 failed, 3 passed in 0.13s\n", + "owner-retention-off.log": "...F [100%]\n=================================== FAILURES ===================================\n_____ test_build_config_retention_is_explicit_and_overrides_ambient_policy _____\n\nmonkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7516ef218750>\n\n def test_build_config_retention_is_explicit_and_overrides_ambient_policy(monkeypatch):\n monkeypatch.setenv('OUSAST_INCLUDE_BUILD_CONFIGS', '0')\n> assert JoernBackend()._jvm_env()['OUSAST_INCLUDE_BUILD_CONFIGS'] == '1'\nE AssertionError: assert '0' == '1'\nE \nE - 1\nE + 0\n\ntests/test_frontend_retention.py:36: AssertionError\n=========================== short test summary info ============================\nFAILED tests/test_frontend_retention.py::test_build_config_retention_is_explicit_and_overrides_ambient_policy\n1 failed, 3 passed in 0.66s\n", + "owner-retention-green.log": ".... [100%]\n4 passed in 0.11s\n", + "owner-census-diagnostic-red.log": ".....F.....F.F [100%]\n=================================== FAILURES ===================================\n___________ test_partial_partition_census_never_completes_questions ____________\n\ntmp_path = PosixPath('/tmp/pytest-of-mc/pytest-1693/test_partial_partition_census_0')\n\n def test_partial_partition_census_never_completes_questions(tmp_path):\n for name in (\"a.py\", \"b.py\"):\n (tmp_path / name).write_text(\"print('readable')\")\n \n class Backend:\n def build(self, root, **kwargs):\n return CpgResult(\n Path(\"graph\"),\n lambda q, p: [],\n run_batch=lambda q, reqs: {**{rid: [] for rid in reqs}, \"__census__\": [{\"methods\": 2, \"files\": 1}]},\n )\n \n region = ScanRegion(path=\"a.py\", function=\"f\", language=\"python\", families=(\"injection\",), rank=1.0, source=\"entry_point\")\n result = scan_repository(tmp_path, [region], backend=Backend(), budget=ScanBudget(tiering=False))\n assert result.question_outcomes[0].status == \"unresolved\"\n> assert \"partition_file_census_unavailable\" in result.scope.unresolved_boundaries\nE AssertionError: assert 'partition_file_census_unavailable' in ('population_not_asserted_complete', 'evidence_unknown:0fe0311405a7be0c4af80d713c6deb70e6c91dd8f99d5e81500010e081d0a881', 'cpg_empty')\nE + where ('population_not_asserted_complete', 'evidence_unknown:0fe0311405a7be0c4af80d713c6deb70e6c91dd8f99d5e81500010e081d0a881', 'cpg_empty') = ScopeDecision(ranker_version='existing-static-evidence-v1', ranking_mode='static', population_complete=False, selected..._asserted_complete', 'evidence_unknown:0fe0311405a7be0c4af80d713c6deb70e6c91dd8f99d5e81500010e081d0a881', 'cpg_empty')).unresolved_boundaries\nE + where ScopeDecision(ranker_version='existing-static-evidence-v1', ranking_mode='static', population_complete=False, selected..._asserted_complete', 'evidence_unknown:0fe0311405a7be0c4af80d713c6deb70e6c91dd8f99d5e81500010e081d0a881', 'cpg_empty')) = ModelScanResult(findings=(), by_rung={'suspicion': 0, 'model_corroborated': 0, 'model_entailed': 0, 'execution_confirm...epository', language='python', family='injection', selected=1, completed=0, unanswered=1, deferred=0, unsupported=0),)).scope\n\ntests/test_model_partitions.py:171: AssertionError\n____________ test_synthetic_census_node_cannot_mask_missing_source _____________\n\ntmp_path = PosixPath('/tmp/pytest-of-mc/pytest-1693/test_synthetic_census_node_can0')\n\n def test_synthetic_census_node_cannot_mask_missing_source(tmp_path):\n from openultrasast.model.partitions import build_partitions\n \n for name in (\"a.php\", \"b.php\"):\n (tmp_path / name).write_text(\"\", str(root / \"a.php\")]}],\n },\n )\n \n graph = build_partitions(tmp_path, build, None)\n try:\n result = graph.run_batch(\"taint\", {\"q\": {\"file\": \"a.php\"}})\n> assert result[\"__census__\"][0][\"files\"] == 2\nE assert 0 == 2\n\ntests/test_model_partitions.py:290: AssertionError\n__________ test_unnamed_sharded_census_remains_explicitly_incomplete ___________\n\ntmp_path = PosixPath('/tmp/pytest-of-mc/pytest-1693/test_unnamed_sharded_census_re0')\n\n def test_unnamed_sharded_census_remains_explicitly_incomplete(tmp_path):\n from openultrasast.model.partitions import build_partitions\n \n (tmp_path / \"app.php\").write_text(\" assert result[\"__census__\"][0][\"methods\"] == 20\nE assert 0 == 20\n\ntests/test_model_partitions.py:337: AssertionError\n=========================== short test summary info ============================\nFAILED tests/test_model_partitions.py::test_partial_partition_census_never_completes_questions\nFAILED tests/test_model_partitions.py::test_synthetic_census_node_cannot_mask_missing_source\nFAILED tests/test_model_partitions.py::test_unnamed_sharded_census_remains_explicitly_incomplete\n3 failed, 11 passed in 0.88s\n", + "owner-census-diagnostic-off.log": ".....F.....F.F [100%]\n=================================== FAILURES ===================================\n___________ test_partial_partition_census_never_completes_questions ____________\n\ntmp_path = PosixPath('/tmp/pytest-of-mc/pytest-1694/test_partial_partition_census_0')\n\n def test_partial_partition_census_never_completes_questions(tmp_path):\n for name in (\"a.py\", \"b.py\"):\n (tmp_path / name).write_text(\"print('readable')\")\n \n class Backend:\n def build(self, root, **kwargs):\n return CpgResult(\n Path(\"graph\"),\n lambda q, p: [],\n run_batch=lambda q, reqs: {**{rid: [] for rid in reqs}, \"__census__\": [{\"methods\": 2, \"files\": 1}]},\n )\n \n region = ScanRegion(path=\"a.py\", function=\"f\", language=\"python\", families=(\"injection\",), rank=1.0, source=\"entry_point\")\n result = scan_repository(tmp_path, [region], backend=Backend(), budget=ScanBudget(tiering=False))\n assert result.question_outcomes[0].status == \"unresolved\"\n> assert \"partition_file_census_unavailable\" in result.scope.unresolved_boundaries\nE AssertionError: assert 'partition_file_census_unavailable' in ('population_not_asserted_complete', 'evidence_unknown:0fe0311405a7be0c4af80d713c6deb70e6c91dd8f99d5e81500010e081d0a881', 'cpg_empty')\nE + where ('population_not_asserted_complete', 'evidence_unknown:0fe0311405a7be0c4af80d713c6deb70e6c91dd8f99d5e81500010e081d0a881', 'cpg_empty') = ScopeDecision(ranker_version='existing-static-evidence-v1', ranking_mode='static', population_complete=False, selected..._asserted_complete', 'evidence_unknown:0fe0311405a7be0c4af80d713c6deb70e6c91dd8f99d5e81500010e081d0a881', 'cpg_empty')).unresolved_boundaries\nE + where ScopeDecision(ranker_version='existing-static-evidence-v1', ranking_mode='static', population_complete=False, selected..._asserted_complete', 'evidence_unknown:0fe0311405a7be0c4af80d713c6deb70e6c91dd8f99d5e81500010e081d0a881', 'cpg_empty')) = ModelScanResult(findings=(), by_rung={'suspicion': 0, 'model_corroborated': 0, 'model_entailed': 0, 'execution_confirm...epository', language='python', family='injection', selected=1, completed=0, unanswered=1, deferred=0, unsupported=0),)).scope\n\ntests/test_model_partitions.py:171: AssertionError\n____________ test_synthetic_census_node_cannot_mask_missing_source _____________\n\ntmp_path = PosixPath('/tmp/pytest-of-mc/pytest-1694/test_synthetic_census_node_can0')\n\n def test_synthetic_census_node_cannot_mask_missing_source(tmp_path):\n from openultrasast.model.partitions import build_partitions\n \n for name in (\"a.php\", \"b.php\"):\n (tmp_path / name).write_text(\"\", str(root / \"a.php\")]}],\n },\n )\n \n graph = build_partitions(tmp_path, build, None)\n try:\n result = graph.run_batch(\"taint\", {\"q\": {\"file\": \"a.php\"}})\n> assert result[\"__census__\"][0][\"files\"] == 2\nE assert 0 == 2\n\ntests/test_model_partitions.py:290: AssertionError\n__________ test_unnamed_sharded_census_remains_explicitly_incomplete ___________\n\ntmp_path = PosixPath('/tmp/pytest-of-mc/pytest-1694/test_unnamed_sharded_census_re0')\n\n def test_unnamed_sharded_census_remains_explicitly_incomplete(tmp_path):\n from openultrasast.model.partitions import build_partitions\n \n (tmp_path / \"app.php\").write_text(\" assert result[\"__census__\"][0][\"methods\"] == 20\nE assert 0 == 20\n\ntests/test_model_partitions.py:337: AssertionError\n=========================== short test summary info ============================\nFAILED tests/test_model_partitions.py::test_partial_partition_census_never_completes_questions\nFAILED tests/test_model_partitions.py::test_synthetic_census_node_cannot_mask_missing_source\nFAILED tests/test_model_partitions.py::test_unnamed_sharded_census_remains_explicitly_incomplete\n3 failed, 11 passed in 0.95s\n", + "owner-census-diagnostic-on.log": ".............. [100%]\n14 passed in 1.36s\n", + "owner-census-empty-red.log": "......FFF........ [100%]\n=================================== FAILURES ===================================\n_______ test_partial_partition_census_never_completes_questions[census1] _______\n\ntmp_path = PosixPath('/tmp/pytest-of-mc/pytest-1697/test_partial_partition_census_1')\ncensus = []\n\n @pytest.mark.parametrize(\"census\", [[{\"methods\": 2, \"files\": 1}], [], [\"invalid\"], None])\n def test_partial_partition_census_never_completes_questions(tmp_path, census):\n for name in (\"a.py\", \"b.py\"):\n (tmp_path / name).write_text(\"print('readable')\")\n \n class Backend:\n def build(self, root, **kwargs):\n return CpgResult(\n Path(\"graph\"),\n lambda q, p: [],\n run_batch=lambda q, reqs: {**{rid: [] for rid in reqs}, **({\"__census__\": census} if census is not None else {})},\n )\n \n region = ScanRegion(path=\"a.py\", function=\"f\", language=\"python\", families=(\"injection\",), rank=1.0, source=\"entry_point\")\n result = scan_repository(tmp_path, [region], backend=Backend(), budget=ScanBudget(tiering=False))\n> assert result.question_outcomes[0].status == \"unresolved\"\nE AssertionError: assert 'completed' == 'unresolved'\nE \nE - unresolved\nE + completed\n\ntests/test_model_partitions.py:173: AssertionError\n_______ test_partial_partition_census_never_completes_questions[census2] _______\n\ntmp_path = PosixPath('/tmp/pytest-of-mc/pytest-1697/test_partial_partition_census_2')\ncensus = ['invalid']\n\n @pytest.mark.parametrize(\"census\", [[{\"methods\": 2, \"files\": 1}], [], [\"invalid\"], None])\n def test_partial_partition_census_never_completes_questions(tmp_path, census):\n for name in (\"a.py\", \"b.py\"):\n (tmp_path / name).write_text(\"print('readable')\")\n \n class Backend:\n def build(self, root, **kwargs):\n return CpgResult(\n Path(\"graph\"),\n lambda q, p: [],\n run_batch=lambda q, reqs: {**{rid: [] for rid in reqs}, **({\"__census__\": census} if census is not None else {})},\n )\n \n region = ScanRegion(path=\"a.py\", function=\"f\", language=\"python\", families=(\"injection\",), rank=1.0, source=\"entry_point\")\n result = scan_repository(tmp_path, [region], backend=Backend(), budget=ScanBudget(tiering=False))\n> assert result.question_outcomes[0].status == \"unresolved\"\nE AssertionError: assert 'completed' == 'unresolved'\nE \nE - unresolved\nE + completed\n\ntests/test_model_partitions.py:173: AssertionError\n________ test_partial_partition_census_never_completes_questions[None] _________\n\ntmp_path = PosixPath('/tmp/pytest-of-mc/pytest-1697/test_partial_partition_census_3')\ncensus = None\n\n @pytest.mark.parametrize(\"census\", [[{\"methods\": 2, \"files\": 1}], [], [\"invalid\"], None])\n def test_partial_partition_census_never_completes_questions(tmp_path, census):\n for name in (\"a.py\", \"b.py\"):\n (tmp_path / name).write_text(\"print('readable')\")\n \n class Backend:\n def build(self, root, **kwargs):\n return CpgResult(\n Path(\"graph\"),\n lambda q, p: [],\n run_batch=lambda q, reqs: {**{rid: [] for rid in reqs}, **({\"__census__\": census} if census is not None else {})},\n )\n \n region = ScanRegion(path=\"a.py\", function=\"f\", language=\"python\", families=(\"injection\",), rank=1.0, source=\"entry_point\")\n result = scan_repository(tmp_path, [region], backend=Backend(), budget=ScanBudget(tiering=False))\n> assert result.question_outcomes[0].status == \"unresolved\"\nE AssertionError: assert 'completed' == 'unresolved'\nE \nE - unresolved\nE + completed\n\ntests/test_model_partitions.py:173: AssertionError\n=========================== short test summary info ============================\nFAILED tests/test_model_partitions.py::test_partial_partition_census_never_completes_questions[census1]\nFAILED tests/test_model_partitions.py::test_partial_partition_census_never_completes_questions[census2]\nFAILED tests/test_model_partitions.py::test_partial_partition_census_never_completes_questions[None]\n3 failed, 14 passed in 1.03s\n", + "owner-census-review-focused.log": "........................................................................ [ 97%]\n.. [100%]\n74 passed in 7.24s\n", + "owner-census-release-suite.log": "........................................................................ [ 5%]\n..................................................................sssss. [ 10%]\n.............................s...............................s.......... [ 16%]\n.........ss............................................................. [ 21%]\n........................................................................ [ 27%]\n........................................................................ [ 32%]\n........................................................................ [ 37%]\n........................................................................ [ 43%]\n........................................................................ [ 48%]\n........................................................................ [ 54%]\n........................................................................ [ 59%]\n........................................................................ [ 64%]\n........................................................................ [ 70%]\n........................................................................ [ 75%]\n........................................................................ [ 81%]\n........................................................................ [ 86%]\n........................................................................ [ 92%]\n........................................................................ [ 97%]\n.................................. [100%]\n1321 passed, 9 skipped in 120.96s (0:02:00)\n", + "owner-census-release-build.log": "#0 building with \"default\" instance using docker driver\n\n#1 [internal] load build definition from Dockerfile\n#1 transferring dockerfile: 3.28kB done\n#1 DONE 0.0s\n\n#2 [internal] load metadata for docker.io/library/eclipse-temurin:21-jre-noble\n#2 DONE 0.0s\n\n#3 [internal] load .dockerignore\n#3 transferring context: 2B done\n#3 DONE 0.0s\n\n#4 [ 1/12] FROM docker.io/library/eclipse-temurin:21-jre-noble\n#4 DONE 0.0s\n\n#5 [internal] load build context\n#5 transferring context: 224.12kB 0.1s done\n#5 DONE 0.1s\n\n#6 [ 3/12] RUN set -eux; cd /tmp; curl -fsSL -O \"https://github.com/joernio/joern/releases/download/v4.0.625/joern-cli-linux-x86_64.zip\"; curl -fsSL -O \"https://github.com/joernio/joern/releases/download/v4.0.625/joern-cli-linux-x86_64.zip.sha512\"; sed 's@ target/@ @' \"joern-cli-linux-x86_64.zip.sha512\" > checksum.sha512; sha512sum -c checksum.sha512; unzip -q \"joern-cli-linux-x86_64.zip\" -d /opt; rm -f \"joern-cli-linux-x86_64.zip\" \"joern-cli-linux-x86_64.zip.sha512\" checksum.sha512; /opt/joern-cli/joern-parse --help > /dev/null\n#6 CACHED\n\n#7 [ 4/12] RUN if [ ! -e /opt/joern-cli/jssrc2cpg ] && [ -x /opt/joern-cli/jssrc2cpg.sh ]; then ln -s jssrc2cpg.sh /opt/joern-cli/jssrc2cpg; fi\n#7 CACHED\n\n#8 [ 5/12] COPY ops/frontend-retention/install.py /tmp/frontend-retention-install.py\n#8 CACHED\n\n#9 [ 6/12] RUN python3 /tmp/frontend-retention-install.py /opt/joern-cli && rm /tmp/frontend-retention-install.py\n#9 CACHED\n\n#10 [ 7/12] WORKDIR /app\n#10 CACHED\n\n#11 [ 2/12] RUN apt-get update && apt-get install -y --no-install-recommends python3 python3-venv python3-pip unzip curl ca-certificates git php-cli && rm -rf /var/lib/apt/lists/*\n#11 CACHED\n\n#12 [ 8/12] COPY pyproject.toml README.md ./\n#12 CACHED\n\n#13 [ 9/12] COPY src ./src\n#13 DONE 0.2s\n\n#14 [10/12] COPY benchmarks ./benchmarks\n#14 DONE 0.3s\n\n#15 [11/12] RUN python3 -m venv /venv && /venv/bin/pip install --no-cache-dir -e \".[semantic]\"\n#15 4.962 Obtaining file:///app\n#15 4.966 Installing build dependencies: started\n#15 6.380 Installing build dependencies: finished with status 'done'\n#15 6.382 Checking if build backend supports build_editable: started\n#15 6.427 Checking if build backend supports build_editable: finished with status 'done'\n#15 6.428 Getting requirements to build editable: started\n#15 6.473 Getting requirements to build editable: finished with status 'done'\n#15 6.474 Preparing editable metadata (pyproject.toml): started\n#15 6.530 Preparing editable metadata (pyproject.toml): finished with status 'done'\n#15 6.787 Collecting tree-sitter<0.27,>=0.23 (from openultrasast==1.1.0)\n#15 6.854 Downloading tree_sitter-0.26.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (10 kB)\n#15 6.890 Collecting tree-sitter-python>=0.23 (from openultrasast==1.1.0)\n#15 6.899 Downloading tree_sitter_python-0.25.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (1.9 kB)\n#15 6.931 Collecting tree-sitter-javascript>=0.23 (from openultrasast==1.1.0)\n#15 6.940 Downloading tree_sitter_javascript-0.25.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (2.2 kB)\n#15 6.972 Collecting tree-sitter-typescript>=0.23 (from openultrasast==1.1.0)\n#15 6.981 Downloading tree_sitter_typescript-0.23.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (2.3 kB)\n#15 7.022 Collecting tree-sitter-c>=0.23 (from openultrasast==1.1.0)\n#15 7.031 Downloading tree_sitter_c-0.24.2-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (1.8 kB)\n#15 7.068 Collecting tree-sitter-cpp>=0.23 (from openultrasast==1.1.0)\n#15 7.077 Downloading tree_sitter_cpp-0.23.4-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (1.8 kB)\n#15 7.105 Collecting tree-sitter-java>=0.23 (from openultrasast==1.1.0)\n#15 7.115 Downloading tree_sitter_java-0.23.5-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (1.7 kB)\n#15 7.152 Downloading tree_sitter-0.26.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (667 kB)\n#15 7.179 \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 667.5/667.5 kB 29.7 MB/s eta 0:00:00\n#15 7.189 Downloading tree_sitter_c-0.24.2-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (94 kB)\n#15 7.193 \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 94.3/94.3 kB 238.0 MB/s eta 0:00:00\n#15 7.203 Downloading tree_sitter_cpp-0.23.4-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (316 kB)\n#15 7.207 \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 316.1/316.1 kB 269.3 MB/s eta 0:00:00\n#15 7.218 Downloading tree_sitter_java-0.23.5-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (84 kB)\n#15 7.221 \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 84.0/84.0 kB 254.1 MB/s eta 0:00:00\n#15 7.234 Downloading tree_sitter_javascript-0.25.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (99 kB)\n#15 7.237 \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 99.7/99.7 kB 259.2 MB/s eta 0:00:00\n#15 7.248 Downloading tree_sitter_python-0.25.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (108 kB)\n#15 7.251 \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 108.1/108.1 kB 259.7 MB/s eta 0:00:00\n#15 7.263 Downloading tree_sitter_typescript-0.23.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (344 kB)\n#15 7.267 \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 345.0/345.0 kB 251.9 MB/s eta 0:00:00\n#15 7.287 Checking if build backend supports build_editable: started\n#15 7.339 Checking if build backend supports build_editable: finished with status 'done'\n#15 7.341 Building wheels for collected packages: openultrasast\n#15 7.343 Building editable for openultrasast (pyproject.toml): started\n#15 7.417 Building editable for openultrasast (pyproject.toml): finished with status 'done'\n#15 7.418 Created wheel for openultrasast: filename=openultrasast-1.1.0-py3-none-any.whl size=11111 sha256=475f740c3ec8dae403a5eb952af7ab11b28e57ba7ac38af19e5b88d18fd8f63a\n#15 7.418 Stored in directory: /tmp/pip-ephem-wheel-cache-_hfsyiid/wheels/54/1b/b7/aa63e25c8f14f4f2ae7b04e6097bdecb770e455c5c1ee0a600\n#15 7.423 Successfully built openultrasast\n#15 7.471 Installing collected packages: tree-sitter-typescript, tree-sitter-python, tree-sitter-javascript, tree-sitter-java, tree-sitter-cpp, tree-sitter-c, tree-sitter, openultrasast\n#15 7.773 Successfully installed openultrasast-1.1.0 tree-sitter-0.26.0 tree-sitter-c-0.24.2 tree-sitter-cpp-0.23.4 tree-sitter-java-0.23.5 tree-sitter-javascript-0.25.0 tree-sitter-python-0.25.0 tree-sitter-typescript-0.23.2\n#15 DONE 8.0s\n\n#16 [12/12] RUN if getent passwd 1000 > /dev/null; then usermod --login ousast --home /home/ousast --move-home \"$(getent passwd 1000 | cut -d: -f1)\"; else useradd --create-home --uid 1000 ousast; fi && chown -R ousast /app\n#16 DONE 10.3s\n\n#17 exporting to image\n#17 exporting layers\n#17 exporting layers 0.5s done\n#17 writing image sha256:898789cb6cc88507a9fa67276fd442ee4ef9e9f1135b333bafda511fb0532b25 done\n#17 naming to docker.io/library/openultrasast:dev 0.0s done\n#17 DONE 0.5s\n", + "owner-census-partition-smoke.stderr": "{\"frontend\": \"javascript\", \"projected_inputs\": {\"Gruntfile.js\": {\"bytes\": 46, \"sha256\": \"a144af904affce5e2ddd5befabb58caf3b5fdfbb5ac580210cf408916ebaa809\"}, \"tests/probe.js\": {\"bytes\": 41, \"sha256\": \"182f7cb37514b7fc6afbba045a9d1b3ca5dc297c35bb98b9f34c18cfd02864f5\"}, \"browser/client.js\": {\"bytes\": 41, \"sha256\": \"42ef299a3c9a53f1ca77f9e4b027c8d6e854f69b7df765bdb833079198513fc4\"}, \"server/api.js\": {\"bytes\": 51, \"sha256\": \"a9f1b897b9361d0d30250f82aebec987cfd99e66308c9ea889d5b6211e3cad5e\"}, \"checks/probe.spec.js\": {\"bytes\": 41, \"sha256\": \"a2677f51273a325b802a4a628ceb54f0bcea533a672c8997c0439d6ea206de37\"}, \"checks/probe.e2e.js\": {\"bytes\": 40, \"sha256\": \"14884e5a7d6c909f30addd8385bb82f6ea3751bb126192052dabe0eedfb42400\"}, \"checks/probe.test.js\": {\"bytes\": 41, \"sha256\": \"182f7cb37514b7fc6afbba045a9d1b3ca5dc297c35bb98b9f34c18cfd02864f5\"}, \"checks/probe.mock.js\": {\"bytes\": 41, \"sha256\": \"bc14cd012a0d2c34a3605ce21a009b7fd335bdf248f81aec3462b506929eabd5\"}}}\n{\"frontend\": \"javascript\", \"graph_witness\": {\"files\": [\"Gruntfile.js\", \"browser/client.js\", \"checks/probe.e2e.js\", \"checks/probe.mock.js\", \"checks/probe.spec.js\", \"checks/probe.test.js\", \"server/api.js\", \"tests/probe.js\"], \"methods\": [\"build_config_marker\", \":program\", \"browser_marker\", \":program\", \"js_e2e_marker\", \":program\", \"js_mock_marker\", \":program\", \"js_spec_marker\", \":program\", \"js_test_marker\", \":program\", \"js_handler\", \":program\", \"js_test_marker\", \":program\", \".assignment\", \"eval\", \".fieldAccess\"]}}\n{\"frontend\": \"php\", \"projected_inputs\": {\"tests/probe.php\": {\"bytes\": 48, \"sha256\": \"93c25992f2b709d4f222d88573a004dfb4e65239ce9a3afc2301f629b148c03d\"}, \"src/api.php\": {\"bytes\": 49, \"sha256\": \"47ee82519e90f5ce2c845035f168bc4eaa4b0cc6e46cfc68626b8144e20b8f72\"}}}\n{\"frontend\": \"php\", \"graph_witness\": {\"files\": [\"src/api.php\", \"tests/probe.php\", \"\"], \"methods\": [\"\", \"php_handler\", \"\", \"php_test_marker\", \"eval\"]}}\n", + "owner-census-gate.log": "detection gate: recall 93.62% (44/47), FP 0.00% (0/44)\n c_cpp recall 93.33% (14/15) FP 0.00% (0/14)\n java recall 100.00% (4/4) FP 0.00% (0/4)\n javascript recall 92.31% (12/13) FP 0.00% (0/12)\n python recall 93.33% (14/15) FP 0.00% (0/14)\ndetection gate: PASS\n", + "owner-census-map.log": "split-sink map gate: PASS\n", + "owner-census-pairs.log": "pair gate: pair_pass 100.00% (3/3), vuln_recall 100.00%, silent_fix 100.00%, Youden +100.00%, labeled_recall 100.00% (28/28), fix_leaks 0\n local pair_pass 100.00% (3/3) vuln 100.00% silent 100.00% Youden +100.00% labeled 100.00%\n PASS local-python-vulnerable vuln 12/12 fix_findings=0 leaks=0\n PASS local-javascript-vulnerable vuln 10/10 fix_findings=0 leaks=0\n PASS local-cpp-buffer vuln 6/6 fix_findings=0 leaks=0\npair gate: PASS (local slice)\n" + }, + "reproducer": "import json, subprocess, time, hashlib\nfrom pathlib import Path\nfrom openultrasast.cpg.backend import JoernBackend\nout=Path('/results')\nprior=json.loads(Path('/context/benchmarks/measurements/2026-09-13-nodegoat-feasibility.json').read_bytes())\nimport gzip\nbundle=json.loads(gzip.decompress(Path('/context/benchmarks/measurements/2026-09-13-nodegoat-feasibility.json.gz').read_bytes()))\nfiles=bundle\nkey=next(k for k in files if k.endswith('lab-prime.json'))\na=json.loads(files[key])\npart=a['scans'][0]['scan']['partitions'][0]\nroot=out/'input';root.mkdir(exist_ok=False)\nreceipt=[]\nfor name in part['paths']:\n p=subprocess.run(['git','-C','/cache/nodegoat/c5cb68a7084e','cat-file','blob','c5cb68a7084e4ae7dcc60e6a98768720a81841e8:'+name],capture_output=True,check=True,timeout=10)\n assert p.stdout\n target=root/name;target.parent.mkdir(parents=True,exist_ok=True);target.write_bytes(p.stdout)\n content=target.read_bytes();receipt.append({'path':name,'bytes':len(content),'sha256':hashlib.sha256(content).hexdigest()})\nassert sum(x['bytes'] for x in receipt)==part['source_bytes']\n(out/'inputs.json').write_text(json.dumps(receipt,indent=2))\nb=JoernBackend(build_timeout=180,query_timeout=180)\nstart=time.monotonic();g=b.build(root,language='javascript')\nassert g is not None,b.last_failure\ntry:\n build=time.monotonic()-start\n start=time.monotonic();raw=g.run('census',{})\n assert isinstance(raw,dict) and isinstance(raw.get('file_names'),list),raw\n expected=set(part['paths']);observed={n.removeprefix(str(root)+'/').removeprefix('./') for n in raw['file_names']}\n result={'build_seconds':build,'census_seconds':time.monotonic()-start,'raw_census':raw,'missing':sorted(expected-observed),'extra':sorted(observed-expected),'input_paths':len(receipt),'input_bytes':sum(x['bytes'] for x in receipt),'unparsed':g.unparsed}\n (out/'result.json').write_text(json.dumps(result,indent=2));print(json.dumps(result),flush=True)\nfinally:g.cleanup()\n", + "limits": [ + "No changed-code latency gate passed.", + "No real capability admitted; old eligibility is stale.", + "VAmPI context/transitive witness and independent qualification remain open.", + "Gruntfile retention is a generic supported build format correction, not a blanket configuration/minification filter override.", + "Main-context Kiro review fallback; no independent reviewer claim.", + "Diagnostic timing samples may overlap static/test work; not representative performance measurements." + ] +} diff --git a/ops/frontend-retention/README.md b/ops/frontend-retention/README.md index c3d67ef..655ca95 100644 --- a/ops/frontend-retention/README.md +++ b/ops/frontend-retention/README.md @@ -34,3 +34,12 @@ the upstream version string alone cannot distinguish these graphs. After building the image, run `ops/smoke_partitions.py` as described in `ops/README.md`. Its off control uses `OUSAST_SMOKE_INCLUDE_TESTS=0` and must fail the test-retention assertion. First-party test retention does not imply security capability admission. + +The 2026-09-14 retention correction adds `OUSAST_INCLUDE_BUILD_CONFIGS=1` for +`Gruntfile.js`, which Joern otherwise filters even after test retention is enabled. +`JoernBackend(include_build_configs=True)` sets this explicit default; false restores +that upstream exclusion. Other configuration/minification filters are unchanged. +The option is part of graph/runtime identity. Source paths and bytes are preserved, +and the configuration is parsed, never executed. Real NodeGoat census and the packaged +PHP/JavaScript partition smoke verify this input-retention correction separately from +hook latency, actionable detection and capability admission. diff --git a/ops/frontend-retention/install.py b/ops/frontend-retention/install.py index 35cb0b1..7da45a7 100644 --- a/ops/frontend-retention/install.py +++ b/ops/frontend-retention/install.py @@ -97,6 +97,14 @@ const IGNORE_FILE_PATTERN = INCLUDE_TESTS "lazy val isIgnoredTest = IgnoredTestsRegex.exists(_.matches(filePath))", 'lazy val isIgnoredTest = !sys.env.get("OUSAST_INCLUDE_TESTS").contains("1") && IgnoredTestsRegex.exists(_.matches(filePath))', ) + # Keep the original Gruntfile path and bytes. Other default filters remain intact. + replace_once( + scala, + "lazy val isIgnored = IgnoredFilesRegex.exists(_.matches(filePath))", + "lazy val isIgnored = IgnoredFilesRegex.exists(_.matches(filePath)) && " + '!(sys.env.get("OUSAST_INCLUDE_BUILD_CONFIGS").contains("1") && ' + 'Paths.get(filePath).getFileName.toString == "Gruntfile.js")', + ) # Upstream stages embedded TS standard-library assets; retain that build recipe, # selecting just the supported Linux x64 target instead of all release platforms. script = astgen / "scripts/build-binaries.ts" @@ -135,7 +143,7 @@ const IGNORE_FILE_PATTERN = INCLUDE_TESTS manifest = { "revision": "ousast-frontend-retention-v1", "builder_sha256": digest(Path(__file__)), - "policy": "OUSAST_INCLUDE_TESTS=1", + "policy": "OUSAST_INCLUDE_TESTS=1; OUSAST_INCLUDE_BUILD_CONFIGS=1 retains Gruntfile.js", "sources": PINS, "original_jar_sha256": JAR_SHA256, "adapted_jar_sha256": digest(adapted), diff --git a/ops/smoke_partitions.py b/ops/smoke_partitions.py index da53714..14699e8 100644 --- a/ops/smoke_partitions.py +++ b/ops/smoke_partitions.py @@ -37,6 +37,7 @@ def main() -> None: } """) sources = { + "Gruntfile.js": "function build_config_marker() { return 42; }\n", "src/api.php": " None: assert any(str(path).endswith("server/api.js") for path in files), witness assert any(str(path).endswith("tests/probe.js") for path in files), witness assert "js_handler" in methods and "js_test_marker" in methods, witness + assert any(str(path).endswith("Gruntfile.js") for path in files), witness + assert "build_config_marker" in methods, witness for path, method in suffix_tests.items(): assert any(str(filename).endswith(path) for filename in files), witness assert method in methods, witness diff --git a/src/openultrasast/cpg/backend.py b/src/openultrasast/cpg/backend.py index 7d78881..c3a9e78 100644 --- a/src/openultrasast/cpg/backend.py +++ b/src/openultrasast/cpg/backend.py @@ -267,7 +267,11 @@ class NullBackend: def engine_runtime_identity( - *, include_tests: bool = True, language: str | None = None, execution_budget: ExecutionBudget | None = None + *, + include_tests: bool = True, + include_build_configs: bool = True, + language: str | None = None, + execution_budget: ExecutionBudget | None = None, ) -> str: """Installed frontend/adaptation bytes, also used by comparison eligibility provenance.""" from .artifact import digest_value, graph_bytes @@ -296,6 +300,7 @@ def engine_runtime_identity( "version": version, "files": installation, "include_tests": include_tests, + "include_build_configs": include_build_configs, "java_options": os.environ.get("JAVA_TOOL_OPTIONS", ""), } ) @@ -307,6 +312,7 @@ class JoernBackend: runner: Runner | None = None include_tests: bool = True # Explicit input policy for the versioned frontend adaptation. + include_build_configs: bool = True # Explicit retention of supported build configuration source. build_timeout: int = BUILD_TIMEOUT_SECONDS query_timeout: int = field(default_factory=lambda: _configured_timeout()) heap_mb: int = 0 # 0 means read the environment, then fall back to CPG_HEAP_MB @@ -380,10 +386,14 @@ class JoernBackend: (("implementation", code),), ( ("include_tests", str(self.include_tests)), + ("include_build_configs", str(self.include_build_configs)), ( "installation", engine_runtime_identity( - include_tests=self.include_tests, language=language, execution_budget=execution_budget or self.execution_budget + include_tests=self.include_tests, + include_build_configs=self.include_build_configs, + language=language, + execution_budget=execution_budget or self.execution_budget, ), ), ("java_options", os.environ.get("JAVA_TOOL_OPTIONS", "default") or "default"), @@ -1189,6 +1199,7 @@ class JoernBackend: """ env = dict(os.environ) env["OUSAST_INCLUDE_TESTS"] = "1" if self.include_tests else "0" + env["OUSAST_INCLUDE_BUILD_CONFIGS"] = "1" if self.include_build_configs else "0" env["JAVA_TOOL_OPTIONS"] = f"{env.get('JAVA_TOOL_OPTIONS', '').strip()} -Xmx{self._heap_mb()}m".strip() return env diff --git a/src/openultrasast/model/partitions.py b/src/openultrasast/model/partitions.py index 418bfdd..7287aa4 100644 --- a/src/openultrasast/model/partitions.py +++ b/src/openultrasast/model/partitions.py @@ -106,6 +106,9 @@ class PartitionGraph: raw = batch(kind, selected) if callable(batch) else {rid: graph.run(kind, params) for rid, params in selected.items()} if not isinstance(raw, Mapping): continue + raw = dict(raw) + if not isinstance(raw.get("__census__"), list): + raw["__census__"] = [] for rid, rows in raw.items(): if rid in selected and isinstance(rows, list): answers[rid] = self._normalize(rows) @@ -117,17 +120,18 @@ class PartitionGraph: observed = {p.removeprefix("./") for n in names if isinstance(n, list) for p in n if isinstance(p, str)} if not named or not expected <= observed: self.diagnostics.append("partition_file_census_incomplete" if named else "partition_file_census_unavailable") + reason = "partition_file_census_incomplete" if named else "partition_file_census_unavailable" rows = [ - { - "methods": 0, - "files": 0, - "shards": max((int(str(r.get("shards", 1))) for r in rows if isinstance(r, Mapping)), default=1), - } - ] + dict(r, census_failure=reason, missing_file_names=sorted(expected - observed) if named else []) + for r in normalized + if isinstance(r, Mapping) + ] or [{"census_failure": reason, "missing_file_names": []}] # Preserve any individual empty or sharded graph's degradation. previous = answers.get(rid, []) if not previous or any( - isinstance(r, Mapping) and (int(str(r.get("methods", 0))) == 0 or int(str(r.get("shards", 1))) > 1) for r in rows + isinstance(r, Mapping) + and (r.get("census_failure") or int(str(r.get("methods", 0))) == 0 or int(str(r.get("shards", 1))) > 1) + for r in rows ): answers[rid] = rows return answers diff --git a/src/openultrasast/model/scan.py b/src/openultrasast/model/scan.py index e7794a4..91b505a 100644 --- a/src/openultrasast/model/scan.py +++ b/src/openultrasast/model/scan.py @@ -355,6 +355,8 @@ def _scan_repository_impl( in { "files_unparsed", "cpg_empty", + "partition_file_census_incomplete", + "partition_file_census_unavailable", "cpg_sharded", "cross_partition_semantics_unresolved", "vendor_semantics_unresolved", @@ -410,8 +412,14 @@ def _scan_repository_impl( if census and not census_reported: entry = census[0] if isinstance(census[0], Mapping) else {} methods = int(str(entry.get("methods", "0")) or 0) - if methods == 0: - degradations.append({"stage": "model", "reason": "cpg_empty", "files": int(str(entry.get("files", "0")) or 0)}) + if methods == 0 or entry.get("census_failure"): + degradations.append( + { + "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}, + } + ) # More than one graph means some files could only be built apart from the rest, and a # flow whose source is in one shard and whose sink is in another does not exist for any # question we can ask. Reporting the split is the difference between a partial answer and @@ -508,6 +516,8 @@ def _scan_repository_impl( in { "files_unparsed", "cpg_empty", + "partition_file_census_incomplete", + "partition_file_census_unavailable", "cpg_sharded", "cross_partition_semantics_unresolved", "vendor_semantics_unresolved", @@ -741,8 +751,14 @@ def _evidence_pass( shards = int(str(entry.get("shards", "1"))) except ValueError: methods, shards = 0, 1 - if methods <= 0: - degradations.append({"stage": "model", "reason": "cpg_empty"}) + if methods <= 0 or entry.get("census_failure"): + degradations.append( + { + "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}, + } + ) if shards > 1: degradations.append({"stage": "model", "reason": "cpg_sharded", "shards": shards}) by_rid = {rid: (region, spec) for rid, region, spec in work} diff --git a/tests/test_frontend_retention.py b/tests/test_frontend_retention.py index 744f0d8..01f6913 100644 --- a/tests/test_frontend_retention.py +++ b/tests/test_frontend_retention.py @@ -29,3 +29,9 @@ def test_builder_rejects_unrecognized_dependency_before_download(tmp_path): builder.build(tmp_path) assert jar.read_bytes() == b"different dependency" assert not (tmp_path / "ousast-frontend-retention-v1").exists() + + +def test_build_config_retention_is_explicit_and_overrides_ambient_policy(monkeypatch): + monkeypatch.setenv("OUSAST_INCLUDE_BUILD_CONFIGS", "0") + assert JoernBackend()._jvm_env()["OUSAST_INCLUDE_BUILD_CONFIGS"] == "1" + assert JoernBackend(include_build_configs=False)._jvm_env()["OUSAST_INCLUDE_BUILD_CONFIGS"] == "0" diff --git a/tests/test_model_partitions.py b/tests/test_model_partitions.py index 9e07800..e8e600b 100644 --- a/tests/test_model_partitions.py +++ b/tests/test_model_partitions.py @@ -1,5 +1,7 @@ from pathlib import Path +import pytest + from openultrasast.cpg.backend import CpgResult from openultrasast.model.regions import ScanRegion from openultrasast.model.scan import ScanBudget, scan_repository @@ -153,7 +155,8 @@ def test_unsupported_only_population_is_named(tmp_path): assert "frontend_unsupported" in result.scope.unresolved_boundaries -def test_partial_partition_census_never_completes_questions(tmp_path): +@pytest.mark.parametrize("census", [[{"methods": 2, "files": 1}], [], ["invalid"], None]) +def test_partial_partition_census_never_completes_questions(tmp_path, census): for name in ("a.py", "b.py"): (tmp_path / name).write_text("print('readable')") @@ -162,13 +165,14 @@ def test_partial_partition_census_never_completes_questions(tmp_path): return CpgResult( Path("graph"), lambda q, p: [], - run_batch=lambda q, reqs: {**{rid: [] for rid in reqs}, "__census__": [{"methods": 2, "files": 1}]}, + run_batch=lambda q, reqs: {**{rid: [] for rid in reqs}, **({"__census__": census} if census is not None else {})}, ) region = ScanRegion(path="a.py", function="f", language="python", families=("injection",), rank=1.0, source="entry_point") result = scan_repository(tmp_path, [region], backend=Backend(), budget=ScanBudget(tiering=False)) assert result.question_outcomes[0].status == "unresolved" - assert "cpg_empty" in result.scope.unresolved_boundaries + assert "partition_file_census_unavailable" in result.scope.unresolved_boundaries + assert "cpg_empty" not in result.scope.unresolved_boundaries def test_projection_refuses_substituted_ancestor(tmp_path): @@ -286,7 +290,9 @@ def test_synthetic_census_node_cannot_mask_missing_source(tmp_path): graph = build_partitions(tmp_path, build, None) try: result = graph.run_batch("taint", {"q": {"file": "a.php"}}) - assert result["__census__"][0]["files"] == 0 + assert result["__census__"][0]["files"] == 2 + assert result["__census__"][0]["methods"] == 3 + assert result["__census__"][0]["missing_file_names"] == ["b.php"] assert "partition_file_census_incomplete" in graph.execution_diagnostics() finally: graph.cleanup() @@ -331,7 +337,10 @@ def test_unnamed_sharded_census_remains_explicitly_incomplete(tmp_path): ) try: result = graph.run_batch("taint", {"q": {"file": "app.php"}}) - assert result["__census__"] == [{"methods": 0, "files": 0, "shards": 2}] + assert result["__census__"][0]["methods"] == 20 + assert result["__census__"][0]["files"] == 20 + assert result["__census__"][0]["shards"] == 2 + assert result["__census__"][0]["census_failure"] == "partition_file_census_unavailable" assert "partition_file_census_unavailable" in graph.execution_diagnostics() finally: graph.cleanup() diff --git a/tests/test_model_scan.py b/tests/test_model_scan.py index f196de3..9f54881 100644 --- a/tests/test_model_scan.py +++ b/tests/test_model_scan.py @@ -463,7 +463,15 @@ def test_tier_zero_pairs_are_not_asked_and_nothing_else_is_skipped(tmp_path: Pat issued.append({"kind": kind, "count": len(requests)}) return {rid: [] for rid in requests} - return CpgResult(cpg_path=Path("cpg.bin"), run=lambda q, p: [], run_batch=batch) + names = [str(p) for p in root.rglob("*.py")] + return CpgResult( + cpg_path=Path("cpg.bin"), + run=lambda q, p: [], + run_batch=lambda kind, requests: { + "__census__": [{"methods": len(names), "files": len(names), "file_names": names}], + **batch(kind, requests), + }, + ) region = ScanRegion( path="a.py", function="run", language="python", families=("injection", "path", "deserialization"), rank=0.9, source="entry_point" @@ -521,7 +529,15 @@ def test_the_budget_is_spent_by_evidence_when_asked(tmp_path: Path) -> None: self.taint_files.append(sorted({str(p.get("file", "")) for p in requests.values()})) return {rid: [] for rid in requests} - return CpgResult(cpg_path=Path("cpg.bin"), run=lambda q, p: [], run_batch=batch) + names = [str(p) for p in root.rglob("*.py")] + return CpgResult( + cpg_path=Path("cpg.bin"), + run=lambda q, p: [], + run_batch=lambda kind, requests: { + "__census__": [{"methods": len(names), "files": len(names), "file_names": names}], + **batch(kind, requests), + }, + ) top = ScanRegion(path="top.py", function="handle", language="python", families=("injection",), rank=1.0, source="entry_point") low = ScanRegion(path="low.py", function="run", language="python", families=("injection",), rank=0.3, source="entry_point") diff --git a/tests/test_model_scope.py b/tests/test_model_scope.py index b9f6845..af9a39c 100644 --- a/tests/test_model_scope.py +++ b/tests/test_model_scope.py @@ -26,7 +26,12 @@ class Backend: assert any(p.read_bytes() for p in root.rglob("*") if p.is_file()) if self.fail: return None - return SimpleNamespace(cpg_path=Path("test.cpg"), run_batch=self.batch, cleanup=lambda: None) + names = [str(p) for p in root.rglob("*") if p.is_file()] + + def batch(kind, requests): + return {"__census__": [{"methods": len(names), "files": len(names), "file_names": names}], **self.batch(kind, requests)} + + return SimpleNamespace(cpg_path=Path("test.cpg"), run_batch=batch, cleanup=lambda: None) def batch(self, kind, requests): if any(p.get("evidenceOnly") == "true" for p in requests.values()): diff --git a/tests/test_push_policy.py b/tests/test_push_policy.py index 336ef19..81be52a 100644 --- a/tests/test_push_policy.py +++ b/tests/test_push_policy.py @@ -117,7 +117,14 @@ def test_targeted_base_uses_real_driver_same_deadline(tmp_path): assert (root / "a.js").read_bytes() seen.append(execution_budget) rows = json.loads(scan(line=5, sanitized=True).question_outcomes[0].raw_rows_json) - return CpgResult(Path("graph"), lambda kind, params: rows) + return CpgResult( + Path("graph"), + lambda kind, params: rows, + run_batch=lambda kind, requests: { + **{rid: rows for rid in requests}, + "__census__": [{"methods": 1, "files": 1, "file_names": [str(root / "a.js")]}], + }, + ) region = ScanRegion("a.js", "handler", "javascript", ("injection",), 1.0, "entry_point") budget = ExecutionBudget(time.monotonic() + 30, 1.0)