0008. A staging twin of the Diagrams service, declared beside production in the same root
Status: accepted (owner, 4 November 2026; the diagrams-staging change)
Context
Section titled “Context”Meltemi Diagrams’ change agent-mcp adds an MCP endpoint, an OAuth authorization server and
server-drawn previews to its hosted service, and its specification counts the delivery only once
thirty acceptance criteria pass on staging and on production, with a controlled test project.
Until now each Worker of the family had one environment: Diagrams’ deploy took CI-approved main
straight to production, and nothing ran a suite against a host that was not production. Every
Worker, database, bucket, domain, secret and GitHub environment of the family is declared in this
repository’s packages/infra (decision 0004), so the twin is declared here
too, and so is the KV namespace per environment that the authorization server keeps its state in.
Decision
Section titled “Decision”- A twin per environment, in the same Terraform root. Two more entries of
local.workers,meltemi-diagrams-stagingatstaging.diagrams.meltemi.devandmeltemi-diagrams-publish-stagingatstaging.meltemi.page, with noworkers.devand no preview address, and their own D1 databasemeltemi-diagrams-staging, R2 bucketmeltemi-diagrams-staging-blobsand KV namespacemeltemi-diagrams-oauth-staging; production gainsmeltemi-diagrams-oauth. Flat resources, as every other one here: no module for two entries. A precondition refuses two Workers with one name or one host, and the plan test refuses a staging database, bucket or namespace with a production name. - Staged like the portal. The staging Custom Domains wait for
staging_deployed: the apply that declares the Workers leaves itfalse, Diagrams’ deploy uploads their first versions, and a commit then sets ittrue. - One identity service, one Store. Staging signs in through
id.meltemi.devlike any host under the cookie’s domain. Its generated secrets are its own,FORMS_SECRET_STAGINGand the events secret namedEVENTS_SECRET_DIAGRAMS_STAGINGfor the identity service andIDENTITY_EVENTS_SECRET_STAGINGfor the Worker, so nothing signed for one environment is accepted by the other;ADMIN_SUBSandSPONSORS_WEBHOOK_SECRETare shared (the owner administers both, and the one Sponsors webhook delivers to production). Staging’s deploy binds the suffixed names through its environment’sSECRETS_SUFFIX. The identity service lists the productdiagrams-staging, so deleting an account reaches staging too. - The
stagingenvironment of the Diagrams repository, deployable frommainonly, holds the same deploy token asproductionand the names production’s deploy reads, with staging’s values (CLOUDFLARE_ACCOUNT_ID,D1_DATABASE_ID,SECRETS_STORE_ID), plusOAUTH_KV_ID,R2_BUCKET_NAMEandSECRETS_SUFFIX(_STAGING). Production gainsOAUTH_KV_IDandR2_BUCKET_NAME; its suffix is empty, which GitHub reads from a variable that is not set. - The acceptance suite’s tokens are received variables.
mcp_test_tokens(sensitive, empty by default) holds themdg_tokens of two test accounts per environment; the infrastructure workflows pass it from four secrets of theinfrastructureenvironment, one per token so that each is masked on its own, and Terraform writes each present one asMELTEMI_TOKENorMELTEMI_TOKEN_2of its environment. Nothing is set by hand in the Diagrams repository. - No new token permission. Cloudflare’s Workers authorization says that deploying a Worker
needs no separate permission on the resources it binds, and Wrangler (4.151.3) looks up an R2
bucket at deploy (the reason for “Workers R2 Storage Read”) but never a KV namespace whose
idthe configuration gives. The Diagrams deploy token keeps its groups.
Alternatives considered
Section titled “Alternatives considered”- A Terraform module instantiated twice: one more layer for two entries, and the tests read flat resources.
- A second Secrets Store for staging: an account holds one, and the pattern is one per account.
- A staging identity service: a second issuer, keys and OAuth apps for a twin that only the suite and the owner use; the one service already serves two products.
- Wrangler environments (
env.stagingin Diagrams’wrangler.jsonc): names and ids would be written in the product repository, where today none is. - Tokens set by hand in GitHub: nothing is configured from a machine (decision 0004).
- Data copied from production: staging holds only what the suite creates and deletes.
Consequences
Section titled “Consequences”- The Free plan’s limits are the account’s, shared by both environments: D1 ten databases (three used), 500 MB each, 5 GB in all, five million rows read and 100,000 written a day; KV 100,000 reads and 1,000 writes a day, 1 GB stored; R2 10 GB-month, one million Class A and ten million Class B operations a month; SQLite Durable Objects 100,000 requests and 13,000 GB-s a day, 5 GB; Workers 100,000 requests a day and 100 Workers (eight declared). A staging that only the suite and the owner use stays far below them.
staging.meltemi.pageserves what the suite publishes under test names, which the suite deletes; the host is linked from nowhere.- The identity service’s binding of the staging events secret and its product entry land in a second pull request, once the staging Workers answer: before then the Store secret it binds would not exist, and its deletion events would go to a host that does not answer.
- Diagrams’ deploy (its change
agent-mcp) reads thestagingenvironment and writes its configuration from those names; the rollout gains a seventh step, the commit that setsstaging_deployed.