Skip to content

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 auditor in schema/authz.zed records 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.go mirrors 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 from auditor).
  • 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.

operationIdMethod + PathPurposeSchemas
ListAuditEntriesGET /v1/domains/{domainId}/audit/entriesCursor-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
GetAuditEntryGET /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
VerifyAuditChainPOST /v1/domains/{domainId}/audit/verifyRange-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
EraseIdentityFromAuditPOST /v1/domains/{domainId}/audit/erase-identityIdempotent 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.

operationIdMethod + PathPurposeSchemas
ListPlatformAuditEntriesGET /v1/platform/audit/entriesCursor-paginated list with the same filters as the per-Domain surface.AuditEntry, AuditEntryList
GetPlatformAuditEntryGET /v1/platform/audit/entries/{seq}Single entry plus the canonical-bytes proof bundle.AuditEntryProof
VerifyPlatformAuditChainPOST /v1/platform/audit/verifyRange-bounded chain verifier. Divergence surfaces as 200 { ok: false, … }, never an error.AuditChainVerifyRequest, AuditChainVerifyResult
EraseIdentityFromPlatformAuditPOST /v1/platform/audit/erase-identityIdempotent 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.