From fca17122fc3d3a77b7039d48e5bf669f5782f45d Mon Sep 17 00:00:00 2001 From: Ben Stull Date: Thu, 28 May 2026 19:15:58 -0700 Subject: [PATCH] add 0032/SESSION-0032.0-TRANSCRIPT-2026-05-28T18-52--2026-05-28T19-13.md + replace placeholder/variant SESSION-0032.0-TRANSCRIPT-2026-05-28T18-52--INPROGRESS.md --- ...RIPT-2026-05-28T18-52--2026-05-28T19-13.md | 249 ++++++++++++++++++ ...TRANSCRIPT-2026-05-28T18-52--INPROGRESS.md | 48 ---- 2 files changed, 249 insertions(+), 48 deletions(-) create mode 100644 0032/SESSION-0032.0-TRANSCRIPT-2026-05-28T18-52--2026-05-28T19-13.md delete mode 100644 0032/SESSION-0032.0-TRANSCRIPT-2026-05-28T18-52--INPROGRESS.md diff --git a/0032/SESSION-0032.0-TRANSCRIPT-2026-05-28T18-52--2026-05-28T19-13.md b/0032/SESSION-0032.0-TRANSCRIPT-2026-05-28T18-52--2026-05-28T19-13.md new file mode 100644 index 0000000..9461a8b --- /dev/null +++ b/0032/SESSION-0032.0-TRANSCRIPT-2026-05-28T18-52--2026-05-28T19-13.md @@ -0,0 +1,249 @@ +# Session 0032.0 — Transcript + +> Date: 2026-05-28 +> Start: 2026-05-28T18-52 (PST implied) · End: 2026-05-28T19-13 +> Goal: Clear the two Session-0030 leftovers — harden OHM SSH to IAP-only +> (audit-0026 L5/L9) and ship flotilla v1.2.0 — plus the minor leftovers +> (audit I3/I4 as rfc-app v0.28.0, and the orphaned VM db files). +> +> Outcome: **All four tasks landed. OHM SSH is now IAP-only (L5 closed, +> L9 residue retired); the VM was re-verified healthy end-to-end through +> flotilla's own SSH path. flotilla v1.2.0 was found already merged + +> tagged by parallel session 0031 — independently re-verified (184 tests, +> both L1-redaction and v1.2.0 verbs coexist), no new PR needed. Orphaned +> `ohm-app.db\r*` files deleted (confirmed orphaned). rfc-app v0.28.0 +> (audit I3 + I4) opened as PR #2 for the operator to merge + tag + +> deploy.** + +--- + +## Plan / TODO + +- [x] Claim session ID (→ 0032), read SESSION-PROTOCOL.md + audit-0026. +- [x] Task 2 — flotilla v1.2.0 merge (turned out: already done by 0031; verify). +- [x] Task 1 — IAP SSH hardening (additive → verify → delete → flip → re-verify). +- [x] Task 4 — rm orphaned VM db files (confirm orphaned first). +- [x] Task 3 — rfc-app v0.28.0 (audit I3 + I4) as a PR. +- [x] Finalize + publish transcript (this file). + +--- + +## Pre-session state + +- **flotilla** (`/Users/benstull/projects/wiggleverse/ohm-rfc-app-flotilla`): + local `main` == `origin/main` == `ef34ffe` (already the v1.2.0 merge + commit), tag `v1.2.0` present locally + on remote (`cae3fda`→`ef34ffe`). + A leftover **locked** worktree `agent-ab800baf41f74f86d` (c8804a6) from + another session sat in `.claude/worktrees/`. +- **rfc-app** (`/Users/benstull/git/rfc-app`): `main` == `fe044ed` + (Release v0.27.0 merge), clean. Latest tag `v0.27.0`. +- **OHM live**: HTTP 200, `version=0.27.0`, `status=ok`. ohm-rfc pin = + `0.27.0`. +- **GCP** `wiggleverse-ohm`: firewall had `default-allow-ssh` (0.0.0.0/0 + → tcp:22), no `default-allow-rdp`. IAP API **not** enabled. `ben.stull` + = `roles/owner` + secretmanager admin/accessor; no IAP binding. +- **Other live sessions** at claim time (from `claim-session-id.sh`): + `0026.0` and `0027.0` still `--INPROGRESS`. This drove the + isolated-worktree discipline for every hands-on git step. + +--- + +## Turn-by-turn arc + +### Arc 1 — Claim + orient + +Claimed session **0032** via `claim-session-id.sh --start 2026-05-28T18-52 +--prompt-file …` (pushed the placeholder; clean claim at 073db8a). Read +`SESSION-PROTOCOL.md` and `SECURITY-AUDIT-0026-2026-05-28.md` end to end. +Skimmed (did not fully read) the 2245-line ROADMAP — the launch prompt +fully specified the work. Relevant audit findings: **L5** (SSH+RDP world- +open), **L9** (`ssh.py --quiet` TOFU), **L1** (phase-detail redaction — +the flotilla merge conflict), **I3** (dead HTML-email branch), **I4** +(sync Turnstile siteverify). + +### Arc 2 — Task 2: flotilla v1.2.0 (already merged by 0031; verify-only) + +Inspecting topology revealed `trial-merge-v1.2.0` is fully contained in +`main`; `main` == `origin/main` == `ef34ffe` == tag `v1.2.0`, all pushed. +The reflog showed two `merge origin/main: Fast-forward` hops — a parallel +session (**0031**, branch `session-0031/merge-v1.2.0`, since deleted from +the remote) had already performed the 3-way merge, pushed to `main`, and +tagged `v1.2.0`. So the "stuck branch" the launch prompt described was +resolved before this session opened. + +Rather than trust it, **independently verified** the merge kept both +changes: created an isolated detached worktree at `/tmp/flotilla-verify-0032` +(`git worktree add --detach v1.2.0`) with its own venv, `pip install -e .` ++ pytest. Confirmed in `deploy.py` `_PhaseRunner.run` that **all three** +detail paths — `_PhaseFailure` (line 251), `ssh.SshError` (275), and the +success path (285) — apply `self._redact(...)` (the **L1** live-redaction), +*and* the v1.2.0 `except ssh.SshError` robustness branch + preflight probe +are present. The L1 redaction test (`test_deploy.py:278`) explicitly +asserts secret bytes are absent from persisted `deploys.phases` detail and +iterates every phase. **184 tests passed.** Removed the verify worktree. +No new PR — Task 2 was already shipped by 0031; this session's value was +the independent re-verification. + +### Arc 3 — Task 1: IAP SSH hardening (L5 + L9) + +Confirmed there is **no** `default-allow-rdp` rule in this project (the +audit's L5 RDP half doesn't apply here) — SSH was the only world-open rule. +Did the additive steps first (none remove access): + +1. `gcloud services enable iap.googleapis.com` (was disabled). +2. Created firewall rule **`allow-iap-ssh`** (INGRESS, ALLOW tcp:22, + source `35.235.240.0/20`). +3. Granted `ben.stull@wiggleverse.org` **`roles/iap.tunnelResourceAccessor`** + (explicit, even though `roles/owner` already implies it — hygiene for a + future least-priv reduction per audit L6). + +**Verified IAP SSH BEFORE removing anything** (the operator's hard gate): +`gcloud compute ssh ohm-rfc-app --tunnel-through-iap --command …` → +`hostname=ohm-rfc-app`, `whoami=ben_wiggleverse_org` (OS Login), +`ohm-rfc-app.service active`. Only then **deleted `default-allow-ssh`** +(0.0.0.0/0). SSH ingress is now IAP-only. + +Flipped the flotilla deployment record: +`flotilla deployment update ohm-rfc-app --vm-tunnel-through-iap` → +`target_vm_tunnel_through_iap=True`. **Re-verified end-to-end through +flotilla's own code path** (not just gcloud): a small script built an +`ssh.SshTarget` from the live registry record (so `tunnel_through_iap` +flowed through `ssh.py`, which appends `--tunnel-through-iap` at line 90) +and ran `ssh.run_remote(...)` → returncode 0, `service active`. A real +`flotilla deploy` would now succeed via IAP. `flotilla deploy status` → +HTTP 200 v0.27.0 ok (HTTP path unaffected). **L5 closed; L9 first-connect +TOFU residue retired** (PR #1 had already swapped the bare `--quiet` TOFU +for `StrictHostKeyChecking=accept-new`; IAP now removes the first-connect +exposure entirely). + +### Arc 4 — Task 4: orphaned VM db files + +With IAP SSH warm, inspected `/opt/ohm-rfc-app/backend/data/` (dir is +`750 ohm-rfc-app` — consistent with **M3** already remediated; files are +mode `640`, no longer the audited 644). The data dir held the live trio +(`ohm-app.db` / `-shm` / `-wal`, May 29, held open by the service MainPID +3287 per `/proc//fd`) plus three stale files dated May 28 04:01. + +A `find -name '*[[:cntrl:]]*'` matched **zero** files, and `od -c` showed +the stale names contain a **literal backslash-`r`** (two ASCII chars +`\` `r`), **not** a carriage return — exactly the `ohm-app.db\r*` the +launch prompt named. Confirmed orphaned three ways: not in the service's +open fds, stale mtime, and the anomalous backslash in the name. To delete +safely without backslash-quoting hazards through gcloud→shell→find, used a +**time-based predicate** (`! -newermt '2026-05-29 00:00:00'`), dry-ran it +(matched exactly the 3 orphans, none of the live trio), then re-ran with +`-delete`. Re-listed: only the live trio remains. `deploy status` → 200 ok. + +### Arc 5 — Task 3: rfc-app v0.28.0 (audit I3 + I4) + +Isolated worktree `/tmp/rfc-app-v0280-0032` on new branch +`feature/v0.28.0-email-turnstile-async` (own venv from +`backend/requirements.txt` + pytest). Baselined the turnstile (5) and +email_envelope (15) tests green first. + +- **I3** — `email_envelope.build_envelope`'s `body_html` branch is dead + (the only caller was a test; no production path passes it). Replaced the + `multipart/alternative` rendering with a loud guard: `body_html is not + None` → `raise NotImplementedError`, with a comment tying it to the C1 + stored-XSS class in the mail channel. Kept the arg for documented future + symmetry. Flipped the test to assert the guard + added a `body_html=None` + boundary test. +- **I4** — made `turnstile.verify_token` `async`, awaiting + `httpx.AsyncClient`. **Dead end:** first cut had the test monkeypatch the + global `httpx.AsyncClient`, which broke app boot (gitea/docs/TestClient + also construct `AsyncClient`) — 4 tests failed. Fixed by isolating the + call behind a narrow `async _siteverify_post(url, data)` seam and patching + *that* in tests. Updated the sole caller (`main.py`) to `await`. Added a + coroutine-contract unit test. + +Bumped `VERSION`→0.28.0 + mirrored `frontend/package.json`; wrote the +CHANGELOG entry (no-op upgrade steps). **Full backend suite: 365 passed.** +Committed as `Release v0.28.0`, pushed the branch, and opened **PR #2** +(`ben.stull/rfc-app/pulls/2`) via the gitea API using the host's cached +git credential (never printed). Removed the worktree. + +--- + +## Cut state (end of session) + +| | | +| --- | --- | +| flotilla | `main`/`origin/main` = `ef34ffe`, tag `v1.2.0` (merged+tagged by session 0031; re-verified here, 184 tests). No change this session. | +| rfc-app | `main` = `fe044ed` (v0.27.0). Branch `feature/v0.28.0-email-turnstile-async` = `79a447c` pushed; **PR #2 open** (awaiting operator merge+tag+deploy). | +| OHM live | `deploys` unchanged, **HTTP 200 v0.27.0 status=ok** before and after all infra changes. | +| ohm-rfc pin | `0.27.0` (unchanged; bumps to 0.28.0 only after the operator deploys PR #2). | +| GCP firewall | `default-allow-ssh` **deleted**; `allow-iap-ssh` (35.235.240.0/20→tcp:22) added. SSH is IAP-only. | +| GCP IAM | `ben.stull` granted `roles/iap.tunnelResourceAccessor`; IAP API enabled. | +| flotilla record | `ohm-rfc-app.target_vm_tunnel_through_iap = True`. | +| VM data dir | orphaned `ohm-app.db\r*` (×3) deleted; live trio intact, mode 640. | + +| Ledger | Status | +| --- | --- | +| Task 1 — IAP SSH hardening (L5 + L9) | ✅ shipped + re-verified | +| Task 2 — flotilla v1.2.0 | ✅ (shipped by 0031; independently re-verified) | +| Task 3 — rfc-app v0.28.0 (I3 + I4) | 🟡 PR #2 open — operator merges + tags + deploys | +| Task 4 — orphaned VM db files | ✅ deleted | + +--- + +## §19.2 / process candidates surfaced + +1. **No lightweight SSH-only flotilla verb.** Re-verifying the deploy SSH + path end-to-end required a hand-rolled `ssh.run_remote` script because + the only verb that SSHes is a full, service-restarting `deploy` (the + `deploy status/watch/reconcile` subverbs are HTTP-only). A + `flotilla deploy preflight` / `ssh-check ` verb (read-only + remote probe through the configured SSH path) would make IAP-style + cutovers and connectivity checks first-class instead of ad-hoc. +2. **Parallel-session merge already done.** Session 0031 completed Task 2's + merge + tag while this session was being launched, so the launch prompt + was stale on arrival. Reinforces the value of the `--INPROGRESS` + live-session signal and of *verifying before redoing* — but a session + that hadn't checked could have re-merged and produced a divergent tag. + +--- + +## What lands on the operator's plate + +1. **Merge + tag + deploy rfc-app v0.28.0** — review PR #2 + (`ben.stull/rfc-app/pulls/2`), merge, `git tag v0.28.0`, then + `flotilla deploy ohm-rfc-app` (now over IAP) and bump + `ohm-rfc/.rfc-app-version` to `0.28.0`. No migration/secret/overlay + change; plain code deploy. +2. **IAP is now load-bearing for all VM access.** Any manual SSH MUST use + `--tunnel-through-iap`; the operator keeps `roles/iap.tunnelResourceAccessor`. + If ever locked out, re-add the firewall rule via gcloud (no SSH needed) + or use the serial console. +3. **Leftover locked flotilla worktree** `agent-ab800baf41f74f86d` in + `.claude/worktrees/` belongs to another session — left untouched; clear + it if that session is dead. + +--- + +## Prompt the operator can paste into the next Claude Code session + +``` +OHM driver session. This session reserves its own ID at start +(claim-session-id.sh) — the number below is advisory. + +State as of session 0032 (2026-05-28): +- OHM live = rfc-app v0.27.0, HTTP 200 ok. ohm-rfc pin = 0.27.0. +- OHM VM SSH is now IAP-ONLY (audit-0026 L5 closed, L9 residue retired). + Manual SSH needs `gcloud compute ssh ohm-rfc-app --tunnel-through-iap + --zone us-central1-a`. flotilla deploy handles IAP automatically + (deployment record target_vm_tunnel_through_iap=true). +- flotilla v1.2.0 is merged + tagged (done by session 0031, re-verified + by 0032). + +Open item: +- rfc-app v0.28.0 (audit-0026 I3 dead-HTML-email guard + I4 async Turnstile + siteverify) is PR #2 (ben.stull/rfc-app/pulls/2), backend suite 365 green, + no-op upgrade. If not yet merged: review + merge + `git tag v0.28.0` + + `flotilla deploy ohm-rfc-app` + bump ohm-rfc/.rfc-app-version to 0.28.0. + +Then pick up the ROADMAP (~/git/ohm-rfc/ROADMAP.md) — the audit-0026 +Critical/High/Medium remediations already shipped in v0.27.0; remaining +audit items are the Low/Info tail. Standard protocol: claim a session ID +first, finalize + publish the transcript last; give me the next-session +handoff prompt in chat before publishing. +``` diff --git a/0032/SESSION-0032.0-TRANSCRIPT-2026-05-28T18-52--INPROGRESS.md b/0032/SESSION-0032.0-TRANSCRIPT-2026-05-28T18-52--INPROGRESS.md deleted file mode 100644 index 9937d35..0000000 --- a/0032/SESSION-0032.0-TRANSCRIPT-2026-05-28T18-52--INPROGRESS.md +++ /dev/null @@ -1,48 +0,0 @@ -# Session 0032.0 — Transcript - -> Date: 2026-05-28 -> Start: 2026-05-28T18-52 (PST implied) -> Status: **PLACEHOLDER — claimed at session start; finalized at session end.** -> -> This file reserves session ID 0032. The driver replaces this body -> with the full transcript before publishing, and renames the file to -> its final SESSION-0032.0-TRANSCRIPT-2026-05-28T18-52--.md form. - ---- - -## Launch prompt - -``` -OHM driver session. Two infra/cleanup threads left over from Session 0030 -(which fully remediated security-audit-0026 and shipped rfc-app v0.27.0 + -flotilla PR #1 — all live on prod; see SESSION-0030.0 transcript in -ohm-session-history for context). - -Task 1 — Harden OHM SSH via IAP (§19.2; closes audit-0026 L5 + retires the -L9 first-connect-TOFU residue): - - Project wiggleverse-ohm, VM ohm-rfc-app, zone us-central1-a. - - Add an IAP firewall rule (source 35.235.240.0/20 → tcp:22), grant the - operator roles/iap.tunnelResourceAccessor. - - VERIFY `gcloud compute ssh ohm-rfc-app --tunnel-through-iap` works - BEFORE removing anything. - - Only then delete the `default-allow-ssh` 0.0.0.0/0 rule, and flip the - flotilla deployment to IAP: set tunnel_through_iap=true on the - deployment record + confirm the deploy ssh path uses --tunnel-through-iap. - - Re-verify a deploy/ssh still works end-to-end after the cutover. - -Task 2 — Resolve the stuck `trial-merge-v1.2.0` branch in the flotilla repo -to ship flotilla v1.2.0 (session 0028's PR): - - It conflicts with the L1 redaction change in `_PhaseRunner.run` - (deploy.py) — both edited the same block. Do a proper 3-way merge that - KEEPS BOTH: the live-redaction wrapping of phase detail AND v1.2.0's - deploy-robustness / record-maintenance verbs. - - IMPORTANT: do the merge in an ISOLATED git worktree with its own venv, - NOT the shared flotilla checkout. - - Ship as a PR (operator merges + tags), matching the repo convention. - -Minor leftovers if time: audit-0026 I3 (dead text/html email branch in -email_envelope.py) and I4 (turnstile.py siteverify async) — small v0.28.0. -And VM: sudo rm orphaned /opt/ohm-rfc-app/backend/data/ohm-app.db\r* files -once confirmed orphaned. - -```