Worker architecture

What this layer does

Phase 4 turns skills.snappy.ai into the only ingress to DO Spaces. The Worker authenticates SNAPPY_MASTER_KEY, derives tenant_id, enforces tenant prefixes on every write, and serves the catalog, detail pages, docs, install one-liner, status page, and changelog. Joe machines never hold DO credentials. KV is a 60s edge cache, not the source of truth.

Files involved

scheduled handlers.

invite + /_install?skills= subset.

triggers.

Route table

RoutePurpose
GET /Catalog grid
GET /<skill>Per-skill detail
GET /docs and /docs/<page>Wiki rendering
GET /_statusPublic health JSON + HTML
GET /installBare bootstrapper
GET /install/<inviteCode>Bootstrapper with key baked in
GET /_install?skills=foo,barSubset install
GET /_search?q=Catalog search
POST /_pushJoe ingress, tenant-prefix enforced
GET /_pullManifest diff + stream
POST /_alertFailure tally
GET /changelog.json and /changelog.atomFeeds
GET /.well-known/skills/...Direct file serve

Scheduled handlers

async scheduled(controller, env, ctx) {
  if (controller.cron === "* * * * *")     await runQuorumPromotion(env);
  if (controller.cron === "*/5 * * * *")   { await rebuildCatalog(env); await syncSkoolThreads(env); }
}

Single writer per task. Per-machine push no longer touches _catalog.json; concurrent pushes from different runtimes can't clobber it.

KV cache strategy

KV bindings: SKILLS_STORE, API_KEYS, SETTINGS_STORE, INVITES, ALERTS, STATUS_LOG. SKILLS_STORE caches DO reads with 60s TTL. On miss the Worker fetches DO Spaces directly and re-populates KV. KV is never the source of truth — losing it triggers a re-population on the next request, no data loss.

DO outbound creds

DO_SPACES_KEY and DO_SPACES_SECRET live as Wrangler secrets, read only inside the Worker process. The 24h grace window for rotation reads DO_SPACES_KEY_NEW first, falls back to the old pair. See secrets-rotation.md.

Operational gotchas

tenant_id; recompute from sha256(SNAPPY_MASTER_KEY).first(12) on every request.

SHA. Client must re-pull, re-apply, re-push.

state/lint/worker-bundle-size.ts warns >500 KB, fails >1 MB.

Auth only on _push, _pull, _alert, and tier-gated detail.

How to verify it's working

state/lint/worker-bundle-size.ts exit 0.