Skip to content

Audit expectations

Every state-changing action you take as a platform admin writes to at least one audit log. Impersonation actions write to two simultaneously. Nothing platform admins do is invisible to the target org Owner. Logs are immutable — GET /api/v1/platform/audit-log is the only registered route on the platform audit handler (cloud/internal/platform/audit_handler.go:36-39); DELETE/PATCH are not registered by design.

Platform-side log (platform_audit_log)

365-day retention, configurable via PLATFORM_AUDIT_RETENTION_DAYS (default 365). The daily sweeper at cloud/cmd/server/main.go:448-468 purges older rows. Visible only to platform admins via GET /api/v1/platform/audit-log and the /platform/audit-log page.

The table below splits emitted (live in code today) from documented-but-deferred entries.

Emitted today

ActionWhen emittedIncludesCode
platform.loginEvery successful platform loginplatform_user_id, IP, User-Agentauth_handler.go:92
platform.logoutExplicit logout (cascades impersonation revoke)platform_user_idauth_handler.go:157
platform.org_createdNew customer org provisioned via POST /platform/orgsactor, target orgorg_service.go:148
platform.org_disabledOrg suspended via POST /platform/orgs/{id}/disableactor, target orgorg_service.go:171
platform.org_enabledOrg re-enabled via POST /platform/orgs/{id}/enableactor, target orgorg_service.go:183
platform.org_delete_scheduledPOST /platform/orgs/{id}/delete flipped status to pending_deletionactor, target orgorg_service.go:221
platform.impersonation_startedScoped token minted via POST /platform/orgs/{id}/impersonatetarget org, reason, impersonation_idimpersonation_handler.go:94,108
platform.impersonation_actionOne row per authenticated request made under impersonation (NOT a session summary — every request gets its own row)impersonation_id, method + path + statusmiddleware/impersonation.go:147-160 + repository.go:881
platform.impersonation_endedExplicit end, TTL expiry, cascade from org disable/delete-scheduled, or owner toggleimpersonation_idimpersonation_handler.go:174,187

Documented but not yet emitted (forward-looking)

These actions appear in the long-term platform-admin design but have no live emitter in code today. Treat any operational pages that depend on them as forward-looking until each emitter ships. See GAP-9-08.

ActionLong-term intentStatus
platform.login_failedOne row per failed platform login attemptFailed attempts are counted in failed_platform_logins for rate-limit purposes but no audit row is written.
platform.password_changedPlatform-admin password changeNo password-change endpoint exists yet.
platform.user_createdA platform admin created another platform user via POST /platform/usersThe handler does not emit audit.
platform.user_disabledA platform admin toggled enabled=false on another platform userThe PATCH handler does not emit audit.
platform.org_deletion_initiated / platform.org_deletedTwo-stage hard-delete (initiate + sweep after 7 days)Code emits platform.org_delete_scheduled once; the post-grace sweeper is unshipped.
platform.impersonation_blockedMint rejected because target org has allow_platform_impersonation = falseThe mint handler returns 403 IMPERSONATION_BLOCKED but does not write an audit row for the rejection.

Org-side log (audit_log)

90-day retention. Visible to the customer’s Owner and Admins via /admin/audit-log. Platform actions appear with a Platform Support badge.

Action written hereactor_typeVisible with these details
platform.impersonation_startedplatform_admin_impersonatingreason, ticket_ref, expiry time
platform.impersonation_endedplatform_admin_impersonatingend reason
Org-domain action under impersonation (e.g. user.role_changed, camera.updated, alert_rule.deleted)platform_admin_impersonatingoriginal action fields + impersonation_id
org.platform_impersonation_disableduser (the Owner)who toggled off, when
org.platform_impersonation_enableduser (the Owner)who toggled on, when
org.created_by_platformplatform_admininitial provisioning, platform actor id
org.disabled_by_platformplatform_adminreason

Visibility matrix

LogPlatform adminsOrg OwnerOrg AdminOrg OperatorOrg Viewer
platform_audit_logfullnononono
Customer audit_log (own org)via impersonation onlyfullfullnono
Customer audit_log (other org)nonononono

What is NOT audited

Read-only platform-side reads do not produce per-request audit rows:

  • GET /api/v1/platform/orgs listing — no audit row. Acts on platform-owned metadata only, never on tenant content.
  • GET /api/v1/platform/fleet/health — no audit row. Returns aggregate counts only.
  • Cross-org search — the endpoint is not yet implemented; the documentation page is forward-looking (GAP-9-11).
  • Browser navigation inside /platform/* pages — frontend route changes are not audited. Only backend API calls produce audit.

State-changing calls inside an impersonation session ARE always audited via the dual-write helper (cloud/internal/audit/dual_write.go:47-107). If you find a state-changing path that bypasses dual-write, treat it as a bug and file an incident.

Retention and export

  • Platform log: 365 days (configurable via PLATFORM_AUDIT_RETENTION_DAYS). No CSV export endpoint ships today; export is via direct DB query (GAP-9-12).
  • Org log: 90 days (configurable via AUDIT_RETENTION_DAYS). Exportable by org admin via the in-product audit-log export.
  • Hard deletion of an org would delete its org-side audit log with the rest of the org’s data; the platform-side record (platform.org_delete_scheduled) survives. The post-grace hard-delete sweeper is unshipped (GAP-9-10).

See also