{
  "slug": "code/operator-runbook",
  "title": "Code Apps and Registry operator runbook",
  "description": "Operate Cuitty Code Apps, Registry, SpiceDB, and Airflow in self-hosted deployments.",
  "url": "https://cuitty.com/docs/code/operator-runbook",
  "markdown_url": "https://cuitty.com/docs/code/operator-runbook.md",
  "json_url": "https://cuitty.com/docs/code/operator-runbook.json",
  "frontmatter": {
    "title": "Code Apps and Registry operator runbook",
    "description": "Operate Cuitty Code Apps, Registry, SpiceDB, and Airflow in self-hosted deployments.",
    "order": 41,
    "section": "Code",
    "updatedAt": "2026-05-24"
  },
  "headings": [
    {
      "depth": 2,
      "slug": "required-services",
      "text": "Required services"
    },
    {
      "depth": 2,
      "slug": "startup-order",
      "text": "Startup order"
    },
    {
      "depth": 2,
      "slug": "environment-variables",
      "text": "Environment variables"
    },
    {
      "depth": 2,
      "slug": "migrations",
      "text": "Migrations"
    },
    {
      "depth": 2,
      "slug": "spicedb-checks",
      "text": "SpiceDB checks"
    },
    {
      "depth": 2,
      "slug": "registry-client-smoke-checks",
      "text": "Registry client smoke checks"
    },
    {
      "depth": 2,
      "slug": "airflow-checks",
      "text": "Airflow checks"
    },
    {
      "depth": 2,
      "slug": "playwright-e2e",
      "text": "Playwright E2E"
    },
    {
      "depth": 2,
      "slug": "failure-modes-and-recovery",
      "text": "Failure modes and recovery"
    },
    {
      "depth": 2,
      "slug": "related-pages",
      "text": "Related pages"
    }
  ],
  "body_markdown": "This runbook covers local and self-hosted operation for Code Apps, App Market, Code Registry, SpiceDB authorization, and Airflow execution.\n\n## Required services\n\n| Service | Purpose | Local default |\n| --- | --- | --- |\n| Cuitty Git API | REST API, git HTTP, app, registry, and callback endpoints | `http://localhost:4351` |\n| Cuitty Git frontend | Browser UI | `http://localhost:4350` |\n| Cuitty Auth | OAuth/OIDC sign-in | `http://localhost:7705` |\n| Database | Persistent metadata and run state | Deployment-specific |\n| SpiceDB | Relationship authorization | `http://localhost:50051` |\n| Airflow | App run orchestration | `http://localhost:8080` |\n| Registry artifact storage | Package tarballs, crates, wheels, and image layers | Deployment-specific |\n| Object storage | Optional backing storage for large artifacts and logs | Deployment-specific |\n\n## Startup order\n\n1. Start the database.\n2. Start SpiceDB.\n3. Load the SpiceDB schema.\n4. Start Cuitty Auth.\n5. Start Airflow.\n6. Run Cuitty Git API migrations, then start the API.\n7. Start the Cuitty Git frontend.\n\n## Environment variables\n\n```bash\nPUBLIC_API_URL=http://localhost:4351\nPUBLIC_CUITTY_AUTH_URL=http://localhost:7705\nAUTH_ISSUER=http://localhost:7705\nAUTH_CLIENT_ID=cuitty-git\nCUITTY_PUBLIC_URL=http://localhost:4350\nCUITTY_GIT_SECRET_KEY=dev-secret-change-me\nSPICEDB_ENDPOINT=http://localhost:50051\nSPICEDB_PRESHARED_KEY=dev-secret\nAIRFLOW_URL=http://localhost:8080\nAIRFLOW_USERNAME=airflow\nAIRFLOW_PASSWORD=airflow\nCUITTY_APP_EXECUTION_MODE=airflow\nCUITTY_APP_RUN_CALLBACK_SECRET=dev-callback-secret\nREGISTRY_STORAGE_URL=file:///var/lib/cuitty/registry\n```\n\n## Migrations\n\nRun migrations before traffic reaches a new API build:\n\n```bash\ncargo run -p git-server -- migrate\n```\n\nVerify the API starts with the expected database URL and no pending migration errors.\n\n## SpiceDB checks\n\n```bash\nzed schema write spicedb/schema.zed\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\nInspect failed authorization relationship writes:\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## Registry client smoke checks\n\n```bash\nnpm config set @acme:registry http://localhost:4351/api/v1/npm/\nnpm publish --registry http://localhost:4351/api/v1/npm/\nnpm install @acme/button --registry http://localhost:4351/api/v1/npm/\n```\n\n```toml\n[registries.cuitty]\nindex = \"sparse+http://localhost:4351/api/v1/cargo/index/\"\n```\n\n```bash\ncargo publish --registry cuitty\ntwine upload --repository-url http://localhost:4351/api/v1/pypi/ dist/*\ndocker push localhost:4351/acme/image:tag\n```\n\n## Airflow checks\n\nSubmit or trigger an app run, then confirm the run has an Airflow DAG ID and DAG run ID. Reconcile submitted runs when callbacks are delayed:\n\n```bash\ncurl -X POST http://localhost:4351/api/v1/apps/runs/reconcile   -H \"Authorization: Bearer $CUITTY_TOKEN\"\n```\n\nCallback failures usually mean the timestamp is stale, the `x-cuitty-signature` HMAC is wrong, or Airflow is signing with a different secret than `CUITTY_APP_RUN_CALLBACK_SECRET`.\n\n## Playwright E2E\n\nFrom the Cuitty Git `tests` directory, run the mocked market, registry, and social specs:\n\n```bash\nbun run test:e2e:apps-registry -- --project=chromium\nbun run test:e2e:apps-registry:headed\n```\n\nThe specs set browser auth state and mock API responses by path, so they cover both direct API calls on `http://localhost:4351/api/v1/...` and frontend-proxied calls on `http://localhost:4350/api/v1/...`.\n\n## Failure modes and recovery\n\n- SpiceDB unavailable: pause security-expanding app, registry, and transfer writes; restore SpiceDB; drain the authz outbox.\n- Authz outbox backlog: inspect `last_error`, reset stale `processing` rows to `pending` only after confirming no dispatcher is active, and restart the dispatcher.\n- Airflow DAG submission failure: check `AIRFLOW_URL`, credentials, DAG ID derivation, API logs, and Airflow scheduler/webserver logs.\n- App run stuck in `submitted` or `running`: poll Airflow for the DAG run ID, check task logs, verify callback delivery, and run reconciliation.\n- npm publish succeeds but artifact is missing: check package version records, artifact storage, API logs, and upload temp directories.\n- Private package returns `404` versus `403`: `404` can be correct when the caller lacks view permission; `403` means the caller can see the resource but lacks the requested operation permission.\n\n## Related pages\n\n- [Self-Hosted Registry](/docs/code/self-hosted-registry)\n- [Cuitty Code Apps](/docs/code/apps)\n- [Code App Market](/docs/code/app-market)\n- [Code Registry](/docs/code/registry)\n- [Authorization with SpiceDB](/docs/code/authz-spicedb)\n- [App execution with Airflow](/docs/code/app-execution-airflow)",
  "body_html": "<p>This runbook covers local and self-hosted operation for Code Apps, App Market, Code Registry, SpiceDB authorization, and Airflow execution.</p>\n<h2 id=\"required-services\">Required services</h2>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<table><thead><tr><th>Service</th><th>Purpose</th><th>Local default</th></tr></thead><tbody><tr><td>Cuitty Git API</td><td>REST API, git HTTP, app, registry, and callback endpoints</td><td><code>http://localhost:4351</code></td></tr><tr><td>Cuitty Git frontend</td><td>Browser UI</td><td><code>http://localhost:4350</code></td></tr><tr><td>Cuitty Auth</td><td>OAuth/OIDC sign-in</td><td><code>http://localhost:7705</code></td></tr><tr><td>Database</td><td>Persistent metadata and run state</td><td>Deployment-specific</td></tr><tr><td>SpiceDB</td><td>Relationship authorization</td><td><code>http://localhost:50051</code></td></tr><tr><td>Airflow</td><td>App run orchestration</td><td><code>http://localhost:8080</code></td></tr><tr><td>Registry artifact storage</td><td>Package tarballs, crates, wheels, and image layers</td><td>Deployment-specific</td></tr><tr><td>Object storage</td><td>Optional backing storage for large artifacts and logs</td><td>Deployment-specific</td></tr></tbody></table>\n<h2 id=\"startup-order\">Startup order</h2>\n<ol>\n<li>Start the database.</li>\n<li>Start SpiceDB.</li>\n<li>Load the SpiceDB schema.</li>\n<li>Start Cuitty Auth.</li>\n<li>Start Airflow.</li>\n<li>Run Cuitty Git API migrations, then start the API.</li>\n<li>Start the Cuitty Git frontend.</li>\n</ol>\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\">PUBLIC_API_URL</span><span style=\"color:#F97583\">=</span><span style=\"color:#9ECBFF\">http://localhost:4351</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">PUBLIC_CUITTY_AUTH_URL</span><span style=\"color:#F97583\">=</span><span style=\"color:#9ECBFF\">http://localhost:7705</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>\n<span class=\"line\"><span style=\"color:#E1E4E8\">CUITTY_PUBLIC_URL</span><span style=\"color:#F97583\">=</span><span style=\"color:#9ECBFF\">http://localhost:4350</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">CUITTY_GIT_SECRET_KEY</span><span style=\"color:#F97583\">=</span><span style=\"color:#9ECBFF\">dev-secret-change-me</span></span>\n<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\">AIRFLOW_URL</span><span style=\"color:#F97583\">=</span><span style=\"color:#9ECBFF\">http://localhost:8080</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">AIRFLOW_USERNAME</span><span style=\"color:#F97583\">=</span><span style=\"color:#9ECBFF\">airflow</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">AIRFLOW_PASSWORD</span><span style=\"color:#F97583\">=</span><span style=\"color:#9ECBFF\">airflow</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">CUITTY_APP_EXECUTION_MODE</span><span style=\"color:#F97583\">=</span><span style=\"color:#9ECBFF\">airflow</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">CUITTY_APP_RUN_CALLBACK_SECRET</span><span style=\"color:#F97583\">=</span><span style=\"color:#9ECBFF\">dev-callback-secret</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">REGISTRY_STORAGE_URL</span><span style=\"color:#F97583\">=</span><span style=\"color:#9ECBFF\">file:///var/lib/cuitty/registry</span></span></code></pre>\n<h2 id=\"migrations\">Migrations</h2>\n<p>Run migrations before traffic reaches a new API build:</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\">cargo</span><span style=\"color:#9ECBFF\"> run</span><span style=\"color:#79B8FF\"> -p</span><span style=\"color:#9ECBFF\"> git-server</span><span style=\"color:#79B8FF\"> --</span><span style=\"color:#9ECBFF\"> migrate</span></span></code></pre>\n<p>Verify the API starts with the expected database URL and no pending migration errors.</p>\n<h2 id=\"spicedb-checks\">SpiceDB checks</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:#B392F0\">zed</span><span style=\"color:#9ECBFF\"> schema</span><span style=\"color:#9ECBFF\"> write</span><span style=\"color:#9ECBFF\"> spicedb/schema.zed</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\"> 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<p>Inspect failed authorization relationship writes:</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=\"registry-client-smoke-checks\">Registry client smoke checks</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:#B392F0\">npm</span><span style=\"color:#9ECBFF\"> config</span><span style=\"color:#9ECBFF\"> set</span><span style=\"color:#9ECBFF\"> @acme:registry</span><span style=\"color:#9ECBFF\"> http://localhost:4351/api/v1/npm/</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\">npm</span><span style=\"color:#9ECBFF\"> publish</span><span style=\"color:#79B8FF\"> --registry</span><span style=\"color:#9ECBFF\"> http://localhost:4351/api/v1/npm/</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\">npm</span><span style=\"color:#9ECBFF\"> install</span><span style=\"color:#9ECBFF\"> @acme/button</span><span style=\"color:#79B8FF\"> --registry</span><span style=\"color:#9ECBFF\"> http://localhost:4351/api/v1/npm/</span></span></code></pre>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8; overflow-x: auto;\" tabindex=\"0\" data-language=\"toml\"><code><span class=\"line\"><span style=\"color:#E1E4E8\">[</span><span style=\"color:#B392F0\">registries</span><span style=\"color:#E1E4E8\">.</span><span style=\"color:#B392F0\">cuitty</span><span style=\"color:#E1E4E8\">]</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">index = </span><span style=\"color:#9ECBFF\">\"sparse+http://localhost:4351/api/v1/cargo/index/\"</span></span></code></pre>\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\">cargo</span><span style=\"color:#9ECBFF\"> publish</span><span style=\"color:#79B8FF\"> --registry</span><span style=\"color:#9ECBFF\"> cuitty</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\">twine</span><span style=\"color:#9ECBFF\"> upload</span><span style=\"color:#79B8FF\"> --repository-url</span><span style=\"color:#9ECBFF\"> http://localhost:4351/api/v1/pypi/</span><span style=\"color:#9ECBFF\"> dist/</span><span style=\"color:#79B8FF\">*</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\">docker</span><span style=\"color:#9ECBFF\"> push</span><span style=\"color:#9ECBFF\"> localhost:4351/acme/image:tag</span></span></code></pre>\n<h2 id=\"airflow-checks\">Airflow checks</h2>\n<p>Submit or trigger an app run, then confirm the run has an Airflow DAG ID and DAG run ID. Reconcile submitted runs when callbacks are delayed:</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\">curl</span><span style=\"color:#79B8FF\"> -X</span><span style=\"color:#9ECBFF\"> POST</span><span style=\"color:#9ECBFF\"> http://localhost:4351/api/v1/apps/runs/reconcile</span><span style=\"color:#79B8FF\">   -H</span><span style=\"color:#9ECBFF\"> \"Authorization: Bearer </span><span style=\"color:#E1E4E8\">$CUITTY_TOKEN</span><span style=\"color:#9ECBFF\">\"</span></span></code></pre>\n<p>Callback failures usually mean the timestamp is stale, the <code>x-cuitty-signature</code> HMAC is wrong, or Airflow is signing with a different secret than <code>CUITTY_APP_RUN_CALLBACK_SECRET</code>.</p>\n<h2 id=\"playwright-e2e\">Playwright E2E</h2>\n<p>From the Cuitty Git <code>tests</code> directory, run the mocked market, registry, and social specs:</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\">bun</span><span style=\"color:#9ECBFF\"> run</span><span style=\"color:#9ECBFF\"> test:e2e:apps-registry</span><span style=\"color:#79B8FF\"> --</span><span style=\"color:#79B8FF\"> --project=chromium</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\">bun</span><span style=\"color:#9ECBFF\"> run</span><span style=\"color:#9ECBFF\"> test:e2e:apps-registry:headed</span></span></code></pre>\n<p>The specs set browser auth state and mock API responses by path, so they cover both direct API calls on <code>http://localhost:4351/api/v1/...</code> and frontend-proxied calls on <code>http://localhost:4350/api/v1/...</code>.</p>\n<h2 id=\"failure-modes-and-recovery\">Failure modes and recovery</h2>\n<ul>\n<li>SpiceDB unavailable: pause security-expanding app, registry, and transfer writes; restore SpiceDB; drain the authz outbox.</li>\n<li>Authz outbox backlog: inspect <code>last_error</code>, reset stale <code>processing</code> rows to <code>pending</code> only after confirming no dispatcher is active, and restart the dispatcher.</li>\n<li>Airflow DAG submission failure: check <code>AIRFLOW_URL</code>, credentials, DAG ID derivation, API logs, and Airflow scheduler/webserver logs.</li>\n<li>App run stuck in <code>submitted</code> or <code>running</code>: poll Airflow for the DAG run ID, check task logs, verify callback delivery, and run reconciliation.</li>\n<li>npm publish succeeds but artifact is missing: check package version records, artifact storage, API logs, and upload temp directories.</li>\n<li>Private package returns <code>404</code> versus <code>403</code>: <code>404</code> can be correct when the caller lacks view permission; <code>403</code> means the caller can see the resource but lacks the requested operation permission.</li>\n</ul>\n<h2 id=\"related-pages\">Related pages</h2>\n<ul>\n<li><a href=\"/docs/code/self-hosted-registry\">Self-Hosted Registry</a></li>\n<li><a href=\"/docs/code/apps\">Cuitty Code Apps</a></li>\n<li><a href=\"/docs/code/app-market\">Code App Market</a></li>\n<li><a href=\"/docs/code/registry\">Code Registry</a></li>\n<li><a href=\"/docs/code/authz-spicedb\">Authorization with SpiceDB</a></li>\n<li><a href=\"/docs/code/app-execution-airflow\">App execution with Airflow</a></li>\n</ul>",
  "links_out": [
    "/docs/code/self-hosted-registry",
    "/docs/code/apps",
    "/docs/code/app-market",
    "/docs/code/registry",
    "/docs/code/authz-spicedb",
    "/docs/code/app-execution-airflow"
  ]
}