From e41ce241ecf147ea4d0dae35fe47c9c847e3d132 Mon Sep 17 00:00:00 2001 From: "pulse-triage[bot]" <249995291+pulse-triage[bot]@users.noreply.github.com> Date: Tue, 13 Oct 2026 05:37:14 +0100 Subject: [PATCH] Keep the audit process owner in strict delivery checks Both required CI and the local gate must treat the new subprocess owner as audit runtime, not an unchanged dependency graph. Carry the actual helper into the isolated local-gate fixture and test positive findings, missing results, large change lists and exact-path lookalikes through both consumers. Contract-Neutral: Extend the existing strict audit-runtime path classification to its new owned helper; advisory rules, dependency graphs, public runtime, credentials and external permissions are unchanged. Change-source: pulse-maintainer --- .github/workflows/build-and-test.yml | 2 +- scripts/dev-prepush.sh | 2 +- .../installtests/build_release_assets_test.go | 6 ++++- scripts/tests/test_dev_prepush_frontend.py | 27 ++++++++++++++----- 4 files changed, 28 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 58ce67ae61..f91d61997d 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -129,7 +129,7 @@ jobs: # Consume the full list: grep -q can close the pipe on an early # match, making printf fail with SIGPIPE under pipefail. A large # dependency change must not enter the inherited-advisory mode. - if printf '%s\n' "$changed" | grep -E '^frontend-modern/package(-lock)?\.json$|^scripts/npm-audit-retry\.sh$' >/dev/null; then + if printf '%s\n' "$changed" | grep -E '^frontend-modern/package(-lock)?\.json$|^scripts/(npm-audit-retry\.sh|npm_audit_process\.py)$' >/dev/null; then frontend_deps=true else frontend_deps=false diff --git a/scripts/dev-prepush.sh b/scripts/dev-prepush.sh index 215cbeb946..e2520fe82f 100755 --- a/scripts/dev-prepush.sh +++ b/scripts/dev-prepush.sh @@ -57,7 +57,7 @@ CHANGED_GO=$(printf '%s\n' "$CHANGED" | grep -E '\.go$' || true) # Unchanged graphs are left to CI's existing inherited-finding warning policy. # Consume the whole list rather than using grep -q: under pipefail an early # match in a large diff must not turn the producer's SIGPIPE into a false skip. -if printf '%s\n' "$CHANGED" | grep -E '^frontend-modern/package(-lock)?\.json$|^scripts/npm-audit-retry\.sh$' >/dev/null; then +if printf '%s\n' "$CHANGED" | grep -E '^frontend-modern/package(-lock)?\.json$|^scripts/(npm-audit-retry\.sh|npm_audit_process\.py)$' >/dev/null; then step "Frontend dependency audit (changed graph or audit runner)" if ! (cd frontend-modern && NPM_AUDIT_REQUIRE_RESULT=true bash "$ROOT_DIR/scripts/npm-audit-retry.sh" all); then echo "Frontend dependency audit failed; remaining source checks have not run." >&2 diff --git a/scripts/installtests/build_release_assets_test.go b/scripts/installtests/build_release_assets_test.go index 081a29d9e8..41508f594c 100644 --- a/scripts/installtests/build_release_assets_test.go +++ b/scripts/installtests/build_release_assets_test.go @@ -5154,19 +5154,23 @@ func TestFrontendChangeClassificationPreservesStrictAudit(t *testing.T) { {name: "manifest", files: "frontend-modern/package.json\n", code: "true", dependencies: "true"}, {name: "lock", files: "frontend-modern/package-lock.json\n", code: "true", dependencies: "true"}, {name: "audit_runner", files: "scripts/npm-audit-retry.sh\n", code: "true", dependencies: "true"}, + {name: "audit_process", files: "scripts/npm_audit_process.py\n", code: "true", dependencies: "true"}, {name: "large_manifest_first", files: "frontend-modern/package.json\n" + docs, code: "true", dependencies: "true"}, {name: "large_lock_first", files: "frontend-modern/package-lock.json\n" + docs, code: "true", dependencies: "true"}, {name: "large_runner_first", files: "scripts/npm-audit-retry.sh\n" + docs, code: "true", dependencies: "true"}, + {name: "large_process_first", files: "scripts/npm_audit_process.py\n" + docs, code: "true", dependencies: "true"}, + {name: "large_process_last", files: docs + "scripts/npm_audit_process.py\n", code: "true", dependencies: "true"}, {name: "large_lock_last", files: docs + "frontend-modern/package-lock.json\n", code: "true", dependencies: "true"}, {name: "large_docs_only", files: docs, code: "false", dependencies: "false"}, {name: "large_unrelated_code", goCompile: true, files: docs + "internal/config/config.go\n", code: "true", dependencies: "false"}, - {name: "exact_path_lookalikes", files: "frontend-modern/package.json.extra\nother/frontend-modern/package-lock.json\nscripts/npm-audit-retry.sh.extra\n", code: "true", dependencies: "false"}, + {name: "exact_path_lookalikes", files: "frontend-modern/package.json.extra\nother/frontend-modern/package-lock.json\nscripts/npm-audit-retry.sh.extra\nscripts/npm_audit_process.py.extra\n", code: "true", dependencies: "false"}, {name: "pull_request_docs", files: "docs/FAQ.md\n", event: "pull_request", code: "false", dependencies: "false"}, {name: "zero_push_base", goCompile: true, base: strings.Repeat("0", 40), code: "true", dependencies: "true"}, {name: "missing_base", goCompile: true, missingBase: true, code: "true", dependencies: "true"}, {name: "manual_dispatch", goCompile: true, event: "workflow_dispatch", base: "empty", code: "true", dependencies: "true"}, {name: "failed_diff", diffStatus: 42}, {name: "large_changed_graph_critical_blocks", files: "frontend-modern/package-lock.json\n" + docs, code: "true", dependencies: "true", audit: true, auditStatus: 1}, + {name: "process_critical_blocks", files: "scripts/npm_audit_process.py\n", code: "true", dependencies: "true", audit: true, auditStatus: 1}, {name: "unchanged_graph_critical_warns", goCompile: true, files: "internal/config/config.go\n", code: "true", dependencies: "false", audit: true, auditStatus: 0}, } for _, tc := range cases { diff --git a/scripts/tests/test_dev_prepush_frontend.py b/scripts/tests/test_dev_prepush_frontend.py index b1a3ba2bcc..04938f902c 100644 --- a/scripts/tests/test_dev_prepush_frontend.py +++ b/scripts/tests/test_dev_prepush_frontend.py @@ -16,6 +16,7 @@ import yaml ROOT = Path(__file__).resolve().parents[2] PREPUSH = ROOT / "scripts/dev-prepush.sh" AUDIT = ROOT / "scripts/npm-audit-retry.sh" +AUDIT_PROCESS = ROOT / "scripts/npm_audit_process.py" class DevPrepushFixture(unittest.TestCase): @@ -53,6 +54,7 @@ class DevPrepushFixture(unittest.TestCase): ) self.write("scripts/dev-prepush.sh", PREPUSH.read_text()) self.write("scripts/npm-audit-retry.sh", AUDIT.read_text()) + self.write("scripts/npm_audit_process.py", AUDIT_PROCESS.read_text()) self.write("frontend-modern/package.json", '{"private":true}\n') self.write("frontend-modern/package-lock.json", '{"lockfileVersion":3}\n') self.write("frontend-modern/src/example.ts", "export const value = 1;\n") @@ -170,6 +172,17 @@ class DevPrepushFrontendTest(DevPrepushFixture): self.env["PREPUSH_AUDIT_MODE"] = "unavailable" self.assert_refused_before_source_checks(*self.run_gate(), reads=2) + def test_changed_audit_process_requires_a_result(self): + self.change("scripts/npm_audit_process.py") + self.env["PREPUSH_AUDIT_MODE"] = "unavailable" + self.assert_refused_before_source_checks(*self.run_gate(), reads=2) + + def test_changed_audit_process_cannot_relax_positive_findings(self): + self.change("scripts/npm_audit_process.py") + self.env["PREPUSH_AUDIT_MODE"] = "critical" + self.env["NPM_AUDIT_REQUIRE_RESULT"] = "false" + self.assert_refused_before_source_checks(*self.run_gate()) + def test_failed_zero_report_is_not_a_passing_local_gate(self): self.change("frontend-modern/package-lock.json") self.env["PREPUSH_AUDIT_MODE"] = "zero-failure" @@ -251,16 +264,18 @@ class DevPrepushFrontendTest(DevPrepushFixture): self.assertEqual(guards, []) def test_audit_path_lookalike_does_not_query(self): - self.change("scripts/npm-audit-retry.sh.extra") - result, calls, guards = self.run_gate(dependencies=False) - self.assertEqual(result.returncode, 0, result.stdout + result.stderr) - self.assertEqual(calls, []) - self.assertEqual(len(guards), 1) + for path in ("scripts/npm-audit-retry.sh.extra", "scripts/npm_audit_process.py.extra"): + with self.subTest(path=path): + self.change(path) + result, calls, guards = self.run_gate(dependencies=False) + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + self.assertEqual(calls, []) + self.assertEqual(len(guards), 1 if path.endswith(".sh.extra") else 2) def test_local_strict_paths_match_ci(self): workflow = yaml.safe_load((ROOT / ".github/workflows/build-and-test.yml").read_text()) classification = next(step for step in workflow["jobs"]["changes"]["steps"] if step.get("id") == "filter")["run"] - paths = r"^frontend-modern/package(-lock)?\.json$|^scripts/npm-audit-retry\.sh$" + paths = r"^frontend-modern/package(-lock)?\.json$|^scripts/(npm-audit-retry\.sh|npm_audit_process\.py)$" self.assertIn(paths, classification) self.assertIn(paths, PREPUSH.read_text())