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.
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#
{
"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_idrequiredstringMust equal the manifest id. Legacy name for pack_id.
version1default 1Policy file version. 1 for HCP/1.x.
verbsrequiredRecord<string, PolicyEntry>One entry per command key in the manifest. Coverage is checked both ways.
stimuliRecord<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.
confirmboolean | "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#
| Ask | If yes | Examples |
|---|---|---|
| Would the caller need to undo anything afterwards? | write | send, create, setup.apply |
| Does it only inspect, list, validate, or report, even for a prospective write? | read | list, 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 runtime | connect |
Land#
| Land | Data belongs to | Runtime also checks |
|---|---|---|
personal | One actor: a personal mailbox, the user's own artifacts | The Principal's own scope |
brain | A shared project or team: team artifacts | Project membership, with project_id set; tenant membership alone is not enough |
local | A local workspace directory (cwd jail), not a tenant. HCP/1.1 | The 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[].
{
"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.enableA 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.