diff --git a/0063/SESSION-0063.0-TRANSCRIPT-2026-06-02T06-18--2026-06-02T08-34.md b/0063/SESSION-0063.0-TRANSCRIPT-2026-06-02T06-18--2026-06-02T08-34.md new file mode 100644 index 0000000..e9dcf87 --- /dev/null +++ b/0063/SESSION-0063.0-TRANSCRIPT-2026-06-02T06-18--2026-06-02T08-34.md @@ -0,0 +1,194 @@ +# Session 0063.0 — Transcript + +> Date: 2026-06-02 +> Start: 2026-06-02T06-18 (PST implied) • End: 2026-06-02T08-34 +> Goal: OHM **wildcard-TLS cutover** — move the origin off its per-host certbot +> Let's Encrypt cert onto a shared `*.wiggleverse.org` cert. Decide cross-project +> secret access; operator loads the cert bytes; assistant deploys; operator flips +> Cloudflare to Full(strict)+orange-cloud; finish with `certbot delete`. +> +> Outcome: **Done — but it grew into a full `ohm`→`rfc` hostname migration.** +> Mid-session the operator decided `rfc.wiggleverse.org` (not `ohm.wiggleverse.org`) +> is the canonical host going forward, with OHM as a project/collection within the +> rfc-app. Origin now serves a shared `*.wiggleverse.org` **Cloudflare Origin** +> cert over **Full(strict)** (scoped per-host), at `https://rfc.wiggleverse.org`; +> `ohm.wiggleverse.org` retired (DNS + certbot cert deleted). Gitea login, +> Turnstile, email OTC, and the webhook all verified working. **No secret byte +> entered the session.** No flotilla code change (operational). +> +> Highlights: +> - **Secret-access decision:** ONE shared wildcard secret, homed in the +> `wiggleverse` org project's Secret Manager, read at deploy time by the +> **operator ADC** — NOT per-VM-SA grants (the brief's framing). flotilla never +> reads secrets via the VM service account (SPEC §7.3; no impersonation). +> - **Two brief-vs-reality discrepancies caught before damage:** (1) the live +> vhost still pointed at the certbot cert, not the wildcard paths as briefed; +> (2) `ohm.wiggleverse.org` DNS had already been deleted (site was down). The +> second triggered the operator's pivot to `rfc` as canonical. +> - **Zone-wide Full(strict) incident:** setting it at the zone level took down +> `git.wiggleverse.org` (521) and `wiggle-snip-ppe` (526) — origins not +> strict-ready. Fixed by reverting the zone to Flexible + a per-hostname +> **Configuration Rule** scoping Full(strict) to `rfc` only. +> - **Wrong-webhook detour:** operator twice edited a "BDD Planner Webhook" (a +> different repo's hook) before we pinned the real one (hook #2 on +> `wiggleverse/ohm-content`) by its exact id via the Gitea API. + +--- + +## Pre-session state + +- OHM rfc-app live at `ohm.wiggleverse.org`, served by nginx on the VM + `ohm-rfc-app` (`136.116.40.66`, project `wiggleverse-ohm`, gcloud config + `wiggleverse`). (Note: the VM had been renamed `ohm-app`→`ohm-rfc-app` since + the old deployment-host memory was written; install `/opt/ohm-rfc-app`, unit + `ohm-rfc-app.service`.) +- `flotilla` is now a thin shim over `flotilla-core` (v1.8.0+); the `secret-file` + verb group (bind/set/list/unbind) exists in flotilla-core and writes a secret + to a VM path with a deduped fail-stop reload. +- No secret files bound; all 8 app secrets lived in `wiggleverse-ohm`. No + wildcard secret existed anywhere. +- Working tree had uncommitted `CHANGELOG.md`/`operator-guide.md` from the + 2026-06-01 gitea-rotation doc work (a prior session's) — left untouched. + +## Decision — cross-project secret access + +Grounded in SPEC §7.3 + `flotilla_core/deploy.py` + `secrets.py`: flotilla +resolves secret-file bytes on the operator's machine via **ADC**, then pushes +them to the VM over SSH. The **VM runtime SA is not in the read path**. So the +brief's "secretAccessor grants to each VM's SA" doesn't apply. Chose (via +AskUserQuestion) **one shared secret homed in the `wiggleverse` org project**: +`wiggleverse/wiggleverse-wildcard-fullchain` + `…-privkey`, read cross-project +by the operator ADC (owner on `wiggleverse`). Rejected per-project copies (N× +renewal toil). The cert turned out to be a **Cloudflare Origin Certificate** +(SAN `*.wiggleverse.org` + `wiggleverse.org`, ~15-yr) — ideal for orange-cloud ++ Full(strict). + +## Turn-by-turn arc + +### Arc 1 — Recon + the two discrepancies +Read-only VM preflight showed the live/effective nginx config still pointed at +`/etc/letsencrypt/live/ohm.wiggleverse.org/…` (NOT the wildcard paths the brief +said were "already" wired), and the wildcard files didn't exist. Separately, +`ohm.wiggleverse.org` returned **NOERROR/0-answers** in public DNS — the A +record had been deleted (it resolved for yesterday's deploy id=52). Surfaced +both to the operator rather than proceeding. + +### Arc 2 — Origin cert cutover (TLS) +1. Enabled Secret Manager API on `wiggleverse` (was off). +2. **Operator** loaded both cert secrets via `flotilla secret-file set + --project wiggleverse --secret-id …` (bytes via stdin; never in the + transcript). Bindings + SM entries verified (refs only). +3. Dry-run confirmed the operator ADC reads both cross-project. **Deploy + id=53** landed both files (crt 0644, key 0600, root) + ran the nginx reload; + phases 1–7 green. Phase 8 (health verify) **false-failed** on a local DNS + lookup of the now-deleted `ohm.wiggleverse.org` — actuation had succeeded. + Verified on-VM: cert↔key moduli match, issuer = Cloudflare Origin CA, SAN + `*.wiggleverse.org`, app healthy on localhost. +4. Repointed vhost `ssl_certificate`/`_key` (lines 80–81) → wildcard paths, + keeping the certbot global includes (`options-ssl-nginx.conf`, + `ssl-dhparams.pem`) which survive `certbot delete`. nginx-t-gated, reloaded. + Verified origin serves the wildcard cert via direct IP+SNI. + +### Arc 3 — The `ohm`→`rfc` pivot (host migration) +Operator clarified: **don't restore `ohm`; make `rfc.wiggleverse.org` canonical**, +OHM as a project within it — reversing the 2026-05-27 settlement. Recon found +`rfc.wiggleverse.org` was a **stale mapping** to `34.59.1.73` (the bdd.ecomm +box — 404 on rfc paths, ecomm `/api/health` "scenarios" shape, its own LE cert). +Confirmed safe to repoint to the OHM VM `136.116.40.66`. Mapped the full surface +(more than DNS): `APP_URL` overlay, vhost `server_name` + redirect, `health_url`, +OAuth redirect URI, Turnstile domains, Gitea webhook target. + +My parts: `overlay set APP_URL=https://rfc.wiggleverse.org`; vhost +`server_name` + `if ($host = …)` redirect repointed `ohm`→`rfc` (nginx-t-gated); +`deployment update --health-url …rfc…`. Verified origin answers as `rfc` via +direct IP+SNI. + +### Arc 4 — Cloudflare + the zone-wide Full(strict) incident +Operator created `rfc` A → `136.116.40.66` proxied. First hit a **301 redirect +loop** (Flexible SSL → CF reaches origin over HTTP:80 → nginx 80→443 redirect). +Operator set **Full(strict) at the zone level** → fixed `rfc` but **broke** +`git.wiggleverse.org` (521) and `wiggle-snip-ppe` (526), whose origins aren't +strict-ready. Diagnosed the blast radius; operator **reverted the zone to +Flexible** (restoring git/wiggle-snip) and added a **Configuration Rule** +(Rules → Configuration Rules, top-level menu — not under SSL/TLS) scoping +**Full(strict)** to `rfc.wiggleverse.org` only. `rfc` → 200, loop gone. + +### Arc 5 — Redeploy + certbot delete +With `git` reachable again, **deploy id=54** ran clean end-to-end (phase 8 +verified health at `rfc` through Cloudflare) — applied `APP_URL` to the VM +`.env`, superseding the id=53 false-failure. Then **`certbot delete +--cert-name ohm.wiggleverse.org`** (it was the only cert); the global certbot +includes survived, `nginx -t` stayed green, reloaded. + +### Arc 6 — External deps B/C/D + the wrong-hook detour +- **OAuth (B):** app emits `redirect_uri=https://rfc.wiggleverse.org/auth/callback`. + The OAuth app ("OHM App", client_id `3e85cebb…`) is registered at the Gitea + **site-admin** OAuth2 level — editable in the admin panel, NOT the owner's + user settings (a Gitea gotcha that cost some hunting). ("RFC Contributor", + `9ec2dd9c…`, is a different, unused app.) Operator updated it; Gitea login + round-trips. +- **Turnstile (C):** operator added `rfc.wiggleverse.org` to the site-key + allowed domains. Verified via the login captcha + email OTC. +- **Webhook (D):** operator twice edited a **"BDD Planner Webhook"** (Push-only, + singular `/api/webhook/` path) — a *different* repo's hook — before we found + the real one. Pinned it via the Gitea API: hook **id=2** on + `wiggleverse/ohm-content` (the only hook there). Operator edited just its URL + → `https://rfc.wiggleverse.org/api/webhooks/gitea`. Updated the flotilla + **expected** webhook set to match; `flotilla webhook check` → green. + +## Cut state (end of session) + +| Layer | State | +|---|---| +| Canonical host | **`https://rfc.wiggleverse.org`** (200; OHM content within rfc-app) | +| `ohm.wiggleverse.org` | **retired** — CF DNS deleted, certbot cert deleted, vhost repointed | +| Origin TLS | shared `*.wiggleverse.org` Cloudflare Origin cert (SM: `wiggleverse/wiggleverse-wildcard-{fullchain,privkey}`) | +| Cloudflare | `rfc` A→`136.116.40.66` proxied; zone=Flexible, **Full(strict) via per-host Configuration Rule** | +| App config | `APP_URL`, `health_url`, OAuth redirect, Turnstile, webhook all on `rfc` | +| Deploys | id=53 (verify false-failed, actuation OK), **id=54 clean success** v0.31.4 | + +No flotilla code change. No pin bump. No release tag. No secret bytes in +transcript (cert key via operator stdin; Gitea read-token used via env var, +never printed). + +## §19.2 / candidates surfaced + +- `flotilla secret-file` has no lightweight "apply files only" path — landing + cert files requires a full deploy (build + restart). Fine here, but a + cert-only refresh gesture is a future nicety. +- `flotilla webhook` is read-only against Gitea (no edit) — retargeting the hook + was a manual UI step. Editing hooks remains a §19.2 candidate. +- Zone-wide Full(strict) is a footgun across a multi-origin zone; per-host + Configuration Rules are the right tool. (Recorded to memory.) + +## What lands on the operator's plate + +1. **Revert the "BDD Planner Webhook"** you edited mid-session — it belongs to + the BDD/ecomm app and is now likely pointing at the rfc-app endpoint, which + breaks the ecomm app's own git-sync. Set its Target URL back to the BDD app's + endpoint. +2. **Ecomm-box cleanup (spawned as a separate task):** the stale + `rfc.wiggleverse.org` vhost + LE cert on `34.59.1.73` + (`wiggleverse-ecomm-bdd-app`) will start failing HTTP-01 renewals now that + `rfc` DNS moved — remove the vhost + `certbot delete` there. +3. **Long-term:** if you ever want zone-wide Full(strict), give `git` and + `wiggle-snip` origins valid certs + 443 listeners first. +4. The uncommitted `CHANGELOG.md`/`operator-guide.md` (2026-06-01 gitea-rotation + docs) are still uncommitted — separate from this session. + +## Prompt the operator can paste into the next Claude Code session + +``` +OHM is now served at https://rfc.wiggleverse.org (canonical as of 2026-06-02, +session 0063); ohm.wiggleverse.org is RETIRED (DNS + certbot cert deleted). The +origin uses a shared *.wiggleverse.org Cloudflare Origin cert (SM: +wiggleverse/wiggleverse-wildcard-{fullchain,privkey}, read by operator ADC at +deploy), with Full(strict) scoped to rfc via a Cloudflare Configuration Rule +(zone default is Flexible — do NOT set the zone to Full(strict), it 521s +git.wiggleverse.org and wiggle-snip). Two loose ends: (1) revert the "BDD +Planner Webhook" (a non-OHM repo hook) that got mis-pointed at the rfc-app +during the cutover; (2) clean up the stale rfc.wiggleverse.org vhost + LE cert +on the bdd.ecomm box (34.59.1.73, project wiggleverse-ecomm-bdd-app) — its +HTTP-01 renewals will now fail. This is an OHM driver session if it touches the +live deployment — claim a session ID at start. +``` diff --git a/0063/SESSION-0063.0-TRANSCRIPT-2026-06-02T06-18--INPROGRESS.md b/0063/SESSION-0063.0-TRANSCRIPT-2026-06-02T06-18--INPROGRESS.md deleted file mode 100644 index 848ebbd..0000000 --- a/0063/SESSION-0063.0-TRANSCRIPT-2026-06-02T06-18--INPROGRESS.md +++ /dev/null @@ -1,15 +0,0 @@ -# Session 0063.0 — Transcript - -> Date: 2026-06-02 -> Start: 2026-06-02T06-18 (PST implied) -> Status: **PLACEHOLDER — claimed at session start; finalized at session end.** -> -> This file reserves session ID 0063. The driver replaces this body -> with the full transcript before publishing, and renames the file to -> its final SESSION-0063.0-TRANSCRIPT-2026-06-02T06-18--.md form. - ---- - -## Launch prompt - -_(launch prompt not captured at claim time)_