Audit Logging
gestaltd emits audit records as structured logs with log.type=audit.
Audit logging covers both invocation traffic and security-sensitive management actions, so you can forward one stream to a SIEM, warehouse, or compliance backend without having to reconstruct behavior from mixed application logs.
Coverage
Audit events cover HTTP and MCP operation invocations, other guarded runtime surfaces such as binding hooks, workflow-manager actions, failed auth on protected routes, HTTP authorization denials rejected before GuardedInvoker runs, login and logout flows, app connect and disconnect flows, and API token inventory and lifecycle events. Each event carries a source field (http, mcp, workflow_manager, or a surface-specific value like binding:...) so consumers can filter by surface.
gestaltd resolves the authenticated canonical subject before emitting user-backed audit events, so both session requests and API-token requests produce stable subject_id values such as user:usr_123. Emitted audit records do not include a separate user_id field.
Fields
Every audit record includes:
| Field | Meaning |
|---|---|
log.type | Always audit |
event_time | Event timestamp |
request_id | Request-scoped correlation ID |
source | Surface that emitted the event, such as http, mcp, or binding:... |
provider | App key for provider-scoped events |
operation | Operation or event name |
depth | Invocation depth for guarded invocation events |
allowed | Whether gestaltd allowed or completed the audited action |
These fields are emitted when available:
| Field | Meaning |
|---|---|
auth_source | Authentication mechanism for the caller: session, api_token, or env. |
subject_id | Canonical caller subject such as user:<id> or service_account:<id> |
subject_kind | Canonical subject type, such as user or service_account |
target_kind | Generic kind for the object the event acted on, such as api_token or connection |
target_id | Stable identifier for the affected object when one exists |
target_name | Human-readable label for the affected object |
error | Denial or failure reason |
client_ip | Client IP, preferring X-Forwarded-For |
remote_addr | Immediate peer address |
user_agent | HTTP user agent |
trace_id | Active OpenTelemetry trace ID |
span_id | Active OpenTelemetry span ID |
For platform-level events such as failed auth or API-token inventory and lifecycle, provider is empty because
the event is not tied to a specific app.
Management events populate the generic target fields when they can identify a
single object. API-token create and revoke events use target_kind=api_token
with the token ID and, for creates, the token name. Connection lifecycle events
use target_kind=connection with target_id and target_name derived from the
provider, connection, and instance being changed. Bulk revocations use
target_kind=api_token_collection because no single token ID applies.
Workflow events also use workflow-specific fields when available:
| Field | Meaning |
|---|---|
caller_app | App that called WorkflowManager, such as a Slack event provider. |
workflow_key_sha256 | SHA-256 hash of the workflow key for run start and signal-or-start events. The raw workflow key is not emitted. |
workflow_target_kind | Target kind, currently steps. |
workflow_target_component | Target subcomponent involved in a workflow target authorization denial, such as target, app_step, agent_provider, or agent_tool_ref. |
workflow_target_provider | Target app, agent provider, or tool provider involved in the workflow action or denial. |
workflow_target_operation | Target operation involved in the workflow action or denial. |
workflow_created_by_subject_id | Original workflow creator subject for audited operations that execute inside a workflow context. |
workflow_created_by_subject_kind | Original workflow creator subject kind. |
workflow_created_by_display_name | Display name for the original workflow creator, when available. |
workflow_created_by_auth_source | Auth source for the original workflow creator, when available. |
Event Names
Invocation events use the catalog operation ID in operation.
Non-invocation audit events use explicit operation names:
auth.authenticateauth.login.startauth.login.completeauth.logoutconnection.oauth.startconnection.oauth.completeconnection.manual.connectconnection.pending.selectconnection.disconnectapi_token.listoperations.listapi_token.createapi_token.revokeapi_token.revoke_all
Workflow-manager audit events use source=workflow_manager. Their provider
field is the selected workflow backend, not an app or agent step. They
emit one audit record per backend for fanout event publishes.
workflow.definition.createworkflow.definition.updateworkflow.definition.deleteworkflow.schedule.createworkflow.schedule.updateworkflow.schedule.deleteworkflow.schedule.pauseworkflow.schedule.resumeworkflow.event_trigger.createworkflow.event_trigger.updateworkflow.event_trigger.deleteworkflow.event_trigger.pauseworkflow.event_trigger.resumeworkflow.run.startworkflow.run.signalworkflow.run.signal_or_startworkflow.run.cancelworkflow.event.publish
Denied requests to /api/v1/{integration}/{operation} that are blocked before
GuardedInvoker still use the attempted catalog operation ID in operation,
matching the guarded invocation audit shape. api_token.create is also emitted
when the CLI login callback successfully mints a token. auth.authenticate
can be emitted from both HTTP and MCP request paths when shared auth middleware
rejects the request before invocation.
Workflow Manager Events
Workflow-manager audit records cover mutating workflow actions and event publishing from HTTP, CLI, and app SDK paths. Read-only workflow list/get requests remain ordinary HTTP request telemetry.
Workflow object actions set the generic target fields:
| Object | target_kind | target_id | target_name |
|---|---|---|---|
| Definition | workflow_definition | Definition ID | Empty |
| Schedule | workflow_schedule | Schedule ID | Empty |
| Event trigger | workflow_event_trigger | Trigger ID | Empty |
| Run | workflow_run | Run ID when available | Empty |
| Published event | workflow_event | Empty | Event type |
Run start and signal-or-start events include workflow_key_sha256 so operators
can correlate repeated events for the same workflow key without logging the raw
key. Targeted workflow actions include workflow_target_kind, and app steps
include workflow_target_provider and workflow_target_operation.
When workflow.run.signal_or_start rejects a stored or requested target during
the target authorization check, the audit record sets
allowed=false, error=not_found, and an authorization_decision value with
the workflow_target_ prefix, such as
workflow_target_authorizer_operation_denied or
workflow_target_principal_operation_permission_denied. In that case the
record also includes the failing workflow_target_component and, when known,
the target provider and operation.
Metrics Vs Audit
gestaltd intentionally does not mirror every metric as an audit record.
| Surface | Metrics | Audit Logs | Notes |
|---|---|---|---|
| Provider operation invocation | Yes: gestaltd.operation.* | Yes | Guarded invocations are both metered and audited. |
| Platform login start and completion | Yes: gestaltd.auth.* | Yes | Login is both operational telemetry and a security event. |
| Platform token validation | Yes: gestaltd.auth.* with action=validate_token | Partially | Successful per-request validation is metrics-only; denied auth in shared middleware emits auth.authenticate. |
| HTTP pre-invoker authorization denials | No dedicated semantic metric family | Yes | Denied subject access before invocation dispatch is audited with the attempted operation or operations.list. |
| Connection auth start and completion | Yes: gestaltd.connection.auth.* | Yes | Covers OAuth start and completion plus manual connect completion. |
| Connection credential refresh | Yes: gestaltd.connection.auth.* with action=refresh | No | Refresh remains metrics-only to avoid audit noise. |
| Credentialed HTTP catalog discovery | Yes: gestaltd.discovery.* with action=list_operations | No | Operation listing that resolves a session catalog stays metrics-only to avoid audit spam. |
| IndexedDB operations | Yes: db.client.operation.duration | No | Datastore calls are internal implementation telemetry, not audit events. |
| API token inventory read | No dedicated semantic metric family | Yes | api_token.list is audited because it exposes stored API-token inventory. |
| API token lifecycle | No dedicated semantic metric family | Yes | Audit records exist for explicit token create/revoke flows and CLI token minting. |
| Workflow-manager mutations and event publishing | No dedicated semantic metric family | Yes | Workflow control-plane actions are audited with source=workflow_manager. |
| Logout, pending selection, disconnect | No dedicated semantic metric family | Yes | These remain audit-only workflow events. |
See Observability for the metric families and attribute definitions.
Example
{
"log.type": "audit",
"event_time": "2026-04-06T18:23:11.842Z",
"request_id": "2a7c1d7f-2b25-4b7a-8ae8-02f4d8f6f2b2",
"source": "mcp",
"auth_source": "api_token",
"subject_id": "user:usr_123",
"subject_kind": "user",
"provider": "github",
"operation": "issues_list",
"depth": 0,
"allowed": true,
"trace_id": "0af7651916cd43dd8448eb211c80319c",
"span_id": "b7ad6b7169203331"
}Workflow-manager records keep workflow routing context in audit logs:
{
"log.type": "audit",
"event_time": "2026-05-14T00:45:59.542Z",
"request_id": "21d82738-3fa7-4d5f-b33f-cff12b92bb24",
"source": "workflow_manager",
"auth_source": "api_token",
"subject_id": "service_account:slack-bot",
"subject_kind": "service_account",
"provider": "temporal",
"operation": "workflow.event.publish",
"target_kind": "workflow_event",
"target_name": "slack.message.created",
"caller_app": "slack",
"depth": 0,
"allowed": true,
"trace_id": "0af7651916cd43dd8448eb211c80319c",
"span_id": "b7ad6b7169203331"
}Routing
By default, audit records follow the main telemetry log pipeline. With providers.telemetry.default.source: stdout they appear in local structured logs; with providers.telemetry.default.source: otlp they export through the OTLP log pipeline.
If you need a dedicated audit route, configure providers.audit separately. Set providers.audit.<name>.source: stdout to keep audit logs on stdout even when telemetry uses a different source, providers.audit.<name>.source: otlp to export only audit records to a dedicated OTLP collector, or providers.audit.<name>.source: noop to disable audit output explicitly.
With the default providers.audit.default.source: inherit, you can still split audit records downstream by filtering on log.type=audit in your collector. See Config File for the config surface and Observability for export options.
Metrics Notes
Audit logs are a good source for invocation-based activity metrics. In
particular, source, auth_source, subject_id, provider, operation, and
allowed are enough to derive distinct-user activity over guarded invocations.
For DAU, WAU, or MAU built from tool usage, filter to successful top-level invocations where allowed = true, depth = 0, and subject_id is non-empty. That produces a clean “subjects who actually invoked a tool” metric. If you need a broader product-activity definition, treat the non-invocation audit events as a separate metric family rather than mixing them into invocation counts.