# 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 ```markdown --- 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. ## Related - 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).