Skip to navigation

Concepts

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

What to protect first

Start with the five categories where one bad agent action is expensive:

  1. Money movement — payments.refund, billing.charge, payouts.create
  2. Identity and permissions — iam.grant_admin, users.promote, tokens.issue
  3. Customer data changes — users.delete, records.update, crm.modify
  4. External communications — email.send, sms.send, webhooks.fire
  5. Infrastructure changes — aws.terminate, dns.update, deploy.promote

One bad call in any of these categories is expensive and hard to undo. Start here, expand by verb.

Writ sits on top of your existing infrastructure — Okta, AWS IAM, Stripe, your API gateway. It does not replace them. It governs the actions your agents take through 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. STEP_UP also pages the tenant’s account email with the receipt id and a deep link to the portal grants page — at most one notification per write per hour. Opt out of email with notify_step_up_email = false on the tenant row, or route STEP_UPs to a generic JSON or Slack webhook with step_up_webhook_url and step_up_webhook_type.

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. The policy is layered — a principal’s override beats the environment override, which beats the tenant default. See Policies.

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.