docs(rbac): DOC-002 + COMP-005 — pin auditor role invariants in operator docs

Acquisition-audit DOC-002 + COMP-005 closure (Sprint 7 ACQ,
2026-05-16). Both findings were UNKNOWN because the auditor
couldn't independently verify the auditor-role permission set is
locked-down. The set IS locked down in three places (schema,
code, tests) — DOC-002 + COMP-005 close by surfacing that pin in
docs/operator/rbac.md so a future SOC 2 / FedRAMP / PCI auditor
can re-derive the proof without rebuilding the trail.

New "Auditor role invariants" subsection in docs/operator/rbac.md
under the existing two-person integrity section. Documents:

  Layer 1 (schema) — migrations/000029_rbac.up.sql:261-262 +
    migrations/000039_audit_crit1_perms.up.sql:111 (the inline
    "r-auditor: NOTHING new" comment).

  Layer 2 (code) — internal/domain/auth/DefaultRoles[RoleIDAuditor].

  Layer 3 (the load-bearing one — tests):
    - TestAuditorRoleHoldsExactlyAuditReadAndExport
        set-equality on {audit.read, audit.export}
    - TestAuditorRoleDoesNotHoldMutatingOrReadingNonAuditPerms
        catches subtle widening even if set-equality is bypassed
    - TestAuditorRoleSeparateFromViewer
        pins auditor and viewer permission sets are disjoint
        except audit.read (which viewer shares by design)

Explicitly notes the audit prompt's recommendation against a bash
CI guard — the property is already enforced at the Go test layer
with stronger semantics (struct-aware set equality) than `grep`
could provide.

No code changes; documentation-only closure (existing tests + schema
already pin the invariant). Verified locally: gofmt clean, go vet
clean across internal/domain/auth + internal/service.
This commit is contained in:
shankar0123
2026-05-16 20:36:44 +00:00
parent 35277c0f2c
commit 1b95709d4b
+39
View File
@@ -68,6 +68,45 @@ giving them the keys to the kingdom. The
`internal/domain/auth/auditor_test.go` invariants pin this set going
forward.
### Auditor role invariants (DOC-002 / COMP-005 closure)
Acquisition-audit DOC-002 + COMP-005 closure (Sprint 7 ACQ, 2026-05-16).
The auditor role's permission set is **pinned at exactly two
permissions** — `audit.read` and `audit.export` — and any drift breaks
the SOC 2 / FedRAMP / PCI separation. The pin is enforced at three
layers and the load-bearing layer is the unit-test set, not a bash CI
guard:
1. **Schema layer**`migrations/000029_rbac.up.sql:261-262` seeds
exactly two `role_permissions` rows for `r-auditor`
(`r-auditor / p-audit-read / global / NULL` and
`r-auditor / p-audit-export / global / NULL`).
`migrations/000039_audit_crit1_perms.up.sql:111` adds an inline
comment confirming `r-auditor` was NOT widened by the migration that
shipped the five admin-only fine-grained perms.
2. **Code layer**`internal/domain/auth/DefaultRoles[RoleIDAuditor]`
matches the schema. A future code change that adds a non-audit
permission to the slice is caught by:
3. **Test layer** (the load-bearing one) —
`internal/domain/auth/auditor_test.go` ships three pinning tests:
- `TestAuditorRoleHoldsExactlyAuditReadAndExport` — set-equality
comparison; fails on any add or remove
- `TestAuditorRoleDoesNotHoldMutatingOrReadingNonAuditPerms`
enumerates the slice and rejects any permission outside the
`{audit.read, audit.export}` set; catches subtle widening even if
the set-equality test is bypassed
- `TestAuditorRoleSeparateFromViewer` — pins that the auditor and
viewer permission sets are disjoint except for `audit.read` (which
viewer shares by design); catches the "auditor inherits viewer
reads" leg
A bash CI guard was deliberately **not** added — the property is
already enforced at the Go test layer with stronger semantics
(struct-aware set equality) than `grep` could provide. If a future
contributor proposes widening `r-auditor`, the three tests above
fail at `go test ./internal/domain/auth/...` BEFORE the change can
land in a merge.
The five **admin-only fine-grained perms** seeded by migration
000030 gate the high-blast-radius endpoints: