Skip to main content

Audit Events

Catalog of every event the exd-server audit log emits. The codes here are normative API surface — SIEM rules, compliance queries, and incident timelines grep against the audit_event field.

Source: crates/exd-server/src/audit.rs (event constants) and the call sites in crates/exd-server/src/routes/, crates/exd-server/src/bin/admin.rs. New codes go through this page first; the reservation isn't real until a row lands here.


Wire format

Every audit line is a regular tracing::info! event with target exd_server::audit. In EXD_LOG_FORMAT=json mode (production default) it lands as one NDJSON line carrying:

{
"timestamp": "2026-05-15T14:23:11.482Z",
"level": "INFO",
"target": "exd_server::audit",
"message": "audit",
"audit_event": "tenant.created",
"tenant": "acme",
"span": {
"request_id": "01HXY3Z6Q9NQF6P2K2T4ZR3T8V",
"principal_kind": "superadmin",
"principal_id": "7c4a8d09",
"method": "POST",
"route": "/api/v1/tenants",
"name": "http_request"
}
}

The §3 field set (request_id, principal_kind, principal_id, tenant, namespace, method, route) is inherited from the surrounding request span when one is active. Audit lines that originate outside a request — bootstrap.run from the admin CLI, for example — carry only the event-specific fields.

To route audit lines to a separate sink (write-only S3, SIEM, tamper-evident store), filter on target == "exd_server::audit" in the log shipper. Today the audit lines are interleaved with the request log on the same stdout/stderr stream; a follow-up will support a dedicated file descriptor (fd:3) for sidecar fan-out.


Catalog

CodeWhen it firesSourceStatusEvent-specific fields
tenant.createdPOST /api/v1/tenants returns 201routes/tenants.rs::createShippedtenant (the new slug)
namespace.createdPOST /api/v1/tenants/{tenant}/namespaces returns 201routes/namespaces.rs::createShippedtenant, namespace (the new slug)
token.mintedPOST /api/v1/tokens returns 201, or exd-server-admin token mint (CLI), or bootstrap.run minting the first superadminroutes/tokens.rs::create, bin/admin.rs::bootstrapShippedtoken_id, token_kind, tenant?, namespace?, source (api / bootstrap — only when CLI-minted)
token.revokedDELETE /api/v1/tokens/{id} returns 200 with non-idempotent pathroutes/tokens.rs::revokeShippedtoken_id, token_kind, revoked_by, self_revoke (bool)
manifest.version_acceptedA new manifest version commits — PUT /manifest or git smart-HTTP pushroutes/manifest.rs::upload, routes/git.rs::receive_packShippedsource (upload / git-push), tenant, namespace, version, token_id?
bootstrap.runexd-server-admin bootstrap succeeds (initial superadmin minted, closure-signing key generated). Fires at most once per database.bin/admin.rs::bootstrapShippedtoken_id (the new superadmin's id)
git.force_push_acceptedThe pre-receive hook accepts a non-fast-forward push to refs/heads/main (only tenant-admin and superadmin tokens can authorize one)bin/git_hook.rsDeferred(planned) tenant, namespace, token_id, old_sha, new_sha

git.force_push_accepted — deferred status

The pre-receive hook is a separate subprocess invoked by git http-backend. Today it writes plaintext rejection messages to stderr; emitting structured audit lines from there requires either:

  1. Initializing tracing inside bin/git_hook.rs with a stderr-only writer (so structured audit lines don't interleave with git's own protocol output on stdout), or
  2. Routing the audit event through the database (the hook inserts a row, the server polls and emits via tracing).

Both paths are tractable but didn't make the v0 cut. The constant audit::events::GIT_FORCE_PUSH_ACCEPTED is reserved against future use.


What is deliberately not audited

  • Authenticated reads. GET /manifest, POST /evaluate, GET /readyz, the closure download, the SSE event stream. These are high-volume and low-value for a compliance trail; the request log already records them with the §3 field set if you need read-side context.
  • 4xx responses. A failed-auth 401, a 404 on a missing namespace, a 422 on a malformed upload — none of these emit audit events. The compliance trail tracks completed operations.
  • Internal state changes. Eval cache invalidations, push-lock acquisitions, SSE connection bookkeeping. Operational, not compliance-relevant.

See also