Files
flex-auth/docs/adr/0002-rego-in-markdown-policy-format.md
tegwick 55120ec20a Land foundations: assessment, ADR-001/002/003, FLEX-WP-0005, Go skeleton
Pre-implementation assessment and boundary review
(docs/pre-implementation-assessment.md) lead to three ADRs:
- ADR-001 Go + repo skeleton
- ADR-002 Rego-in-Markdown policy package format
- ADR-003 Topaz-aligned MVP (Topaz spike moves into foundations)

New workplan FLEX-WP-0005 (Foundations and Topaz Alignment) is inserted
between WP-0001 (done) and WP-0002 (core). WP-0002 pins Rego-in-Markdown
for P2.3; WP-0004 P4.1 refocused from Topaz evaluation to Topaz adapter.

Go skeleton at repo root: cmd/flex-auth + internal/{registry,policy,
decision,audit,adapters} + pkg/api + Makefile + .golangci.yml + GitHub
Actions CI. make ci green locally; bin/flex-auth --version works.

INTENT/SCOPE cite the NetKingdom IAM Profile and add the ops-warden /
ops-bridge disjoint-surface clarifications.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 01:54:44 +02:00

6.7 KiB
Raw Permalink Blame History

ADR-0002: Rego-in-Markdown Policy Package Format

Date: 2026-05-15 Status: Accepted Deciders: Bernd, with assessment from Claude (Opus 4.7) Supersedes: the "simple declarative rule format" placeholder in FLEX-WP-0002 P2.3.

Context

flex-auth's product surface is policy-as-code with reviewable, testable, versioned packages and an explain(decision_id) API. Three candidate package formats were on the table:

  1. Bespoke YAML rules now, port to Rego later. Lowest immediate complexity, but every adapter spike (Topaz, OPA, OPAL) would need a translator, and the "thin-wrapper-around-Topaz" trap (see assessment) gets worse the longer the bespoke layer exists.
  2. Rego from day one. Standard, battle-tested policy language with first-class Go library support (open-policy-agent/opa/rego). Reuse for Topaz, OPA, OPAL, Permit, and most ReBAC-adjacent ecosystems.
  3. Cedar. Strong typing and analyzability; smaller ecosystem; less alignment with Topaz, which is the MVP backend target in ADR-0003.

The product also values intent + validation co-location. Markdown is already the lingua franca of the NetKingdom knowledge plane (Markitect is the first consumer), and reviewable policy benefits more from prose context than YAML or pure .rego files can provide on their own.

Decision

A policy package is a Markdown document with:

  1. YAML frontmatter describing package metadata (id, version, namespace, action vocabulary scope, owner, status, activation, fixtures source).
  2. Prose sections (free-form) capturing intent: what the policy protects, why, what the failure modes look like, what break-glass semantics apply.
  3. Fenced rego blocks containing the rules.
  4. Fenced rego blocks tagged test containing OPA-compatible tests.
  5. Fenced yaml blocks tagged fixture containing decision fixtures (input/expected-decision pairs) that the loader can also evaluate against the Rego module.

The loader extracts and concatenates the rego blocks into one OPA module per package (using the package declaration in frontmatter), the test blocks into a sibling test module, and the fixture blocks into a fixtures table. Validation runs opa parse, opa test, and the fixtures evaluator before a package can be marked valid.

Minimal example

---
id: markitect.documents.internal-read
version: 0.1.0
namespace: markitect:document
package: flexauth.markitect.documents
actions: [read, query, search]
owner: team:platform-architecture
status: draft
fixtures:
  - examples/markitect/internal_doc_allow.yaml
  - examples/markitect/internal_doc_deny.yaml
---

# Internal Document Read

This package gates `read`, `query`, and `search` on documents labelled
`internal`. A subject must be in the `reader` group of the document's
owning team, or hold the `steward` role on the document's repository.

## Failure modes

- Stale group membership: resolver freshness must be within 15 minutes
  or the decision becomes `audit_only` with a `stale_directory` reason.
- Break-glass: an `emergency_principal` claim with a logged reason
  produces `allow` with an obligation to record an export receipt.

## Rules

```rego
package flexauth.markitect.documents

import future.keywords.if
import future.keywords.in

default decision := {"effect": "deny", "reason": "no_matching_rule"}

decision := {"effect": "allow", "reason": "reader_group"} if {
  input.action in {"read", "query", "search"}
  input.resource.labels[_] == "internal"
  some g in input.subject.groups
  g == sprintf("reader:%s", [input.resource.owner_team])
}

decision := {"effect": "allow", "reason": "steward_role"} if {
  input.action in {"read", "query", "search"}
  "steward" in input.subject.roles_on[input.resource.repository]
}
```

## Tests

```rego test
package flexauth.markitect.documents_test

import data.flexauth.markitect.documents

test_reader_group_allows_read if {
  documents.decision.effect == "allow" with input as {
    "action": "read",
    "subject": {"groups": ["reader:platform-architecture"]},
    "resource": {"labels": ["internal"], "owner_team": "platform-architecture"}
  }
}
```

## Fixtures

```yaml fixture
- name: reader group allow
  input:
    action: read
    subject: {groups: ["reader:platform-architecture"]}
    resource: {labels: ["internal"], owner_team: "platform-architecture"}
  expect: {effect: allow, reason: reader_group}
- name: no group deny
  input:
    action: read
    subject: {groups: []}
    resource: {labels: ["internal"], owner_team: "platform-architecture"}
  expect: {effect: deny, reason: no_matching_rule}
```

(Fence backticks above are zero-width-spaced for documentation. The real loader expects normal triple backticks.)

Rationale

  • Intent and validation co-located. A reviewer reading the package sees why alongside what. ADR text and policy code don't drift.
  • Standard evaluator. OPA's rego library is mature, well-tested, and the dominant Rego implementation. flex-auth gets correctness and performance work for free.
  • Topaz alignment from day one. ADR-0003 commits to shaping the standalone core so the Topaz adapter (FLEX-WP-0004 T01) is a small step. Rego is Topaz's native policy language.
  • Markitect synergy. The first consumer is a Markdown knowledge system. Policy packages and the documents they protect share one authoring substrate; Markitect renders policy packages natively; policy package versions sit comfortably in the same review process as any other document.
  • Tooling reuse. opa fmt, opa test, opa eval, and the broader OPA tool ecosystem just work on extracted modules.

Consequences

  • The loader must implement Markdown extraction. Off-the-shelf Goldmark
    • a small block walker covers it; tests must lock the fence syntax.
  • Authors must learn Rego. This is the universally accepted cost of Rego adoption. The literate format lowers the cliff by surrounding the language with intent prose.
  • Some pure-data fixtures still live in examples/ and are referenced from the frontmatter — keeps large fixture files out of the policy document body.
  • An ADR amendment will be needed if Cedar or a typed policy language enters the picture as a peer (not adapter). The literate Markdown envelope can accommodate other fenced languages, but rego is the baseline.

Out of Scope

  • Whether activation, rollout, and rollback semantics live in frontmatter or in a sibling activation manifest — settled in FLEX-WP-0002 P2.3.
  • The exact extraction library — implementation detail of P2.3.
  • ADR-0001: Go implementation (provides the OPA library path).
  • ADR-0003: Topaz-aligned MVP (provides the backend rationale).
  • FLEX-WP-0002 P2.3 (policy package loader and validator).