Appearance
Read access and OpenAPI surface
This page covers how the read endpoints are gated by ReBAC and the shape of the four OpenAPI operations they expose. For the chain mechanics behind the data the operations return see Hash chain and residency model; for the entry point and ubiquitous language see the index.
ReBAC rule — domain#auditor reused
The read surface is gated by the existing auditor relation on the Domain (schema/authz.zed:77). No new relation is introduced.
zed
definition domain {
relation owner: user
relation admin: user | group#member
relation auditor: user | group#member
relation member: user | serviceaccount | group#member
permission manage = owner + admin
permission read = owner + admin + auditor + member
}The DECISION to reuse domain#auditor rather than route audit-read through cloud#manage (the canonical Cloud mutation permission on the Cloud Inventory aggregate) or introduce a parallel audit_reader relation is pinned in two places to keep the rationale local to the code. Reusing cloud#manage would conflate read access with mutation rights, and a parallel audit_reader relation forces every Domain admin to grant two relations (admin + audit_reader) for the same caller — a drift hazard. The legacy cloud-admin token was retired in lockstep with the introduction of cloud#manage as a real permission on the cloud definition.
- The comment block above
relation auditorinschema/authz.zedrecords the alternatives considered and the reason for rejection (a parallel relation forces every Domain admin to grant two relations for the same caller — a drift hazard). - The package-level DECISION block in
internal/audit/doc.gomirrors the same rationale on the Go side, so a reader chasing the read-relation from code finds the rationale without leaving the file.
internal/authz.Authorizer.Check(domain, "auditor", subject) is the single call site every audit handler routes through. Denials on the audit-read path are themselves audited (object="audit-archive:<domain>", reason="insufficient_relation") so a hostile read attempt is recorded on the same chain it failed to read.
The integration test tests/integration/audit_query_authz_test.go pins the four-arm contract:
domain#admin→ 200 (admin permission derives fromauditor).domain#auditor→ 200.domain#member→ 403 with the denial recorded as a chain row.- A cursor minted for Domain A replayed against Domain B → 400 with
code: cursor_invalid.
OpenAPI surface
Four operations are defined on the audit tag, all under /v1/domains/{domainId}/audit. Every endpoint requires domain#auditor and surfaces the four-status contract (200/ 401/403/404) consistently. The full schemas live in api/openapi/plexsphere-v1.yaml; this section is the navigation map.
| operationId | Method + Path | Purpose | Schemas |
|---|---|---|---|
ListAuditEntries | GET /v1/domains/{domainId}/audit/entries | Cursor-paginated list with filters (subject, relation, object_type, object_id, reason, from, to, correlation_id). Page size clamped server-side at 200. | AuditEntry, AuditEntryList, AuditReason, AuditSubject, AuditObject, AuditDecision, AuditRequestContext |
GetAuditEntry | GET /v1/domains/{domainId}/audit/entries/{seq} | Single entry plus the canonical-bytes proof bundle for off-line hash recomputation. Cross-Domain seqs surface as 404 (no enumeration oracle). | AuditEntryProof |
VerifyAuditChain | POST /v1/domains/{domainId}/audit/verify | Range-bounded chain verifier. Divergence is NOT an error — tampered chains surface as 200 { ok: false, divergent_seq, expected_hash, observed_hash }. The 5xx channel is reserved for infrastructure faults so the operator's incident playbook (named divergence) and oncall page (5xx) stay distinct. | AuditChainVerifyRequest, AuditChainVerifyResult |
EraseIdentityFromAudit | POST /v1/domains/{domainId}/audit/erase-identity | Idempotent right-to-erasure entry point. Returns the derived pseudonym so operators can correlate the self-audit row. | AuditEraseIdentityRequest, AuditEraseIdentityResponse |
The AuditDecision.caveat_context schema carries the x-plexsphere-names-only: true vendor extension, policed by the plexsphere-audit-caveat-names-only Spectral rule (tools/openapi/.spectral.yaml) which fails any future schema declaring the extension on a non- array<string> shape.
Platform-residency read surface
The audit aggregate maintains a second chain for actions no single Domain owns — the platform-residency chain. Cross-Domain list reads such as domain list and project list stamp their granted rows onto the reserved platform anchor (platform:plexsphere), so those rows live on this chain rather than any Domain's.
ReBAC rule — platform#auditor
The platform read surface is gated by the auditor relation on the platform definition, the residency-isolated peer of domain#auditor: a per-Domain domain#auditor cannot read the platform chain, and a platform#auditor cannot read a Domain's chain.
zed
definition platform {
relation admin: user | group#member
relation auditor: user | group#member
permission manage = admin
permission read = admin + auditor
}The list, get, and verify handlers gate on the auditor relation; the erase-identity handler gates on the manage permission — mirroring the per-Domain split on domain#auditor / domain#manage.
OpenAPI surface
Four operations are defined on the audit tag under /v1/platform/audit, wire-symmetric to the per-Domain operations (identical request and response schemas) with the fixed platform anchor substituted for the path-supplied Domain id. The full schemas live in api/openapi/plexsphere-v1.yaml.
| operationId | Method + Path | Purpose | Schemas |
|---|---|---|---|
ListPlatformAuditEntries | GET /v1/platform/audit/entries | Cursor-paginated list with the same filters as the per-Domain surface. | AuditEntry, AuditEntryList |
GetPlatformAuditEntry | GET /v1/platform/audit/entries/{seq} | Single entry plus the canonical-bytes proof bundle. | AuditEntryProof |
VerifyPlatformAuditChain | POST /v1/platform/audit/verify | Range-bounded chain verifier. Divergence surfaces as 200 { ok: false, … }, never an error. | AuditChainVerifyRequest, AuditChainVerifyResult |
EraseIdentityFromPlatformAudit | POST /v1/platform/audit/erase-identity | Idempotent right-to-erasure entry point. | AuditEraseIdentityRequest, AuditEraseIdentityResponse |
CLI reachability
The platform read surface is reachable from the CLI via the --platform flag on the plexctl audit command family — plexctl audit entries list --platform, … verify --platform, and so on. See the plexctl audit reference for the flag contract and exit-code mapping.