# Approvals, receipts, and verification

This page is for operators and anyone deciding whether to trust agent work in
Company OS: when work pauses for a human, what evidence an action leaves, and how
to check that evidence yourself.

Related pages: [CLI reference](./CLI_REFERENCE.md),
[connecting agents](./CONNECT_AGENTS.md), [Console guide](./CONSOLE_GUIDE.md).

In the commands below, `companyos` is the CLI binary. The CLI's own usage text
prints the same commands as `company-os`. Every command accepts
`--company-id ID` to name the company explicitly.

## 1. Why approvals exist

Some actions are costly or hard to undo, so Company OS pauses them until a
person decides. A fluent agent answer is not proof that an action was allowed or
that it worked.

### Which actions pause

The production write policy (`PRODUCTION_READINESS_POLICIES.md`) states the
default: agents work in **dry-run shadow** mode. They may inspect, plan, and
validate, but they may not perform production writes until a workflow has an
explicit mutation policy and an evidence trail.

Human approval is required for actions that are non-reversible, high-value,
customer-facing, or permission-changing. These are blocked until an explicit
policy exists: delete, bulk update, external payment or purchase, customer email
send, credential or permission change, and cross-tenant data move.

Other pause points in the sources:

- Agent-proposed email replies stay `pending_approval` for a separate human
  verifier; Company OS does not send them (`services/company-os-mcp-server/README.md`).
- Learned autonomy (`HITL_AND_SKILL_PROMOTION_MOAT_CONTRACT.md`): a missing or
  incomplete decision shape always pauses. A learned `auto` preference cannot
  authorize a different resource, scope, value band, policy revision, or a higher
  risk band. `legacy-unbound` preference rows never authorize autonomous proceed.

### Who may decide

- `company_agents.role` is a job title; only `owner` and `admin` carry authority
  (`src/company_os/governance_doc.rs`).
- Some actions are **member-reserved**: only an active human owner may take them;
  an admin gets `403 member_reserved_owner_required` and a refusal receipt. They
  include granting owner or admin, changing an owner, changing a model agent's
  `adapter_type` or model config, granting a root delegation, minting a token
  that acts as another owner, lifting a malfunction flag, and deciding an
  approval whose `request_payload.required_authority` is `"owner"`
  (`MEMBER_RESERVED_AUTHORITY_AND_AGENT_AUTO_SUSPEND.md`). That document says no
  approval producer declares that value yet.
- "Active human owner": an active `owner` `company_agents` row with no model
  adapter, or an active `owner` `company_human_memberships` row.
- An approval cannot be used if it was decided by its own requester
  (`approvalBindingProblem` in `services/company-os-mcp-server/src/client.ts`).

### What is not approval

- A model-supplied `confirm=true` is not human approval
  (`services/company-os-mcp-server/README.md`).
- `--confirm` on a CLI command is not an approval by itself. API principal,
  idempotency, spend, settlement, and Gate checks still apply (CLI usage text in
  `src/cli.ts`).
- MCP `strict` mode (the default) returns `approval_required` for mutating tools
  unless the caller passes `confirm=true` after explicit operator approval.
  `permissive` is for trusted local automation only. The remote Grok bridge
  always uses strict.
- In `bound` approval mode (defined in `client.ts`, not described in the package
  README), a mutating call must carry one approval id that is `approved`, has a
  deciding principal, was not decided by its requester, is unconsumed, and is
  unexpired. The id is cleared after one use. A model-driven MCP session cannot
  decide approvals in this mode; a human uses the Console or CLI.
- The desktop app does not manufacture approval locally
  (`COMPANY_OS_START_HERE.md`).

## 2. Lifecycle of an approval

```text
requested -> shown -> decided (approved | rejected) -> executed, or not
```

1. **Requested.** Policy pauses the work. An approval row is created with risk
   domain, risk level, action type, subject, `requested_by`, optional
   `expires_at`, and a `request_payload`.
2. **Shown.** It appears in the company's inbox. Rows with a decision envelope
   carry subject, presentation, and request digests plus a
   `presentation_revision`, describing what the approver was shown.
3. **Decided.** A person approves or rejects; `decided_by` and `decided_at` are
   recorded.
4. **Executed or not.** In `bound` mode an approved, unconsumed, unexpired
   approval authorizes one action. A rejected, expired, or consumed one cannot.

### Digest-bound decisions

For a row with a decision envelope, the CLI reads the authoritative inbox
first, then submits your decision together with the server-stored
`subject_digest`, `presentation_digest`, `request_digest`, and
`presentation_revision`. It never accepts a caller-invented approval projection.
If the stored envelope is incomplete, the command fails rather than guessing.

**`legacy_unverifiable`** applies to approval rows, not receipts. It classifies
rows created before the decision envelope existed. Their `decision_contract`
reports `verification_supported: false` because no digests exist to bind a
decision to; Company OS cannot prove what the approver was shown. The CLI sends
digests only for rows with an envelope, so a decision on such a row is not
digest-bound by the CLI. Whether the server accepts it is not stated in the
sources.

### Where to decide

| Place | How |
| --- | --- |
| Console | Open **Approvals** (see the [Console guide](./CONSOLE_GUIDE.md)). |
| CLI | `companyos approval approve ID --confirm` or `companyos approval reject ID --confirm` |
| Tern palette | "Open Company OS approvals waiting on you" only lists pending approvals; decide in the Console or CLI (`integrations/tern-companyos/README.md`). |

### CLI commands

```bash
# List the inbox (filter by state, page with --limit and --offset)
companyos approvals --status pending

# Inspect one approval before deciding
companyos approval show APPROVAL_ID

# Decide (all three forms require --confirm)
companyos approval approve APPROVAL_ID --reason "why" --confirm
companyos approval reject  APPROVAL_ID --reason "why" --confirm
companyos approval decide  APPROVAL_ID --decision approved|rejected --reason "why" --confirm
```

`--reason` is optional. `--dry-run` runs the real handler path, including the
authoritative reads, and prints the planned request instead of sending it; no
`--confirm` is needed. Run `approval show` first: the decision binds to the
digests the server stored, not to what you remember seeing.

## 3. Receipts

A receipt records what happened during important work, read from the company
ledger and scoped to the authenticated company. Refusals are receipted too, never
silent (`src/company_os/governance_doc.rs`).

### Receipt id families

Ids are family-qualified so `receipt show` resolves the exact tenant-scoped row.

| Prefix | Meaning | Verifiable |
| --- | --- | --- |
| `terminal:` | Terminal receipt for a task or run outcome. Carries `sequence`, `terminal_generation`, `previous_hash`, `receipt_hash`. | Yes |
| `run_event:` | Run execution event. | No |
| `governance:` | Governance event, such as an approval decision or a refusal. | No |
| `spend:` | Spend event. | No |

The list code also emits `review_delivery:`; the package README names only the
four above.

### List, show, verify

```bash
companyos receipt list
companyos receipt show   terminal:RECEIPT_UUID
companyos receipt verify terminal:RECEIPT_UUID
companyos verify task TASK_ID
companyos verify run  RUN_ID
```

### What `receipt verify` checks

For a `terminal:` receipt, the server recomputes the database-owned canonical
hash and checks both chain linkage and the stored company head. The response
(`hsm.company_os.receipt_verification.v1`) includes:

- `verified`: true only if `hash_matches` (stored equals recomputed hash),
  `previous_link_matches` (`previous_hash` equals the expected previous hash),
  and `company_chain.valid` all hold.
- `evidence`: `sequence`, `stored_hash`, `computed_hash`, `previous_hash`,
  `expected_previous_hash`, and `company_chain` (`valid`, `checked_count`,
  `first_bad_sequence`, `expected_head_hash`).

### What `verification_supported=false` means

Timeline families without a digest contract (`run_event:`, `governance:`,
`spend:`) return `verification_supported=false`. They are readable evidence, but
they never claim to be verified. The MCP presentation layer reports this as
`not_available` ("This result family does not expose a verification contract").
`not_available` is not a failure and it is not a pass.

A successful tool call is never treated as verification. The result envelope
(`hsm.company_os.result.v1`) says `not_available` unless the canonical payload
explicitly reports verification.

### `verify task` and `verify run`

These replay the immutable lifecycle history and compare it with the live
projection, using the same authenticated company principal.

- Task responses (`hsm.company_os.task_verification.v1`) include `verified`,
  `has_drift`, `drift_kind`, and `aggregate_versions` for state, metadata,
  context, operational, and run snapshot, plus redacted evidence for each.
- Run responses (`hsm.company_os.run_verification.v1`) include `verified`,
  `has_drift`, `drift_kind`, `aggregate_version`, and `evidence.lifecycle`.
- `verified` is `!has_drift`. Missing history or a replay failure is never
  reported as verified.

## 4. Auto-suspension and safety rails for agents

Source: `MEMBER_RESERVED_AUTHORITY_AND_AGENT_AUTO_SUSPEND.md`. Rules live in
`src/company_os/agent_oversight.rs`.

A model-driven principal is suspended, fail closed, while either condition holds:

- `oversight_vacant`: the company has no active human owner.
- `agent_malfunction_flagged`: an unlifted row in
  `company_agent_malfunction_flags` names the agent.

- Suspension is derived on every read. It is never written into agent status.
  A read failure refuses.
- Human principals are never suspended, so an owner can always act.
- Every refusal is `423 Locked` with `code: "agent_suspended"` and the
  `reason_code` above.
- Enforcement points and their receipts:

| Check point | Receipt |
| --- | --- |
| Worker dispatch | `task_events.dispatch_refused` |
| Tool calls inside a run (live read for the run's agent) | task-trail `tool_denied` |
| Principal-token mutations | `governance_events.company_mutation_refused` |
| Signed run-capability `authorize` | `governance_events.run_capability_action_refused` |

- Settlement of an already-authorized run-capability action stays open, so its
  outcome is still recorded.

Flagging and lifting:

```text
GET  /api/company/companies/:company_id/agents/:agent_id/suspension
POST /api/company/companies/:company_id/agents/:agent_id/suspension        {"reason": "..."}
POST /api/company/companies/:company_id/agents/:agent_id/suspension/lift   {"reason": "..."}
```

- Any active human owner or admin may flag a model-backed agent. Repeating the
  flag while one is open is idempotent. Flags cannot be raised on human rows.
- Only an owner may lift. A lifted flag is final; a new incident is a new flag.
- Flag rows are evidence: the database refuses any rewrite except the single
  lift, and the runtime role has no `DELETE`.

Related rails:

- Delegation enforcement is on by default; a live principal token is not
  authority without a scoped delegation from an owner or admin
  (`src/company_os/governance_doc.rs`).
- With no active human owner, an active human admin may install a human
  (adapter-less) agent as owner, and nothing else. With no usable owner or admin
  credential, use the existing root-transport owner recovery.

## 5. FAQ

**Is `--confirm` the same as approving?**
No. `--confirm` acknowledges a mutating CLI command, and the other server checks
still apply. To decide an approval, use `companyos approval approve|reject ID
--confirm` as an authenticated principal.

**Can an agent approve its own request?**
Not through the checked paths. A model-supplied `confirm=true` is not human
approval; in `bound` mode a model-driven MCP session cannot decide approvals;
an approval decided by its requester cannot authorize an action; and
`required_authority: "owner"` approvals refuse an admin.

**A receipt says `verification_supported=false`. Is something wrong?**
No. That family (`run_event:`, `governance:`, `spend:`) has no digest contract.
Use the `terminal:` receipt, `verify task`, or `verify run` instead.

**`receipt verify` returned `verified: false`. What now?**
Read `evidence`: `hash_matches`, `previous_link_matches`, and
`company_chain.first_bad_sequence` show which check failed. The sources do not
describe a repair procedure.

**Why was my agent refused with 423?**
`code: "agent_suspended"`: either `oversight_vacant` (no active human owner) or
`agent_malfunction_flagged`. Restore an owner, or have an owner lift the flag.
