Yuwakisa Cloud — architecture

As of 2026-10-05

Everything runs on one Cloudflare account under the yuwakisa.com zone. Two independent monorepos, auth/ (identity) and jsonsolver/ (an SMT constraint-solving service), share only the auth service: jsonsolver never stores credentials, it verifies tokens that auth/ issues.

Cloudflare provides the edge (TLS and routing); auth/ provides identity. There is no Cloudflare Access anywhere.

Topology

auth/ monorepo jsonsolver/ monorepo static sites (outside both monorepos) auth-admin Worker user and client management console no signing key, mints no tokens bskyoidc Worker “Sign in with Bluesky” OIDC provider multi-tenant; AT Protocol OAuth to the PDS auth Worker OIDC: /authorize /signin /token /.well-known/* holds AUTH_SIGNING_KEY (Ed25519) admin Worker OAuth via auth/; proxy to api /mcp/* /reports/* stub: not yet implemented api Worker customer API: /v1/jobs, /v1/rulesets hosts DOs PerJsonsolverClientGate, PerJobActor solver Worker queue consumer, no hostname hosts SolverContainer DO Container: Node 22 + Z3 zero Cloudflare bindings Pages project yuwakisaweb marketing site, git-deployed site Worker (assets only) this page auth-admin.yuwakisa.com HTTPS auth-bsky.yuwakisa.com HTTPS auth.yuwakisa.com HTTPS jsonsolver-admin.yuwakisa.com HTTPS jsonsolver-api.yuwakisa.com HTTPS yuwakisa.com HTTPS cloud.yuwakisa.com HTTPS BSKYOIDC_SERVICE AUTH_SERVICE API_SERVICE AUTH_SERVICE Bluesky PDS user’s server, AT Protocol D1 auth shared by 3 Workers D1 jsonsolver jobs, rulesets, app_users R2 buckets payloads results rulesets logs (Logpush target) api: all three buckets admin: rulesets only Queue jobs api produces, solver consumes Analytics Engine events written by api, solver, admin AT Protocol OAuth enqueue consume Legend public HTTPS service binding (internal) storage binding not yet implemented
Eight Workers-and-sites sit behind public hostnames; auth/ and jsonsolver/ share only the auth service. Workers reach each other through service bindings, never over the public internet. Dashed lines are service bindings (Worker to Worker, internal). Storage lines are bindings to D1, R2 and the queue; every Worker that touches Analytics Engine writes to the one dataset, so it is shown as a chip without edges. The Cloudflare Pages project and the site Worker are independent of everything else.

Key: the Cloudflare pieces

Box colors in the diagrams: auth/   jsonsolver/   storage   external or static site.

Worker
A small server-side program that runs on Cloudflare's network on each request. No servers to manage; it starts in milliseconds and runs close to the user. Every box above that says "Worker" is one.
Durable Object (DO)
A single named instance of a class (one per job, one per jsonsolver client) with its own private storage and a timer ("alarm"). All requests for that name go to the same instance, one at a time, so it is a safe place to keep counters and run a multi-step process.
Container
A regular Linux process image (here Node 22 + the Z3 solver) that a Durable Object starts on demand. Used for the heavy compute a Worker can't do.
D1
A SQLite database. Holds the structured records: users, sessions, OAuth clients, jobs, rulesets.
R2
File/blob storage (like Amazon S3), organised in buckets. Holds the bulky bytes: job payloads, results, ruleset source.
Queue
A message queue. The api Worker drops a job message on it; the solver Worker is handed each message and picks it up.
Analytics Engine
A write-heavy event log for metrics, queried later with SQL. Workers write events into it; nothing reads it on the request path.
Service binding
A direct, private call from one Worker to another inside Cloudflare. It never goes over the public internet, and nothing outside can use it.
Custom domain / route
Attaches a hostname (e.g. auth.yuwakisa.com) to a Worker. Cloudflare handles DNS and the TLS certificate.
Pages / static assets
Hosting for plain HTML, CSS and JS files. Pages deploys from a Git repo; a Worker can also serve files directly (that's how this page is served).

Signing in

either: email and password or: Sign in with Bluesky Browser jsonsolver-admin auth bskyoidc Bluesky PDS 1 open the app 2 redirect to auth 3 GET /authorize + PKCE challenge 4 no session: redirect to /signin 5a email + password (argon2id); users are admin-created 5b “Sign in with Bluesky” 6 OIDC (service binding) 7 AT Protocol OAuth 8 verified identity 9 create session, issue code, redirect to /auth/callback 10 GET /auth/callback?code=… code + PKCE verifier 11 POST /token 12 ID token + access token 13 sets its own session service binding HTTPS via the browser
Sign-in is OAuth 2 Authorization Code with PKCE: the browser proves who the user is to auth, and jsonsolver-admin only ever receives a one-time code it exchanges for tokens. Browser hops for the Bluesky leg (browser to bskyoidc, browser to the PDS) are omitted. The whole exchange stays between auth and the browser until step 11, where admin trades the code for tokens.

Token formats

ID token: a JWT signed with EdDSA (Ed25519).

Access token: 01.<base64url(64-byte Ed25519 signature ‖ CBOR claims)>. The claims are integer-keyed CBOR: iss, sub, aud, exp, iat, kid, scope (keys 1 to 7). 01 is the version, which is the rotation lever.

Consumers verify with the public key published at /.well-known/jwks.json, so no secret is shared. The admin scope is granted only to the single is_admin user.

Solving a job

api Worker solver Worker PerJobActor.runSolve, in the api Worker’s Durable Object 1 Client POST /v1/jobs Bearer access token 2 Verify token auth-client, cached JWKS JIT-provision app_users sub → jsonsolver_client_id 3 Client gate DO PerJsonsolverClientGate: per-jsonsolver-client cap 4 Store input payload to R2 payloads job row to D1 jsonsolver 5 Enqueue message to queue jobs enqueue 6 queue() handler calls PerJobActor.acceptJob(jobId), then ACKs at once max_retries 0: no queue retry acceptJob(jobId) a Fetch inputs payload + ruleset source from R2, in parallel b Container solve SolverContainer.solve() compile MIAS, run Z3 (cached by SHA-256) c Write result to R2 results d D1 commit THE commit point do not move or retry e DO self-transition PerJobActor updates its own state f Release slot PerJsonsolverClientGate .release g Delete payload removes input from R2 payloads Safety net DO deadline alarm: 30 s (SOLVE_DEADLINE_MS). After the queue ACK it is the only recovery path for an in-flight failure.
A job is accepted at the api Worker, handed to a Durable Object by the queue consumer, and becomes real only at one step: the D1 commit. Arrows follow the numbers, then the letters. After the commit the client polls GET /v1/jobs/:id and receives status plus, for a finished job, the model or unsat core read from R2 results.

Who can touch what

ComponentD1R2QueueDurable ObjectsService bindingsSecrets
authauth———to bskyoidc (BSKYOIDC_SERVICE)AUTH_SIGNING_KEY
bskyoidcauth————holds auth’s public key; own AT Protocol client identity
auth-adminauth only———nonenone; no signing key
api (and its DOs)jsonsolverpayloads, results, rulesetsproducerhosts PerJsonsolverClientGate, PerJobActor; binds SolverContainerto auth (AUTH_SERVICE)—
solvernonenoneconsumerhosts SolverContainer; binds PerJobActor, PerJsonsolverClientGate (only calls acceptJob)——
ContainerNothing. Zero Cloudflare bindings: pure compute over bytes.
adminjsonsolverrulesets only (not payloads or results)——to auth (AUTH_SERVICE), to api (API_SERVICE)—
siteStatic assets only.

The design goal is that “what can this component touch?” is answerable by inspection: a compromised admin Worker cannot reach customer payloads or results.

Status: not done yet

Deploys

GitHub Actions deploy auth/ and jsonsolver/ on push to main, gated by a repository variable. The site Worker deploys manually with just deploy from site/. Cloudflare creates the DNS records and certificates for the custom domains.