Severity: HIGH
Location: src/queue/processors.ts:2749
Found via a full-system adversarial audit of the ORB review/scoring engine (2026-07-15) — 2 independent skeptic passes, both had to vote "confirmed" for this to survive.
Failure scenario
A maintainer sets .loopover.yml's gate.screenshotTableGate (or the dashboard equivalent, backed by screenshotTableGateAction in the DB and exposed via the public settings API at src/openapi/schemas.ts:866 as z.enum(["close", "advisory"])) to { enabled: true, whenLabels: ["frontend"], action: "advisory" }, expecting contributors to get a soft nudge to add a before/after screenshot table without their PR being auto-closed. A contributor opens a frontend-labeled PR with no screenshot table. evaluateScreenshotTableGate (src/review/screenshot-table-gate.ts:343) correctly computes { violated: true, reason: ... }. But at processors.ts:2756-2759, the result is only converted into a usable signal when screenshotTableGateConfig.action === "close"; for action === "advisory" the ternary yields undefined and the violated result is discarded entirely. evaluateScreenshotTableGate is called exactly once in the whole codebase (verified by grep across src/), and nothing else reads screenshotTableGateResult — it is never pushed into advisory.findings, never rendered in the PR comment/check-run output, never applied as a label. The maintainer and the contributor see literally nothing different from the gate being fully disabled.
Impact
The dashboard/config explicitly offers "advisory" as a legitimate, distinct alternative to "close" (the code's own validation-warning message literally says the field 'must be "close" or "advisory"'), so a maintainer reasonably expects it to behave like every other advisory-mode gate in this codebase — missing_linked_issue, self_authored_linked_issue, manifest_missing_tests, readiness/quality, lockfile_tamper_risk (src/queue/processors.ts:6777-6793, which explicitly pushes a finding for ANY non-off mode including advisory), and copycat (packages/loopover-engine/src/signals/copycat.ts:217-224, which also produces a finding for any non-off mode) — all of which surface a visible warning finding under their advisory mode even though they never block. screenshot-table-gate is the sole exception: choosing "advisory" silently disables the entire feature while looking fully configured. A maintainer who deliberately picked the softer option over "close" (specifically to avoid auto-closing legitimate contributors over missing screenshots) gets no enforcement AND no visibility — they believe they have a working soft-nudge gate, and don't, so visual/UI PRs missing before/after evidence sail through with no signal to either party. The bundled config docs at config/examples/loopover.full.yml:1131 even state (now-stale) 'close is the only supported value', reinforcing that the "advisory" branch of this feature was never wired up end-to-end.
Severity: HIGH
Location:
src/queue/processors.ts:2749Found via a full-system adversarial audit of the ORB review/scoring engine (2026-07-15) — 2 independent skeptic passes, both had to vote "confirmed" for this to survive.
Failure scenario
A maintainer sets
.loopover.yml'sgate.screenshotTableGate(or the dashboard equivalent, backed byscreenshotTableGateActionin the DB and exposed via the public settings API at src/openapi/schemas.ts:866 asz.enum(["close", "advisory"])) to{ enabled: true, whenLabels: ["frontend"], action: "advisory" }, expecting contributors to get a soft nudge to add a before/after screenshot table without their PR being auto-closed. A contributor opens afrontend-labeled PR with no screenshot table.evaluateScreenshotTableGate(src/review/screenshot-table-gate.ts:343) correctly computes{ violated: true, reason: ... }. But at processors.ts:2756-2759, the result is only converted into a usable signal whenscreenshotTableGateConfig.action === "close"; foraction === "advisory"the ternary yieldsundefinedand theviolatedresult is discarded entirely.evaluateScreenshotTableGateis called exactly once in the whole codebase (verified by grep across src/), and nothing else readsscreenshotTableGateResult— it is never pushed intoadvisory.findings, never rendered in the PR comment/check-run output, never applied as a label. The maintainer and the contributor see literally nothing different from the gate being fully disabled.Impact
The dashboard/config explicitly offers "advisory" as a legitimate, distinct alternative to "close" (the code's own validation-warning message literally says the field 'must be "close" or "advisory"'), so a maintainer reasonably expects it to behave like every other advisory-mode gate in this codebase — missing_linked_issue, self_authored_linked_issue, manifest_missing_tests, readiness/quality, lockfile_tamper_risk (src/queue/processors.ts:6777-6793, which explicitly pushes a finding for ANY non-off mode including advisory), and copycat (packages/loopover-engine/src/signals/copycat.ts:217-224, which also produces a finding for any non-off mode) — all of which surface a visible warning finding under their advisory mode even though they never block. screenshot-table-gate is the sole exception: choosing "advisory" silently disables the entire feature while looking fully configured. A maintainer who deliberately picked the softer option over "close" (specifically to avoid auto-closing legitimate contributors over missing screenshots) gets no enforcement AND no visibility — they believe they have a working soft-nudge gate, and don't, so visual/UI PRs missing before/after evidence sail through with no signal to either party. The bundled config docs at config/examples/loopover.full.yml:1131 even state (now-stale) 'close is the only supported value', reinforcing that the "advisory" branch of this feature was never wired up end-to-end.