.md

Policy

Every policy.json field, how to choose each command's operation, land and confirm gate, and the coverage rules lint enforces against the manifest.

On this page

Policy states what each command and each trigger does: its side-effect class, where its data may land, and whether a write must ask first. Who may cause it is the runtime's business (Authorization), and what runs without asking is the user's or operator's call (Access). Write one row per command and per trigger in policy.json, choosing each value as below.

Policy file#

policy.json
{
  "surface_id": "mail",
  "version": 1,
  "verbs": {
    "mail.list":    { "operation": "read",  "privacy": { "land": "personal" } },
    "mail.get":     { "operation": "read",  "privacy": { "land": "personal" } },
    "mail.send":    { "operation": "write", "privacy": { "land": "personal" }, "confirm": true },
    "mail.connect": { "operation": "write", "privacy": { "land": "personal" } },
    "notes.create": { "operation": "write", "privacy": { "land": "brain" } }
  },
  "stimuli": {
    "new_item":         { "operation": "read", "privacy": { "land": "personal" } },
    "binding.degraded": { "operation": "read", "privacy": { "land": "personal" } }
  }
}
surface_idrequired
string

Must equal the manifest id. Legacy name for pack_id.

version
1default 1

Policy file version. 1 for HCP/1.x.

verbsrequired
Record<string, PolicyEntry>

One entry per command key in the manifest. Coverage is checked both ways.

stimuli
Record<string, PolicyEntry>default {}

One entry per stimulus ref in the manifest. Empty object if none.

Each entry, for a command or a trigger, has these fields:

operationrequired
'read' | 'write'

Side-effect class.

privacy.landrequired
'personal' | 'brain' | 'local'

Where data or side effects may land.

confirm
boolean | "always"

Writes only. true: the command asks before it runs, unless the user's access mode is run. "always": it asks in every mode; only an exact allow rule written by the user pre-approves it. See Access.

Unknown fields and unknown operation or land tokens fail parse. A trigger is always read, since Sense cannot write, but its land still decides where the sink records it.

Operation#

AskIf yesExamples
Would the caller need to undo anything afterwards?writesend, create, setup.apply
Does it only inspect, list, validate, or report, even for a prospective write?readlist, status, validate, doctor
Is it a dry-run of a write?Still write, and it must not persist
Does it establish or refresh a Binding?write, and it needs pack.connect at runtimeconnect

Land#

LandData belongs toRuntime also checks
personalOne actor: a personal mailbox, the user's own artifactsThe Principal's own scope
brainA shared project or team: team artifactsProject membership, with project_id set; tenant membership alone is not enough
localA local workspace directory (cwd jail), not a tenant. HCP/1.1The sandbox's working directory

Products map brain to whatever they call a shared work context, and packs never invent a fourth land. Choose by where the data belongs, never by adapter or Binding mode, since a local Binding pack can have personal land and a user Binding pack brain land. Arguments like --entity or --workspace refine accounts within Binding scope and never select the tenant, which comes from the authenticated Principal's session (L5).

Confirm#

Set confirm: true on any write that must not fire from a hallucinated turn, such as transfers, sends to external parties, deletes, publishes and anything with cost. Leave it off idempotent writes an agent should retry, such as setup.apply, sync and categorize. Use confirm: "always" for the few commands that must never run unattended, even in access mode run: payments, irreversible deletes, anything that leaves the company. Users can still pre-approve those with an exact allow rule and limits (Access).

The runtime enforces the gate before any Adapter runs. The JSON envelope must carry confirm: true (or apply: true), and the CLI face takes --confirm or --apply in its cli[].

JSON
{
  "ok": false,
  "pack_id": "ledger",
  "verb_key": "transfer",
  "code": "confirm_required",
  "error": "confirm required",
  "preview": {
    "command": "transfer",
    "args": { "amount": "2.80", "payee": "demo-transit" },
    "cli": "ledger transfer 2.80 --to demo-transit --confirm"
  },
  "hint": "Nothing ran. Re-run with confirm=true only if the user has approved this exact action with these values …"
}

No Invoker can skip it: a UI shows an approval, an agent shows the user the preview and waits, and a person at the terminal types the flag. error is the conformance string, and the reference runtime adds code, preview and hint. An agent may pass confirm=true without asking again only when the user's own words approve that exact action with those values ("pay 2.80 to Demo Transit"). "Pay the fare" names no amount, so the agent shows the preview first.

Coverage rules#

Lint fails when a manifest command or stimulus ref has no Policy row, or when Policy names a key the manifest lacks:

verb "transfer" missing from policy.json          # manifest has it, Policy lacks it
policy verb "refund" not in pack manifest         # Policy has it, manifest lacks it
stimulus "new_item" missing from policy.json      # declared trigger without a row
policy stimulus "old_event" not in stimulusTriggers
policy surface_id "ledgr" !== "ledger"

How grants scale Policy#

Policy says what a command does, and capability grants say who may run it.

Policy operation  ──►  required capability
read              ──►  verb.exec.read
write             ──►  verb.exec.write
(connect command) ──►  pack.connect
(trigger)         ──►  sense.enable

A runtime may grant per pack_id or per verb_key. A multi-tenant product must not collapse read, write and connect into one boolean, because sending mail must never imply re-linking the mailbox.

What stays out#

Roles, user lists, allow-lists, tenant ids, API keys, rate limits, schedules and host paths stay out. Every runtime, UI and author must understand each knob Policy carries, so it keeps two axes and one gate: a runtime with serious RBAC layers its rules over these rows without the pack knowing, and a runtime with none still enforces the confirm gate.