# Session 0017.0 — Transcript

> Date: 2026-05-28
> Start: 2026-05-28T08-50 PST
> End:   2026-05-28T09-22 PST
> Goal: drive Wave 8 — pick a shippable scope from the post-Wave-7 candidate list now that the OTC sign-in gate is open; the operator marked roadmap #30 (docs nav + on-site sessions browser + session-history repo restructure) as **NEXT** in ROADMAP.md, so that became the target.
>
> Outcome: **Wave 8 = roadmap #30 shipped end-to-end. rfc-app v0.19.0 tagged + pushed (canonical + benstull mirror); ohm-rfc pin bumped to 0.19.0; OHM live at v0.19.0 healthy. `wiggleverse/ohm-session-history` restructured into one folder per session + `README.md` (operator-review draft) + `sessions.json` title manifest (15 entries, all titled). `publish-transcript.sh` extended for the folder convention + sessions.json upsert. SESSION-PROTOCOL.md §1 + §5 amended. Three subagents (0017.1 rfc-app code, 0017.2 session-history restructure, plus the driver-inline publish-script + protocol edits) all delivered.**
>
> Highlights:
> - The OTC sign-in end-to-end gate (Wave 7 carry-forward) was operator-confirmed working at session start — first-time-since-launch.
> - Two other sessions were running concurrently (0015.0 troubleshooting an OTC-config edge case; 0016.0 completed). Driver took ID 0017.0 per operator confirmation.
> - Settled at session time: gitea-only (no GitHub mirror); runtime fetch with cache for `/docs/sessions`; full `SESSION-NNNN.M-TRANSCRIPT-…md` filename retained inside each folder (overrode roadmap example's trimmed form).
> - SSE-keepalive SSH-watchdog timeout fired again at deploy time (4th occurrence; flotilla v1.2.0 §19.2 candidate). Service came up at v0.19.0 healthy regardless.

---

## Pre-session state

- rfc-app `main` @ `ac3513a` ("Merge: CONTRIBUTING.md + SPEC.md §21 analytics chapter (Session 0013.1)"); `v0.18.0` was the latest tag; clean working tree.
- ohm-rfc `main` @ `74a3e12` ("ROADMAP: +#24..#29"); pin `.rfc-app-version=0.18.0`. (Between session start and the post-deploy pin push, the operator landed at least one more commit on ohm-rfc — `9c47792` — adding roadmap item #30 with its **NEXT** marker; my pin-bump committed onto the updated `9c47792` cleanly.)
- ohm-rfc-app-flotilla `main` @ `417b6c5` (v1.1.0, Wave 7 deploy with VITE_* overlay propagation). Local working tree had one modified file: `CLAUDE.md`.
- OHM live: v0.18.0 healthy (`/api/health` 200, `{"version":"0.18.0","status":"ok"}`).
- ohm-infra (`~/git/ohm-infra/`, not a git repo locally): `publish-transcript.sh` at the post-#23 form (accepting numeric + legacy letter regex); `SESSION-PROTOCOL.md` at the post-Session-0014.0 amendment shape. Stale `SESSION-0012.0-TRANSCRIPT-…INPROGRESS.md` still on disk (handoff said leave as-is).
- Session 0014.0's main driver transcript NOT yet on disk at session start (it appeared at 08:43 mid-session — the parallel-running driver was finalizing). 0014.1 subagent transcript was on disk from 07:42. 0016.0 transcript not seen on disk this session.

The operator's handoff prompt (pasted at session start) was the load-bearing summary of what Wave 7 shipped + the Wave 8 candidate list. Two clarifying gates the operator surfaced at the top:
1. **OTC sign-in end-to-end was unverified.** Wave 7's full triage stopped before the operator retested.
2. **Two other sessions were running.** Session ID for this driver had to be calculated from what the other sessions had claimed.

---

## Turn-by-turn arc

### Arc 1 — Orient + clear the two gates

Read `SESSION-PROTOCOL.md`, `ohm-rfc/ROADMAP.md` (which surfaced a NEW item #30 marked **NEXT** by the operator — newer than the handoff prompt and effectively re-shaping the Wave 8 priority order), the on-disk transcript listing (showed 0001–0013.3 + 0014.1 in numeric form), and the publish-transcript.sh + flotilla state. Health-probed OHM (v0.18.0 ok). Confirmed `flotilla deploy ohm-rfc-app` venv at `.venv/bin/ohm-rfc-app-flotilla`.

Asked the operator two questions in one tool call:
1. Session ID — other sessions claimed 0015.0 + 0016.0; driver took **0017.0**.
2. OTC sign-in test in a fresh incognito window — **"Code arrived, sign-in worked"** — first-time-since-launch confirmation of the Wave 7 v0.18.0 chain working end-to-end. The Wave 7 load-bearing gate is closed.

Asked a follow-up: what are 0015.0 and 0016.0 doing, so Wave 8 doesn't collide? **0016.0 is complete (transcript pending publish — at this point not yet on disk locally, but operator confirmed it's done); 0015.0 is troubleshooting an OTC-not-being-sent edge case described as a config issue, no code changes**, and the operator was going to context-switch to 0015.0 directly. That cleared the way for #30 since neither competing session was touching the rfc-app or session-history surfaces.

### Arc 2 — Propose Wave 8 scope + settle the open #30 decisions

Recommended Wave 8 = #30 since the operator had marked it **NEXT** and nothing else was more pressing. Proposed shape: three parallel subagents touching three different repos (session-history restructure + rfc-app v0.19.0 + ohm-infra publish-script + protocol updates).

Asked the operator's two open session-time questions from the roadmap text:
1. **Repo host**: gitea-only / gitea+GitHub mirror / GitHub canonical. Operator chose **gitea-only**.
2. **Fetch mode**: runtime fetch with cache (recommended) / build-time bundle. Operator chose **runtime fetch with cache**.

Then asked two coordination questions:
3. **Is restructuring the session-history repo safe to start now?** (The handoff's "don't do transcript renames yet — another session is running" caveat might still apply.) Operator chose **"Wait — I'll signal when clear"** for the restructure subagent specifically; authorized the other two subagents to proceed.
4. **Session titles + README prose drafting**: subagent drafts / operator hand-writes / hybrid. Operator chose **subagent drafts both, operator reviews**.

Renumbered the subagent labels by dispatch order per protocol §5: subagent dispatched first = `.1`, second = `.2`. So:
- 0017.1 = rfc-app v0.19.0 code subagent (dispatched first)
- 0017.2 = session-history restructure subagent (held until operator signal)

Driver does the ohm-infra publish-script + protocol updates inline (small enough; not a forked subagent — captured in this transcript).

### Arc 3 — Driver-inline edits: publish-script + SESSION-PROTOCOL.md

Read the existing `scripts/publish-transcript.sh`. Rewrote the body to:

- Parse out `NNNN` from `SESSION-NNNN.M-TRANSCRIPT-…md` via bash regex `^SESSION-([0-9]{4})\.[0-9]+(\.[0-9]+)*-TRANSCRIPT`.
- For numeric form: place transcript in `NNNN/` subfolder (`mkdir -p`); for legacy letter form: place at repo root (backward compat).
- Upsert `sessions.json` via inline `python3` heredoc (pure stdlib, no jq): if `NNNN` key is missing, add `{"NNNN": {"title": ""}}`, sort keys numerically, write 2-space-indented JSON with trailing newline. If `sessions.json` is malformed JSON, fail cleanly with exit 2.
- Compute commit message based on what's in the diff (`add NNNN/SESSION-… + sessions.json: add entry for NNNN`, or just one of those).
- Use `git status --porcelain` after staging to detect any change; no-op exit 0 if nothing changed.
- Keep dry-run behavior; expand its logging to name both the transcript-action and the manifest-action.

Smoke-tested the regex locally (numeric forms parse, legacy letters reject) and the Python upsert in isolation (first-add returns 1, re-add returns 0, malformed JSON exits 2, sort order works).

Edited `SESSION-PROTOCOL.md`:
- §1 gained a **"Published repo layout (Session 0017.0 amendment, 2026-05-28)"** subsection describing the `NNNN/` folder shape, the `sessions.json` schema, and the "local files stay flat — folders are publish-time" rule. Documents the decision to retain the full `SESSION-NNNN.M-TRANSCRIPT-…md` filename inside folders (no trim of the `SESSION-` prefix; overrides the roadmap example).
- §5 gained a short **"Published-repo placement"** note saying subagent transcripts inherit their parent's `NNNN/` folder (publish-script handles this by parsing the leading `NNNN`).

Documented that the local working files at `~/git/ohm-infra/SESSION-…` keep the flat shape — folders are a publish-time gesture only.

### Arc 4 — Dispatch subsession 0017.1 (rfc-app v0.19.0)

Dispatched a forked general-purpose subagent in the background with a self-contained prompt:
- Pre-assigned subsession ID `0017.1`.
- Repo `/Users/benstull/git/rfc-app`, branch `feature/v0.19.0-docs-sessions-browser` off `main@ac3513a`.
- Settled decisions: gitea-only, runtime fetch + cache.
- Concrete scope (backend endpoints + TTL cache + frontend route tree + flyout nav + CHANGELOG + version bumps).
- Hard rules: do NOT tag, do NOT bump ohm-rfc pin, do NOT push to benstull mirror, do NOT deploy. Driver handles all of those after 0017.2 also returns.
- Transcript expectations per §5 (write `SESSION-0017.1-TRANSCRIPT-…md` BEFORE returning report).
- Pasted roadmap item #30 verbatim for self-containment.

Subagent ran ~15 minutes and reported back:
- Branch `feature/v0.19.0-docs-sessions-browser` @ `477f496`, pushed to origin only.
- 313 backend pytest passing (18 new in `test_docs_sessions_vertical.py`); frontend `npm run build` green; no frontend test framework (per dispatch); dev-server smoke not run (sandboxed agent, no browser — flagged for driver).
- Settled in-session: per-session listing source = separate `/api/docs/sessions/<NNNN>/index` endpoint (not extending the manifest, keeps it cheap); negative-cache 404s (rationale: at ship time the repo restructure is racing the rfc-app deploy; empty-state path may be hot for hours); the dispatch's `pyproject.toml` was a non-op (repo uses `backend/requirements.txt` only — canonical version sources are `VERSION` + `frontend/package.json#version`, both bumped).
- 5 §19.2 candidates surfaced (`package-lock.json` version drift, missing `pyproject.toml`, validator-leak, CHANGELOG style drift, cache-invalidation seam).
- Transcript at `SESSION-0017.1-TRANSCRIPT-2026-05-28T08-52--2026-05-28T09-07.md`.
- Notable in-flight snag (full detail in its transcript): the shared `app_with_fake_gitea` fixture monkeypatches `httpx.AsyncClient` globally on the module — the first-attempt test fixture captured a `real_client_cls = httpx.AsyncClient` that was already the gitea-patched wrapper; subagent fixed by importing the truly-unpatched `AsyncClient` from `httpx._client` directly. `_UpstreamHandler` gained a host-marker filter so reconciler-side gitea calls don't inflate cache-hit assertions.

Driver verified deliverables on disk (don't depend on report-back text per §5 amendment):
- `feature/v0.19.0-docs-sessions-browser` exists locally at `477f496`, matches origin.
- `VERSION` = `0.19.0`; `frontend/package.json` version = `0.19.0`; `main` untouched at `ac3513a`.
- Transcript exists at the named path, 350 lines, header matches expected shape.
- CHANGELOG diff: 12 lines added; full v0.19.0 entry with `Upgrade steps:` block.
- Backend diff: 855 LOC across `api.py` + `docs_sessions.py` + tests.
- Frontend diff: 864 LOC across `App.css` / `App.jsx` / `api.js` / 5 new components / `analytics.js` (+ `Docs.jsx` deletion).

Noticed one quick fix the subagent flagged as §19.2 (`## v0.19.0` vs `## 0.18.0` style mismatch — every prior CHANGELOG header drops the `v` prefix; the new entry having `v` was MY dispatch-prompt typo, not architectural). Made a small driver-inline commit on the feature branch normalizing the header — `bb5137f CHANGELOG: normalize 0.19.0 header (drop 'v' prefix to match surrounding style)`, pushed to origin. The other §19.2 candidates are genuinely architectural and stay deferred.

(One operator-config note: git emitted the "your name and email were configured automatically based on hostname" warning for the commit — `benstull@mac.lan` rather than `ben@wiggleverse.org`. Per the "no `git config` edits" hard rule I left it; same identity shape as the subagent's commits and as 0017.2's later commit. Operator can normalize globally if desired.)

### Arc 5 — Operator clears the restructure gate; dispatch 0017.2

Operator said "we're good to go. Go for it" — clearing 0017.2's hold.

Dispatched the second forked general-purpose subagent in the background:
- Pre-assigned subsession ID `0017.2`.
- Mission: restructure `wiggleverse/ohm-session-history` (gitea SSH remote at `ssh://git@git.wiggleverse.org:2222/wiggleverse/ohm-session-history.git`); one atomic commit on `main`.
- Settled decisions baked into the prompt (keep `SESSION-` prefix in folder filenames; gitea-only).
- README ingredients (per roadmap #30): how OHM is built, why transcripts are public, the verbatim "Oh and Jan, your joke is in there somewhere." line, link to canonical repo. Subagent drafts; operator reviews via embedded `<!-- operator: rewrite this section in your voice -->` markers.
- Title-drafting heuristic: read each transcript's `> Goal:`/`> Outcome:` header block. Driver supplied starter sketches for 0001 + 0009–0014; subagent filled in 0002–0008 + 0015 from the actual transcripts.

Subagent ran ~5 minutes and reported back:
- Public repo `main` @ `ad01c08` (fast-forward push from `b6a7b7d`); 19 transcripts `git mv`'d into 15 `NNNN/` folders, rename detection 100%; zero legacy letter-form at root (Session 0014.0's #23 rename had already cleared them).
- `sessions.json`: 15 entries, every session titled, no placeholders. Did NOT include 0016/0017 (unpublished in the public repo as of 0017.2's run).
- README.md drafted with 3 operator-review markers on key sections (~520 words, includes canonical-link callout + publish-script how-to + license pointer).
- Transcript at `SESSION-0017.2-TRANSCRIPT-2026-05-28T09-10--2026-05-28T09-15.md`.
- 3 §19.2 candidates (publish-script placeholder-on-first-publish contract; README ↔ /docs/sessions/about render contract; local-vs-published shape asymmetry).
- Cosmetic note: committer identity `Ben Stull <benstull@mac.lan>` (same as 0017.1's and the driver inline commit).

Driver verified:
- Transcript exists at named path, 13.5 KB.
- Gitea raw probe: `sessions.json` returns 200; `README.md` returns 200; `0014/SESSION-0014.0-TRANSCRIPT-2026-05-28T07-01--2026-05-28T08-25.md` returns 200.
- `sessions.json` content sane (15 entries; titles read like decent first drafts; 0013 is a semicolon-list — likely worth compression; 0014 uses subagent's phrasing rather than §1-example verbatim form — operator's call).

### Arc 6 — Merge, tag, push, pin, deploy, verify

With both subagents returned and verified, executed the coordinated landing:

1. `git checkout main` on rfc-app, `git merge --no-ff feature/v0.19.0-docs-sessions-browser -m "Merge feature/v0.19.0-docs-sessions-browser"` → merge commit `69a166a` (16 files changed, 1732+/57-).
2. `git tag v0.19.0` on the merge commit.
3. `git push origin main v0.19.0` → success.
4. `git push benstull main v0.19.0` → success.
5. `cd ohm-rfc && echo "0.19.0" > .rfc-app-version && git commit -m "Pin rfc-app 0.19.0 — Wave 8 deploy: /docs nav + on-site sessions browser (#30)"` → commit `de17e79`. (Local main was at `9c47792`, not `74a3e12` as seen at session start — operator landed at least one more ohm-rfc commit in between; commit graph stayed clean.)
6. `git push` → success.
7. `.venv/bin/ohm-rfc-app-flotilla deploy ohm-rfc-app` → **hit the §19.2 SSE-keepalive SSH-watchdog timeout** at phase 7 (`SshError: ssh to ohm-app timed out after 60.0s running: 'sudo sh -c ... systemctl restart ohm-app.service ...'`). 4th occurrence of this pattern (Session 0009.0 §19.2 / Session 0014.0 carry-forward).
8. **Verification via `/api/health`** (not the flotilla exit code, per Session 0014.0's lesson): first probe right after the timeout returned 502 from nginx (service was mid-restart); second probe 15s later also 502; but then `.venv/bin/ohm-rfc-app-flotilla deploy status ohm-rfc-app` returned `HTTP 200 version=0.19.0 status=ok`. Re-probed the public URL — 5 consecutive 200s, all `{"version":"0.19.0","status":"ok"}`, ~190 ms each. So: deploy succeeded; the 502 window was transient (a few seconds while the service was finishing restart); SSH watchdog falling off doesn't reflect actual deploy outcome.

(Side observation: an `ssh ohm-app` attempt to read journalctl failed "Could not resolve hostname ohm-app" — my local SSH config doesn't have the alias the flotilla CLI uses internally. `flotilla deploy status` was the canonical path anyway. Not blocking.)

deploys.id row for this deploy will read aborted-on-SSH-timeout despite the service running healthy at v0.19.0 — same dichotomy as deploys.id 23/24/25 from Session 0014.0.

### Arc 7 — Operator asks for the next-session prompt mid-arc; transcript wraps after

Operator asked for the closing-handoff prompt at the moment the deploy verification finished. Driver drafted it inline in the conversation (full prompt included below in "Prompt the operator can paste into the next Claude Code session"). Browser-side verification of `/docs/sessions/about` + a per-transcript view is the next session's first gesture, not 0017.0's — the deploy is up; the on-site rendering is the operator's eyeball confirmation.

Then this transcript got written (you are reading it).

---

## Cut state (end of session)

| | |
| --- | --- |
| rfc-app | `main` @ `69a166a` (merge commit); tag `v0.19.0`; pushed to origin + benstull mirror |
| ohm-rfc | `main` @ `de17e79` — `.rfc-app-version=0.19.0` |
| ohm-rfc-app-flotilla | `main` @ `417b6c5` — no flotilla code change this session (`CLAUDE.md` modified file from session start was carried through unchanged) |
| `wiggleverse/ohm-session-history` | `main` @ `ad01c08` — restructured into 15 `NNNN/` folders + `README.md` (with 3 operator-rewrite markers) + `sessions.json` (15 entries, all titled) |
| OHM live | v0.19.0 healthy (`/api/health` 200, `{"version":"0.19.0","status":"ok"}`); deploys.id row = aborted-on-SSH-timeout per §19.2 (h) below |
| ohm-infra | `scripts/publish-transcript.sh` extended for NNNN/ folders + sessions.json upsert; `SESSION-PROTOCOL.md` §1 + §5 amended. (Not a git repo locally; on-disk changes are the artifact.) |
| Session 0017 transcripts on disk | `SESSION-0017.0-TRANSCRIPT-2026-05-28T08-50--2026-05-28T09-22.md` (this file), `SESSION-0017.1-TRANSCRIPT-2026-05-28T08-52--2026-05-28T09-07.md`, `SESSION-0017.2-TRANSCRIPT-2026-05-28T09-10--2026-05-28T09-15.md`. All three publish via `publish-transcript.sh` at session close. |

| Wave 8 ledger | Status |
| --- | --- |
| #30 docs nav + on-site sessions browser + session-history restructure (rfc-app v0.19.0 + ohm-session-history reshape) | ✅ shipped |

---

## §19.2 candidates surfaced

a. **`frontend/package-lock.json` version drift** (0017.1). Lockfile top-level `"version"` reads `0.15.0` while `frontend/package.json#version` is `0.19.0`. Cosmetic but ugly; symptom of no automated lockfile-discipline check. Repo doesn't run `npm install` as part of the version bump, so the lockfile drifts on every minor.

b. **rfc-app backend has no `pyproject.toml`** (0017.1). Dispatch named one as a version source; the repo uses `backend/requirements.txt` only. Canonical version sources are `VERSION` + `frontend/package.json#version`. If a backend `pyproject.toml` is desired (for editable installs, build-tool integration, etc.), it earns its own small release.

c. **`docs_sessions.py` private validators leak into the route layer** (0017.1). `_is_valid_session_dir` / `_is_valid_transcript_filename` are referenced from route handlers; consolidating into a public `validate_path()` would tighten the seam.

d. **`docs_sessions` cache has no external invalidation** (0017.1). In-process TTL only; no `reset_cache()` admin endpoint or test seam. Operationally fine (TTL is short); developer-experience worth improving.

e. **`publish-transcript.sh` should auto-populate `sessions.json` placeholder entries on first-publish** (0017.2). **Already partially shipped** in the driver-inline edit (publish-script upserts `{"NNNN": {"title": ""}}` if the key is missing). Worth confirming the contract end-to-end against a real fresh-session publish; surfaced as a §19.2 mostly to track the testing follow-up.

f. **Contract between README and rfc-app `/docs/sessions/about` renderer** (0017.2). Operator-review HTML comments (`<!-- ... -->`) should render as nothing on-site (markdown stripping). Worth confirming in the next session's first browser-verification gesture; if they show up as raw text the renderer needs a small fix.

g. **Local-vs-published shape asymmetry** (0017.2). Local working dir `~/git/ohm-infra/` stays flat; published repo is nested in `NNNN/` folders. Already documented in `SESSION-PROTOCOL.md` §1, but worth a one-line callout if future drivers get confused.

h. **SSE-keepalive SSH-watchdog timeout — 4th occurrence** (driver). Hit at phase 7 of every recent deploy: SIGTERM doesn't drain SSE connections in under 60 s, the SSH watchdog falls off, flotilla reports aborted, the service actually restarts and comes up healthy. flotilla v1.2.0 candidate: pre-drain SSE connections (or raise the watchdog to e.g. 180 s) before `systemctl restart`. Driver verification now leans entirely on `/api/health` + `flotilla deploy status` post-deploy rather than the flotilla exit code.

i. **Driver-inline commit identity drift** (driver). Three commits this session (one driver-inline CHANGELOG fix + two subagent merges + the pin bump) carried `Ben Stull <benstull@mac.lan>` as the committer rather than the operator's preferred identity. Per the hard rule, no `git config` edit; operator can rewrite if desired. Worth surfacing as an operator-tooling §19.2: a one-line "ensure git identity is set on this machine" gesture would prevent it.

j. **`publish-transcript.sh` `sessions.json` formatting churn** (driver, noticed at publish-time after 0017.2's restructure had already shipped). The script's inline-Python upsert uses `json.dump`'s default `indent=2` (expands every entry to multi-line) AND default `ensure_ascii=True` (escapes em-dashes / `§` / en-dashes as `—` / `§` / `–`). 0017.2's subagent had hand-written `sessions.json` with compact one-line entries per session + raw UTF-8. The first publish-time upsert (0017.0's transcript publish, which added the `0017` placeholder) rewrote the file in the expanded ASCII-escaped form, so the operator's next edit starts from that shape rather than the subagent's. Fix is one-line in `publish-transcript.sh`: `json.dump(out, f, indent=2, ensure_ascii=False)` plus a small custom emitter to keep each session's `{ "title": "…" }` on one line. Cosmetic; no functional impact.

---

## What lands on the operator's plate

1. **Browser-verify `/docs/*` surfaces** (next-session first gesture). Concrete URLs in the next-session prompt below. ~5 minutes.
2. **Rewrite the 3 README.md placeholder sections** on `wiggleverse/ohm-session-history` (sections marked with `<!-- operator: rewrite this section in your voice -->`). Direct commit to main; no PR ceremony needed.
3. **Refine `sessions.json` titles** if desired. 0013's title is a semicolon-list of three subagent workstreams (may want compression); 0014's title is the subagent's phrasing rather than the §1-example verbatim form. Add a title for **0017** (currently absent from the manifest — it'll be added as `{"title": ""}` when this transcript publishes; operator fills in something like `"Wave 8 driver — docs nav + on-site sessions browser (#30)"`).
4. **Merge the two Session-M docs PRs** still awaiting operator merge:
   - rfc-app `feature/contributing-and-spec-analytics` @ `213f686`
   - ohm-rfc `feature/contributing-roadmap-guide` @ `f26fae4`
5. **DMARC Phase A → Phase B**: ≥1 week of clean `rua` reports at `ben.stull@wiggleverse.org` since Session 0014.0; ETA 2026-06-04 to flip `p=quarantine`.
6. **#1 VM rename + flotilla v1.2.0 SSE-keepalive drain** (combined session) — the watchdog pattern is now 4× confirmed; worth fixing alongside the rename when the maintenance window opens.
7. **Update ohm-rfc/ROADMAP.md**: strikethrough #30 row in the version-target table; link to v0.19.0 + the deploys.id row (when one materializes — currently aborted-on-timeout).

---

## Prompt the operator can paste into the next Claude Code session

```
You are the OHM roadmap driver, Session 0018.0. Read this whole prompt
before doing anything.

# What Session 0017.0 shipped

Wave 8 was roadmap item #30 — the docs nav + on-site sessions browser
+ session-history repo restructure — picked because the operator
marked #30 **NEXT** in ROADMAP.md and the load-bearing OTC sign-in
gate (the Wave 7 / v0.18.0 carry-forward) was confirmed working
end-to-end at session start. Three parallel subagents:

1. **flotilla / ohm-infra** (driver inline) — `publish-transcript.sh`
   extended to place new transcripts into their `NNNN/` subfolder and
   upsert `sessions.json` (a top-level title manifest, keyed by
   4-digit `NNNN`, value `{"title": ""}`). Backward-compat: legacy
   letter-form transcripts still land at repo root. JSON upsert is
   pure stdlib via inline `python3` heredoc, no jq dep.
   SESSION-PROTOCOL.md §1 gained a "Published repo layout" amendment
   documenting the new folder shape, the `sessions.json` schema, and
   the "local files stay flat — folders are publish-time" rule. §5
   gained a short "Published-repo placement" note saying subsession
   transcripts inherit their parent's `NNNN/`.

2. **subsession 0017.1 — rfc-app v0.19.0** (merged + tagged + pushed).
   Backend: `/api/docs/sessions/{manifest,about,<NNNN>/index,<NNNN>/<file>}`
   with in-process TTL cache (60s manifest, 5min content; both
   env-tunable via `OHM_DOCS_SESSIONS_MANIFEST_TTL_SEC` /
   `OHM_DOCS_SESSIONS_CONTENT_TTL_SEC`). Negative caching for 404s
   (rationale: at ship time the repo restructure is racing the rfc-app
   deploy; empty-state path may be hot for hours). Gitea raw base
   configurable via `OHM_SESSION_HISTORY_RAW_BASE`. 18 new pytest
   cases (`test_docs_sessions_vertical.py`, 313 backend total green).
   Frontend: `/docs` → redirect to `/docs/user-guide`; `/docs/sessions`
   → redirect to `/docs/sessions/about`; `/docs/sessions/<NNNN>` and
   `/docs/sessions/<NNNN>/<filename>` for index + transcript view;
   `DocsLayout.jsx` with left-side flyout nav driven by the manifest;
   reuses the v0.14.0 react-markdown renderer; Amplitude `Doc Viewed`
   events + `data-amp-track-*` decoration. Branch
   `feature/v0.19.0-docs-sessions-browser` merged to main at `69a166a`,
   tag v0.19.0, pushed to origin + benstull mirror.

3. **subsession 0017.2 — wiggleverse/ohm-session-history restructure**
   (one atomic commit `ad01c08` on `main`, fast-forward push). 19
   transcripts `git mv`'d into 15 `NNNN/` folders (rename detection
   100%); `0013/` holds 4 transcripts (driver + 3 subagents from
   Session 0013.0); `0014/` holds 2 (driver + 1 subagent). Top-level
   `README.md` rewritten with 3 `<!-- operator: rewrite this section
   in your voice -->` markers (sections: "How OHM gets built", "Why
   transcripts are public" — which includes the verbatim
   "Oh and Jan, your joke is in there somewhere." line — and
   "What's in the repo"). Top-level `sessions.json` drafted with 15
   entries, every session titled (no placeholders); titles read off
   each transcript's `Goal:`/`Outcome:` header block. Operator-review
   notes: 0013's title is a semicolon-list of three subagent
   workstreams (may want compression); 0014's title is the
   subagent's phrasing rather than the §1-example verbatim form.
   Filename convention inside each folder: full
   `SESSION-NNNN.M-TRANSCRIPT-…md` form retained (no trim of the
   `SESSION-` prefix). The roadmap example showed trimmed form;
   Session 0017.0 settled on retained-prefix for file-self-
   identification. Document in SPEC.md if operator wants to flip.

# Cut state at handoff

- rfc-app `main`: `69a166a` (merge) → tag `v0.19.0` published; pushed
  to origin + benstull mirror.
- ohm-rfc `main`: `de17e79` — `.rfc-app-version=0.19.0`.
- ohm-rfc-app-flotilla `main`: `417b6c5` (no flotilla code change
  this session).
- wiggleverse/ohm-session-history `main`: `ad01c08` — restructured
  into `NNNN/` folders + README.md + sessions.json at root.
- OHM live: **v0.19.0 healthy** (`/api/health` returns
  `{"version":"0.19.0","status":"ok"}`).
- deploys.id row for v0.19.0: aborted-due-to-SSH-timeout (the
  §19.2 SSE-keepalive pattern fired again; service IS healthy at
  v0.19.0; the aborted row reflects the SSH watchdog falling off,
  not the deploy itself — 4th occurrence; flotilla v1.2.0
  candidate).
- ohm-infra: `scripts/publish-transcript.sh` extended;
  `SESSION-PROTOCOL.md` §1 + §5 amended (NOT in a git repo locally,
  so no commit; the files ARE what they are on disk).
- DMARC Phase A unchanged from Session 0014.0 (`p=none`, `pct=100`,
  TTL=3600s, `rua=mailto:ben.stull@wiggleverse.org`). Watch `rua=`
  mailbox for ≥1 week of clean reports (around 2026-06-04) before
  flipping to `p=quarantine`.
- Two Session-M docs feature branches still awaiting operator merge:
  - rfc-app `feature/contributing-and-spec-analytics` @ `213f686`
  - ohm-rfc `feature/contributing-roadmap-guide` @ `f26fae4`

# What is and isn't proven working

- ✅ OTC sign-in end-to-end (Session 0017.0 operator-confirmed at
  session start — the Wave 7 / v0.18.0 carry-forward gate).
- ✅ v0.19.0 service health (`/api/health` returns 0.19.0).
- ✅ wiggleverse/ohm-session-history restructure (gitea raw probes
  on `sessions.json`, `README.md`, and a sample 0014/ transcript
  all return 200).
- ⏸ End-to-end `/docs/*` surfaces in a real browser **not yet
  operator-confirmed**. Session 0017.0 ended before the operator
  loaded the site. First gesture this session: have the operator
  load these URLs in a fresh browser tab:
  - `https://ohm.wiggleverse.org/docs` → should redirect to
    `/docs/user-guide` (existing v0.14.0 content, no behavior change
    beyond the URL move).
  - `https://ohm.wiggleverse.org/docs/sessions/about` → renders the
    README.md from `wiggleverse/ohm-session-history`. Expect the
    3 operator-review HTML comments to render as nothing on-site
    (one of the §19.2 candidates is to verify this; if they're
    visible as raw text, that's a markdown-renderer bug to fix).
  - `https://ohm.wiggleverse.org/docs/sessions/0009` → session index
    page for Session 0009.0 (the multi-wave driver). Should show
    "Session 0009.0 — multi-wave driver (rfc-app v0.4–v0.13, skipping
    vacants)" as the title from sessions.json.
  - `https://ohm.wiggleverse.org/docs/sessions/0013` → should show
    4 transcripts (driver + 3 subagents) and resolve the right list
    via the per-session `/index` endpoint.
  - `https://ohm.wiggleverse.org/docs/sessions/0014/SESSION-0014.0-TRANSCRIPT-2026-05-28T07-01--2026-05-28T08-25.md`
    → renders Wave 7 driver transcript in-browser.
  - Flyout nav: open from any /docs/* route; on mobile, confirm the
    drawer toggles.
- ⏸ Operator hasn't reviewed the README draft prose or sessions.json
  titles — they're shipped as drafted; refinement is a follow-up.

# Wave 9 candidates (priority is operator's call)

1. **Verify /docs/* surfaces in browser** (above). Five minutes if
   they work; longer if not.
2. **Operator rewrites the 3 README.md placeholder sections** on
   `wiggleverse/ohm-session-history`. Operator-only gesture; commit
   directly to main.
3. **Operator refines sessions.json titles** if desired (0013's
   semicolon-list, 0014's phrasing, or anywhere else); also fills in
   the empty title for 0017 (added as `{"title": ""}` when 0017.0's
   transcript published).
4. **Operator merges the two M docs PRs**.
5. **#1 VM rename + flotilla v1.2.0 SSE-keepalive drain** — the
   §19.2 SSE-keepalive timeout pattern was hit a 4th time this
   session; worth fixing alongside the rename. flotilla v1.2.0
   would pre-drain SSE connections before `systemctl restart` to
   close the watchdog gap.
6. **DMARC Phase A → Phase B (`p=quarantine`)** — ≥1 week of clean
   rua data; ETA 2026-06-04.
7. **#20 ops side**: delete stale `wiggleverse/meta` hook on
   deprovisioned `rfc.wiggleverse.org`.
8. **#18 ops side**: bounce-source wiring (Path A or B in the
   runbook).
9. **#21 Part A Amplitude audit** — defer to ~2026-06-04 for a week
   of post-fix dashboard data (Amplitude + OTC both now confirmed
   working).
10. **#22 pro-consent copy** — operator-drafted + counsel-reviewed;
    subagent wires once approved.
11. **#26-29** — four operator-added items from Session 0014.0
    (propose-RFC fields, Haiku tags, PR cross-references, sign-in
    resume). Each is a clean rfc-app minor.
12. **#17 repo naming alignment** — operator decides Q1 first.

# §19.2 candidates surfaced in Session 0017.0

From subsession 0017.1 (rfc-app v0.19.0):
a. `frontend/package-lock.json` top-level "version" field drifted
   (lockfile reads `0.15.0` while `package.json` is `0.19.0`).
   Cosmetic but ugly. Symptom of no automated lockfile-discipline
   check.
b. rfc-app backend has no `pyproject.toml` — the dispatch assumed
   one. Repo uses `backend/requirements.txt` only. Canonical version
   sources are `VERSION` + `frontend/package.json#version`.
c. `docs_sessions.py`'s private validators (`_is_valid_session_dir`,
   `_is_valid_transcript_filename`) leak into the route layer —
   consider consolidating into a public `validate_path()`.
d. `docs_sessions` in-process cache cannot be invalidated externally
   without a process restart or a `reset_cache()` test seam.
   Operationally fine (TTL is short); developer-experience worth
   improving.

From subsession 0017.2 (session-history restructure):
e. publish-transcript.sh should auto-add `sessions.json` placeholder
   entries on first-publish for a new NNNN. **Already partially
   shipped** in 0017.0's publish-script extension (the upsert logic
   adds `{"NNNN": {"title": ""}}` if the key is missing). Worth
   confirming the contract end-to-end.
f. Contract between README and rfc-app `/docs/sessions/about`
   renderer — operator-review HTML comments (`<!-- ... -->`) should
   render as nothing on-site. Worth confirming in the first-test
   gesture.
g. Local-vs-published shape asymmetry (local flat in
   `~/git/ohm-infra/`, published nested in `NNNN/` folders) is
   already documented in SESSION-PROTOCOL.md §1; worth surfacing as a
   one-line callout if it confuses future drivers.

From driver:
h. The §19.2 SSE-keepalive SIGTERM holding past the 60s SSH watchdog
   — hit AGAIN this session (4th occurrence). flotilla v1.2.0 should
   pre-drain SSE connections before `systemctl restart`. Driver
   verification now leans entirely on `/api/health` post-deploy
   rather than the flotilla exit code.
i. Driver-inline commit identity drift (`benstull@mac.lan` rather
   than `ben@wiggleverse.org`). Per hard rule no `git config` edit
   this session; surfaced for operator tooling.
j. `publish-transcript.sh` `sessions.json` formatting churn. The
   script's inline-Python upsert uses `json.dump`'s default
   `indent=2` (every entry expands to multi-line) and default
   `ensure_ascii=True` (em-dashes / `§` / en-dashes escape to
   `\uXXXX`). 0017.2's subagent had hand-written compact one-line
   entries with raw UTF-8; the first publish-time upsert (0017.0's
   publish, adding the `0017` placeholder) rewrote the file in the
   expanded ASCII-escaped form. Operator's next edit starts from
   that shape. One-line fix: `json.dump(out, f, indent=2,
   ensure_ascii=False)` + a small custom emitter to keep each
   session on one line. Cosmetic only; no functional impact.

# Hard rules carried forward (binding)

- **Never EVER ask the operator to paste secret bytes into the
  conversation.** Always give the operator-run gesture
  (`pbpaste | flotilla secret set ohm-rfc-app <KEY>`). Public
  bundle-embedded values (VITE_*) are fine in-conversation via
  `flotilla overlay set`.
- **§5 subsession-transcript convention** (binding from Session
  0013.0 onward): forked subagents write their own transcripts
  BEFORE returning their report. Driver re-verifies deliverables on
  disk rather than depending on the report-back text.
- **Protocol amendments must land with the code change.**
- **Naming**: this is Session 0018.0. Driver transcript:
  `SESSION-0018.0-TRANSCRIPT-<start>--<end>.md`. Subagent transcripts:
  `SESSION-0018.1-…`, etc.
- **Numeric session ID**: if other sessions are running concurrently,
  ask the operator which IDs are taken before claiming yours.

# First actions for Session 0018.0

1. Read `~/git/ohm-infra/SESSION-PROTOCOL.md` (the §1 + §5
   amendments from Session 0017.0 are the relevant new content).
2. Read `/Users/benstull/projects/wiggleverse/ohm-rfc/ROADMAP.md`
   end-to-end. #30 is the most recent item shipped (strikethrough
   pending — operator may or may not have updated the version-target
   table; if not, do it on the next ohm-rfc edit you make).
3. Read the Session 0017.0 transcript at
   `~/git/ohm-infra/SESSION-0017.0-TRANSCRIPT-2026-05-28T08-50--2026-05-28T09-22.md`
   for the full triage detail (this prompt is the summary).
4. Have the operator load `/docs/sessions/about` and at least one
   `/docs/sessions/<NNNN>/<filename>` in a fresh browser tab. That's
   the load-bearing first test.
5. Pick Wave 9's shippable scope. The roadmap doesn't have an
   explicit **NEXT** marker after #30 ships, so the picking is the
   operator's call.
```
