.md

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.

On this page

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 understands

Modes#

A pack declares one mode, so two faces of a CLI that need different modes are two packs (L9).

ModeCredentialRuntime expectationTypical pack
publicNone, and connect is skippedThe Principal is still attributedPublic read (transit)
userDelegated user credential (OAuth, user token)Minted for this PrincipalLinked channel (mail, calendar)
localMachine-local credential hub (dev, appliance, personal workspace)The Principal is the machine's userGated write (ledger)
mothershipOperator or fleet identityElevation grant for any non-operator PrincipalControl plane, as a second pack
mockExplicit mock in the exec envelopeNever ambient; env-flag mocks fail closed in sandboxTests, paper mode

Hubs#

The hub is where the credential lives, and the pack names only its kind:

hubMeaning
keyringA product credential service
local-credentialA machine-local store
publicNo credential
nonePack 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.

JSON
"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 | error

A 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:

TypeScript
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:

JSON
{ "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