ADR-0010: Configuration files and governed-repo layout¶
| Status | Accepted (partial: prove/require envelope shapes per ADR-0017 §2/§5; P2-E5) |
| Date | 2026-07-21 |
| Deciders | Konrad Heimel |
| Context links | ADR-0002 policy surface · ADR-0007 effects · ADR-0008 routing · OQ-5 |
Context¶
Adopters interact with the tool almost exclusively through files in their repo. The file taxonomy is the UX. It must separate concerns cleanly: repo wiring (environments, classes, providers) vs. routing (bindings, thresholds) vs. rules (packs) vs. tests — so that packs are shareable across repos while wiring stays local.
Decision (proposed)¶
Everything lives under .assent/ in the governed repo (name follows OQ-1):
.assent/
├── config.yaml # repo wiring: environments, change classes, providers
├── bindings.yaml # routing: (class, environment) -> packs + risk thresholds
├── packs/
│ └── topics/
│ ├── pack.yaml # pack metadata (name, version, description)
│ ├── rules/ # MergePolicy documents (YAML envelope, ADR-0002)
│ └── rego/ # escape-hatch Rego modules referenced by rules
└── tests/
└── topics/
└── partition-increase-ok/
├── given/ # fixture: changed files (base/ and head/ variants) + facts.yaml
└── expect.yaml # expected decision + findings
Remote packs (central policy repos, pinned by git ref) are planned via
packs: [git::https://…//packs/topics?ref=v1.2.0] in bindings — local overrides win (OQ-5).
config.yaml — repo wiring¶
apiVersion: assent.dev/v1alpha1
kind: Config
environments:
- name: prod
match: { paths: ["topics/prod/**", "envs/prod/**"] }
- name: dev
match: { paths: ["**"] } # last match wins as default
classes:
- name: kafka-topic
match: { paths: ["topics/**/*.yaml"] }
- name: infra-vars
match: { paths: ["**/*.tfvars"] }
providers:
author: # -> facts.author.*
type: builtin/gitlab-groups
quota: # -> facts.quota.*
type: http
url: https://quota.example.com/api/v1/lookup
failure: closed # closed (default) -> REVIEW; open -> skip facts
bindings.yaml — routing + risk¶
apiVersion: assent.dev/v1alpha1
kind: RulesetBinding
bindings:
- class: kafka-topic
environment: dev
packs: [topics]
risk: { threshold: 10 }
- class: kafka-topic
environment: prod
packs: [topics, topics-strict]
risk: { threshold: 4 }
- class: infra-vars
environment: "*"
packs: [tfvars]
risk: { threshold: 6 }
A rule file — envelope with effects, scope, both predicate backends¶
apiVersion: assent.dev/v1alpha1
kind: MergePolicy
metadata: { name: topic-safety }
spec:
rules:
- name: partition-increase-within-quota # tier 1: assert predicate
match: { changes: [{ path: "**/partitions", kind: modify }] }
assert: "new >= old && new <= facts.quota.max_partitions"
effect: vouch
points: 1
- name: retention-shrink-challenge
match: { changes: [{ path: "**/retentionMs", kind: modify }] }
assert: "new < old" # predicate true -> effect applies
effect: challenge
message: "Retention shrinks from {{ old }} to {{ new }} — data loss possible. Sure?"
- name: naming-convention # tier 2: rego, branch scope (ADR-0008)
match: { classes: [kafka-topic] }
scope: branch
rego: { file: ../rego/naming.rego } # returns findings data only
effect: comment
points: 2
- name: no-topic-deletion
match: { changes: [{ path: "topics/**", kind: delete }] }
effect: block
message: "Topic deletion is never auto-mergeable."
Consequences¶
config.yamlis the only file that knows company-specific wiring (providers!); packs stay portable and publishable. This is the seam that makes per-company permission reimplementation (ADR-0004) a config exercise plus one small provider service.- Tests are first-class repo citizens next to the packs they test;
assent lintfails packs without tests. - All kinds share one
apiVersionline for engine-version gating and future migrations.
Counterpoints considered¶
- "One big file is simpler." — For toy repos, yes; it destroys pack shareability and makes
ownership (CODEOWNERS on
.assent/packs/x/) impossible.initcan still generate a minimal single-pack layout.
Amendment (2026-07-21, second review P2-9 / security review A-12)¶
- Starter packs must match destructive intent, not one change kind:
no-topic-deletion-style rules matchkind: [delete, rename]— otherwise the rename-fold (ADR-0003) walks a de-facto delete+recreate past the block rule. - Remote packs (when they land, OQ-5): pinned by commit SHA (tags are mutable), checksum/ signature verified, and subject to the same target-ref/no-self-modification rule as local policy (ADR-0015 §1).
- The lint hard-error list (vouch scoping, reserved classes, environment priority,
fail-open restrictions, docs-on-challenge/block, tests-per-rule) is consolidated in the
Phase 3 spec for
assent lint.