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>
6.7 KiB
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:
- 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.
- 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. - 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:
- YAML frontmatter describing package metadata (id, version, namespace, action vocabulary scope, owner, status, activation, fixtures source).
- Prose sections (free-form) capturing intent: what the policy protects, why, what the failure modes look like, what break-glass semantics apply.
- Fenced
regoblocks containing the rules. - Fenced
regoblocks taggedtestcontaining OPA-compatible tests. - Fenced
yamlblocks taggedfixturecontaining 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
regolibrary 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
regois 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).