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.
nonedrops the entry. Every other level records the same fields.- The
managementtier is everysystementry plusaudit.query,audit.verify,audit.subscribeandserve.reload. Every other entry is in thedatatier. - An action in a rule may use the behavior spelling:
behavior.at its start is read asmodel.. Stored actions keep themodel.spelling. serve.config.reloadedis decided under the policy in force before the reload, so a reload that lowers auditing still records itself.- A
successentry is dropped when an entry with the samerequestIdwas already recorded asdenied.
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.runstarted again under an idempotency key it already used:replayed start.model.method.runof an integration behavior (swamp/integration):state=<state> ceiling=<digest>, plusprevious=<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.subscribeandcommits.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: actionadmin, resource kindaccess, resource name the request type. - A request refused because the audit log cannot record durably
(
audit_unavailable, withfail-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.
Delegation links
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.
Related
- Authorization — resource kinds,
access actions, and the
audit.*requests - Hot Reload — what
swamp serve reloadapplies - Webhooks — webhook routes and provider schemes
- REST API — the cancel endpoints