Query / UI model & access control
Part of the Universal Audit Log Specification. Query patterns surfaced by the audit UI (§12) and the access-control model that gates who can read which audit entries (§13).
12. Query and UI Model
12.1 Shared Query Contract
Every app SHALL be able to query audit entries through the shared query API with filters for:
- tenant
- organisation
- resource type and resource id
- parent resource type and parent resource id
- actor id
- module
- action
- outcome
- date range
- changed field (via
changed_fieldsarray) - pagination (dual: offset/limit + keyset cursor)
12.2 Pagination: Dual Support with Keyset Migration Path
The existing audit query contract uses limit and offset (tasks.md §18.2, AuditTrailResult in audit.tools.ts). This spec does NOT silently replace that contract.
Phase 1 — Dual support: queryAuditTrail() SHALL accept BOTH pagination styles:
interface AuditQueryOptions {
// ... filters ...
// Existing contract (retained for backward compatibility)
limit?: number; // default: 50, max: 200
offset?: number;
// New keyset pagination (preferred for new consumers)
cursor?: { createdAt: Date; id: string };
}
interface AuditQueryResult {
entries: AuditEntry[];
// Existing contract fields (retained)
total: number;
limit: number;
offset: number;
// New keyset fields (added alongside)
nextCursor?: { createdAt: Date; id: string } | null;
}
- When
cursoris provided, it takes precedence overoffset. The response includes bothnextCursorand the legacyoffset/totalfields for backward compatibility. totalalways uses exactCOUNT(*)with the same tenant-scopedWHEREclause as the main query.pg_class.reltuplesis NOT suitable here because it reflects unfiltered table/partition cardinality, not tenant-filtered counts. For performance on large result sets, the count query benefits from the(tenant_id, created_at DESC)index and partition pruning. If count performance becomes a bottleneck, a per-tenant count cache (updated via trigger or background job) MAY be introduced as an optimisation.- Existing consumers (Directory
queryAudit, API routes) continue to work unchanged.
Phase 3+ — Migration: New consumers SHOULD use keyset pagination. Existing consumers SHOULD migrate when their UI supports cursor-based "load more" instead of page numbers. Offset support MAY be deprecated via a future ADR once all consumers have migrated.
The API cap of 200 rows per page applies to interactive queries. Streaming export (Section 15) handles bulk extraction.
12.3 Two Shared Read Patterns
Constellation SHALL support two standard audit views:
-
Per-resource history
- used on entity detail pages
- filters by
resource_type/resource_id - may optionally include related children via parent resource fields
-
Tenant-wide audit search
- used by admins, compliance officers, and investigators
- filters by actor, module, action, date, resource, organisation, and outcome
12.4 UI Reuse
A shared audit UI package or shared component set SHOULD be introduced after the API contract is stable.
It should provide:
AuditTrail/VersionHistorypanel- diff renderer for field changes
- pagination / load-more behaviour (keyset-aware)
- resource- and actor-display adapters
- outcome badge (success / failure / denied)
This is a reusable component pattern, not a standalone app.
13. Audit Access Control
13.1 Granular Permissions
Access to audit data SHALL be controlled by granular permissions, not a single read:audit flag:
| Permission | Scope | Description |
|---|---|---|
audit:read:own | Actor's own entries | User can view their own audit trail |
audit:read:org | Organisation | User can view audit entries within their organisation |
audit:read:tenant | Full tenant | Admin can view all audit entries for the tenant |
audit:read:classified | Classified entries (RESTRICTED and above) | Security officer can view entries with classification in (RESTRICTED, CONFIDENTIAL, SECRET) per Constellation_Architecture_Spec_v1 §Classification |
audit:export | Tenant | Permission to trigger bulk export (Section 15) |
13.2 RLS Enforcement
RLS policies SHALL enforce these scopes at the database level. Default: tenant isolation. audit:read:own adds actor filter; audit:read:org adds organisation filter; audit:read:classified is required for rows where classification is RESTRICTED, CONFIDENTIAL, or SECRET (the Constellation classification set defined in Constellation_Architecture_Spec_v1). UNCLASSIFIED entries are visible to all scopes that pass tenant isolation.
13.3 Audit-of-Audit-Access
Audit data access SHALL itself be logged. Query parameters and result counts are recorded in audit.access_log_entries (Section 16). Required for SOC 2 Type II evidence.