257 lines
13 KiB
Markdown
257 lines
13 KiB
Markdown
# 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
|
||
> `019c8a9` on `main`. It is **not yet tagged or deployed**; OHM still runs
|
||
> v0.27.0 until the operator tags `v0.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
|
||
|
||
- [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/<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`'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 | **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
|
||
|
||
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 <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.
|
||
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. **Tag + deploy rfc-app v0.28.0** — PR #2 is already **merged** to
|
||
`main` (`019c8a9`). Remaining: `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) — 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.
|
||
```
|