Concepts

Everything in Writ is built from three objects. Understand these and the rest of the product is just operations on them.

Principal

A principal is who is acting, and on whose behalf: a human sponsor, an agent, a task, and a purpose — bound together. It is not a shared service account. svc-support-admin can do anything; a principal can only do the thing its sponsor approved, for the purpose stated.

Two credentials represent the two halves:

CredentialLooks likeHeld byUnlocks
API keywrit_...the agent / tenantchecks, receipts, policy reads
Sponsor tokenwrit_sp_...the humangrants, revokes, reinstate

The API key says which tenant is asking. The sponsor token says a human approved this. The sponsor token is shown once at tenant creation and never recoverable — if it leaks, the tenant’s human side is compromised. It can also come from the WRIT_SPONSOR_TOKEN environment variable.

Decision

A decision is Writ’s answer at the moment of the write: ALLOW, DENY, or STEP_UP. It is made by POST /v1/check, which matches the request against the tenant’s policy and any live grants on five fields — sponsor, agent, verb, target, purpose. All five must match. Anything else is DENY.

  • ALLOW mints a 90-second auth token bound to the exact write: this verb, this target, this purpose, this policy version. The agent performs the write, then verifies the token against what it actually did with POST /v1/tokens/verify. The grant is single-use semantics: it cannot be reused for a different write.
  • DENY means the write must not run. A receipt is still written — the refusal itself is auditable.
  • STEP_UP means a human sponsor must approve. The agent stops and tells the user. The sponsor mints a one-time grant (dashboard or POST /v1/grants with the sponsor token), and the agent calls POST /v1/check again.

Receipt

A receipt is the audit record of a decision — ALLOW, DENY, STEP_UP, REVOKE, all of them. Receipts are hash-chained (prevHash / chainHash, HMAC-SHA256 keyed by the deploy secret), so altering a stored receipt breaks the chain detectably. They are tenant-isolated: one tenant’s receipts are never visible to another.

The receipt is the product’s answer to the auditor. Instead of “send us the CloudTrail,” the compliance owner opens the receipt: who, what, which purpose, which policy version, when. See Audit log for streaming, SIEM ingestion, and chain verification.

Policy modes

Each verb in a tenant has a mode, set with ./writ policy --set <verb> <mode> or the dashboard policy editor:

ModeMeaning
allowChecks on this verb return ALLOW when the principal is valid
denyChecks always return DENY (with a receipt)
require_grantALLOW only with a live sponsor grant
step_upReturns STEP_UP until a sponsor approves

Start strict (deny or step_up) on consequential verbs, relax as the agent earns trust. Policy changes take effect immediately.

Grants

A grant is a sponsor’s one-time approval for a specific write: sponsor, agent, verb, target, purpose, 90-second lifetime. Minted in the dashboard or with ./writ grant --sponsor-token .... The agent’s next POST /v1/check with matching fields returns ALLOW. Grants are how STEP_UP resolves — the human stays in the loop without becoming the bottleneck for every write.

Kill switch

A sponsor can revoke a principal at any time (POST /v1/revoke with the sponsor token, or the dashboard). Every later check for that sponsor and agent is DENY — even under an allow policy, even with a live grant. The revocation itself writes a receipt. POST /v1/reinstate undoes it.

Use it when an agent misbehaves, a credential leaks, or a purpose drifts. Revocation is per-tenant: one tenant’s sponsor can never touch another tenant’s principals.

Tenants and teams

One tenant = one isolation boundary: its own keys, policy, receipts, and chain. A tenant starts with one member (whoever created it). Members are added with invite codes (./writ invite), each getting their own API key; the sponsor token stays with the tenant creator. Members share the tenant’s policy and receipts.