# Cloudhal: Architecture decisions

- [ADR-0001: JSON Schema is the contract source; TypeScript and Rust are generated](https://cloudhal.dev/docs/adr/0001-contract-pipeline.md): Cloudhal ships in TypeScript first and moves the core to Rust later. Both must agree on Project/Site/Page/Section/ContentEntry/Snapshot/SkillManifest without hand-maintained duplicates.
- [ADR-0002: Thin SQL/blob driver interface, batch-only, two backends](https://cloudhal.dev/docs/adr/0002-storage-interface.md): The self-host promise is real only if the same code runs on Cloudflare (D1/R2/KV) and on a laptop (SQLite + filesystem).
- [ADR-0003: Studio is a Vite React SPA on Kumo; Astro only renders sites](https://cloudhal.dev/docs/adr/0003-studio-architecture.md): The initial plan proposed Astro + React islands for Studio with Kumo UI. Verified today: React context does not cross Astro islands (Kumo's portal/toast/command-palette providers cannot be shared), children from .astro…
- [ADR-0004: Stateless MCP server (spec 2026-07-28) with layered approval](https://cloudhal.dev/docs/adr/0004-mcp-server.md): MCP spec 2026-07-28 removed sessions and initialize; every request is self-describing, elicitation became multi-round-trip (inputrequired + retry with inputResponses), SSE is deprecated, Dynamic Client Registration is…
- [ADR-0005: Skills follow the Agent Skills spec; Cloudhal data lives in metadata](https://cloudhal.dev/docs/adr/0005-skills-format.md): The Agent Skills specification (agentskills.io) defines exactly six frontmatter fields (name, description, license, compatibility, metadata, allowed-tools); metadata is a string-to-string map.
- [ADR-0006: Node-first toolchain and runtime; Bun supported, not primary](https://cloudhal.dev/docs/adr/0006-toolchain-and-runtime.md): Cloudhal must run on Cloudflare (workerd) and self-hosted. Wrangler, miniflare and the Vite plugin are officially Node-only and have open Bun hangs; Bun 1.4 is a weeks-old Rust rewrite that mis-reports its Node ABI.
- [ADR-0007: Apache-2.0 open core, DCO, Contributor Covenant 3.0](https://cloudhal.dev/docs/adr/0007-license-and-community.md): An open-source launch needs the license, contribution terms and conduct policy settled before the first external PR; changing them later breaks trust.
- [ADR-0008: Precompiled schema validators; D1 tested through Miniflare](https://cloudhal.dev/docs/adr/0008-runtime-validation-and-d1-testing.md): Agent mutations must be validated against the contracts at the write boundary, in Node and on Cloudflare Workers. Ajv compiles schemas with new Function, which Workers forbid.
- [ADR-0009: Renderer reads live nodes in dev and a snapshot in builds; starters own routes](https://cloudhal.dev/docs/adr/0009-renderer-sources-and-local-dev.md): The renderer must serve two situations with one code path: an author previewing drafts locally, and a production build that must be reproducible from an approved snapshot.
- [ADR-0010: Better Auth for human identity, tenants and (later) MCP authorization](https://cloudhal.dev/docs/adr/0010-auth-better-auth.md): Cloudhal needs user authentication for Studio, a tenant/role model, non-interactive credentials for agents, and — per MCP 2026-07-28 — an OAuth 2.1 authorization server whose tokens the MCP server verifies.
- [ADR-0011: Agent layer — policy engine, audit, skills and the MCP server](https://cloudhal.dev/docs/adr/0011-agent-layer.md): Sprint 5 makes Cloudhal usable by agents: an MCP client must be able to create content, see a preview, and ask to publish — with publishing stopping at a human.
- [ADR-0012: Cloudflare deployment — two Workers, pointer-flip publishing, Workers Cache](https://cloudhal.dev/docs/adr/0012-cloudflare-deployment.md): Sprint 6 puts a Cloudhal site on Cloudflare. Publishing and rollback must be instant and must not need a build server: agents publish through MCP, and an approved publish should be live on the next request.
- [ADR-0013: Publishing to npm — sources in the monorepo, dist in the tarball](https://cloudhal.dev/docs/adr/0013-npm-packaging.md): Every package exports its TypeScript sources so the monorepo needs no build step (Node 24 strips types, Vite/wrangler/Vitest read TS).
- [ADR-0014: Approvals happen in Studio, once, for the state the person saw](https://cloudhal.dev/docs/adr/0014-out-of-band-approvals.md): ADR-0004/0011 approved gated calls (publish, rollback, delete) through MCP elicitation, with a signed confirmToken for clients that cannot elicit. The review confirmed two flaws.
- [ADR-0015: Agent modes — how independently an agent works](https://cloudhal.dev/docs/adr/0015-agent-modes.md): Owners chose raw scopes when creating an agent key. Scopes say what an agent may touch, not how independently it works — the question an owner actually has, and the one IDE agents answer with permission modes (plan,…
- [ADR-0016: Time-boxed elevation — a more autonomous mode, for a while](https://cloudhal.dev/docs/adr/0016-time-boxed-elevation.md): Owners pick a mode per key (ADR-0015). Often they want more autonomy only for a while: "let the newsroom agent publish small updates on its own during launch day", "let it edit freely for the next hour while I watch".
- [ADR-0017: Taxonomy and redirects are graph nodes; content references terms by id](https://cloudhal.dev/docs/adr/0017-taxonomy-and-redirects.md): Taxonomy was one field: ContentEntry.tags, up to 50 free slugs. Tags had no name, description or archive; there were no categories, no hierarchy and no primary term.
- [ADR-0018: Staff roles are scope sets; agent keys never exceed their creator; invites are one-time links](https://cloudhal.dev/docs/adr/0018-roles-and-invites.md): Every signed-in person had scopes, and sign-up closed after the owner, so a team could not be added safely. The scopes model already treats people and agents alike, and agent keys, modes, elevations and approvals…
- [ADR-0019: Menus are Menu nodes holding a tree of id-referenced items](https://cloudhal.dev/docs/adr/0019-navigation-menus.md): The header was derived from top-level pages. There was no editor, no footer menu, and no tree that a docs sidebar could reuse. Links typed as URLs break when slugs change.
- [ADR-0020: Assets are content-addressed nodes with SHA-256; uploads are SSRF-safe; scopes asset:upload / asset:manage](https://cloudhal.dev/docs/adr/0020-assets-and-media.md): Images were URLs typed by hand. There was no upload, no library, and no way to serve responsive images. Scholarly and data sites (the third vertical) need checksums and external storage for large files.
- [ADR-0021: Site analytics — first-party cookie-less script, Workers Analytics Engine via bindings, D1 rollups, analytics:read](https://cloudhal.dev/docs/adr/0021-site-analytics.md): Editors and agents cannot see what readers read. Most page views on a deployed site are served from Workers Cache, where the site Worker does not run, so counting in the Worker sees only cache misses.
- [ADR-0022: Reader comments — guest comments held for moderation, D1 outside the graph, first-party proof of work, agents label and hide while people approve](https://cloudhal.dev/docs/adr/0022-reader-comments.md): Sites have no way for readers to respond. Comments are the first free-form visitor text Cloudhal stores and the first that reaches agents, so they bring spam, privacy and prompt-injection risk.
- [ADR-0023: Schedules — content releases pinned to exact versions, render-time time gates, one every-minute tick, deterministic jobs plus MCP agent tasks](https://cloudhal.dev/docs/adr/0023-schedules.md): Editors need to publish at a set time, keep content under embargo, take campaigns down on a date and run recurring checks; agents need a safe way to propose these and to do recurring work unattended.
- [ADR-0024: Snapshot bundles — the site Worker loads a published snapshot from a few shard objects](https://cloudhal.dev/docs/adr/0024-snapshot-bundles.md): The site Worker renders a published snapshot. Its node bodies live in R2 as nodes/<hash>.json, and SnapshotSource read them one get each on the first render of a snapshot in an isolate: after every cold start and after…
- [ADR-0025: Email delivery — one MailSender on the Cloudflare Email Service binding](https://cloudhal.dev/docs/adr/0025-email-delivery.md): MailSender has existed since RFC-0005 without an implementation, so invites were copied by hand and only an Administrator could reset a password; an Owner who forgot theirs had nobody to ask.
- [ADR-0026: HTML bodies are sanitized at render time](https://cloudhal.dev/docs/adr/0026-html-body-sanitizer.md): Content bodies and rich-text sections can be markdown or html. Markdown has always been rendered as data. HTML was published exactly as stored, on the assumption that only humans write it.
- [ADR-0027: cloudhal.json settings for observability and placement](https://cloudhal.dev/docs/adr/0027-observability-and-placement.md): Both Workers logged every request, with no way to sample short of editing generated files that the next deploy overwrites. Traces could not be turned on. The admin Worker makes several D1 round trips per request.
- [ADR-0028: Body blocks and body operations](https://cloudhal.dev/docs/adr/0028-body-blocks-and-operations.md): People. Bodies were edited in a monospace textarea. Studio now has a rich editor that keeps markdown canonical (phase 1).
- [ADR-0029: In-editor AI on Workers AI, model chosen per site](https://cloudhal.dev/docs/adr/0029-ai-commands-and-model-choice.md): What editors lack. Since RFC-0014 they have a real editor, block ids and proposals, but no help with the text itself.
- [ADR-0030: One block registry for page sections and bodies](https://cloudhal.dev/docs/adr/0030-block-registry.md): Page sections are a closed set of four kinds, and markdown bodies have no typed parts. Editors want callouts, buttons, videos and FAQs, and agents can only describe them in prose.
- [ADR-0031: A generator that writes data — theme settings, draft plans, Kumo catalog panels](https://cloudhal.dev/docs/adr/0031-theme-and-ui-generator.md): Starting a site and giving it a look is still manual, and agents have no typed handle on design. Changing the look means editing CSS, which is code.
- [ADR-0032: Studio messages are an ICU MessageFormat 1 subset, compiled to data at build time](https://cloudhal.dev/docs/adr/0032-icu-messages.md): Studio's catalogs knew only {name} substitution. English's two plural forms were coded by hand (46 …One key pairs, 26 count ternaries), and grammar was handled by passing several forms of a word.
- [ADR-0033: Right-to-left Studio — logical properties, dir from the registry, a Kumo patch until upstream](https://cloudhal.dev/docs/adr/0033-rtl-layout.md): No right-to-left language (Arabic, Hebrew, Persian, Urdu) can be added to Studio yet. DESIGN.md §1 lists RTL as a prerequisite. Measured on main at 7b1ec23 with node scripts/i18n-audit.ts:
- [ADR-0034: Translation workflow — catalogs stay in the repo, ICU JSON exchange, Weblate self-hosted (or Tolgee), AI drafts reviewed by people](https://cloudhal.dev/docs/adr/0034-translation-platform.md): Studio's catalogs are TypeScript modules: apps/studio/src/i18n/<locale>.ts and lazy/<ns>.<locale>.ts. - English is the source and the type.
- [ADR-0035: Published content in several languages — one node per locale in translation groups, locale in the URL, gated agent translation, Site = publication](https://cloudhal.dev/docs/adr/0035-multilingual-publishing.md): Pages, entries, terms, menus and assets already carry a locale or per-locale labels, but only the default locale is served and nothing links translations.
- [ADR-0036: Multi-site installations — one Studio, one D1/R2, many isolated sites served by hostname](https://cloudhal.dev/docs/adr/0036-multi-site-installations.md): Every Cloudhal site is a whole installation today: a site Worker, an admin Worker (Studio), a D1 and an R2 for one CLOUDHALSITEID. A second site means a second Studio, login, team and deploy.
- [ADR-0037: Domain strategy: cloudhal.com and cloudhal.dev](https://cloudhal.dev/docs/adr/0037-domain-strategy.md): Cloudhal owns exactly two domains, cloudhal.com and cloudhal.dev, and will not buy a third. The repo already uses https://cloudhal.dev/schemas/v1/ as JSON Schema $id, https://cloudhal.dev/docs, the bot User-Agent page…
- [ADR-0038: Public playground: one SQLite Durable Object per visitor, store and renderer inside it, deny-list guard with a route walk](https://cloudhal.dev/docs/adr/0038-public-playground.md): Trying Cloudhal needs Node, wrangler and a Cloudflare account. RFC-0036 proposes try.cloudhal.com (ADR-0037): every visitor gets a private, disposable Cloudhal (Studio, API and rendered site) with no account.
- [ADR-0039: cloudhal.dev: Starlight over the repo's docs, a link contract checked in CI, docs for agents](https://cloudhal.dev/docs/adr/0039-cloudhal-dev-docs-site.md): ADR-0037 makes cloudhal.dev the developer domain and the only copy of the docs. Packages, cloudhal.com, the schemas' $ids and the fetcher's User-Agent already link there.
- [Architecture Decision Records](https://cloudhal.dev/docs/adr.md): One file per decision, numbered, immutable once accepted (write a new ADR to supersede). Template:
