Skip to content

ADR-021: Halt-On-Violation Quality Gate

Accepted

2026-06-11

“Run the tests before you call it done” is only meaningful if there is one command that defines “done” and it fails loudly. Previously the repo had separate task test, task lint, task test:binary-size, and task scan:vuln with no single gate, and no rule binding agents to run them. Agents would declare work complete on a green happy-path build while lint, race, budget, or vulnerability checks were never run.

Define a single halt-on-violation gate, task ci, that an agent must run before claiming any change complete (Constitution rule 9). It chains:

fmt:check → lint → go test -race -covermode=atomic ./... → agents:check → versions:check → check:adr → check:generated → test:binary-size → test:asset-budgets → scan:vuln

(Chain as of 2026-08-19 — versions:check (ADR-030), check:adr (ADR-033) and check:generated (#90, warn-only regeneration-drift check for ADR-003/ADR-017) joined the gate after acceptance; Taskfile.yml is authoritative.)

If it exits non-zero, the agent halts and fixes the failure. It must not lower a threshold, exclude files, or bypass git hooks with --no-verify.

2026-07 Amendment: the GitHub Actions workflow originally re-implemented these checks as parallel jobs, and they drifted (fmt:check ran locally but never in CI). The workflow now installs the environment (Postgres, toolchain, Task) and invokes task ci itself — the same single-source-of-truth fix ADR-022 applied to AGENTS.md. If the gate needs a new check, add it to the ci task; the workflow inherits it.

  • “Done” has one definition, locally and in CI.
  • Formatting, race conditions, binary-size budget, agent-spine drift, and known vulnerabilities are all caught before review.
  • Slightly slower inner loop — mitigated by running task test/task lint individually during iteration and reserving task ci for the final gate.
  • Rely on CI only. Rejected — pushes failures to the slowest feedback point and lets agents claim completion prematurely.
  • A pre-push hook running everything. Complementary, not a replacement — the constitution rule makes the agent run it before claiming done, not just before pushing.
  • Testable consequences:
    • TC-1: task ci chains the full gate (fmt, lint, race+coverage tests, agents:check, versions:check, check:adr, binary/asset budgets, vuln scan).
    • TC-2: .github/workflows/ci.yml invokes task ci rather than re-implementing its steps.
  • Checks:
    • TC-1 → the Taskfile definition itself; any removal is a public diff (status: block, pre-existing)
    • TC-2 → adr021-ci-invokes-gate in scripts/adrcheck (status: block, promoted 2026-09-30)
  • Not machine-checkable: That an agent actually ran the gate before claiming done — approximated by the Stop-gate hook (ADR-033), which runs tests and the check suite when an agent tries to finish.
  • Graduation log:
    • 2026-09-30 — promoted warn → block (ADR-033 §4): clean since 2026-07-12 (80 days, zero false positives; the rule asks for 7). checks/enforcement.config.json carries graduated: 2026-09-30. Demotion back to warn is allowed with the same trail.