Skip to content

Reference

Audit trail

AI-assistedThis page includes both human and AI contributions.

Every record submilli-server writes to its audit trail: the fields all records share, and the decision, execution, session, admin, auth, and server records with their events and fields.

The server writes one audit record per line, in logfmt, to its log’s output or to logging.audit.file. Server settings has the settings that turn the trail on and off, choose its file, and decide how allowed operations are recorded.

ts=2026-10-03T20:02:39.647Z level=info stream=audit target=submilli_server::audit msg=admin event=blueprint_created event_id=f3343d01-752d-479e-999e-0c5036af2b48 name=support new_hash=5a2a1e61c440e6b36c43c60b2eff7ecad0ed7989bf5dee30d486678206a480d0 old_hash=null outcome=ok principal=SUBMILLI_SERVER_TOKEN route=/v1/blueprints/support schema=submilli.audit/1 status=200 type=admin
Field Value
ts When the record was written, UTC, to the millisecond
level info
stream audit, which tells a record from a log line
target submilli_server::audit
msg The record’s type, as in type
type decision, execution, session, admin, auth, or server
event What happened, for every type except decision
event_id A UUID unique to this record
schema submilli.audit/1. A field added keeps the version; a field renamed or removed changes it

After the first five keys the fields are in alphabetical order. A nested value is flattened into dotted keys, and a list by position, as in vars.0.name=customerId vars.0.value=cus_northwind. The selected quick-search context.* text fields are cut at 512 characters, and a URL loses its user, password, query string, and fragment. The full JSON in context.payload_json has neither transformation.

One for each operation a program was refused, and for operations it was allowed as the allows setting says.

ts=2026-10-04T05:44:32.121Z level=info stream=audit target=submilli_server::audit msg=decision blueprint=test blueprint_hash=35d4367e3fb94fcad8d47c0af8c7f2a054a229a69f069928dfcca5f53d835ac5 caller=main capability=fs.write context.length=11 context.path=/denied context.payload_json="{\"length\":11,\"path\":\"/denied\"}" decision=deny event_id=b2a431fb-87ba-4602-8859-013f5f2a8e83 execution_id=375d5dae-685c-4920-a471-c350f1b33271 principal=unauthenticated reason="policy denied the capability" rule=default schema=submilli.audit/1 source=policy type=decision
Field Value
execution_id The run that asked. Absent for the MCP file tools, which read a session’s files outside a run
session_id The session, when there is one
blueprint, blueprint_hash The Blueprint, and a SHA-256 of the version in force
principal The name of the API token the request carried
caller main, or the Package that made the call
capability The operation, such as fs.write or acme.com/credits.apply
context.* The operation’s fields, as below, including payload_json
decision allow or deny
source What decided: policy, the Blueprint’s rules; invariant, a refusal no rule can change, such as secrets.get from main; read_only, a write into a read-only volume; egress_guard, the block on private addresses; quota, a model-token or session-state budget
rule The rule that decided, by its position in the caller’s list from 0, or default
reason Why it was refused, on refusals only

context.payload_json is the complete JSON context passed to the policy check, serialized as one logfmt text value. Parse that value as JSON to recover objects, arrays, numbers, booleans, and nulls. It is written for Package check() calls and built-in operations alike, including refusals. It is not redacted or shortened, so Package authors and operators should treat the audit destination as a store of the data passed to permission checks. An encoded audit record over 1 MiB is rejected in full, reported to standard error, and does not stop the program.

For quick searches, context also keeps these fields of the operation, and only when their value is text, a number, or a boolean: host, path, url_path, vfs_path, from, to, method, tool, tool_name, name, secret, model, key, prefix, body_size, timeout_ms, max_bytes, overwrite, decompress, prompt_count, op, length, recursive, remote, remoteName, branch, and transport.

This refused acme.com/credits.apply call came from a Package run with check("acme.com/credits.apply", { customerId: "cus_northwind", amount: 42, customerClass: "business" }):

ts=2026-10-04T05:37:48.530Z level=info stream=audit target=submilli_server::audit msg=decision blueprint=test blueprint_hash=35d4367e3fb94fcad8d47c0af8c7f2a054a229a69f069928dfcca5f53d835ac5 caller=main capability=acme.com/credits.apply context.payload_json="{\"amount\":42,\"customerClass\":\"business\",\"customerId\":\"cus_northwind\"}" decision=deny event_id=0991217e-e863-4f0d-b4f5-cd85e61aea85 execution_id=3506cc41-9d9d-4c2e-b76a-2f11d6fb433e principal=unauthenticated reason="policy denied the capability" rule=default schema=submilli.audit/1 source=policy type=decision

Under allows: summary, a run’s allowed operations are written when the run finishes, one record for each caller, capability, and rule, with the number of operations and up to ten distinct contexts:

ts=2026-10-04T05:44:32.155Z level=info stream=audit target=submilli_server::audit msg=decision blueprint=test blueprint_hash=587d2f32ff8c32f8808d17e4ff4a41b3250b70628c067500c527b726a193e6a9 caller=main capability=acme.com/credits.apply contexts.0.payload_json="{\"amount\":42,\"customerClass\":\"business\",\"customerId\":\"cus_northwind\"}" count=1 decision=allow event_id=555cbab7-33e1-4c34-9d9b-bcbe23b5b5a6 execution_id=7adecd7f-273e-41a7-ad4d-8e606315b102 principal=unauthenticated rule=0 schema=submilli.audit/1 source=policy type=decision
Field Value
count How many operations the record stands for
contexts.* Distinct contexts numbered from 0, each including payload_json
summary_overflow true when the summary count, context count, or encoded size limit is reached. Further distinct operations are written one by one

Two for each run: event=started when it begins and event=finished when it ends. A request that is refused before the program runs, such as one naming a Blueprint the server doesn’t hold, still gets both.

ts=2026-10-03T20:02:39.695Z level=info stream=audit target=submilli_server::audit msg=execution blueprint=support blueprint_hash=5a2a1e61c440e6b36c43c60b2eff7ecad0ed7989bf5dee30d486678206a480d0 embedding_tokens=0 entry_point=http event=finished event_id=9b94bd43-647f-4b60-a740-0dee88e0d048 execution_id=0dfa846a-4483-4b8a-b271-45339daecbaf fuel=2264 host_fuel=2211 memory_peak=65536 model_tokens=0 outcome=ok principal=SUBMILLI_SERVER_TOKEN schema=submilli.audit/1 source_hash=9869da7b5d729a383bbfb33edf1a8ab68ab23fcc3fd3939a675b91bd906179cc source_size=265 type=execution vars.0.name=customerId vars.0.value=cus_northwind wall_ms=38 wasm_fuel=53
Field Value
execution_id The run. The execute response carries the same ID
entry_point http, a one-off POST /v1/execute; session, a session’s execute endpoint; mcp, the MCP execute tool
session_id The session, when there is one
principal The name of the API token
blueprint, blueprint_hash The Blueprint, and a SHA-256 of the version in force
vars.* The variables bound for the run, each a name and a value. A value is [redacted] when the name contains secret, token, password, credential, authorization, or api_key
source_hash, source_size A SHA-256 of the program’s source and its length in bytes, never the source

event=finished adds:

Field Value
outcome ok, error, fuel_exhausted, timeout, memory_exhausted, stack_exhausted, or cancelled
error_class The error’s kind, when the run failed. A permission denial that no catch handled is permission_denied
fuel, wasm_fuel, host_fuel Fuel consumed, as in Set limits
memory_peak The most memory the run held, in bytes
model_tokens Model tokens the run spent
embedding_tokens Embedding tokens the run spent
wall_ms Elapsed milliseconds

A request repeated with the same Idempotency-Key returns the first run’s result and execution_id, and writes no new records.

ts=2026-10-03T20:02:39.674Z level=info stream=audit target=submilli_server::audit msg=session blueprint=support event=created event_id=1b32fa8e-1dfe-43de-ae7d-c2483f5f3972 file_area_mode=per_session principal=SUBMILLI_SERVER_TOKEN schema=submilli.audit/1 session_id=3b0e881b-753f-491c-8259-257016f5c448 type=session vars.0.name=customerId vars.0.value=cus_northwind

Every session record has session_id and principal.

event When Other fields
created A session was opened blueprint, vars.*, file_area_mode
rebound Its variables or harness secrets were bound again vars.*, old_vars.*
deleted It was closed reason=disconnect
evicted Its Blueprint was removed reason=blueprint_deleted, blueprint
expired It was idle past its Blueprint’s idle_timeout reason=idle_timeout
found It was reloaded when the server started blueprint
lost At startup, its files were missing, so it was dropped reason=workspace_missing

Variables are redacted as in execution.

One for each request that changes the server, written when the request completes.

ts=2026-10-03T20:11:15.828Z level=info stream=audit target=submilli_server::audit msg=admin event=secret_put event_id=cb6549c4-c63e-4056-9ab0-f68221120177 key=billing_api_key outcome=ok principal=admin route=/v1/secrets schema=submilli.audit/1 status=200 type=admin

Every admin record has:

Field Value
principal The name of the API token
route The request’s path
status The HTTP status of the response
outcome ok for a 2xx status, error for any other, cancelled when the request was abandoned
event Request Other fields
blueprint_created POST /v1/blueprints, or PUT /v1/blueprints/{name} for a name the server didn’t hold name, new_hash, old_hash
blueprint_replaced PUT /v1/blueprints/{name} for a name it held name, new_hash, old_hash
blueprint_deleted DELETE /v1/blueprints/{name} name, old_hash
secret_put POST /v1/secrets key, never the value
secret_deleted DELETE /v1/secrets/{key} key
package_installed POST /v1/packages/install packages.*, each a name, version, and digest
package_removed DELETE /v1/packages/{name} name, version, digest
oauth_refresh_token_set POST /v1/mcp/{blueprint}/{server}/refresh-token blueprint, server
oauth_refresh_token_deleted DELETE /v1/mcp/{blueprint}/{server}/refresh-token blueprint, server
oauth_code_exchanged POST /v1/mcp/{blueprint}/{server}/oauth/exchange blueprint, server
shutdown_requested POST /v1/shutdown

new_hash and old_hash are SHA-256s of the Blueprint. old_hash is null when there was none.

One for each request refused for its token, with event=refused. The token itself is never recorded.

ts=2026-10-03T20:11:15.888Z level=info stream=audit target=submilli_server::audit msg=auth event=refused event_id=9de64a66-aa15-47a0-8327-82e557fec87e reason=admin_required remote_address=127.0.0.1:55111 route=/v1/status schema=submilli.audit/1 type=auth
Field Value
reason missing_token, no token was sent; unknown_token, the token matches none the server holds; admin_required, a user token on an admin endpoint
route The request’s path
remote_address The address the request came from
ts=2026-10-03T20:02:26.100Z level=info stream=audit target=submilli_server::audit msg=server allow_unauthenticated=false event=started event_id=47cc5240-5811-4399-b4ae-5515a897e11c schema=submilli.audit/1 settings_hash=72cdde19df1b28e43588b3dcb1da965498144093975dcdaf5d8d5a940c280150 type=server version=0.1.6
event When Other fields
started The server began serving version; settings_hash, a SHA-256 of its effective settings, without credentials, so a changed setting changes it; allow_unauthenticated; egress_grants, the outbound grants added by environment variables, when there are any
stopped The server stopped

Secret-management and authentication records omit secret values and API tokens. Execution records omit program source and model output. Decision records store the complete context supplied to the permission check in payload_json. If a caller puts a body, prompt, token, or file contents in that context, the audit destination holds it.