docs(deploy): a runbook that describes the system as it is (18b) master
Production could not boot. `stringVar` errors in production when a variable is unset, `AP_USER_ORIGIN` is read through it, and prod compose set none of the v2 environment — so today's compose applied to today's HEAD stops at startup with "config: AP_USER_ORIGIN is required in production". Failing closed is correct and stays; the fix is that the compose file now carries the variables the binary actually requires. A trailing slash is also fatal, since CanonicalizeOrigin refuses any path, and `https://coves.social/` is the spelling most people would type. The governing rule for this pass was to document what the code does rather than what the plan intended, and applying it changed several answers. MINT_RATE_PER_MINUTE IS NOT AN ANNOUNCEMENT THROTTLE, though this commit was briefed to describe it as one. The gate is wired into the materializer and governs INBOUND DID minting for bridged Lemmy actors; the consumer's persona minter is handed to startConsumer directly and never passes through it, and internal/outbound contains no limiter, sleep, or throttle at all. So the runbook names it for what it does, and says plainly that the canary IS the throttle: one community, one worker. Recorded as missing rather than described, because an operator reaching for a lever mid-rollout is exactly who a wrong runbook hurts. Two related facts shape that canary and are now written down. The scoped kill switches are DENYLIST-ONLY — there is no allowlist form — so canarying one community means enumerating the others by AP id, and a community added to the follow list later silently escapes the canary. And configuration is read once at startup with no reload path, so every switch costs a container recreate. No AP path currently reaches Tidepool. The coves.social site sends /.well-known/* to a static file server and everything else to the appview, so the Caddy section is written as a cross-repo instruction set against the real Coves Caddyfile — webfinger, nodeinfo, /ap/*, and an apex Accept matcher, since instance.go says in its own comment that content negotiation is deliberately Caddy's job. It carries the inode warning, and a hard note that no `header_up Host` may be set on those proxies because the host router keys on r.Host. Documented as MISSING rather than described, each with its blast radius: KEK and signing-key rotation (no path exists, and the one function whose name suggests it manages the PLC escrow key, itself sealed under the KEK), backup and restore (the mount exists, nothing writes it, and no database backup covers the KEK in .env), a divergence off switch (the sweep is unconditional, sweeps at startup, and durationVar refuses 0 — a large interval is the only lever), and periodic vote re-seed. Also corrected two claims already in the README: that everything in the config table is required in production (only the stringVar values are), and that flipping CONSUMER_ENABLED lands on stubs from tasks 15-17 (they are wired, which makes the old text actively misleading about what the flag does). And JETSTREAM_URL's example pointed at a port nothing serves. Unresolved and flagged rather than papered over: the e2e image pins Lemmy 0.19.19 while decision 19 and the task docs name 0.19.20 as the target and strictness ceiling, and the behaviours those tasks verified were read from 0.19.20 source. The support matrix says 0.19.x pending that call. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>