185 lines
6.7 KiB
Markdown
185 lines
6.7 KiB
Markdown
|
|
# 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).
|