ADR-0014: Adopter test format — policy tests as a public contract¶
| Status | Accepted (partial: expect.yaml → obligation/exact defaults per ADR-0017; P2-E5) |
| Date | 2026-07-21 |
| Deciders | Konrad Heimel |
| Context links | ADR-0006 testing · ADR-0007 effects · ADR-0010 config · D-010 |
Context¶
Adopters will not trust automerge they cannot test. The fixture format under
.assent/tests/ is therefore not tooling detail — it is a frozen public contract
(meta-plan Phase 3) on par with PolicyInput, and the primary vehicle for descriptive,
example-driven policy development in any self-service repo. Design goals: readable as
documentation, writable without knowing Go, diffable in review, and expressive enough to
pin decisions, findings, and score arithmetic.
Decision (proposed)¶
Directory case (full form)¶
.assent/tests/<pack>/<case-name>/
├── given/
│ ├── base/… # files as on the target branch (may be empty for "new file" cases)
│ ├── head/… # the same files as the MR proposes them
│ ├── facts.yaml # stubbed provider results (providers are never called in tests)
│ └── mr.yaml # optional MR metadata: author, labels, target branch (defaults exist)
└── expect.yaml
The engine derives the ChangeSet from base/ vs head/ with the production differ and
classifier — tests exercise the real pipeline (integration-level by construction), with only
providers and the forge stubbed.
expect.yaml¶
decision: REVIEW # APPROVE | REVIEW | BLOCK (required)
findings: # must-contain by default; `exact: true` for closed lists
- rule: topics/retention-shrink-challenge
effect: challenge
path: "/retentionMs"
message~: "data loss" # `~` suffix = substring/regex match on rendered message
absent: # rules that must NOT fire
- topics/no-topic-deletion
score: { total: 3, threshold: 4 } # optional, pins the arithmetic
Inline shorthand (single-file cases)¶
For the common "one field changed" case, a single YAML file
(.assent/tests/<pack>/cases.yaml) holds many small cases:
cases:
- name: partition-increase-ok
file: topics/prod/orders.yaml
base: { name: orders, owner: team-a, partitions: 12 }
head: { name: orders, owner: team-a, partitions: 24 }
facts: { quota: { max_partitions: 24 }, author: { groups: [team-a] } }
expect: { decision: APPROVE }
Runner semantics (assent test)¶
- Runs every case; failure output shows expected vs actual decision, the finding diff, and a ready-to-copy actual block (ADR-0012 hints style, cf. walkthrough).
--updategolden-flow: writes actuals intoexpect.yamlfor review-by-diff.- Rule coverage: every rule must be exercised by ≥1 case where its predicate holds and
(for
vouchrules) ≥1 where it does not;assent lintfails otherwise. Coverage report viaassent test --coverage. - Determinism: each case runs twice, results must be identical (same gate as golden L0 tests).
- CI templates run
assent teston every MR that touches.assent/— policies gate themselves.
Consequences¶
- Fixtures double as documentation and as our own e2e seeds (
examples/packs must keep their fixtures green — dogfooding, ADR-0006). - The format needs a JSON schema + versioned
apiVersionlike every other contract; changes go through openspec proposals. --updatemakes golden maintenance cheap but demands review discipline — the CI template therefore always runs tests from the target branch's expectations when policies change (interaction with the policy-ref question raised by the security review — resolve together).
Counterpoints considered¶
- "Reuse
opa test/ Rego unit tests." — Covers only Rego-backend rules, tests predicates in isolation rather than the envelope+aggregation pipeline, and is unwritable for the YAML-first audience. The harness may still runopa testadditionally for rego/ modules. - "Table-driven Go tests." — Our internal tests, yes (D-010); adopters never write Go.
Amendment (2026-07-21, ADR-0016 / design roast P2-2): safety vs presentation assertions¶
Policy tests assert structured safety semantics: decision, rule id, effect, paths,
score arithmetic. message~: wording assertions are discouraged (a template/locale change
must never break a safety test) and reserved for cases where the message is the behavior.
Rendered-output expectations (expect_comment.md goldens) belong to the template/theme
test layer, exercised via assent render against fixtures (ADR-0016 §4) — separate files,
separate failure domain. assent test --coverage counts only safety assertions.