13 KiB
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).
Post-publish addendum: the operator merged PR #2 (rfc-app v0.28.0) immediately after this transcript first published — merge commit
019c8a9onmain. It is not yet tagged or deployed; OHM still runs v0.27.0 until the operator tagsv0.28.0, deploys, and bumps the pin. The cut-state, ledger, and operator-plate below are updated to match.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
- Claim session ID (→ 0032), read SESSION-PROTOCOL.md + audit-0026.
- Task 2 — flotilla v1.2.0 merge (turned out: already done by 0031; verify).
- Task 1 — IAP SSH hardening (additive → verify → delete → flip → re-verify).
- Task 4 — rm orphaned VM db files (confirm orphaned first).
- Task 3 — rfc-app v0.28.0 (audit I3 + I4) as a PR.
- Finalize + publish transcript (this file).
Pre-session state
- flotilla (
/Users/benstull/projects/wiggleverse/ohm-rfc-app-flotilla): localmain==origin/main==ef34ffe(already the v1.2.0 merge commit), tagv1.2.0present locally + on remote (cae3fda→ef34ffe). A leftover locked worktreeagent-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 tagv0.27.0. - OHM live: HTTP 200,
version=0.27.0,status=ok. ohm-rfc pin =0.27.0. - GCP
wiggleverse-ohm: firewall haddefault-allow-ssh(0.0.0.0/0 → tcp:22), nodefault-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.0and0027.0still--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.runthat all three detail paths —_PhaseFailure(line 251),ssh.SshError(275), and the success path (285) — applyself._redact(...)(the L1 live-redaction), and the v1.2.0except ssh.SshErrorrobustness branch + preflight probe are present. The L1 redaction test (test_deploy.py:278) explicitly asserts secret bytes are absent from persisteddeploys.phasesdetail 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):
gcloud services enable iap.googleapis.com(was disabled).- Created firewall rule
allow-iap-ssh(INGRESS, ALLOW tcp:22, source35.235.240.0/20). - Granted
ben.stull@wiggleverse.orgroles/iap.tunnelResourceAccessor(explicit, even thoughroles/owneralready 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/<pid>/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'sbody_htmlbranch is dead (the only caller was a test; no production path passes it). Replaced themultipart/alternativerendering 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 abody_html=Noneboundary test. - I4 — made
turnstile.verify_tokenasync, awaitinghttpx.AsyncClient. Dead end: first cut had the test monkeypatch the globalhttpx.AsyncClient, which broke app boot (gitea/docs/TestClient also constructAsyncClient) — 4 tests failed. Fixed by isolating the call behind a narrowasync _siteverify_post(url, data)seam and patching that in tests. Updated the sole caller (main.py) toawait. 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 | PR #2 merged by the operator post-publish → main = 019c8a9 (v0.28.0 code, VERSION=0.28.0). Not yet tagged/deployed — v0.28.0 tag + flotilla deploy + pin bump remain operator gestures. |
| 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 merged (019c8a9) post-publish; 🟡 tag + deploy + pin bump remain operator gestures |
| Task 4 — orphaned VM db files | ✅ deleted |
§19.2 / process candidates surfaced
- No lightweight SSH-only flotilla verb. Re-verifying the deploy SSH
path end-to-end required a hand-rolled
ssh.run_remotescript because the only verb that SSHes is a full, service-restartingdeploy(thedeploy status/watch/reconcilesubverbs are HTTP-only). Aflotilla deploy preflight/ssh-check <deployment>verb (read-only remote probe through the configured SSH path) would make IAP-style cutovers and connectivity checks first-class instead of ad-hoc. - 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
--INPROGRESSlive-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
- Tag + deploy rfc-app v0.28.0 — PR #2 is already merged to
main(019c8a9). Remaining:git tag v0.28.0, thenflotilla deploy ohm-rfc-app(now over IAP) and bumpohm-rfc/.rfc-app-versionto0.28.0. No migration/secret/overlay change; plain code deploy. - IAP is now load-bearing for all VM access. Any manual SSH MUST use
--tunnel-through-iap; the operator keepsroles/iap.tunnelResourceAccessor. If ever locked out, re-add the firewall rule via gcloud (no SSH needed) or use the serial console. - Leftover locked flotilla worktree
agent-ab800baf41f74f86din.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) — PR #2 is MERGED to main (019c8a9), backend suite 365 green,
no-op upgrade, but NOT yet tagged/deployed. Remaining: `git tag v0.28.0`
+ `flotilla deploy ohm-rfc-app` + bump ohm-rfc/.rfc-app-version to 0.28.0.
(Skip if the operator already did this between sessions — check the pin.)
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.