{"protocol":"clank-doc/1","frameworkVersion":"0.19.5","slug":"governance","title":"Governance, approvals, entitlements, and feature flags","description":"Clank uses one data only policy vocabulary for browser users, agents, services, hosted limits, and staged feature delivery. Evaluation is deterministic and deny by default.","group":{"id":"agents","title":"Agents and generation"},"url":"https://docs.clank.run/docs/governance","source":"docs/governance.md","headings":["Agent action approval","Typed feature delivery","CLI evaluation"],"tableOfContents":[{"id":"agent-action-approval","title":"Agent action approval","level":2},{"id":"typed-feature-delivery","title":"Typed feature delivery","level":2},{"id":"cli-evaluation","title":"CLI evaluation","level":2}],"markdown":"# Governance, approvals, entitlements, and feature flags\n\nClank uses one data-only policy vocabulary for browser users, agents, services, hosted limits,\nand staged feature delivery. Evaluation is deterministic and deny-by-default.\n\n```ts\nimport {\n  defineGovernancePolicy,\n  entitlement,\n  evaluateFeatureFlag,\n  evaluatePolicy,\n} from \"@clank.run/framework/governance\";\n\nconst policy = defineGovernancePolicy({\n  revision: \"workspace-18\",\n  rules: [\n    {\n      id: \"agents-delete-production\",\n      actions: [\"todos.delete\"],\n      principalKinds: [\"agent\"],\n      resource: \"production:*\",\n      effect: \"approval\",\n      approvalTtlMs: 5 * 60_000,\n    },\n    {\n      id: \"members-write\",\n      actions: [\"todos.*\"],\n      roles: [\"member\"],\n      effect: \"allow\",\n    },\n  ],\n  entitlements: [\n    { key: \"projects\", limit: 10 },\n    { key: \"custom-domains\", limit: true },\n  ],\n  flags: [{\n    key: \"new-board\",\n    enabled: true,\n    default: \"classic\",\n    variants: [{ name: \"new\", weight: 2_500, value: \"new\" }],\n    allowRoles: [\"operator\"],\n  }],\n});\n\nconst decision = evaluatePolicy(policy, {\n  action: \"todos.delete\",\n  resource: \"production:todos\",\n  principal: { id: \"codex\", kind: \"agent\", roles: [\"member\"] },\n});\n\nconst projectLimit = entitlement(policy, \"projects\");\nconst board = evaluateFeatureFlag(policy, \"new-board\", {\n  subject: \"workspace_4\",\n});\n```\n\nRules use exact actions or a trailing wildcard. They can select roles, principal kinds, resource\npatterns, and exact request attributes. Rules are first-match, so put specific deny or approval\nrules before broader allows. Policies reject unknown fields, duplicate IDs, invalid schedules,\nambiguous variant totals, and non-JSON values.\n\n## Agent action approval\n\n`issueApproval()` creates a short-lived HMAC grant bound to an action, principal, resource, rule,\nand policy revision. `verifyApproval()` checks those bindings, lifetime, signature, and an optional\nused-nonce set. Store a consumed nonce transactionally to enforce one-time use. Use a dedicated\nrandom secret of at least 32 bytes; never reuse a session key or store it in policy JSON.\n\n```ts\nconst grant = await issueApproval({\n  policy,\n  request,\n  approvedBy: signedInUser.id,\n  secret: process.env.APPROVAL_HMAC_KEY!,\n});\n\nconst accepted = await verifyApproval({\n  grant,\n  policy,\n  request,\n  secret: process.env.APPROVAL_HMAC_KEY!,\n  usedNonces,\n});\n```\n\n## Typed feature delivery\n\nFlags can be disabled, scheduled, targeted, or assigned to weighted variants. A stable hash of\npolicy revision, flag key, and subject keeps assignment consistent across servers. Weights use\n10,000 basis points; unallocated traffic receives the declared default. Every evaluation records\nits variant and reason for audits and revision traces.\n\n## CLI evaluation\n\n```sh\nclank workbench policy policy.json todos.delete \\\n  --principal=codex --kind=agent --roles=member \\\n  --resource=production:todos --json\n\nclank workbench flag policy.json new-board \\\n  --subject=workspace_4 --json\n```\n\nThe workbench reads bounded JSON files and prints protocol-versioned output for humans, agents,\nand CI.\n"}