ADR-019 — Quality gates are executable contracts; defects are classified
canonical· tech-lead · updated 2026-09-07 · source
- Status: Accepted (José, 2026-09-07)
- Date: 2026-09-07
- Owner: Tech Lead
- Related issues: effort #1703 · subs #1704 (this ADR +
quality-vocabulary.md) #1705 (gate command) #1706 (blocking entry check) #1707 (observable exit evidence) #1708 (severity labels) #1709 (bug form) #1710 (stale task form) · triggered by #1689 → #1692 · #1693/#1694 (the finding-extraction fix)
Context
Our review and merge criteria existed only as prose, so nothing verified them:
- PR #1689 merged with four untriaged CodeRabbit findings, two of them real bugs — one directly undercutting a fix shipped in that same PR — while every declared gate read green.
- Three rules stated a different "the gates" (
risk-tiered-review.md,delegation-brief.md,react-style-guidelines.md), and none was checkable. - The entry check existed but was narrower than the rules claimed:
.githooks/pre-push(#786) blocks a push onpnpm linterrors only — not typecheck, not the affected build, not the knowledge lint — so "the gates are green" was still unverifiable at push time. It is wired viacore.hooksPath=.githooks, set by the rootpackage.jsonpreparescript, so it already applies to every worktree. (.git/hooksbeing empty is a red herring:core.hooksPathredirects. An earlier draft of this ADR wrongly said no hook was installed — corrected here.) Separately, the kit's own seamprePushin.claude/kit.config.jsonshipped{enabled:false, command:""}— declared and unused. The 16.claude/hooksscripts are Claude Code hooks: they observe assistant tool calls only, never a human's or a delegated agent's push. - The upstream captain (
cckit scripts/lib/captain.sh) classifies a PRCLEAN → mergefrommergeable+mergeStateStatus+statusCheckRollupalone. Localtypecheck/lintleave no trace and CodeRabbit's triage state is invisible there — only its own green check, which #1689 proved is not the same thing. An empty rollup collapses toNONE→CLEAN. - Defects had no classification: labels carry
priority:p0–p3+risk:*and no severity, and no bug form exists — so causal analysis is impossible and impact/urgency are one axis.
A standards review of our own mechanics supplied the missing definitions: ISO/IEC 20246:2017, ISO/IEC/IEEE 1044-2009, 24765, 29119-2/-3, 15288/12207, ISO 19011:2018, ISO 9000:2015, ISO/IEC 17021-1:2015. Two findings are load-bearing: "quality gate" has no ISO/IEEE definition (the normative concept is a decision gate = entry criteria + exit criteria + deciders), and severity and priority are independent attributes that 1044 deliberately keeps apart.
Decision
- One vocabulary, two families that never mix — engineering (issue/anomaly/defect/fault/
failure/severity/priority) vs audit (finding/nonconformity/major-minor/OFI). Canonical home:
.claude/rules/quality-vocabulary.md. "Observation" is removed as a finding category (ISO 19011:2018 dropped it — an unbreached requirement is an OFI). - Every gate names four elements: entry criteria · exit criteria · the decider · the waiver path. A gate nobody can fail is a milestone, not a gate.
- The repo gate becomes executable and observable. Entry criteria are enforced by widening
the existing blocking pre-push hook from lint-only to the whole gate — a git hook gates humans
and delegated agents identically, which a Claude Code hook cannot, and
git push --no-verifybypasses any hook, so this is a fast equal-for-everyone check, never an unbypassable one. Exit criteria are enforced by evidence the merge step can read, so "the gates are green" stops being an assertion. - Severity is a separate axis (
sev:blocker|major|minor|trivial), and a defect closes on exactly one disposition from a closed set (fixed/rejected/duplicate/deferred/not reproducible). - It lands in tedos first, then goes upstream to cckit once proven — the pattern already used
by
coderabbit.shandds-audit.sh, and it keeps 1 effort = 1 PR.
Consequences
- A push that fails the gate is rejected, and a merge without gate evidence is refused. Expect friction on the first pushes; the waiver exists for the genuine exception and records itself.
- The three divergent "the gates" wordings become pointers to one rule (latest-touched wins).
- Defect data becomes analysable (severity × disposition × detection point), which also feeds the builder-model corpus (#1080) — the same argument as the effort trace in ADR-006.
cap_policy_flooralready implements the always-human floors (workflows, lockfile/graph, secrets,hold); this ADR does not rebuild them.- Cost: zero — rules, bash, labels and issue forms. No infrastructure, no API credits.
Alternatives considered
- Document the criteria only, no enforcement — rejected: the rule already existed when #1689 merged. A criterion nobody checks is the exact failure mode.
- Enforce at merge only — rejected: expensive review then gets spent on unready artefacts, which is precisely what ISO/IEC 20246 entry criteria exist to prevent.
- Enforce at push only — rejected: #1689 was a merge failure; the code was fine.
- Build it in cckit first — rejected: it would be designed in the repo where the incident did not happen, and every iteration would need a release plus an upgrade.
- Corrective-action records with effectiveness verification (ISO 9001 10.2) and a review-type formality map (20246's ten types) — considered, deliberately deferred to a sibling effort.