Skip to main content

AUDIT ACTIONS

swamp serve writes an audit log when its config file has an audit block. Read it with swamp audit log --server, stream it with swamp audit log --follow --server, and alert on it with audit.alerts rules. Reading it needs admin on access:audit; see Authorization — Audit log.

This log is separate from the repository's command audit, which records agent and CLI commands in .swamp/audit and is read with swamp audit. See The Audit System for that log.

Entry fields

Every entry has these fields. Optional fields are absent, not empty, when they do not apply.

Field Value
id A UUID for the entry
timestamp ISO 8601 time the entry was written
instanceId The serve instance that wrote it
category One of the categories
stage Always response
outcome success, failure or denied
action The action name. This page lists every one
resourceKind The kind of thing acted on, such as workflow, model, vault, server
resourceName Its name, or * when the request named none
principalKind user, worker, service, integration, system, anonymous or unknown
principalId The principal's bare id: a user's OAuth sub, a worker's name, scheduler, webhook, system
initiatedBy The principal as displayed: user:<username> when serve knows the name, otherwise <kind>:<id>; ghost for an anonymous one
principalUsername Optional. The user's username, from OAuth login or a name serve knows for the sub
principalEmail Optional. The email the OAuth provider returned at login
client Optional. The client named by the caller's credential, such as integration:<id>
delegation Optional. The delegation id a linked person acted under
sourceIp The caller's address. 127.0.0.1 for serve's own events
requestId The request id, or a UUID serve generates for an event with no request
methodName Optional. The method of a model.method.run request
detail Optional. Free text, mostly space-separated key=value pairs, described per action below
decision Optional. On access denials only: see Access denial entries
sequence Position in the hash chain
digest SHA-256 digest linking the entry to the one before it
version Chain format version, 1
hmacKeyVersion Optional. The HMAC key version, when audit HMAC is on

An action, resourceName or detail value that contains an absolute file path is recorded as [redacted]. No entry carries a secret value, a token, a signal payload or a request body.

Categories

Category Covers
auth Logins, token use, session terminations, delegation links
access Access denials, and requests on grants, groups, tokens and delegations
execution Runs, approvals, signals, cancellations, scheduled and webhook triggers
secrets Vault requests, run-time vault denials, worker secret loads, access.token.reveal
admin Changes to behaviors, workflows, vaults, workers, extensions, the datastore and serve
data Reads of data, behaviors, workflows, reports and run history
system Serve's own lifecycle: start, stop, peers, health, retention, alerts, automatic resumes

Recording policy

No entry is written unless the serve config file has an audit block. Within it, audit.policy decides per entry whether it is recorded:

audit:
  policy:
    default-level: metadata
    rules:
      - action: workflow.schedule.skipped
        level: none
      - category: data
        level: none
Key Meaning
default-level Level for an entry no rule matches. Default metadata
rules[].category Matches entries of this category
rules[].action Matches entries with exactly this action
rules[].tier management or data
rules[].level none, metadata, request or requestResponse
  • Rules are tried in order and the first rule whose every given key matches decides the level.
  • none drops the entry. Every other level records the same fields.
  • The management tier is every system entry plus audit.query, audit.verify, audit.subscribe and serve.reload. Every other entry is in the data tier.
  • An action in a rule may use the behavior spelling: behavior. at its start is read as model.. Stored actions keep the model. spelling.
  • serve.config.reloaded is decided under the policy in force before the reload, so a reload that lowers auditing still records itself.
  • A success entry is dropped when an entry with the same requestId was already recorded as denied.

swamp serve reload applies a changed audit.policy without a restart.

Entry shapes

Entries come in three shapes:

Shape action Written
Request entry The request type, such as workflow.run Once per answered request
Access denial entry The access action: read, write, run, approve, signal, tag or admin Each time authorization refuses something
Named event A dotted name, such as workflow.schedule.fire When the event happens; listed by area from Authentication and sessions on

A refused request writes an access denial entry and then a request entry with outcome failure under the same requestId.

Request entries

Every request on the WebSocket API is recorded when serve answers it. The HTTP routes that run requests through the same path (/api/v1/request, /api/v1/action) record the same entries.

Field Value
action The request type
outcome success, or failure when the handler answered with an error or threw
resourceName The name from the request, replaced by the canonical name once serve resolves it; * when the request names none
detail On failure, the error message or handler sent error response. On success, empty except for the cases below

On success, detail is set for:

  • workflow.run started again under an idempotency key it already used: replayed start.
  • model.method.run of an integration behavior (swamp/integration): state=<state> ceiling=<digest>, plus previous=<digest> when the ceiling changed.

Category and resource kind by request type:

Request types Category resourceKind
workflow.run, workflow.evaluate, workflow.approve, workflow.reject, workflow.signal, workflow.cancel, workflow.resume execution workflow
workflow.get, workflow.search, workflow.schema, workflow.validate, workflow.approvals, workflow.waits, workflow.history.get, workflow.history.logs, workflow.history.search, workflow.run.search, workflow.run.summary, workflow.trigger.get, workflow.schedule.list data workflow
workflow.create, workflow.edit, workflow.delete, workflow.trigger.set, workflow.trigger.remove, workflow.schedule.pause, workflow.schedule.resume admin workflow
model.method.run, model.evaluate execution model
model.get, model.search, model.validate, model.method.describe, model.type.describe, model.type.search, model.output.get, model.output.data, model.output.logs, model.output.search, model.usage.summary, model.method.history.get, model.method.history.logs, model.method.history.search data model
model.create, model.edit, model.delete admin model
run.history, run.doctor, run.attach execution run
run.gc admin run
data.get, data.list, data.query, data.search, data.versions, data.delete, data.rename, summarise data data
data.gc, data.prune admin data
report.get, report.search, report.describe, report.type.search data report
vault.get, vault.put, vault.delete, vault.describe, vault.inspect, vault.list-keys, vault.search, vault.annotate, vault.audit-trail, vault.read-secret, vault.type.search secrets vault
vault.create, vault.edit, vault.migrate admin vault
access.grant.list, access.group.list, access.group.list-idp, access.check, access.can-i, access.integration.list, access.delegation.list, access.delegation.end, access.token.list, access.token.mint, access.token.revoke, access.token.rotate access access
access.token.reveal secrets access
access.reload, serve.reload admin access
audit.query, audit.verify, audit.export, audit.subscribe admin access
audit.timeline, audit.alerts, audit.report, audit.rotate-key admin audit
worker.list, worker.queue.list, worker.lease.list, worker.verify, worker.prune, worker.token.create, worker.token.list, worker.token.revoke admin worker
datastore.status, datastore.setup.extension, datastore.claim, datastore.release, datastore.namespace.list admin datastore
extension.list, extension.search, extension.info, extension.install, extension.pull, extension.rm, extension.outdated, extension.update admin extension
doctor.datastores, doctor.vaults, doctor.secrets, doctor.workflows, doctor.extensions admin doctor
server.version, serve.config admin server
cluster.instances admin cluster

Requests with no request entry:

  • commits.subscribe and commits.head, which filter what they return per caller and refuse nothing.
  • audit.unsubscribe, which only ends the caller's own stream.
  • A request refused by --restricted-commands. Its access denial entry is the record: action admin, resource kind access, resource name the request type.
  • A request refused because the audit log cannot record durably (audit_unavailable, with fail-open: false).

Access denial entries

Authorization writes an entry each time it refuses an action. Allowed decisions are not recorded.

Field Value
category access
outcome denied
action The access action refused: read, write, run, approve, signal, tag or admin
resourceKind The resource kind of the grant model, such as workflow, model, data, vault, access
resourceName The resource's name
detail unauthorized (no grant allows it, or a deny grant matched), no_principal (no authenticated principal) or access_not_configured (no policy snapshot is loaded)
decision The decision behind the refusal

decision fields:

Field Value
action The access action
resourceKind The resource kind
resourceName The resource name
effect deny
grantId The deny grant that matched, or null when no grant allowed it
principalGroups The principal's groups when the decision was made

The same shape records refusals from the HTTP datastore API, the signal route and the run cancel route.

A run that a schedule or webhook starts and authorization refuses is recorded the same way, with action run, principal service:scheduler or service:webhook, and detail holding key=value pairs: fireTime and replayed for a schedule, route, run and replayed for a webhook, and reason (for example no grant allows it or denied by grant <id>) for both.

The resource kinds and access actions are described in Authorization.

Authentication and sessions

Category auth. These entries are written on the HTTP path before any WebSocket exists.

Action Written when Outcome Resource detail
auth.login.started An OAuth device login starts success server, the instance id —
auth.login.completed An OAuth login completes and a server token is minted success server-token, the token name —
auth.login.denied The user is not admitted, or denied the authorization at the provider denied server, the instance id Not admitted: the admission reason and what was read for collectives. Denied at the provider: none
auth.login.expired The device code expired before the login completed failure server, the instance id —
auth.token.used A server token authenticates a connection or HTTP request success server-token, the token name Where it was used: a URL path, websocket:<transport>, http-request, http-request:cookie, http-action, http-signal, http-cancel, http-datastore or dashboard-session
auth.token.not_admitted A token issued to a person is refused because the person no longer passes admission denied server-token, the token name The admission reason
auth.session.terminated Serve closes a WebSocket session or health stream because its token lost its authority. One entry per session success server-token, the token name The cause: revoked, rotated, expired, deleted, invalid, integration-revoked, integration-narrowed, delegation-ended or not-admitted

For auth.session.terminated, initiatedBy is the admin who revoked or rotated the token, or system when serve's own revalidation ended the session, and sourceIp is the closed session's address.

Category auth. Resource kind delegation, resource name and delegation field the delegation id. The principal is the linked person once Swamp Club has reported them, and the integration before that; client is integration:<id>. sourceIp is server.

detail always starts with outcome=<value> request=<id>, then link=<id> once Swamp Club has issued one, then external=<platform>/<team>/<user>.

Action Written when Outcome outcome= values and extra detail
delegation.link_started An integration starts a link for a person success started; from=<address>
delegation.consent_result Swamp Club answers the consent request success when approved, otherwise denied approved, denied, refused or expired; or not_admitted, followed by the admission reason
delegation.refused Serve refuses the link denied The reason: consent_denied, consent_refused, consent_expired, digest_mismatch, identity_unavailable, not_admitted or integration_revoked
delegation.recorded Swamp Club records the link success recorded
delegation.relinked A new link for the same person folds into an existing one success consent_replaced or consent_unchanged; pending=<id>
delegation.tokens_issued The integration redeems the link for tokens success issued; relink=<id> for a relink
delegation.refreshed The integration refreshes the link's tokens success refreshed
delegation.ended Serve ends the link, or refuses one still awaiting consent success The cause. Serve's own: operator (swamp access delegation end), app_ended, expired, superseded, integration_revoked, not_redeemed, replaced. From Swamp Club: club_ended, disowned, plan_lapsed, club_client_refused
delegation.club_ended Serve tells Swamp Club the link ended success ok; retired_link=<id> when ending a replaced link
delegation.club_end_failed Telling Swamp Club the link ended fails; serve retries failure failed; retired_link=<id> when ending a replaced link, then the error
delegation.suspended A hold is placed on the link success The hold cause: person_suspended, club_client_refused or club_unknown
delegation.suspension_lifted A hold is lifted success The hold cause, as for delegation.suspended
delegation.identity_updated The linked person's collectives or groups change success updated, or outside_collective when no collective is left

Runs and cancellation

Action Category Written when Outcome Resource detail
cancel execution A run is cancelled through POST /api/v1/cancel/... or a WebSocket cancel frame success, failure, or denied workflow or model, the run or request id Success: cancelled or cancellation_requested, or <kind>=<name> on WebSocket. Denied: admin required, run required or access_not_configured. Failure: the error
cancel.all execution Every running execution is cancelled through the REST API success or denied workflow, model or execution, * count=<n>, or the refusal reason
workflow.auto_resume system Serve resumes a suspended run on its own success server, the instance id workflow=<name> run=<id>, plus approvedBy=<principal> after an approval, nestedRun=<id> after a nested run ends, or cause=<kind> and waitsTimedOut=<n> after a signal wait
workflow.auto_resume_failed system An automatic resume is refused or fails success server, the instance id As workflow.auto_resume, plus code=<error code>
workflow.auto_resume_skipped system An automatic resume is not attempted success server, the instance id As workflow.auto_resume, plus reason=<reason>, such as workflow_not_found
workflow.nested_run_cancelled system A parent run ends and serve cancels a nested run it started success server, the instance id workflow, run, parentWorkflow, parentRun, reason
workflow.nested_run_stop_requested system A parent run ends and serve asks another instance's nested run to stop success server, the instance id workflow, run, parentWorkflow, parentRun, reason

system entries always have outcome success, principal system and source address 127.0.0.1; read workflow.auto_resume_failed by its action, not its outcome.

Runs started by request are recorded as the workflow.run and model.method.run request entries.

Workflow schedules

Category execution, resource kind workflow, resource name the workflow. Principal service:scheduler, source address 127.0.0.1.

Action Written when Outcome detail
workflow.schedule.fire A scheduled run starts success run, fireTime, replayed=true for a fire replayed at startup, catchUp=true for a catch-up of a fire missed while serve was down
workflow.schedule.missed Serve finds, at startup or on taking the lead, fires it missed while down success when the latest was caught up, otherwise failure fireTime (latest missed), oldestFireTime, missed (the count, <n>+ when capped), reason=serve_down, caughtUp
workflow.schedule.skipped A fire does not run failure for overlap; success for dedup and paused fireTime, reason (overlap: the previous run is still going; dedup: another instance took the fire; paused), replayed=true, and for paused the JSON-quoted pauseReason and pausedBy

Pausing and resuming a schedule are the workflow.schedule.pause and workflow.schedule.resume request entries. A fire refused by authorization is an access denial entry.

Webhooks

Action Category Written when Outcome Resource detail
workflow.webhook.fire execution A webhook delivery starts a run success workflow, the workflow route, run, replayed=true for a replayed delivery
workflow.webhook.rejected execution A delivery is refused before it is queued failure workflow, the workflow route, reason, suppressed=<n>
webhook.routes.applied admin Applying routes changes the route table or the set of refused or kept route files success, or failure when a route file was refused or kept or the routes directory was unusable server, the instance id See below

workflow.webhook.fire and workflow.webhook.rejected have principal service:webhook and the sender's address (X-Forwarded-For with --trust-proxy). reason is one of Missing <header> header, Request body too large, Invalid signature, Not the leader, Server starting, Queue full, Server shutting down, Webhook handler unavailable, Webhook handler failed, or a refusal of a scheme from an untrusted collective. Rejections are coalesced per route and reason: the first in each 60-second window is written, and suppressed counts the ones dropped since the previous entry.

A delivery whose run authorization refuses is an access denial entry with action run, not a webhook action. The webhook_denied event in serve's output is not an audit action.

webhook.routes.applied names the caller of swamp serve reload as its principal, or system for a poll. Its detail holds these keys, each present only when it has a value:

Key Value
trigger reload or poll
added Comma-separated <route>(<source>); the source is config, flag or file:<file>
changed As added
removed As added
kept <route>(file:<file>,<reason>), where the reason is outage or last_good
refused <route>(file:<file>), or file:<file> when no route was read
directory unusable when the routes directory could not be read

Each list shows at most 50 items, then +<n> more. Route and file names percent-encode spaces, control characters and %=,().

Signals

Category execution, resource kind workflow, resource name the workflow.

Action Written when Outcome detail
workflow.signal A signal request is answered, on the WebSocket API or the HTTP signal route success for delivered, pending or accepted; otherwise failure Over HTTP, the delivery status when it is not delivered, such as not_found, no_open_wait, expired
workflow.signal.delivered A signal reaches an open wait success wait, receipt, run, and key, taken=true, duplicate=true when they apply
workflow.signal.pending A signal is held for a wait that has not opened yet success receipt, key, heldUntil, and duplicate=true when it applies
workflow.signal.taken A wait takes a held signal success receipt, key, wait, run, workflow (the workflow id)

workflow.signal.taken is a system entry: principal system, resource server. The signal payload is never recorded.

Approvals and rejections are the workflow.approve and workflow.reject request entries.

Vaults and secrets

Category secrets.

Action Written when Outcome Resource detail
vault.read A run's vault read is refused by the run-time vault rule denied vault, the vault run, workflow, reason
vault.write A run's vault write is refused by the run-time vault rule denied vault, the vault run, workflow, reason
worker.secrets.load A worker asks serve for the secrets of a dispatch (POST /data/secrets) success, denied or failure dispatch, the dispatch id <code>; references=<n>, where the code is loaded or the refusal code

vault.read and vault.write name the run's principal and carry a decision like an access denial entry, with grantId set when a deny grant matched. A run writes one entry per refused vault, key and action.

worker.secrets.load is written at every level but none, and names the worker as principal worker:<name> when the dispatch credential verified, or anonymous when it did not. denied covers an unverified credential, an ended dispatch or a refused reference; failure covers a malformed or oversized request or a vault error.

Vault requests from clients are request entries.

Datastore administration

Category admin, written by the HTTP datastore API. The principal is the caller.

Action Written when Outcome Resource detail
datastore.lock.force-release An admin force-releases another caller's lease success or failure lock, the lock key, with held by <principal> when held no lease held, needs admin, or lease ended; its kept record could not be removed
datastore.quarantine.accept An admin accepts a quarantined change success or failure quarantine, <partition> <txnId> by <principal>, or the txn id when refused needs admin, or why the change did not land, such as replication not confirmed
datastore.quarantine.discard An admin discards a quarantined change success or failure quarantine, <partition> <txnId> by <principal>, or the txn id when refused needs admin when refused

Serve config and instance

Action Category Written when Outcome detail
serve.config.reloaded admin Serve reloads its settings, before they are applied success trigger, then each present: applied, landing, needs-restart, overridden, each a comma-separated list of setting names
serve.config.reloaded admin Settings named as applied did not land because their follow-up failed failure trigger, not-landed
instance.start system Serve starts with audit on success version=<version>
instance.stop system Serve shuts down gracefully success —
instance.join system A peer instance appears in the cluster success instanceId=<id>
instance.leave system A peer instance leaves the cluster success instanceId=<id>
health.transition system The instance's health changes success <previous>-><current>, each healthy, degraded or unhealthy
retention.pass system A retention pass finishes success JSON: complete, stoppedBy, and kinds, a list of kind, cutoff, deleted, bytesReclaimed
alert.fired system An alert rule with action: log reaches its threshold success Alert "<rule>" fired: <n> events in window (matched event <id>)

Resource kind server and resource name the instance id, except alert.fired, which has resource kind alert-rule, the rule name, and principal id audit-engine. An alert rule with action: webhook posts to its URL and writes no entry.

serve.config.reloaded names the caller of swamp serve reload as its principal; trigger is reload for that request, signal for SIGHUP, and poll for a config poll, which have principal system. The entry holds setting names only, never values. The serve.reload request writes its own request entry.

  • Authorization — resource kinds, access actions, and the audit.* requests
  • Hot Reload — what swamp serve reload applies
  • Webhooks — webhook routes and provider schemes
  • REST API — the cancel endpoints