From 991566b1edfafd56769f2d6e685e7ad495a1cbd1 Mon Sep 17 00:00:00 2001 From: bitfathers94 <237535319+bitfathers94@users.noreply.github.com> Date: Sun, 26 Jul 2026 00:15:10 +0000 Subject: [PATCH] fix(engine): guard the guardrail engine and gate-advisory/predicted-gate files themselves MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ENGINE_DECISION_GUARDRAIL_GLOBS listed the host/engine shim pairs for autonomy.ts and guardrail-config.ts, but never the packages/loopover-engine files that actually decide a PR's disposition: change-guardrail.ts (the glob matcher powering isGuardrailHit/guardrailPathMatches), gate-advisory.ts (the gate-decision core and check-run sanitizer), and predicted-gate.ts (the orchestrator wiring both). A PR weakening the matcher, flipping a block/advisory disposition, or rewiring the orchestrator would not itself trip the hard guardrail — a self-referential blind spot in the exact mechanism meant to catch that class of change. Add all three engine-side paths and cover each with a test asserting isGuardrailHit trips against resolveHardGuardrailGlobs(null). Closes #8698 --- .../src/review/guardrail-config.ts | 8 ++++++ test/unit/guardrail-config.test.ts | 25 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/packages/loopover-engine/src/review/guardrail-config.ts b/packages/loopover-engine/src/review/guardrail-config.ts index 1277463b6d..57dd22b721 100644 --- a/packages/loopover-engine/src/review/guardrail-config.ts +++ b/packages/loopover-engine/src/review/guardrail-config.ts @@ -51,6 +51,14 @@ export const ENGINE_DECISION_GUARDRAIL_GLOBS = [ "src/review/cutover-gate.ts", "src/review/linked-issue-hard-rules.ts", "src/review/outcomes-wire.ts", + // #8698: the guardrail-matching engine and the gate-decision core themselves were a self-referential blind + // spot -- none of the packages/loopover-engine files that actually decide a PR's disposition were listed, so + // a PR weakening the glob matcher, flipping a block/advisory disposition, or rewiring the orchestrator would + // not itself trip the hard guardrail. Listed on the engine side (no src/ shim exists for these) for the same + // reason the autonomy/guardrail-config real paths are: it is the substantive logic a PR edit would change. + "packages/loopover-engine/src/signals/change-guardrail.ts", + "packages/loopover-engine/src/advisory/gate-advisory.ts", + "packages/loopover-engine/src/predicted-gate.ts", ]; // Default, safe-by-default invariant set (restored by #3943 after the original pure-config-as-code design diff --git a/test/unit/guardrail-config.test.ts b/test/unit/guardrail-config.test.ts index febe8a3b6c..d5a838c47a 100644 --- a/test/unit/guardrail-config.test.ts +++ b/test/unit/guardrail-config.test.ts @@ -37,6 +37,31 @@ describe("ENGINE_DECISION_GUARDRAIL_GLOBS — post-#6203-migration real paths (# }); }); +// #8698: the guardrail-matching engine itself (change-guardrail.ts), the gate-decision advisory core +// (gate-advisory.ts), and the predicted-gate orchestrator were never listed -- a self-referential blind spot, +// since a PR weakening exactly those files would not itself trip the hard guardrail. Each must now be a hit. +describe("ENGINE_DECISION_GUARDRAIL_GLOBS — guardrail engine + gate-advisory/predicted-gate self-protection (#8698)", () => { + it("lists the guardrail-matching engine, the gate-advisory core, and the predicted-gate orchestrator", () => { + expect(ENGINE_DECISION_GUARDRAIL_GLOBS).toContain("packages/loopover-engine/src/signals/change-guardrail.ts"); + expect(ENGINE_DECISION_GUARDRAIL_GLOBS).toContain("packages/loopover-engine/src/advisory/gate-advisory.ts"); + expect(ENGINE_DECISION_GUARDRAIL_GLOBS).toContain("packages/loopover-engine/src/predicted-gate.ts"); + }); + + it("a PR touching only the guardrail-matching engine trips the hard guardrail", () => { + expect(isGuardrailHit(["packages/loopover-engine/src/signals/change-guardrail.ts"], resolveHardGuardrailGlobs(null))).toBe( + true, + ); + }); + + it("a PR touching only the gate-advisory core trips the hard guardrail", () => { + expect(isGuardrailHit(["packages/loopover-engine/src/advisory/gate-advisory.ts"], resolveHardGuardrailGlobs(null))).toBe(true); + }); + + it("a PR touching only the predicted-gate orchestrator trips the hard guardrail", () => { + expect(isGuardrailHit(["packages/loopover-engine/src/predicted-gate.ts"], resolveHardGuardrailGlobs(null))).toBe(true); + }); +}); + describe("resolveHardGuardrailGlobs", () => { it("uses invariant guardrails when effective settings omit hardGuardrailGlobs", () => { expect(resolveHardGuardrailGlobs(undefined)).toEqual(DEFAULT_HARD_GUARDRAIL_GLOBS);