Skip to content
assent assent

Deterministic approval for repository changes

Canonical repo: GitHub (PlatformRelay/assent). Status: alpha — the GitLab CI path is Core (E1–E9 engine, forge, provider, renderer). Pre-1.0: policy schema and CLI flags may change between releases; see API stability.

assent is a deterministic, policy-driven auto-merge gate for self-service configuration repositories. Drop it into a repo's CI pipeline and it turns merge requests into decisions: approve, comment, request changes, or block — based on rules you write in Kyverno-style declarative YAML with CEL predicates.

Why

Most changes to config repos (topic definitions, service catalogs, tfvars, tenant onboarding files) are routine: a team edits their own entries within safe bounds. Yet a human still has to review every MR, reconstructing the same context each time — what changed, who owns it, is it destructive, which policy applies. assent encodes that reasoning as policy so the routine 90% merges itself and reviewers spend their attention on the risky 10%.

  • Fail-safe decisions — every run emits an auditable DecisionRecord; ambiguous policy fails closed (ADR-0015).
  • Semantic diffs — JSON, YAML, and HCL/tfvars parse into field-level adds/modifies/deletes, not line noise; .tf files are governed but opaque (whole-file REVIEW, never a partial parse) (ADR-0003).
  • Testable policies — fixture changes in, expected decision out; policies without tests are a lint error (ADR-0014).

How it works

flowchart LR
  MR["Merge request"] --> CI["GitLab CI job"]
  CI --> Assent["assent run"]
  Assent --> Forge["GitLab forge API"]
  Assent --> Providers["Permission & fact providers"]
  Assent --> Policy[".assent/ policies"]
  Assent --> Out["Threads · comments · approve · merge"]

Key property: assent is stateless per invocation — every run recomputes the decision from (diff, repo snapshot, facts, policy version). No database, no long-lived service in v1. See system context for the full C4 diagram.

Quick start

Install from source (install guide):

go install github.com/PlatformRelay/assent/cmd/assent@latest
assent version

go install compiles without link-time stamping, so the binary it produces reports assent 0.0.0-dev — even when you pin a tag (@v0.4.0). For a version-stamped binary take the Homebrew tap or a release archive: goreleaser injects the version (-X main.version) and the archives are checksum- and signature-verifiable. Both routes are in docs/usage/install.md.

Lint and test policies locally. Both commands take the repository root — assent appends .assent itself, so passing .assent/ makes it look for .assent/.assent:

assent lint .
assent test .

No repo of your own yet? A clone of this one ships runnable sample policy trees; run the two commands above from examples/packs/service-catalog (that is the fixture the README's quick-start block is smoke-tested against — hack/docs/readme_smoke_test.sh reads the README, not this page).

Runs on a real merge request select their forge explicitly (E10-S13 — no default forge):

GITLAB_TOKEN=<pat> assent run --forge gitlab --project 42 --mr 7 --subject file:topics/orders.yaml --bot-author assent-bot
GITHUB_TOKEN=<token> assent run --forge github --project owner/repo --mr 7 --bot-author assent-bot

On GitHub v1 runs comment-only — they decide and comment but never arm auto-merge until a decidable protected-pipeline-source predicate exists (OQ-33; OQ-34 holds require-review evidence to the same bar). Unprobed capabilities never arm (ADR-0021 §3).

Start here

Contributing