The event system
Included from Basic tier
Events are the server's append-only, typed audit stream: every run and
step transition, every supervised verb, every plane lifecycle fact lands
as one events row. The stream is what opschain events and the GUI's
Activity feed read, what the trigger engine matches (opschain docs triggers), and what chains are threaded from — every event carries a
correlation_id and a chain_depth, so any row is a handle into its
whole cascade.
Emission, storage and the event shape
Events are written server-side at the seam the action passes through —
the run engine's state machine, the create/admin endpoints, the runner and
agent RPC planes, the trigger engine — never by clients, so no client code
path can omit its own trail. Rows are stored with UUIDv7 ids (so id order
is chronological), a type, the acting username, a JSON data payload,
the polymorphic source (source_type/source_id), an event_node_path
for scoping, and the chain spine (correlation_id, caused_by_event_id,
initiated_by_type/initiated_by_id, chain_depth). Emission is best
effort: a failed event insert never fails the operation it narrates.
Retention is the retention.events_days config key (opschain docs retention).
One store caveat: the server-emitted lifecycle events — the api:/warn:
rows no run writes, such as node delete/restore/destroy, maintenance
windows, EULA and licence facts, and database maintenance — are written
through the PostgreSQL pool only. On the embedded SQLite (trial) store
those operations run but record no event; the operator notice and the
relevant status endpoint are the visibility there. Run and step events are
store-agnostic and land on both backends.
A serialized event (type event) carries the fixed attributes type,
username (nullable), system, event_node_path, created_at,
correlation_id and chain_depth, with the payload's own keys spread
beside them, plus a source relationship (a link always; the resource
identifier too when the request includes source). The full attribute
dictionary is opschain docs resources/event.
The audit envelope — actor. Every event a request causes — the
api:* mutation audit, the authentication family, the server-emitted
lifecycle events (node delete/restore/destroy, maintenance, licence,
template-version and actions-refresh lanes, artefact creation) — carries an
actor object inside its payload, so an audited action ties to who, from
where, with which credential without a join against the request log:
| Key | Present when | Meaning |
|---|---|---|
principal_id | the request authenticated | The acting user's id (never the username — that is the row's username column, and emails never appear). |
session_id | the credential was a native session | The auth_sessions row the request rode (opschain docs resources/session). |
token_id, token_kind | the credential was anything else (a native API key, a step token, a legacy token) | The credential row id and its kind. Basic-auth requests carry neither. |
client_address | always, for a request | The client address, resolved behind any trusted reverse proxy (server.trusted_proxies, opschain docs config) — the same value the request log's remote_ip records. |
user_agent | the request sent one | The User-Agent header, bounded to 512 characters. |
request_id | always, for a request | The request correlation id the response echoed as X-Request-Id — the join key to the request log line. |
The object is absent on engine-originated events (run and step
transitions, scheduler and retention roll-ups, trigger firings): no request,
no actor. A login attempt carries the origin keys before it has a
principal (warn:auth:login_failed names where the attempt came from). An
emitter that sets its own actor is never overwritten. The object rides
data verbatim into the SIEM forwarder and the events API (payload keys are
spread beside the fixed attributes, so actor.client_address is filterable
like any payload key), and ages out with the row under
retention.events_days — the retention control is the proportionality for
holding an address and a user agent in the trail (opschain docs security-audit).
Querying
GET /api/events— newest first, default limit 10, maximum 1000, with the shared Ransack filter grammar (scalar predicates, in/not_in, grouped and/or, sorts) plus the special-casedfilter[for_change]=<change id>scope: the change's own events and its steps'.GET /api/events/{id}— one event.EventService.ListEvents/GetEvent/WatchEvents(/api/v2,opschain docs api-v2) — the typed v2 twins: cursor-paginated listing keyed on(created_at, id), and the live stream the GUI feed rides instead of polling — resumable withfrom_cursorafter a disconnect, replaying the missed events oldest first before the tail. Searching by type:type_searchmatches text anywhere in the type (agentfindswarn:agent:halted), resolved against this registry plus the stored types and read through the type index — the audit history search box andopschain audit --type-search.EventService.GetEventStats(/api/v2, admin-scoped) — the aggregation read over the same vocabulary: time-bucketed counts by event class, top types and actors, and an optional payload facet — set-based SQL, never a row fetch. CLI:opschain events stats(opschain docs cli/events-stats).opschain events(opschain docs cli/events) — the estate-wide triage entry point; its--errorsconvention selectserror:/warn:prefixes and:error/:system_error/:pre_executionsuffixes.
The type grammar
Types are namespaced <root>:<area>:<verb>. The roots in use:
| Root | Carries |
|---|---|
api: | A performed action or lifecycle fact (create, start, success, applied, …). |
audit: | A supervised human verb on live work (cancel, pause, resume, approve, …) and the maintenance override. |
info: | A notice — plane lifecycle, parked automation, governance trails. |
warn: | A degraded or loud condition that did not fail a request. |
error: | A failure recorded in its own right. |
Steps can emit custom events — the declarative emit: step, or
opschain step emit-event (opschain docs cli/step-emit-event) writing
to the $OPSCHAIN_EVENTS file the executor folds in on step success.
Custom types are free-form namespaced identities matching
^[a-z0-9_]+:[a-z0-9_:]+$ with no registry; the reserved prefixes
api: system: info: error: warn: are the server's and are refused
at the write gate. A custom event is sourced from the emitting step,
placed on the change's target path (so trigger scope matching works),
inherits the change's correlation chain, and its data passes the run's
secret masker before storage. Local (--local) runs store step-emitted
custom events the same way — type-gated, masked, visible in
runs show --events — into the local store.
The type registry
Every event type the server emits, verbatim, grouped by area. (Types that appear only in test fixtures are excluded.)
Run lifecycle (changes and workflow runs)
The run engine emits these per run; <kind> is changes or
workflow_runs:
api:changes:create,api:workflow_runs:create— a run was created.api:changes:start,api:workflow_runs:start— the run began executing.api:changes:success,api:workflow_runs:success— the run finished successfully.api:changes:error,api:workflow_runs:error— the run finalised in error.api:changes:system_error,api:workflow_runs:system_error— the run finalised in system error (infrastructure, not the action).api:changes:skip,api:workflow_runs:skip— the run's root step was skipped.api:changes:abort,api:workflow_runs:abort— the run finalised aborted.audit:changes:cancel,audit:workflow_runs:cancel— a cancellation, with actor and reason.audit:changes:pause,audit:workflow_runs:pause— a pause, with actor and reason.audit:changes:resume,audit:workflow_runs:resume— a resume, with actor and reason.error:changes:pre_execution,error:workflow_runs:pre_execution— a step failed before execution started (runner-plane setup: infrastructure, git or secrets).
Step lifecycle
Steps of both run kinds share one vocabulary:
api:steps:start— a step started.api:steps:success— a step succeeded.api:steps:error— a step failed.api:steps:system_error— a step failed with a system error (an infrastructure failure such as a lost runner, not the action's verdict).api:steps:skip— a step was skipped.api:steps:abort— a step was aborted.api:steps:waiting— a step entered the waiting state (a wait step or approval gate); the notification spine's waiting_on_gate condition and the zero-config approvals default consume it.audit:steps:approve— an approval gate was approved (actor, message).audit:steps:continue— a wait step was continued (actor, message).audit:steps:reject— a wait/approval step was rejected (actor, message).audit:steps:cancel— a step was cancelled (actor, message).
Artefacts
api:artifacts:create— an artefact was stored (code, labels, node path, digest, size) — DSL-stored and step-collected artifacts alike (opschain docs artifacts).warn:artifacts:collection— a declared step artifact was not collected without failing the step (an optional declaration, or a required one on a step that already failed); the message names the artifact and the cause.
Triggers
api:triggers:fired— a firing settled (status, matched event, author/executed_as, created object or message).info:triggers:deferred— a firing parked under a maintenance window.warn:triggers:depth_cap— a firing suppressed at the chain-depth cap (trigger-loop damping).warn:triggers:deferral_expired— a released deferral past its window's declared until, suppressed.warn:triggers:identity— a firing's executing identity refused resolution.warn:triggers:engine— the engine failed several consecutive sweeps; event automation is stalled.
Inbound webhooks
external:webhook:received— a signature-verified inbound webhook delivery landed (opschain docs cli/admin-inbound-webhooks): the connection code, the envelope event type, the delivery id and the raw payload underdata.payload. Triggers with subjectexternalbind to it, and every firing it causes executes as the connection's service account. A GitHub delivery additionally exposes a normalised typed field set underdata.github, and a Jira/JSM delivery (recognised by the payload'swebhookEvent) underdata.jira(key, project, status, summary, priority, labels, comment id and the internal-note flag).warn:webhooks:refused— an inbound delivery was refused by name (connection, reason:bad_signature,stale_timestamp,replayed_delivery,connection_disabled,unknown_connection,no_signing_secret,payload_too_large,payload_not_json,missing_signature,missing_timestamp) beside the endpoint's typed 4xx — a refusal is never silent. No signature material is recorded.
SIEM export
warn:siem:delivery_failing— a SIEM forwarder connection (opschain docs integrations-siem) failed several consecutive delivery attempts: the connection code and kind, the failure streak and the receiver's last refusal (never credential material). Throttled to one event per connection per window; events queue behind the durable cursor and deliver once the receiver recovers.info:siem:test— the synthetic eventopschain admin siem testsends through a connection's real formatter and sender. It is delivered to the receiver only, never recorded on the event stream — listed here so the vocabulary is complete.
Maintenance windows
api:nodes:maintenance_started— a window opened (manual or auto-start).api:nodes:maintenance_ended— a window closed; sweeping it releases the deferred firings.api:nodes:maintenance_scheduled— a scheduled window was created.api:nodes:maintenance_extended— a window's end moved later (manual or auto-extension).audit:maintenance:override— a create was admitted through the gate by an authorised override.warn:maintenance:auto_extend_cap— an auto-extending window ended at its cap with work still running.warn:maintenance:declared_run_failed— a scheduled window's declared workflow could not start.
Infrastructure
api:infrastructure:registered— an infrastructure node was registered through the infrastructure API (datacarries the typed request fields and the node path). Re-registering a code a deregistered node holds revives the same row and records the same event.api:nodes:update— an infrastructure node was updated through the infrastructure API (the same family the estate node writes record).api:infrastructure:deregistered— the deregister verb closed the node's estate membership (the soft-close posture; the node row, its history and its events remain queryable).api:infrastructure:provisioned/api:infrastructure:destroyed— reserved for the integration that performs provision and destroy operations and asserts the matching states; nothing currently emits them, and no manual surface can.api:infrastructure:facts— discovered facts landed on the node's properties document and its freshness stamp moved.api:infrastructure:bulk_upsert— a batch register/upsert ran (datacarries the item, created, updated and failed counts and the parent path).
Node lifecycle
api:nodes:deleted— a node (subtree) was soft-deleted.api:nodes:restored— a soft-deleted node was restored.api:nodes:destroyed— a node was destroyed permanently.
Workflow lifecycle
api:workflows:destroyed— an archived workflow was destroyed permanently (data: project path, code, name, versions and runs counts, with_runs).warn:workflows:deprecated_approval_gate— a stored workflow version carrying the step-levelrequires_approval_from:wait gate was composed for a run or a plan. The gate keeps enforcing; the event is the usage evidence behind its staged absorption into approval policies (data: project path, workflow code, version and the gate-carrying step names — never an approver; the conversion path isopschain approval-policies convert-step-gates). Throttled to one event per workflow version per day, estate-wide (the same atomic per-window claim aswarn:authz:denied), emitted asynchronously off the compose path.
Templates
warn:template:action_file_invalid— a declarative action file at a template version's pinned commit failed validation or compilation when the version's action catalog was resolved (source: the template version;event_node_path: the owning project; data:file— the repository path,action— the identity it would have listed under,error— the validation error verbatim,template_version_id,template_code,message). Emitted once per template version and file per resolution outcome — the same file with the same error never warns twice, a changed error warns again — never per run or per catalog read; the version'sinvalid_action_filesattribute keeps the list. The broken action lists in the catalog as an invalid entry, a change created from it is refused with the same error, and a workflow step that reaches it endssystem_errorcarrying the error as itslast_error_message.
Actions refresh
api:generate_actions_request:create— an actions-refresh task was created.error:generate_actions_request:generate— the refresh failed (with a log excerpt; for a group-routed refresh the fleet cannot serve, the placement verdict naming the required group).api:action_refresh:warn— the refresh completed with a warning.info:action_refresh:skipped— a refresh was skipped (one already in progress for the asset/template-version combination).
Git remotes and credentials
error:git_remote:fetch— a git-remote fetch failed.api:git_credentials:create/api:git_credentials:update/api:git_credentials:destroy— a shared git credential changed (names and kinds only, never values).api:git_credentials:test— a credential's ls-remote probe ran (data: the target url and the outcome).api:git_credentials:extract— a remote's inline credentials were extracted into a shared credential (data: the credential's name/kind, the remote's id).git_credentials:use— a fetch/clone resolved a shared credential (scoped to the remote's project; data: credential id/name, remote name, url).
Authentication
The client address an attempt came from, resolved behind any trusted
proxy, is the actor envelope's client_address ("The audit envelope"
above) — the family carries no separate data.source key, so a SIEM rule
on the origin of a login keys on actor.client_address.
api:auth:login— a successful authentication.api:auth:logout— a caller revoked the session it presented (the AuthServiceLogoutverb — the GUI's logout);data.token_kindnames the revoked credential's kind.api:auth:session_refreshed— a session's refresh rotation: the refresh token was redeemed and replaced;datacarries the session id, the authentication method and strength and the new refresh generation (the address is the envelope'sclient_address).api:auth:session_revoked— an explicit session revocation (logout, a self revoke, an admin revoke or the user-disable cascade);data.reasonnames which.warn:auth:refresh_reuse— reuse detection fired: an already-rotated refresh token was replayed, and the whole session was revoked.api:auth:api_key_created— a native API key was minted (datacarries the key id, description and expiry — never the token value).api:auth:api_key_revoked— a native API key was revoked.warn:auth:v1_api_keys_retired— the one-time upgrade report naming the surviving legacyapi_keytokens that stopped authenticating, each to be re-minted as a native key.api:auth:mapped_grants_changed— a federated sign-in's mapped grants gained or lost outcomes versus the previous group snapshot: a group edit at the identity provider is a role change the stream attests.warn:auth:login_failed— the provider chain rejected a login (never carries the password).warn:auth:throttled— login backoff engaged (throttling, never hard lockout).warn:auth:provider_unavailable— an identity provider was skipped as unavailable (the mechanical half of break-glass). Throttled to one per 10 minutes against the events table;data.directorynames the unreachable directory's host:port,data.errorthe fault.warn:auth:break_glass— a successful break-glass login; never throttled by sinks.warn:auth:break_glass_refused— a break-glass login refused by theauth.break_glass_networksallowlist.warn:auth:provider_collision— a login whose username an earlier provider in the chain also holds.api:auth:mfa_enrolment_started— a pending TOTP factor was minted for enrolment (the secret never rides the event).api:auth:mfa_factor_activated— a factor was activated by a valid code, with the recovery-code count minted alongside it.api:auth:mfa_factor_removed— a factor was removed from the registry.api:auth:step_up— a session elevation, platform factor and IdP re-authentication alike.warn:auth:step_up_failed— a step-up proof that did not verify (a wrong or replayed code, a mismatched IdP re-authentication).warn:auth:step_up_refused— a floored verb refused for a missing or expired elevation (every expiry-refusal is audited); a refusal driven by an authorisation rule'srequire_step_upbit names the rule (rule_id,rule_name,rule_path) and the matched action.api:auth:step_up_rule_pass— an authorisation rule's step-up demand met by a live elevation, once per rule and action per request (the rule-layer half of the every-elevation-audited posture).api:users:password_changed— a self-serve password change.
Users, groups and reconciliation
api:users:create— a local user was created.api:users:disable/api:users:enable— a user was disabled/enabled.api:users:update— a user was updated.api:users:reset_password— an admin password reset.api:groups:add_member/api:groups:remove_member— local group membership changed.warn:auth:group_lookup_failed— a group lookup against the directory failed.api:users:create_service_account— a service account was minted.api:users:reassign_automations— a user's standing automations were repointed onto a service account;datacarries the target and the per-kind counts.api:users:reconciled— one directory-reconciliation sweep's summary.warn:auth:leaver_disabled— a leaver was disabled by reconciliation.warn:auth:reconciliation_skipped— a reconciliation sweep was skipped.api:auth:identity_rekeyed— a legacyauth_provider='ldap'row linked onto its (connection, entry id) identity (or eagerly by the provider sweep verb).
Authorisation configuration
api:authorisation_policies:create/api:authorisation_policies:update/api:authorisation_policies:destroy— a policy changed.api:policy_assignments:update— a policy's assignment set was replaced.api:authorisation_rules:create/api:authorisation_rules:update/api:authorisation_rules:destroy— a standalone rule changed.api:policy_rules:create/api:policy_rules:destroy— a rule was linked to / unlinked from a policy.
Authorisation denials
warn:authz:denied— a v2 request was refused by the compiled authorisation trie (a deny renderednot_foundorpermission_denied): the refused procedure, the first denied rule path and action, and the caller's principal id (UUID) and credential kind — never a username or email. Throttled to one event per refused procedure per five-minute window, estate-wide (an atomic per-window claim against the events table, so N replicas emit once): the event is the governance evidence that denials are happening; the Prometheus HTTP counters carry the volume. The emission rides the request tail asynchronously — the decide path itself touches no database. Feeds the dashboard's security-pulse panel and the SIEM lane like any otherwarn:event. Licence/pack refusals and step-up refusals are not this event — they carry their own types (warn:licence:feature_refused,warn:auth:step_up_refused).
Secrets
api:secrets_providers:create/api:secrets_providers:update/api:secrets_providers:destroy— SecretsProvider connection lifecycle (connection facts only, never a credential; a rotation carriescredential_rotated: true).api:secrets_providers:test— a test-verb probe ran;datacarries the provider's code and the success flag.api:packs:install/api:packs:pin/api:packs:destroy— installed pack-bundle lifecycle: a verified signed bundle landed in the pack store (name, version, signing key, bundle digest, uploaded filename), a pack's unversioned resolution target was pinned or cleared, or an installed version was deleted. Verification refusals (bad signature, digest mismatch, unknown signing key, missing entitlement, platform too old) are the caller's named error and store nothing.warn:packs:blob_orphaned— a pack-version delete removed the row but the best-effort bundle-blob release failed; the bytes stay orphaned in the blobstore (data.bundle_digestnames them) and the delete itself still succeeded. A blob already absent at delete time is a silent no-op, never a warning.api:packs:re_resolve— the bulk re-resolve verb cut new template versions onto an installed pack version;datacarries the pack, the target version and one<template code> <new version>entry per cut. Each cut version also carries its owninfo:template_version:pack_re_resolvedevent naming the pack, the target version and the source version the cut copied.api:transport_providers:create/api:transport_providers:update/api:transport_providers:destroy— TransportProvider record lifecycle (connection facts and thecredential_refpointer only, never a credential value; an inline rotation carriescredential_rotated: true).api:transport_providers:test— a record-validation test ran (no host dialled);datacarries the provider's code and the success flag.api:github_connections:create/api:github_connections:update/api:github_connections:destroy— GitHub App connection lifecycle (host, App id, audience and theprivate_key_refpointer only, never key material; an inline rotation carriesprivate_key_rotated: true).api:github_connections:test— a record-validation test ran (GitHub never called);datacarries the connection's code and the success flag.api:github_trust_policies:create/api:github_trust_policies:update/api:github_trust_policies:destroy— trust-policy lifecycle (claim predicates, service-account id, TTL and enabled state).info:github:token_exchange— a GitHub Actions OIDC token exchanged for a short-lived OpsChain token under a trust policy;datacarries the connection, policy, repository, ref, environment, workflow run id, session id and expiry — never token material.warn:github:exchange_refused— a token exchange was refused;datanames the connection and the reason (token_invalid,no_policy_match,daily_cap,feature_entitlement,connection_disabled,service_account_unusable) — the licence-side refusals double as uplift telemetry.api:github_connections:provision_webhook— GitHub webhook ingestion was provisioned onto the generic inbound-webhook lane;datacarries the connection facts, the inbound connection's code and the executing service account — never the signing secret.info:github:token_mint— a just-in-time GitHub App installation token was minted at step dispatch (the outboundGH_TOKENlane);datacarries the connection, installation id, run id, step id and expiry — never token material.warn:github:mint_refused— a JIT installation-token mint was refused and the step failed by name;datanames the connection and the reason (unknown_connection,connection_disabled,feature_entitlement,daily_cap,no_installation_id,key_unresolvable,mint_failed).info:github:attestation— a GitHub-side outcome was attested against a change (AttestGitHubRun/ theopschain/attestaction);datacarries the status, summary, repository, workflow run id and evidence links, sourced at the change.api:jsm_connections:create/api:jsm_connections:update/api:jsm_connections:destroy— JSM connection lifecycle (site base URL, auth mode, project keys and theapi_token_refpointer only, never token material or the account email; an inline rotation carriesapi_token_rotated: true).api:jsm_connections:test— a record-validation test ran (JSM never called);datacarries the connection's code and the success flag.api:jsm_connections:provision_webhook— Jira webhook ingestion was provisioned onto the generic inbound-webhook lane;datacarries the connection facts, the inbound connection's code and the executing service account — never the signing secret.info:jsm:internal_comment— an internal note was posted on a JSM request through the audited write door;datacarries the connection, request key, comment id and the note's length — never its content (visibilityis alwaysinternal; the surface has no customer-visible-comment capability).info:jsm:transition— a JSM request moved through a workflow transition;datacarries the connection, request key and the transition's id and name.info:jsm:fields— labels/fields were written on a JSM request;datacarries the labels and the field names, never field values.info:jsm:resolution— a JSM request was resolved with evidence links: the typedjsm:requestreference (relationshipresolves) landed on the resolving subject's governance block, the internal evidence comment was posted and the resolving transition performed;datacarries the subject, request key, transition, evidence links and the recorded reference, sourced at the resolving subject.api:secrets_provider_bindings:create/api:secrets_provider_bindings:update/api:secrets_provider_bindings:destroy— a scope binding was declared, re-pointed or removed;datacarries the scope path and the bound provider's code.api:transport_provider_bindings:create/api:transport_provider_bindings:update/api:transport_provider_bindings:destroy— a transport scope binding was declared, re-pointed or removed;datacarries the scope path and the bound provider's code.api:transport_grants:create/api:transport_grants:approve/api:transport_grants:reject/api:transport_grants:revoke— TransportGrant lifecycle (subject, provider pattern, scope, actions, step-up demand and status; a grant carries no credential material). The approve/reject pair is recorded whichever lane finalised the grant: the direct verb or the approvals funnel (quorum completion, rejection, TTL expiry).api:user_vault_credentials:set/api:user_vault_credentials:destroy— a user stored/removed their own interactive-lane vault credential (provider and auth method only, never the value).api:secrets:vault_read— one interactive-lane vault read: the user, the path/key read and which credential identity served it (user|platform) — never a value.api:secrets:decrypt— one interactive cipher-stacking decrypt (the secrets screen's decrypt-for-display): the user, the owner context and the value count — never a plaintext or ciphertext. Encrypt is deliberately un-audited.
Node metadata
api:metadata_profiles:create/api:metadata_profiles:update— a metadata profile was adopted or revised;datacarries the key, publisher, version and adoption flag.api:node_metadata:set— field values were written on one node;datacarries the node path and every field's before/after.api:node_metadata:bulk_annotate— one node's share of a BulkAnnotate, recorded per node with the same before/after shape plus the selector scope.api:node_metadata:migrate— one node's share of an explicit profile migration, same before/after shape plus the profile key.
All four ride the node's path, so estate-scoped subscriptions match them
(object kind node) — "notify security-governance on any
reclassification" is one subscription on the metadata event family.
Runner plane
info:runner:registered— a runner registered.info:runner:goodbye— a runner said goodbye (clean stop).info:runner:forgotten— a runner was forgotten by an admin.info:runner:drain_changed— a runner's drain flag changed.info:runner:group_drain_changed— a runner group's drain flag changed.info:runner:group_deleted— a runner group was deleted.info:runner:fleet_quiesced/info:runner:fleet_resumed— this replica's dispatch was held / resumed.warn:runner:fleet_drained— a fleet-wide drain was ordered.warn:runner:aged_out— a registered runner went silent past the liveness window without a goodbye (once per offline episode).warn:runner:log_spool_overflow— a runner's log spool overflowed for an attempt (log lines were dropped).warn:runner:version_skew— a registering or session-establishing runner's build version differs from (or is unknown to) the server's; once per (runner, version-pair), with both builds named in the payload.api:runner_groups:upsert— a runner group was created or updated through the v2 RunnerService (the drain/forget/fleet verbs keep recording theinfo:runner:*events above).api:runner_attributes:put/api:runner_attributes:delete— a placement-attribute definition was registered or removed through the v2 RunnerService.api:registration_tokens:create— a registration token was minted through the v2 RunnerService; the payload carries the token's fingerprint and scope, never the token value.
Agent plane
info:agent:registered/info:agent:goodbye/info:agent:forgotten/info:agent:revoked— agent lifecycle.info:agent:drain_changed— an agent's drain flag changed.info:agent:channel_opened/info:agent:channel_closed— an agent channel lifecycle.info:agent:chat— an agent chat exchange.warn:agent:transcript_error— a chat exchange's mandatory transcript capture failed to persist (the exchange streamed; the loss surfaces here instead of vanishing).info:agent:adapter_transcript— an AI-type agent's adapter-transit transcript exchange was sealed (lines are redacted before persistence).info:agent:owner_changed— an agent's owning user or team changed.info:agent:invoked— a caller invoked one of an agent's own capabilities through the platform proxy (the minimum-spec surfaces and chat excluded): the chain's root hop for a unit of agent work, carrying thecapability, theexchange_id, the caller's principal id and thecorrelation_idthe proxy stamped on the exchange — inherited from the caller'sX-Opschain-Correlation-Id, else minted for the invocation.info:agent:request— an agent capability request (a call the agent made through its request lane); carries thecorrelation_idof the SDK-managed scope it ran under, so the chain reads the agent's governed reads beside the run they led to.warn:agent:request_refused— an agent capability request was refused.warn:agent:type_attestation_refused— the class/type attestation refused an enrolment: anai-type manifest without its AI disclosures, or a non-aimanifest declaring them.warn:agent:adapter_transit_refused— adapter-transit transcripts arrived from an agent whose manifest does not declare theaitype (the contradiction detected at the adapter seam).warn:agent:tunnel_denied— an agent tunnel was denied.warn:agent:halted/info:agent:halt_resumed— one agent halted / resumed.warn:agent:release_stage_refused— an agent's action, run or governed read was refused by the target environment's release-stage floor: the payload names the release and its stage (empty for an agent enrolled without a release), the floor, the environment and the target path (opschain docs agent-releases).warn:agent:group_halted/info:agent:group_halt_resumed— an agent group halted / resumed.info:agent:rollover_started— a rollover of one lane of the agent to a release started (or was held in the approval funnel:held), with the strategy, the source and target release ids and the rollover'scorrelation_id— the one chain every step of the rollover joins.info:agent:instance_drained— a rollover drained one instance (instance_id,member_id): in-flight work finishes, nothing new is dispatched to it.info:agent:instance_restarted— an instance re-enrolled from the release its rollover expected (instance_id, the release and its stage).info:agent:canary_held— the canaries run the target release; the rest of the lane waits for continue or abort.info:agent:green_ready— a blue-green rollover's green lane holds the configured number of instances on the target release (green_lane); dispatch still lands on the serving lane and waits forcontinue(the flip) orabort.info:agent:rollover_flipped— a blue-green rollover flipped dispatch: the two lanes swapped labels in one write (green— the instances now serving,parked— the old ones kept for rollback,relabelled).info:agent:rollover_continued/rollover_aborted/rollover_rolled_back/rollover_finished— the rollover verbs applied;rollover_finishedcarries theretire_offer(the retirement command offered for the source release, never run).info:agent_release:retired/info:agent_release:revoked(platform path) — a release moved to the ladder's last rung (no new enrolment or rollover to it) / was revoked, withinstances_halted— every instance running it halted through the per-agent halt with the typedAGENT_RELEASE_REVOKEDreason.warn:agent:rollover_refused— a rollover (or one of its steps) was refused, with a typedreason:stage_policy,release_retired,release_revoked,no_instance,lane_busy,same_release,approverless,strategy,restart_refused(the runtime declined the restart instruction),release_mismatch(an instance came back on a release the rollover did not expect and stays drained),rejected(the funnel refused the held rollover).api:model_providers:create/update/destroy— a model-provider record changed through the v2 ModelProviderService; the payload carries the connection facts only, never the credential. Composed at the API write seam, so these stay out of the fixed-type census below.
Platform postures
warn:platform:halted— a break-glass halt was imposed (actor, plane, scope, reason).info:platform:resumed— a halt was lifted.warn:platform:resume_rate_limited— resume refused by the rate limiter.warn:platform:attempt_disposition— a halt's disposition was applied to a running attempt.warn:platform:agent_verb_escalation— an agent-plane emergency verb escalated.info:platform:attempt_suspended— a running attempt was suspended under a halt posture.warn:platform:halt_fallback— a suspend fell back to a harder disposition.api:platform:quiesced/api:platform:resumed— the system-quiesce barrier closed / reopened.warn:platform:quiesce_aborted— a system quiesce's drain timed out and was aborted.warn:server:secrets_refresh_deferred— a replica's secrets-provider composition refresh failed on a store read (a store outage) and was deferred: the last good composition stayed in force (kept_last_good: true) — or, on a replica that had not yet read a composition from the store, secret resolution refused by name — until the readiness-keyed retry composed afresh. Recorded by the replica (server_name) once the store answers, withfailed_at,deferred_for_seconds,failed_readsand the first read'serror.
Trust and governance
info:trust:granted/info:trust:revoked— a trust grant was made / revoked.info:trust:grant_proposed/info:trust:grant_approved/info:trust:grant_rejected— the four-eyes grant flow.info:trust:revoke_proposed/info:trust:revoke_rejected— the four-eyes revoke flow.info:trust:assigned/info:trust:unassigned— a trust assignment changed.info:trust:ceiling_set— a per-type tier ceiling was written (the payload carries the previous ceiling).info:trust:node_ceiling_set/info:trust:node_ceiling_removed— a node-classification tier ceiling rule was written / removed (the payload carries the rule's predicates and ceiling, and the previous ones on a rewrite or removal).warn:trust:cap_refused— an agent's governed work request exceeded a grant's rate or budget cap, or a brokered model call exceeded a grant's token budget (the payload names the grant, the cap and the window reset; a token refusal also names the provider and the spend).info:agent_release:ladder_changed— the estate's release stage ladder was replaced (stages,previous) or a rung renamed (renamed_from,renamed_to,releases_attested).info:agent_release:floor_changed— an environment's release-stage floor was set or cleared (environment,floor).
Governance foundation
api:governance_controls:create/update— a control catalog write (retirement is an update).api:attestations:create— an attestation was appended to the evidence ledger.api:evidence_ledger:legal_hold— a ledger row's legal hold was flagged or unflagged (the payload carrieslegal_hold); both directions are audited administrative acts.api:review_cadences:create/update— a review cadence changed.api:review_campaigns:create/close— a review campaign was opened by hand / closed (closure appends the closing attestation).audit:review_campaigns:approve/reject/flag— a campaign item's decision (retain / revoke / flag) in the shared approval grammar; a revoke'srejectpayload carries thework_item_idit opened.api:governance_work_items:complete— an operator recorded a work item's by-hand remediation.api:governance_waivers:create— a waiver was granted (never self-granted; time-boxed).warn:review_cadences:lapsed— loud lapse: a review campaign is still open past its due time plus the cadence's grace; emitted once per campaign.warn:governance:ledger_archive_blocked— the retention sweep found the evidence ledger's archivable prefix unverifiable, so nothing was sealed or deleted; restore the database or re-anchor the chain, then the next sweep archives (opschain docs retention).
Change governance
api:approval_policies:create/update/destroy— an ApprovalPolicy write (change-governance determination + step-up).api:asset_owners:create/update/destroy— a subtree owner declaration write (asset-owner determination on/governance/owners<path>+ step-up; the update payload carries before/after owners).audit:approvals:pending— a covered create entered the pre-startpending_approvalstate; the payload carries the fingerprint, the matched policies and the quorum deadline.audit:approvals:approve— an approval row was recorded against the checklist (the lines satisfied and the fingerprint signed ride the payload).audit:approvals:reject— an eligible approver finalised the subject as rejected, with the mandatory reason.audit:approvals:timeout— the quorum TTL expired before the checklist completed; the subject finalised rejected.audit:approvals:lapsed— the approval validity lapsed; every approval row voided and the subject re-gated.audit:approvals:release— an approved subject started (quorum completion, the window sweep, orStartApprovedRun), with its maintenance stamp.warn:approvals:break_glass— the one discretionary lever fired: a held subject released without quorum, with its mandatory reason (never quiet).warn:approval_policies:degraded— policies matched a create while thepacks:change-governanceentitlement had lapsed; the non-strict create proceeded ungated (the loud-degrade posture).api:governance_metadata:update— a subject's governance block changed; the payload carries before/after.
Recommendations
info:recommendation:filed/accepted/dismissed/expired/superseded— the recommendation lifecycle, one event per transition; the payload carries the filing instance'slane.info:recommendation:duplicate— a second proposal for an open (agent, subject node path, correlation root) key was linked to the open card (duplicate_of) — never a second card, never a second change.
Licence, EULA, packs, database
api:licence:applied/api:licence:removed— a licence was applied / removed.warn:licence:expiring— the licence is in its grace window.warn:licence:expired— the work stop is in force.warn:licence:limit_approaching— usage reached 90% of a finite licensed limit (per-kind used/limit counts), throttled like the expiry warnings.api:eula:accepted— the EULA was accepted.warn:eula:updated— the bundled EULA changed after acceptance.warn:packs:unverified_manifest— a pack's manifest failed signature verification (the pack still runs, ungated and loudly).api:database:maintenance— a database-maintenance run completed.api:database:analyze— an operator ranANALYZEthrough the admin plane (v2SystemService.AnalyzeDatabaseor the REST twin); the payload carries the trigger, duration, table count and failures.api:database:vacuum— an operator ranVACUUMthrough the admin plane; same payload shape.api:database:migrations— an operator applied pending schema migrations through the admin plane; the payload lists the applied versions.warn:database:maintenance— a database-maintenance statement failed.api:logs:migration— a log_lines-backfill phase changed state (started/paused/resumed/completed). The completed event is the verdict: owners verified, lines and bytes written, failures and skips for the migrate phase, owners and lines deleted — plusowners_held, the owners an active legal hold kept unpurged — for the purge phase.warn:logs:migration— the backfill skipped or failed something loudly: a family of groups whose rows cannot become segments, an owner whose pre-existing segments do not match its source rows, a purge owner whose rows drifted from the verified count, a purge owner under an active legal hold (actionowner_held— left untouched and unstamped until the hold lifts), or a phase that paused itself on an error.api:logs:migration_start/api:logs:migration_pause/api:logs:migration_resume/api:logs:purge_start— an operator drove the backfill control plane (v2SystemServicelog-migration verbs); the payload carries both phase states after the flip.warn:logs:chunks_dropped— log chunks of a step attempt were given up and the attempt's log carries a*** log gap: <n> chunk(s) <first..last> not delivered — <reason> ***marker in their place (step_id,attempt,runner_id,reason,count,first_seq,last_seq— ids and counts only, never log content). The reason class says who gave up:refused_invalid_argument/refused_unauthenticated/refused_permission_denied(the server refused the chunk or the job credential by verdict and the runner reported the loss),spool_unreadable(the runner's on-disk spool entry could not be read back),seal_timeout(the runner's end-of-step flush timed out with chunks still spooled, reported on its result submission),sealed(a chunk reached the server after the attempt's log was sealed — a runner that lost its lease, or a straggler). Once per attempt and reason class, so a burst of refused chunks is one event while every chunk keeps its own marker. A step never fails for a dropped chunk; the marker and this event are the audit record of the loss.warn:logs:ingest_forward_failed— in a multi-replica estate a step's log chunk (or its end-of-attempt seal) reached a server replica that does not hold the attempt's log and could not be forwarded to the replica that does (owner_replica_id,operationchunkorseal,message). The runner keeps re-uploading, so no line is lost while the owner is live; once per attempt per minute. A persistent stream means the owner'speer_urlis unset or unreachable from its peers.warn:database:schema_ahead— the server booted against a database schema newer than its own migration set (rollback mode): a newer build migrated this database. Boot proceeds — migrations are additive-only — and the payload carries the database's newest applied migration, the binary's, and the newer versions. Emitted once per boot.warn:database:version_n_minus_1— the server booted against a PostgreSQL major that is supported only as the previous major (N-1 — 17 while 18.6+ is the minimum for new deployments): boot proceeds, and the payload carries the server version, its numeric form and the minimum so operators can plan the upgrade. Emitted once per boot; an older major refuses the boot instead.warn:database:plan_invalidated— a store transaction failed because PostgreSQL refused a cached query plan whose result type changed under it (cached plan must not change result type, SQLSTATE 0A000 — a column type altered by a migration another replica applied mid-roll) and the server retried it once; the payload carriesoperation(read/write),sqlstateand the database'smessage. Emitted once per replica per boot — the first invalidation is recorded, later ones in the same roll are retried silently; the serving pools plan queries so that a column type change cannot raise it, so this event means the retry caught something the plan posture did not.retention:sweep— a retention sweep deleted, expired or released something; the payload is the sweep summary (opschain docs retention).warn:retention:blob_missing— the retention sweep found expiring rows whose blobs are absent from the blobstore: the rows are skipped, never deleted, and the payload names each withheld row and its missing digest.
Notifications
The notification engine consumes this stream (see
reference/resources/notification); its own happenings:
api:notification_subscriptions:create/update/destroy/mute/snooze— a subscription changed through the v2 NotificationService.api:notification_routes:create/update/destroy— a delivery route changed.api:notification_team_subscriptions:create/update/destroyandapi:notification_team_routes:create/update/destroy— a group-owned team subscription or route changed.api:notifications:read/api:notifications:archive— feed rows were marked read / archived (counts only, never the summaries).api:notification_connectors:create/update/destroy— a channel connector changed through the v2 NotificationAdminService (names and ids only — never allowlists, headers or the signing secret).api:notification_connectors:test— an admin sent a signed test delivery (the target URL and whether it was delivered).api:notification_mandates:create/update/destroy— a mandated subscription changed through the v2 NotificationAdminService (admin +packs:security-governance); deleting one keeps its dispatched evidence rows.api:notification_retention:update— the feed/evidence retention tunables changed.warn:notifications:engine— the engine failed several consecutive sweeps; notification dispatch is stalled.notifications:retention:sweep— the hourly notification-retention sweep archived feed-expired rows and/or deleted evidence-expired rows (emitted only when a pass changed something; the payload carries the effective policy and the counts).
The fixed-type census
The registry above is prose; the census below is mechanical: every
fixed event-type constant in the server source, verbatim. The families
composed at runtime — the run, step and API write seams'
api:<resource>:<verb> types — are described in their registry sections
above and do not appear here.
A census gate in the server's own test suite pins this list against the
source, so a new fixed event type cannot ship until this page is
regenerated with it.
api:action_refresh:warnapi:admin_queue:sweepapi:admin_queue:terminateapi:artifacts:createapi:auth:api_key_createdapi:auth:api_key_revokedapi:auth:identity_rekeyedapi:auth:loginapi:auth:logoutapi:auth:mapped_grants_changedapi:auth:mfa_enrolment_startedapi:auth:mfa_factor_activatedapi:auth:mfa_factor_removedapi:auth:session_refreshedapi:auth:session_revokedapi:auth:step_upapi:auth:step_up_rule_passapi:database:maintenanceapi:database:migrationsapi:eula:acceptedapi:generate_actions_request:createapi:git_credentials:extractapi:governance_metadata:updateapi:groups:add_memberapi:groups:createapi:groups:deleteapi:groups:remove_memberapi:licence:appliedapi:licence:refreshedapi:licence:removedapi:logs:migrationapi:nodes:deletedapi:nodes:destroyedapi:nodes:maintenance_endedapi:nodes:maintenance_extendedapi:nodes:maintenance_scheduledapi:nodes:maintenance_startedapi:nodes:restoredapi:notification_connectors:testapi:platform:quiescedapi:platform:resumedapi:secrets:decryptapi:secrets:vault_listapi:secrets:vault_readapi:steps:abortapi:steps:retryapi:steps:skipapi:steps:startapi:steps:successapi:steps:waitingapi:transport_grants:approveapi:transport_grants:createapi:transport_grants:rejectapi:triggers:firedapi:users:createapi:users:disableapi:users:enableapi:users:password_changedapi:users:reconciledapi:users:reset_passwordapi:users:updateapi:workflows:destroyedaudit:approvals:approveaudit:approvals:lapsedaudit:approvals:pendingaudit:approvals:rejectaudit:approvals:releaseaudit:approvals:timeoutaudit:maintenance:overrideaudit:steps:approveaudit:steps:cancelaudit:steps:continueaudit:steps:rejecterror:generate_actions_request:generateerror:git_remote:fetcherror:mint_model_concretise_task:concretiseerror:template_version:refresh_cancelledinfo:action_refresh:skippedinfo:agent:adapter_transcriptinfo:agent:canary_heldinfo:agent:channel_closedinfo:agent:channel_openedinfo:agent:chatinfo:agent:drain_changedinfo:agent:forgotteninfo:agent:goodbyeinfo:agent:green_readyinfo:agent:group_halt_resumedinfo:agent:halt_resumedinfo:agent:instance_drainedinfo:agent:instance_restartedinfo:agent:invokedinfo:agent:model_callinfo:agent:owner_changedinfo:agent:registeredinfo:agent:requestinfo:agent:revokedinfo:agent:rollover_abortedinfo:agent:rollover_continuedinfo:agent:rollover_finishedinfo:agent:rollover_flippedinfo:agent:rollover_rolled_backinfo:agent:rollover_startedinfo:agent_release:createdinfo:agent_release:floor_changedinfo:agent_release:key_registeredinfo:agent_release:key_status_changedinfo:agent_release:ladder_changedinfo:agent_release:promotedinfo:agent_release:promotion_heldinfo:agent_release:promotion_rejectedinfo:agent_release:retiredinfo:agent_release:revokedinfo:github:attestationinfo:github:token_exchangeinfo:github:token_mintinfo:jsm:fieldsinfo:jsm:internal_commentinfo:jsm:resolutioninfo:jsm:transitioninfo:platform:attempt_suspendedinfo:platform:resumedinfo:recommendation:acceptedinfo:recommendation:dismissedinfo:recommendation:duplicateinfo:recommendation:expiredinfo:recommendation:filedinfo:recommendation:supersededinfo:runner:drain_changedinfo:runner:fleet_quiescedinfo:runner:fleet_resumedinfo:runner:forgotteninfo:runner:goodbyeinfo:runner:group_deletedinfo:runner:group_drain_changedinfo:runner:registeredinfo:server:node_quiescedinfo:server:node_resumedinfo:server:site_quiescedinfo:server:site_resumedinfo:siem:testinfo:template_version:refresh_cancelledinfo:transport:sessioninfo:triggers:deferredinfo:trust:assignedinfo:trust:ceiling_setinfo:trust:grant_approvedinfo:trust:grant_proposedinfo:trust:grant_rejectedinfo:trust:grantedinfo:trust:node_ceiling_removedinfo:trust:node_ceiling_setinfo:trust:raise_heldinfo:trust:revoke_proposedinfo:trust:revoke_rejectedinfo:trust:revokedinfo:trust:unassignednotifications:retention:sweepretention:sweepwarn:agent:adapter_transit_refusedwarn:agent:group_haltedwarn:agent:haltedwarn:agent:model_call_refusedwarn:agent:model_provider_refusedwarn:agent:release_stage_refusedwarn:agent:request_refusedwarn:agent:rollover_refusedwarn:agent:transcript_errorwarn:agent:tunnel_deniedwarn:agent:type_attestation_refusedwarn:agent_release:enrolment_refusedwarn:agent_release:refusedwarn:approval_policies:degradedwarn:approvals:break_glasswarn:artifacts:collectionwarn:auth:break_glasswarn:auth:break_glass_refusedwarn:auth:group_lookup_failedwarn:auth:leaver_disabledwarn:auth:login_failedwarn:auth:provider_collisionwarn:auth:provider_unavailablewarn:auth:reconciliation_skippedwarn:auth:refresh_reusewarn:auth:step_up_failedwarn:auth:step_up_refusedwarn:auth:throttledwarn:auth:v1_api_keys_retiredwarn:authz:deniedwarn:database:maintenancewarn:database:migrationswarn:database:plan_invalidatedwarn:database:schema_aheadwarn:database:version_n_minus_1warn:eula:updatedwarn:github:exchange_refusedwarn:github:mint_refusedwarn:governance:ledger_archive_blockedwarn:governance:ledger_re_anchoredwarn:licence:expiredwarn:licence:expiringwarn:licence:feature_refusedwarn:licence:invalid_documentwarn:licence:limit_approachingwarn:licence:revokedwarn:licence:verify_failedwarn:logs:chunks_droppedwarn:logs:ingest_forward_failedwarn:logs:migrationwarn:maintenance:auto_extend_capwarn:maintenance:declared_run_failedwarn:mintmodel:actions_unavailablewarn:notifications:enginewarn:packs:blob_orphanedwarn:packs:unverified_manifestwarn:platform:agent_verb_escalationwarn:platform:attempt_dispositionwarn:platform:halt_fallbackwarn:platform:haltedwarn:platform:quiesce_abortedwarn:platform:resume_rate_limitedwarn:retention:blob_missingwarn:review_cadences:lapsedwarn:runner:aged_outwarn:runner:fleet_drainedwarn:runner:log_spool_overflowwarn:runner:version_skewwarn:scheduled_activities:errorwarn:security:git_remote_refusedwarn:server:secrets_refresh_deferredwarn:siem:delivery_failingwarn:template:action_file_invalidwarn:transport:cap_refusedwarn:transport:refusedwarn:triggers:deferral_expiredwarn:triggers:depth_capwarn:triggers:identitywarn:trust:cap_refusedwarn:webhooks:refusedwarn:workflows:deprecated_approval_gate
The retired v1 catalog (removed)
The parked v1 read families (/api/event_filters,
/api/event_subscribers, /api/notification_jobs and their
notification_filters/notification_subscribers aliases) are removed
(a clean break, no importer). What a
filter + subscriber + job expressed is now one v2
NotificationService subscription: the selector grammar picks the
events, the subscription's routes pick the channels, and connectors
(NotificationAdminService) replace the job/channel catalog. See
reference/resources/notification.
Levers, and who owns them
| Lever | Authority | Effect |
|---|---|---|
retention.events_days | config.yaml (deploy-authoritative — opschain docs config) | Ages events out of the stream (default: never) — and with them the actor envelope's address and user agent. |
server.trusted_proxies | config.yaml (deploy-authoritative) | Which peers' forwarding headers decide the actor.client_address (and every other recorded client address); unset, the TCP peer is recorded. |
| Notification subscriptions / routes / connectors | v2 NotificationService + NotificationAdminService (opschain docs resources/notification) | Who gets notified of what. |
emit: steps and opschain step emit-event | The action author, gated by the custom-type grammar and reserved prefixes | Puts domain events on the stream for triggers to match. |
| Triggers | opschain docs triggers | Turn events into new runs. |
Failure modes
- Emission is best effort — a failed event insert is noticed in the server log, never fails the operation it narrates; a degraded database therefore thins the audit trail rather than blocking work.
- A reserved-prefix custom type is refused at the write gate with the
named error (
custom event type "<t>" uses the reserved prefix "<p>" — api: system: info: error: warn: are the system's), failing the emitting step rather than polluting the system vocabulary. - Secrets never land in the stream: custom event data passes the run's masker before storage.
- A truncated events page hides older matches — the CLI says so
explicitly; raise
--limitor tighten--sincerather than trusting an empty result.
Related pages
opschain docs resources/event— the full attribute dictionary.opschain docs triggers— matching events into automation, and chains.opschain docs cli/events— the CLI listing and watch loop.opschain docs cli/step-emit-event— emitting custom events from steps.opschain docs retention— how long the stream is kept.