{
  "slug": "code/authz-spicedb",
  "title": "Authorization with SpiceDB",
  "description": "Relationship-based authorization for Cuitty Code Apps and Registry.",
  "url": "https://cuitty.com/docs/code/authz-spicedb",
  "markdown_url": "https://cuitty.com/docs/code/authz-spicedb.md",
  "json_url": "https://cuitty.com/docs/code/authz-spicedb.json",
  "frontmatter": {
    "title": "Authorization with SpiceDB",
    "description": "Relationship-based authorization for Cuitty Code Apps and Registry.",
    "order": 15,
    "section": "Code",
    "updatedAt": "2026-05-24"
  },
  "headings": [
    {
      "depth": 2,
      "slug": "required-services",
      "text": "Required services"
    },
    {
      "depth": 2,
      "slug": "environment-variables",
      "text": "Environment variables"
    },
    {
      "depth": 2,
      "slug": "schema-and-object-types",
      "text": "Schema and object types"
    },
    {
      "depth": 2,
      "slug": "permissions-model",
      "text": "Permissions model"
    },
    {
      "depth": 2,
      "slug": "outbox-statuses",
      "text": "Outbox statuses"
    },
    {
      "depth": 2,
      "slug": "replay-and-reconciliation",
      "text": "Replay and reconciliation"
    },
    {
      "depth": 2,
      "slug": "failure-modes-and-recovery",
      "text": "Failure modes and recovery"
    },
    {
      "depth": 2,
      "slug": "related-pages",
      "text": "Related pages"
    }
  ],
  "body_markdown": "Cuitty Git uses SpiceDB for relationship-based authorization across repositories, organizations, apps, installs, packages, and registry resources. The database remains the source of metadata, while SpiceDB answers permission checks.\n\n## Required services\n\n- Cuitty Git API.\n- SpiceDB reachable from the API.\n- Database with the `authz_outbox` migration applied.\n- A worker path that drains relationship writes from the outbox.\n\n## Environment variables\n\n```bash\nSPICEDB_ENDPOINT=http://localhost:50051\nSPICEDB_PRESHARED_KEY=dev-secret\nAUTH_ISSUER=http://localhost:7705\nAUTH_CLIENT_ID=cuitty-git\n```\n\nUse TLS and managed secrets for production SpiceDB deployments.\n\n## Schema and object types\n\nLoad the schema before API writes:\n\n```bash\nzed schema write spicedb/schema.zed\n```\n\nRepresentative object types include:\n\n- `user`\n- `organization`\n- `org_invite`\n- `account_slug`\n- `repository`\n- `app_listing`\n- `app_installation`\n- `code_app`\n- `code_app_installation`\n- `code_app_grant`\n- `registry_namespace`\n- `package`\n- `code_registry_namespace`\n- `code_package`\n\nVerify representative permissions with known local data:\n\n```bash\nzed permission check user:alice read repository:acme/demo\nzed permission check user:alice view package:acme/npm/acme-widget\nzed permission check user:alice install app_listing:acme/review-bot\n```\n\n## Permissions model\n\nSecurity-expanding writes, such as creating public visibility, granting install permission, or publishing package access, should fail closed if SpiceDB writes cannot be committed. Security-reducing writes, such as revoking access or making a package private, should continue to be retried until the graph reflects the database state.\n\nPrivate apps and packages may return `404` to callers without view permission. Authenticated callers with partial permission may receive `403` for actions such as publish, install, or settings updates.\n\n## Outbox statuses\n\nAuthorization relationship writes are recorded in `authz_outbox` with these statuses:\n\n- `pending`: queued for dispatch.\n- `processing`: claimed by a dispatcher.\n- `succeeded`: written to SpiceDB.\n- `failed_retryable`: failed but should be retried.\n- `failed_terminal`: failed in a way that needs operator inspection.\n\nInspect stuck rows directly when operational tooling is not available:\n\n```sql\nSELECT id, operation_kind, resource_kind, resource_id, status, attempts, last_error\nFROM authz_outbox\nWHERE status IN ('pending', 'processing', 'failed_retryable', 'failed_terminal')\nORDER BY created_at ASC\nLIMIT 50;\n```\n\n## Replay and reconciliation\n\nReplay retryable relationship writes with the deployment's authz dispatcher or maintenance command. If the deployment exposes only SQL access, mark stale `processing` rows back to `pending` after confirming no dispatcher is running, then restart the API or worker that drains the outbox.\n\n```sql\nUPDATE authz_outbox\nSET status = 'pending'\nWHERE status = 'processing'\n  AND updated_at < datetime('now', '-15 minutes');\n```\n\nAfter replay, spot-check resource permissions with `zed permission check` and compare database visibility against SpiceDB relationships.\n\n## Failure modes and recovery\n\n- SpiceDB unavailable: pause security-expanding app, package, and transfer writes; restore SpiceDB; drain the outbox.\n- Schema not loaded: re-run `zed schema write spicedb/schema.zed` and retry failed rows.\n- Retryable backlog: inspect `last_error`, fix connectivity or schema drift, and replay.\n- Terminal failures: compare `payload_json` with the current schema and repair the row or resource manually.\n\n## Related pages\n\n- [Cuitty Code Apps](/docs/code/apps)\n- [Code Registry](/docs/code/registry)\n- [App execution with Airflow](/docs/code/app-execution-airflow)\n- [Operator runbook](/docs/code/operator-runbook)",
  "body_html": "<p>Cuitty Git uses SpiceDB for relationship-based authorization across repositories, organizations, apps, installs, packages, and registry resources. The database remains the source of metadata, while SpiceDB answers permission checks.</p>\n<h2 id=\"required-services\">Required services</h2>\n<ul>\n<li>Cuitty Git API.</li>\n<li>SpiceDB reachable from the API.</li>\n<li>Database with the <code>authz_outbox</code> migration applied.</li>\n<li>A worker path that drains relationship writes from the outbox.</li>\n</ul>\n<h2 id=\"environment-variables\">Environment variables</h2>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8; overflow-x: auto;\" tabindex=\"0\" data-language=\"bash\"><code><span class=\"line\"><span style=\"color:#E1E4E8\">SPICEDB_ENDPOINT</span><span style=\"color:#F97583\">=</span><span style=\"color:#9ECBFF\">http://localhost:50051</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">SPICEDB_PRESHARED_KEY</span><span style=\"color:#F97583\">=</span><span style=\"color:#9ECBFF\">dev-secret</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">AUTH_ISSUER</span><span style=\"color:#F97583\">=</span><span style=\"color:#9ECBFF\">http://localhost:7705</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">AUTH_CLIENT_ID</span><span style=\"color:#F97583\">=</span><span style=\"color:#9ECBFF\">cuitty-git</span></span></code></pre>\n<p>Use TLS and managed secrets for production SpiceDB deployments.</p>\n<h2 id=\"schema-and-object-types\">Schema and object types</h2>\n<p>Load the schema before API writes:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8; overflow-x: auto;\" tabindex=\"0\" data-language=\"bash\"><code><span class=\"line\"><span style=\"color:#B392F0\">zed</span><span style=\"color:#9ECBFF\"> schema</span><span style=\"color:#9ECBFF\"> write</span><span style=\"color:#9ECBFF\"> spicedb/schema.zed</span></span></code></pre>\n<p>Representative object types include:</p>\n<ul>\n<li><code>user</code></li>\n<li><code>organization</code></li>\n<li><code>org_invite</code></li>\n<li><code>account_slug</code></li>\n<li><code>repository</code></li>\n<li><code>app_listing</code></li>\n<li><code>app_installation</code></li>\n<li><code>code_app</code></li>\n<li><code>code_app_installation</code></li>\n<li><code>code_app_grant</code></li>\n<li><code>registry_namespace</code></li>\n<li><code>package</code></li>\n<li><code>code_registry_namespace</code></li>\n<li><code>code_package</code></li>\n</ul>\n<p>Verify representative permissions with known local data:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8; overflow-x: auto;\" tabindex=\"0\" data-language=\"bash\"><code><span class=\"line\"><span style=\"color:#B392F0\">zed</span><span style=\"color:#9ECBFF\"> permission</span><span style=\"color:#9ECBFF\"> check</span><span style=\"color:#9ECBFF\"> user:alice</span><span style=\"color:#9ECBFF\"> read</span><span style=\"color:#9ECBFF\"> repository:acme/demo</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\">zed</span><span style=\"color:#9ECBFF\"> permission</span><span style=\"color:#9ECBFF\"> check</span><span style=\"color:#9ECBFF\"> user:alice</span><span style=\"color:#9ECBFF\"> view</span><span style=\"color:#9ECBFF\"> package:acme/npm/acme-widget</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\">zed</span><span style=\"color:#9ECBFF\"> permission</span><span style=\"color:#9ECBFF\"> check</span><span style=\"color:#9ECBFF\"> user:alice</span><span style=\"color:#9ECBFF\"> install</span><span style=\"color:#9ECBFF\"> app_listing:acme/review-bot</span></span></code></pre>\n<h2 id=\"permissions-model\">Permissions model</h2>\n<p>Security-expanding writes, such as creating public visibility, granting install permission, or publishing package access, should fail closed if SpiceDB writes cannot be committed. Security-reducing writes, such as revoking access or making a package private, should continue to be retried until the graph reflects the database state.</p>\n<p>Private apps and packages may return <code>404</code> to callers without view permission. Authenticated callers with partial permission may receive <code>403</code> for actions such as publish, install, or settings updates.</p>\n<h2 id=\"outbox-statuses\">Outbox statuses</h2>\n<p>Authorization relationship writes are recorded in <code>authz_outbox</code> with these statuses:</p>\n<ul>\n<li><code>pending</code>: queued for dispatch.</li>\n<li><code>processing</code>: claimed by a dispatcher.</li>\n<li><code>succeeded</code>: written to SpiceDB.</li>\n<li><code>failed_retryable</code>: failed but should be retried.</li>\n<li><code>failed_terminal</code>: failed in a way that needs operator inspection.</li>\n</ul>\n<p>Inspect stuck rows directly when operational tooling is not available:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8; overflow-x: auto;\" tabindex=\"0\" data-language=\"sql\"><code><span class=\"line\"><span style=\"color:#F97583\">SELECT</span><span style=\"color:#E1E4E8\"> id, operation_kind, resource_kind, resource_id, </span><span style=\"color:#F97583\">status</span><span style=\"color:#E1E4E8\">, attempts, last_error</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">FROM</span><span style=\"color:#E1E4E8\"> authz_outbox</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">WHERE</span><span style=\"color:#F97583\"> status</span><span style=\"color:#F97583\"> IN</span><span style=\"color:#E1E4E8\"> (</span><span style=\"color:#9ECBFF\">'pending'</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#9ECBFF\">'processing'</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#9ECBFF\">'failed_retryable'</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#9ECBFF\">'failed_terminal'</span><span style=\"color:#E1E4E8\">)</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">ORDER BY</span><span style=\"color:#E1E4E8\"> created_at </span><span style=\"color:#F97583\">ASC</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">LIMIT</span><span style=\"color:#79B8FF\"> 50</span><span style=\"color:#E1E4E8\">;</span></span></code></pre>\n<h2 id=\"replay-and-reconciliation\">Replay and reconciliation</h2>\n<p>Replay retryable relationship writes with the deployment’s authz dispatcher or maintenance command. If the deployment exposes only SQL access, mark stale <code>processing</code> rows back to <code>pending</code> after confirming no dispatcher is running, then restart the API or worker that drains the outbox.</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8; overflow-x: auto;\" tabindex=\"0\" data-language=\"sql\"><code><span class=\"line\"><span style=\"color:#F97583\">UPDATE</span><span style=\"color:#E1E4E8\"> authz_outbox</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">SET</span><span style=\"color:#F97583\"> status</span><span style=\"color:#F97583\"> =</span><span style=\"color:#9ECBFF\"> 'pending'</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">WHERE</span><span style=\"color:#F97583\"> status</span><span style=\"color:#F97583\"> =</span><span style=\"color:#9ECBFF\"> 'processing'</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">  AND</span><span style=\"color:#E1E4E8\"> updated_at </span><span style=\"color:#F97583\">&#x3C;</span><span style=\"color:#F97583\"> datetime</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">'now'</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#9ECBFF\">'-15 minutes'</span><span style=\"color:#E1E4E8\">);</span></span></code></pre>\n<p>After replay, spot-check resource permissions with <code>zed permission check</code> and compare database visibility against SpiceDB relationships.</p>\n<h2 id=\"failure-modes-and-recovery\">Failure modes and recovery</h2>\n<ul>\n<li>SpiceDB unavailable: pause security-expanding app, package, and transfer writes; restore SpiceDB; drain the outbox.</li>\n<li>Schema not loaded: re-run <code>zed schema write spicedb/schema.zed</code> and retry failed rows.</li>\n<li>Retryable backlog: inspect <code>last_error</code>, fix connectivity or schema drift, and replay.</li>\n<li>Terminal failures: compare <code>payload_json</code> with the current schema and repair the row or resource manually.</li>\n</ul>\n<h2 id=\"related-pages\">Related pages</h2>\n<ul>\n<li><a href=\"/docs/code/apps\">Cuitty Code Apps</a></li>\n<li><a href=\"/docs/code/registry\">Code Registry</a></li>\n<li><a href=\"/docs/code/app-execution-airflow\">App execution with Airflow</a></li>\n<li><a href=\"/docs/code/operator-runbook\">Operator runbook</a></li>\n</ul>",
  "links_out": [
    "/docs/code/apps",
    "/docs/code/registry",
    "/docs/code/app-execution-airflow",
    "/docs/code/operator-runbook"
  ]
}