Binding
Who acts on each exec and which credentials the runtime mints for it, so a pack reaches a protected system without ever holding a secret.
A pack declares the kind of credential its commands need, and the runtime's hub mints it at exec time for the Principal who asked. The pack never sees, stores or ships a secret, and a failed mint refuses the command instead of falling back to a token exported in the shell (L4). Pack authors choose a mode below; runtime builders wire the hub and the Principal.
pack declares binding.mode = user
│
runtime hub mints delegated credential for this Principal
│
adapter receives it as env or file the wrap bin already understandsModes#
A pack declares one mode, so two faces of a CLI that need different modes are two packs (L9).
| Mode | Credential | Runtime expectation | Typical pack |
|---|---|---|---|
public | None, and connect is skipped | The Principal is still attributed | Public read (transit) |
user | Delegated user credential (OAuth, user token) | Minted for this Principal | Linked channel (mail, calendar) |
local | Machine-local credential hub (dev, appliance, personal workspace) | The Principal is the machine's user | Gated write (ledger) |
mothership | Operator or fleet identity | Elevation grant for any non-operator Principal | Control plane, as a second pack |
mock | Explicit mock in the exec envelope | Never ambient; env-flag mocks fail closed in sandbox | Tests, paper mode |
Hubs#
The hub is where the credential lives, and the pack names only its kind:
hub | Meaning |
|---|---|
keyring | A product credential service |
local-credential | A machine-local store |
public | No credential |
none | Pack needs none and declares so |
The protocol requires only that the hub mints fail-closed and that Invokers mint through it. Its name, token storage and refresh are runtime details with no HCP type.
Families#
Related packs share one login by declaring the same family, so a user who
connected mail is not asked again for calendar on the same account.
"binding": { "mode": "user", "hub": "keyring", "family": "office" }Connecting one pack connects the whole family for that Principal with one
consent, and linking several packs unions their delegated scopes. A family is a
manifest field, so packs stay flat under packs/<id>/.
Connect#
Establishing a Binding is itself a command (connect, or a setup.* step) with
operation: write. It is gated by pack.connect, separate from the
verb.exec.write grant for daily writes, because sending mail must not imply
re-linking the mailbox. Domain CLIs that split control-plane link from runtime
send should keep two grants as well.
Health#
A Binding degrades when a refresh token expires or a provider revokes consent.
Every pack with a user, local or mothership Binding should then announce
the Sense trigger binding.degraded, which the runtime's
hub usually produces, with one state:
linked | stale | needs_reauth | pending | errorA human then runs connect. Health commands (doctor, status) report the
state and never mint a long-lived secret as a side effect.
Principal#
The Principal is who acts when a command runs: human (an authenticated person
or profile), service (an API key or automation), agent (acting on behalf of
a human or service) or mothership (operator or fleet identity). The kinds are
informative. The runtime establishes exactly one Principal per exec from the
authenticated session, records it and evaluates grants for it, so the exec
envelope never carries one and packs never embed, name or allow-list one.
The tenant, the organisation or workspace boundary, comes from the same session
and never from a pack argument. A command whose privacy.land is brain also
needs a project, the shared work context (lands).
Arguments refine within that scope, so --entity acme-eu may select a linked
account under the tenant but never the tenant itself, or one user could read
another's data.
The Principal answers who asked for this exec, and Binding answers which
credentials may be minted for this pack, mode and family. They stay separate
columns in the audit tuple, since one string for
both hides the classic elevation bug: an end-user Principal reaches an operator
Binding because a mothership bin was on PATH. A runtime must refuse that
pairing without an explicit elevation grant.
The reference SDK takes the Principal and its scope at creation:
const hcp = await Hcp.create({
packDirs,
principal: { id: 'user_42', kind: 'human' },
tenant_id: 'acme',
project_id: 'proj_7', // needed for any brain-land command
grants,
})Its default authoriser denies a brain command when project_id is null:
{ "ok": false, "error": "denied: privacy.land brain requires project_id" }What stays out of the pack#
- Tokens, refresh secrets, client ids, key files
- Tenant ids and account allow-lists
- The hub's table names, provider SDK calls, or refresh schedule