Skip to main content

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_fields array)
  • 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 cursor is provided, it takes precedence over offset. The response includes both nextCursor and the legacy offset/total fields for backward compatibility.
  • total always uses exact COUNT(*) with the same tenant-scoped WHERE clause as the main query. pg_class.reltuples is 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:

  1. Per-resource history

    • used on entity detail pages
    • filters by resource_type/resource_id
    • may optionally include related children via parent resource fields
  2. 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 / VersionHistory panel
  • 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:

PermissionScopeDescription
audit:read:ownActor's own entriesUser can view their own audit trail
audit:read:orgOrganisationUser can view audit entries within their organisation
audit:read:tenantFull tenantAdmin can view all audit entries for the tenant
audit:read:classifiedClassified entries (RESTRICTED and above)Security officer can view entries with classification in (RESTRICTED, CONFIDENTIAL, SECRET) per Constellation_Architecture_Spec_v1 §Classification
audit:exportTenantPermission 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.