---
title: "Audit records and run identity"
description: "Audit records and run identity for tenant admins and governance reviewers."
canonical_url: "https://docs.oz0.ai/govern/audit-trail/records"
---
# Audit records and run identity

> Audit records and run identity for tenant admins and governance reviewers.

For: tenant admins and governance reviewers.

## What a run leaves

Every job leaves these entries, recorded by the edge as the job runs:

<table>
<thead>
  <tr>
    <th>
      Action
    </th>
    
    <th>
      What it records
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        run.start
      </code>
    </td>
    
    <td>
      Who started the job, through what (<code>
        api
      </code>
      
      , <code>
        webhook
      </code>
      
      ), its budget, and whether it is about a <a href="https://docs.oz0.ai/govern/personal-data">
        data subject
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        run.model_call
      </code>
    </td>
    
    <td>
      Each call through the gateway: provider, the model and version that answered (<code>
        claude-sonnet-4-5-20250929
      </code>
      
      ), tokens, cost, status, the step
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        run.service_call
      </code>
    </td>
    
    <td>
      What a plugin <a href="https://docs.oz0.ai/build/parts/tools/model-calls#report-what-your-own-services-cost">
        reported
      </a>
      
       for a service it called
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        run.tool_call
      </code>
    </td>
    
    <td>
      Each tool call: the function (as the call's header named it to the edge), the node, how it went, its activity, run and <code>
        input_sha256
      </code>
      
       (which tie it to its <code>
        run.tool_intent
      </code>
      
      ), <code>
        reused_activity
      </code>
      
       when its run had scheduled another activity under the same ID, and its error in the entry's <a href="https://docs.oz0.ai/govern/audit-trail/records#what-a-job-read-or-wrote">
        private part
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        run.hook
      </code>
    </td>
    
    <td>
      Each <a href="https://docs.oz0.ai/build/parts/hooks">
        hook
      </a>
      
       and its verdict: <code>
        allow
      </code>
      
      , <code>
        deny
      </code>
      
      , <code>
        modify
      </code>
      
       or <code>
        escalate
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        run.command.decided
      </code>
    </td>
    
    <td>
      Each harness shell command the <a href="https://docs.oz0.ai/govern/command-rules">
        command rules
      </a>
      
       decided: the harness tool (<code>
        claude-agent-sdk.Bash
      </code>
      
      ), the action (<code>
        allow
      </code>
      
      , <code>
        approve
      </code>
      
       or <code>
        refuse
      </code>
      
      ), the rule that decided (a tenant rule's ID, or a default such as <code>
        cmd.download-run
      </code>
      
      , with its check), the program and classes of the part that decided, whether the floor decided, the programs the command runs, what shadow rules would have decided, and the defaults' version. Never the command or its arguments
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        run.approval.decide
      </code>
    </td>
    
    <td>
      An <a href="https://docs.oz0.ai/agents/approvals">
        approval
      </a>
      
       decided: who, when, and the comment (in the private part); what was decided (its kind, function and action class), how long it waited and how long its card was on screen, for the <a href="https://docs.oz0.ai/agents/approvals/statistics#approver-statistics">
        approver statistics
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        run.approval
      </code>
    </td>
    
    <td>
      The approval as the job recorded it, <code>
        timeout
      </code>
      
       included
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        run.child_job
      </code>
    </td>
    
    <td>
      A child job the job started
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        run.flow_tool
      </code>
    </td>
    
    <td>
      A <a href="https://docs.oz0.ai/build/parts/flows/flow-tools#flows-as-tools">
        flow tool's
      </a>
      
       call: the tool (<code>
        refunds.issue-refund
      </code>
      
      ) and the child job its flow ran as. That child's own entries (<code>
        run.tool_call
      </code>
      
      , <code>
        run.approval
      </code>
      
       and the rest) carry <code>
        flow_tool
      </code>
      
       and <code>
        parent_job
      </code>
      
       in their detail, so each step is audited as its own call and leads back to the job that called the flow
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        run.skill_read
      </code>
    </td>
    
    <td>
      A <a href="https://docs.oz0.ai/build/parts/skills">
        skill
      </a>
      
       an agent read with <code>
        oz.skill
      </code>
      
      : the skill as <code>
        &lt;plugin>/&lt;skill>
      </code>
      
      , the hash of what it read, the file of the skill it read if any, and whether its text was trusted (an imported plugin's or another plugin's is not, and its read tainted the job). Never the text
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        skill.hidden_read
      </code>
    </td>
    
    <td>
      A read of a skill the tenant <a href="https://docs.oz0.ai/build/plugins/configure#skills">
        hides from the agent
      </a>
      
      , which the edge let run: its job started before the hide reached its node. The skill, the agent and the job; never the text
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        run.file_read
      </code>
    </td>
    
    <td>
      A job file an <a href="https://docs.oz0.ai/improve/outcome-checks/judges#a-judge-that-reads-the-jobs-files">
        outcome check's judge
      </a>
      
       read with <code>
        files: read
      </code>
      
      : the job, the judge's step (its activity and attempt), the file's SHA-256 and its size, written by the edge before the bytes go out. Never the content
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        run.evidence_read
      </code>
    </td>
    
    <td>
      A read with <a href="https://docs.oz0.ai/improve/evidence-for-agents">
        <code>
          oz.evidence
        </code>
      </a>
      
       of what a granted plugin's jobs came to: the job and agent that read, the reading plugin, the target plugin (and agent, if one was named), the kinds, how many rows of each came back, whether content was in it, and how far back it looked. Every read, never deduplicated, so a change an agent proposes can cite the reads it was based on. Never the content
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        run.session.lost
      </code>
    </td>
    
    <td>
      A <a href="https://docs.oz0.ai/build/parts/harnesses/recovery#when-a-node-is-lost">
        harness session
      </a>
      
       lost with its node: the node, the session's number, how long it ran, its model calls, tokens and cost, which stays spent
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        run.session.restarted
      </code>
    </td>
    
    <td>
      A fresh session the job started after a lost one: its number, and the lost session and node it follows
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        run.output
      </code>
    </td>
    
    <td>
      The job's end: status, cost, its labels, and the SHA-256 of its answer, so you can prove what it answered without keeping the answer in the log; a failed job's error goes in the private part
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        run.refused
      </code>
    </td>
    
    <td>
      A model call the gateway refused: a spent budget or quota, or a <a href="https://docs.oz0.ai/govern/kill-switches">
        kill switch
      </a>
      
      ; or a job a <a href="https://docs.oz0.ai/spend/budgets-and-quotas">
        quota
      </a>
      
       did not let start, at the runtime API, a webhook or a routine's fire. A budget's, a quota's or a tree limit's entry keeps the refusal's numbers in <code>
        refusal_detail
      </code>
      
       (<a href="https://docs.oz0.ai/reference/spend-refusals">
        Spend refusals
      </a>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        run.a2a.task
      </code>
      
      , <code>
        run.a2a.message
      </code>
      
      , <code>
        run.a2a.artifact
      </code>
    </td>
    
    <td>
      The outside agent's tasks, messages and artifacts, recorded before the step returns. See <a href="#a2a-entry-contents">
        A2A entry contents
      </a>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        run.sandbox.tier0
      </code>
    </td>
    
    <td>
      A shell or write harness session of the job ran without a sandbox, on a node allowed to: one a tenant admin <a href="https://docs.oz0.ai/fleet/nodes#the-sandbox-opt-out">
        opted out
      </a>
      
       of sandbox tier 1, or a dev server's. It names the node and what allowed it (<code>
        allowed_by
      </code>
      
      ): <code>
        optout
      </code>
      
       with that opt-out's who, when, why and decision, or <code>
        dev_server
      </code>
      
      ; <code>
        unknown
      </code>
      
       when the node no longer held it when the entry was written, and then the node's <code>
        tenant.sandbox_optout
      </code>
      
       entries name it
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        egress.denied
      </code>
    </td>
    
    <td>
      A request a job's sandbox made that the <a href="https://docs.oz0.ai/concepts/security/sandbox-tiers#sandboxes">
        egress proxy
      </a>
      
       refused: host, port, the kind of refusal (<code>
        not_allowed
      </code>
      
      , <code>
        deny_entry
      </code>
      
      , <code>
        internal
      </code>
      
      , <code>
        private
      </code>
      
      , <code>
        tls_name
      </code>
      
      , <code>
        not_tls
      </code>
      
      , <code>
        host_header
      </code>
      
      , <code>
        protocol_switch
      </code>
      
      ), and why in the <a href="https://docs.oz0.ai/govern/audit-trail/records#what-a-job-read-or-wrote">
        private part
      </a>
      
       (it may quote what the process sent); each target once, at most 20 per job. <code>
        oz0 egress learn &lt;job>
      </code>
      
       proposes entries from them
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        run.workspace_secret
      </code>
    </td>
    
    <td>
      A file in a job's working directory that its harness session could read and that looked like a secret, found by the <a href="https://docs.oz0.ai/concepts/security/identity-and-secrets#secrets-in-the-working-directory">
        scan before and after the session
      </a>
      
      : its kind (<code>
        env_file
      </code>
      
      , <code>
        private_key
      </code>
      
      , <code>
        tenant_secret
      </code>
      
      ), its path relative to the working directory and how many files of that kind there were; once per kind and path, at most 20 paths of each kind per job. Never a value, nor which secret matched
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        run.judge_same_model
      </code>
    </td>
    
    <td>
      A judge whose model was the model of the work it judges (<a href="https://docs.oz0.ai/improve/outcome-checks/judges#a-judge-on-another-model-than-the-agent">
        A judge on another model
      </a>
      
      ): the judge's kind (<code>
        outcome
      </code>
      
      , <code>
        reviewer
      </code>
      
      , <code>
        best_of
      </code>
      
      ), the judge's model, the judged model and why they are one; <code>
        refused
      </code>
      
      , since such a judge gates the job's answer and the job stopped before it was asked. Model names only
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        quality.judge_same_model
      </code>
    </td>
    
    <td>
      A sampled judge, or a judge on its anchors, that ran on the model of the work it judged: the agent, the judge's kind (<code>
        sample
      </code>
      
      , <code>
        anchor
      </code>
      
      ), both models, the judge job and the finding it opened or updated; <code>
        warned
      </code>
      
      , since it judged anyway
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        event.*
      </code>
    </td>
    
    <td>
      An <a href="https://docs.oz0.ai/build/parts/event-sources">
        event source's
      </a>
      
       deliveries and where their events went: <code>
        event.delivery
      </code>
      
      , <code>
        event.refused
      </code>
      
      , <code>
        event.failed
      </code>
      
      , <code>
        event.received
      </code>
      
       and <code>
        event.match
      </code>
      
       (see <a href="https://docs.oz0.ai/agents/waiting-for-events#audit">
        Waiting for events
      </a>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        kill.*
      </code>
    </td>
    
    <td>
      A kill switch set, enforced (which jobs it paused or cancelled), released, and the jobs it resumed
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        run.calls
      </code>
    </td>
    
    <td>
      When a job ends: every step of its history with its kind and last attempt, and for each model step the calls its runtime recorded, as SHA-256 digests of what it sent and got (the node's side of the <a href="https://docs.oz0.ai/govern/audit-trail/evidence-bundles#the-two-way-check">
        two-way check
      </a>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        run.tool_intent
      </code>
    </td>
    
    <td>
      <a href="#runtool_intent">
        Details
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        run.origins
      </code>
    </td>
    
    <td>
      <a href="#runorigins">
        Details
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        run.result.withheld
      </code>
    </td>
    
    <td>
      A tool result kept from the agent's model by its <a href="https://docs.oz0.ai/govern/taint-and-labels/label-policies#clearance-per-agent">
        clearance
      </a>
      
       or a <a href="https://docs.oz0.ai/govern/taint-and-labels/label-policies#walls">
        wall
      </a>
      
      : the function, the call, why, and the result's labels; never its text
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        run.labels.joined
      </code>
    </td>
    
    <td>
      A join the job's runtime recorded that is not a tool call's own result (a child's, a colleague's, the board's, a file's, an event's labels) with the workflow's time, which <code>
        oz0 evidence verify
      </code>
      
       bounds later calls by
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        label.source.unlabelled
      </code>
    </td>
    
    <td>
      A function of a declared label source returned a result without a level, which got the tenant's default level (<a href="https://docs.oz0.ai/govern/taint-and-labels/label-policies#label-sources-that-do-not-label">
        label sources that do not label
      </a>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        tenant.sandbox_optout
      </code>
    </td>
    
    <td>
      A node's <a href="https://docs.oz0.ai/fleet/nodes#the-sandbox-opt-out">
        sandbox opt-out
      </a>
      
       set or ended (<code>
        removed
      </code>
      
      ): the node, and the reason and decision of the governance change
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        tenant.label_policies
      </code>
    </td>
    
    <td>
      The tenant's <a href="https://docs.oz0.ai/govern/taint-and-labels/label-policies#label-policies">
        label policies
      </a>
      
       changed: the clearances, walls and egress lists before and after and what loosens; the people only as a count, never who is cleared for what
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        mail.sent
      </code>
    </td>
    
    <td>
      A message a plugin's call sent through the edge (<a href="https://docs.oz0.ai/build/plugins/verified/email#what-is-recorded">
        ai.oz0/email
      </a>
      
      ): the job, the agent, the transport, the Message-ID as the target, how many recipients and their domains, the size and each attachment's SHA-256; never an address, the subject or a body
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        mail.sending
      </code>
      
      , <code>
        mail.unsent
      </code>
    </td>
    
    <td>
      A message the edge is about to give the operator's relay, written before the relay has it, and one the relay then refused: the job, the transport and the Message-ID as the target. A call tried again after a <code>
        mail.sending
      </code>
      
       with neither <code>
        mail.sent
      </code>
      
       nor <code>
        mail.unsent
      </code>
      
       is refused rather than sent twice
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        mail.refused
      </code>
    </td>
    
    <td>
      A message the edge refused for its recipients: the job, how many were refused and their domains
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        candidate_check.requested
      </code>
      
      , <code>
        candidate_check.started
      </code>
      
      , <code>
        candidate_check.finished
      </code>
    </td>
    
    <td>
      A <a href="https://docs.oz0.ai/build/plugins/verified/plugin-improver/job-lifecycle#the-candidate-check">
        candidate check
      </a>
      
       of an agent-made changeset: asked by the job (recorded by the edge, with the check, the target, the installed commit, the bundle's and the patch's digests and how many files), placed (by management: the private candidate, its nodes, the comparisons and their budget) and ended (its state, each agent's verdict, how many problems and widenings). IDs and digests only, never what the change or the runs said
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        change.plugin_update.requested
      </code>
      
      , <code>
        .approved
      </code>
      
      , <code>
        .applied
      </code>
      
      , <code>
        .expired
      </code>
    </td>
    
    <td>
      A <a href="https://docs.oz0.ai/govern/reviewed-changes/proposals#change-requests-a-job-files">
        change request a job filed
      </a>
      
       to update an installed plugin: the filing job and its plugin, who started it and the trigger, whether it was tainted, the plugin and the commit; who approved it, and how applying it went. The job's note is never in the log
    </td>
  </tr>
</tbody>
</table>

### A2A entry contents

These entries record an [outside A2A agent](https://docs.oz0.ai/build/apps/remote-agents/a2a#the-audit-log)'s task as the job saw it: the task started or ended (with its state), each message the job sent it or it sent back, each artifact of its answer.

Each names the outside agent by its card's origin and name, the task, the size and the SHA-256 of what was exchanged, never its content; the first entry of each step also says how the card was checked (`card`: signed, unsigned or unverified); the task's context ID is in the [private part](https://docs.oz0.ai/govern/audit-trail/records#what-a-job-read-or-wrote).

The node records each before the step that made it returns.

### `run.tool_intent`

Written by the edge **before** each tool call is scheduled, and the call is not scheduled until it is: the function, its plugin, the node, and the call's receipt, each sensitive argument's role, floor and origin (as digests, never the value) and the [sequence rules](https://docs.oz0.ai/govern/tool-policy/origins-and-sequences#sequence-rules) that decided it; and `floors`, the floor the tenant's policy sets for each of the call's sensitive arguments as the edge reads it from its own copy of the policy and the plugin's approved listing.

`oz0 evidence verify` holds each verdict to that floor, not to the one the receipt names: a receipt that names a weaker floor than any tool policy in force since the job's tree started set, or leaves out an argument the call was made with that the edge set a floor for, and a call made with such an argument and no receipt, are differences, found by the call's run, activity and `input_sha256` (the digest of its input as the history keeps it), never by a name the node wrote.

`marking` records the function's marked arguments the floors were read with.

The entry of a shadow run's call says `shadow`, and `read_only` when the function is read-only by its snapshot.

A call the exported segment holds no record of is a note, not a difference

### `run.origins`

A job's start as its [origin index](https://docs.oz0.ai/govern/tool-policy/origins-and-sequences#where-arguments-came-from) saw it: whether its input counts as the request, and the digests of its input's and its starter's identity's units; a child's also lists the digests of the entries it inherited from its parent's index (`inherited`).

`run.tool_call` and `run.child_job` carry what each result gave the index (`origin`), where in the job's history the step was (`history`: its run and event IDs), and, for a tool call, the digests of its sensitive arguments as the call was made (`argument_units`); `oz0 evidence verify` rebuilds the index from them, decides every `run.tool_intent`'s verdicts again and compares them with the arguments. Every digest here is keyed per tenant, so a reader of the trail cannot test a guessed value against it

Tool calls, hooks, approvals and outputs come from the job's history, which the edge reads while the job runs and when it ends, so they arrive within seconds of the step. Model calls are recorded as they happen.

A [test approval](https://docs.oz0.ai/agents/approvals/statistics#test-approvals) decided is `approval.test.decide`: the card, who decided, whether it was a miss or a catch, and the catalogue entries it used; it names a job that never existed. Changes of the setting are `tenant.test_approvals` in the tenant's log, with the reason.

Approvals add `run.approval.refused` (a decider the approval's [deciders](https://docs.oz0.ai/agents/approvals/configure#who-decides) did not let decide) and `run.recheck.failed` (a yes that came after a long wait and was refused when [checked again](https://docs.oz0.ai/agents/approvals/security#checked-again-after-a-long-wait)).

### Who started it

Every entry of a job tree says on whose behalf it happened. Since this release, besides its actor (the node that ran a tool, the edge that metered a model call, the person who decided an approval), each entry of a job, of its child jobs and of their delegations carries:

<table>
<thead>
  <tr>
    <th>
      Field
    </th>
    
    <th>
      What it is
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        starter
      </code>
    </td>
    
    <td>
      The identity that started the tree's root job. See <a href="#starter-identities-and-retries">
        starter identities and retries
      </a>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        agent_version
      </code>
    </td>
    
    <td>
      The version of the agent's plugin, such as <code>
        1.2.0@3f9c2a1b7d4e
      </code>
      
      , that the job recorded when it started: an upgrade, a pin or a candidate version never makes an entry name a version the job did not run. Only a job from before versions were recorded gets the version installed when the entry was written
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        plugin
      </code>
    </td>
    
    <td>
      The plugin of the tool an entry is about (tool calls, hooks, approvals of a call), else the agent's
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        tool
      </code>
    </td>
    
    <td>
      The function, such as <code>
        github.merge
      </code>
      
      , of a tool call, a hook or an approval
    </td>
  </tr>
</tbody>
</table>

### Starter identities and retries

The `starter` field names who started the tree's root job: a person (`user:ada`), an app's [API key](https://docs.oz0.ai/build/apps/runtime-api/authentication#authentication) (`apikey:<id>`), an operator (`operator:<name>`), a routine (`routine:<name>`), a flow's webhook (`webhook:<flow>`), an event source (`event:<source>`) or a chat channel's identity.

A child job's entries name its root's starter.

Starting a job with an ID that is already taken never makes you its starter: a call refused because the job runs leaves no trace on it, and a retry that joins someone else's running job is written as `run.start` with `started_by` naming them.

`oz0 audit` shows the starter in its own column, the console's audit page in *Started by* and in each entry's details.

### On whose authority, and at what level

Since version 3 of the entries, each entry of a job tree also names the authority it ran on, as fields of their own (never inside the detail, which an erasure blanks):

<table>
<thead>
  <tr>
    <th>
      Field
    </th>
    
    <th>
      What it is
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        envelope
      </code>
    </td>
    
    <td>
      The hash of the job's <a href="https://docs.oz0.ai/concepts/run-receipts">
        authority envelope
      </a>
      
      : on whose behalf the tree runs, which agent, for what, with which scopes and ceilings, each hop only narrowing the one before. <code>
        oz0 evidence export
      </code>
      
       carries the envelope itself
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        binding
      </code>
    </td>
    
    <td>
      For a model call, a tool call or a harness session, the digest of the call's <a href="https://docs.oz0.ai/concepts/run-receipts/calls-and-charges#calls-bound-to-the-node">
        binding
      </a>
      
      : the job's envelope, its run, the step and its attempt, the function, the digest of the call's arguments and the node that ran it. It is the digest of the binding the edge handed the node for that step, so a tool call's entry and the model calls a harness session made name the same binding; a harness's tool call names its session's
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        level
      </code>
    </td>
    
    <td>
      The autonomy level the job ran at: <code>
        L2
      </code>
      
       for every job until autonomy levels arrive. A child's level is never above its parent's
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        granted_by
      </code>
      
      , <code>
        granted_at
      </code>
    </td>
    
    <td>
      Who granted a level other than the default, and when; empty for the default. It names a person, so erasure and retention blank it like the starter, keeping its salted hash
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        level_policy_version
      </code>
    </td>
    
    <td>
      The version of the policy that set the level: <code>
        builtin:L2/1
      </code>
      
       for the default
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        action_class
      </code>
    </td>
    
    <td>
      The class of the function an entry is about: <code>
        read
      </code>
      
      , <code>
        write
      </code>
      
      , <code>
        destructive
      </code>
      
      , <code>
        egress
      </code>
      
       or a <code>
        host.*
      </code>
      
       class, from its annotations (an unannotated function is <code>
        destructive
      </code>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        labels
      </code>
    </td>
    
    <td>
      The job's sensitivity labels, when the tenant has them
    </td>
  </tr>
</tbody>
</table>

`oz0 audit --json` and the export show them; a version 1 or 2 entry has none.

### Searching by starter

`--starter` finds the entries of the job trees one principal started, across their child jobs:

```bash [Terminal]
oz0 audit --tenant default --starter user:ada --reason "Ticket 4711: a refund nobody approved"
```

It is an investigation tool, not a statistic: it takes a tenant and a reason of at least ten characters, the search itself is written to the audit log first (`audit.search.starter`, with who searched, for whom and why, and whether [works-council mode](https://docs.oz0.ai/govern/works-council-mode) was on), and it returns entries, never counts or totals.

Only the tenant's admins, who may read its audit log, may search.

The starter is a person's identifier in most trees, so searching for it is processing an employee's data; whether your organization needs more than a reason and a record for such searches (a works council's consent, a second person, a narrower group of people allowed to search) is for your policies and your counsel.

The console's audit page offers the same search, with the reason, for one tenant at a time.

```bash [Terminal]
oz0 audit --target job-6d106603-ee3d-422e-a1b0-e17cd8f7be20
oz0 audit --action run.          # every runtime event
oz0 audit --action run.model_call --json
oz0 audit --action egress.denied --target <job>   # what a job's sandboxes were refused
```

### What a job read or wrote

Some entries hold what a job read or wrote: an approval's comment, the error a step or a job failed with (which often quotes what a tool returned).

That part of an entry is its private part. It shows in the log, in `--json` and in exports only to those with the [content permission](https://docs.oz0.ai/getting-started/web-ui/accounts#accounts-and-roles) on the tenant; for everyone else the entry says `withheld`, and the rest of it is there as usual.

Where the tenant's [walls](https://docs.oz0.ai/govern/taint-and-labels/label-policies#walls) apply to the reader, the private part also follows the job's compartments: the edge reads the job's labels for each entry with a private part when it records it and keeps them in the entry's detail as `content_labels` (`unread` when they could not be read then), and the part is withheld when a wall closes those, the entry's own labels, or an entry of a job that kept neither.

A withheld part is still covered by the entry's hashes, and the export's signed head stands in for what those can no longer show (below).

### Content digests

The digests in an entry's detail are of a job's content too: a call's arguments, a result, a file or an exchange. A digest of a short value can confirm a guess of it, so a reader without the content permission reads them keyed: in `audit list` and `audit export`, an incident's export, a tenant's export and an [evidence bundle](https://docs.oz0.ai/govern/audit-trail/evidence-bundles#evidence-bundles), each is an HMAC under a key drawn for that one answer or bundle.

So is an entry's call binding, which is made from its other fields and the digest of the call's arguments.

Equal digests stay equal within the answer and compare with nothing outside it, so a SIEM that follows the export without the permission can match an intent to its call only within one poll.

An entry's ID is the same in every answer, so where it is made with such a digest (a file read's and a tool call's intent) it is made under a key of the tenant's that only the edge holds, and it still tells one read or call from another; entries written before that keep the ID they have, which a guess can be held against.

A digest of something published (a plugin's bundle, an index, advisories, a model's weights) covers no job's content and stays as it is, so it can be compared with what was published.

### Blinded entries and signed exports

An exported entry that keeps anything back, a private part or a digest, is marked `blinded` and has no `salt`, because the salt and a part's hash would confirm a guess of the part as surely as the digest would.

Its hashes stay in the chain and its `content_hash` and links are checked, but its actor, target and detail can no longer be checked against their hashes.

An entry with a call binding is marked `binding_keyed` as well, a redacted one too, and has no `detail_hash`: everything else its `content_hash` covers is in the line, so that hash would otherwise confirm a guess of the binding, and with it of the arguments.

Its `content_hash` cannot be worked out again, only its link to the entries around it, so its sequence, ID, time and action rest on what follows.

What stands in for them is a signature: `audit export` adds a `seal` to every head line, the audit key's signature over the number of blinded entries and the hash of every line since the head before.

`audit verify --file` refuses a blinded entry no such head covers, and any line that is not as it was signed.

It also fails on a file with blinded entries unless you give it `--key` with the fingerprint `oz0 audit key` prints: an edit of a blinded entry leaves the chain's head as it was, so comparing the head shows nothing, and whoever made the edit can sign the file again with a key of their own.

`--unpinned` checks such a file all the same, and shows only that it is whole, not whose it is.

An export with no blinded entries is checked without a key, and carries its signature all the same: only that shows whose it is.

An incident's export and a tenant's export need no seal: their own signature covers the lines, so `incident verify` and `tenant export --verify` likewise fail on blinded entries without `--key`, or `--unpinned`.

A line cut off by a `--follow` export that stopped before its next head is refused too, since nothing has signed it yet.

### When an entry cannot be written

No entry of something a person can see is dropped without a trace. What answers a person with data or a verdict is recorded first: looking a data subject up (`subject.find`) and verifying the chain (`audit.verify`) answer nothing when their entry cannot be written, and say so (`unavailable`: try again).

An entry about a node, a join token or a rollout goes to that object's tenant, so its admins read it; when the object's tenant cannot be read, the entry is not written as the cluster's instead, and the action fails with it.

What the platform does on its own and cannot take back, such as the end of a tenant's move or a rollout's step, stands when its entry cannot be written, and the server logs the whole entry at error level instead, so an alert on the server's error logs finds it.


## Sitemap

See the full [sitemap](https://docs.oz0.ai/sitemap.md) for all pages.
