# Session 0013.0 — Transcript

> Date: 2026-05-28
> Goal: Drive Wave 6 — three Track Ω inventory / draft subsessions in
> parallel. No rfc-app feature releases this wave; the wave's job is
> to surface the operator-led gestures that the post-Wave-5 plate
> needs (DNS edits, repo renames, counsel-reviewed copy) and to land
> docs that make OHM's contribution surface honest.
>
> Outcome: **All three subsessions produced their deliverables.**
> M.1 wrote docs feature branches in rfc-app + ohm-rfc (CONTRIBUTING
> + SPEC §21 analytics chapter) — pushed, awaiting operator PR
> review. M.2 wrote a 33 KB repo-naming inventory + move proposal
> with six open questions for the operator. M.3 wrote a deliverability
> + webhook hardening runbook + a rfc-app v0.18.0 framework-hygiene
> proposal — and surfaced one potentially-load-bearing finding:
> SMTP_HOST may be missing from the live flotilla overlay (every OHM
> email path silently broken if so). M.1 hit a stream-watchdog stall
> on the report-back step AFTER writing its transcript + pushing both
> feature branches — recovery was driver-reads-transcript-direct, no
> re-dispatch.
>
> First session running under the §5 subsession-transcript amendment
> (Session-L addition to SESSION-PROTOCOL.md). Three forked
> subagents pre-assigned M.1 / M.2 / M.3; each writes its own
> `SESSION-M.<n>-TRANSCRIPT-…md` before returning a tight report to
> the driver. Driver had to extend the publish script's filename +
> header validators mid-session — Session 0012.0 documented the §5
> validator extension but never landed it in the script (Arc 7).
> 14 §19.2 candidates surfaced across the three subs + driver.

---

## Pre-session state

**Time**: opened 2026-05-28T05-26 PST (Thursday, just-pre-dawn —
operator handed off the wheel after Session 0012.0's three-deploy
Wave 5 finish at 05:17 PST and is sleeping). Session 0012.0's main
transcript is at
`~/git/ohm-infra/SESSION-0012.0-TRANSCRIPT-2026-05-28T04-22--2026-05-28T05-17.md`.

**Repos at session open:**

| Repo | State at open |
| --- | --- |
| `rfc-app` (`~/git/rfc-app`, branch `main`) | clean tree, HEAD at `1456c8b` (Release 0.17.0: admin-create user + invite email + #21 Part C). Tags through `v0.17.0`. |
| `ohm-rfc` (`~/projects/wiggleverse/ohm-rfc`, branch `main`) | clean tree, HEAD at `47cb2f9` (ROADMAP strike of #12 / #13 / #16). `.rfc-app-version` = `0.17.0`. |
| `ohm-rfc-app-flotilla` (`~/projects/wiggleverse/ohm-rfc-app-flotilla`, branch `main`) | clean tree, HEAD at `65c0e55` (CLAUDE.md: correct the secret-set gesture). v1.0.1. |
| `ohm-infra` (`~/git/ohm-infra/`) | Plain directory (not a local git repo). Transcripts A–L published to `wiggleverse/ohm-session-history` via `scripts/publish-transcript.sh`. SESSION-PROTOCOL.md at the §5-amended version. |

**OHM live state**: `ohm.wiggleverse.org` serves rfc-app v0.17.0,
deploys.id=21 (last deploy from Session 0012.0). v0.13.0 cookie banner +
v0.14.0 /docs route + v0.15.0 Amplitude analytics & session replay +
v0.16.0 per-RFC owner invites + v0.17.0 admin-create user invites
all live in production for the first time. #21 Part C identity-
lifecycle wiring (identify-with-properties on sign-in,
setUserProperties on mid-session changes, amplitude.reset on
sign-out, identify-BEFORE-track on invite-claim paths) shipped
inline with the v0.15.0 / v0.16.0 / v0.17.0 release commits.

**Roadmap state**: #2–#16 + #21 Part C all shipped (struck through
in `~/projects/wiggleverse/ohm-rfc/ROADMAP.md`). Wave 6 candidates
open:
- #20 Email deliverability (HIGH priority — invites are now the
  recipient's first contact with OHM, spam-foldering is silent
  invite failure)
- #18 Secure SMTP relay + Gitea webhook (bundles with #20 — shared
  DNS edits)
- #19 CONTRIBUTING guides + transcript-linked onboarding
- #21 Part B — SPEC analytics chapter + CONTRIBUTING checklist
  (rides #19)
- #17 Repo naming + location alignment (operator-led; subagent
  inventories + proposes)
- #1 VM rename (operator-led)
- #22 Pro-analytics-consent copy (BLOCKED — waiting on operator-
  drafted + counsel-reviewed copy)
- #21 Part A — deep audit of v0.15.0 taxonomy + autocapture +
  replay quality (BLOCKED — needs ~1 week of Amplitude data; v0.17.0
  is <12 hours old)

**Housekeeping noticed at open**:
- A stale `SESSION-0012.0-TRANSCRIPT-2026-05-28T04-22--INPROGRESS.md`
  (4.3 KB) sits next to Session 0012.0's final transcript at
  `~/git/ohm-infra/`. The final transcript is the load-bearing one
  (was published). The INPROGRESS placeholder is leftover.
  Recommend the operator delete it (or driver deletes at the end
  of M); not load-bearing for Session 0013.0's work.
- Three rfc-app worktrees from Session 0012.0 still on disk under
  `~/git/`:
  - `rfc-app-v0.15.0-amplitude/`
  - `rfc-app-v0.16.0-owner-invite/`
  - `rfc-app-v0.17.0-admin-create-user/`
  Each release has shipped and merged to `main`; the worktrees are
  no longer load-bearing. Could be cleaned up via
  `git worktree remove` when the operator next runs through housekeeping.

---

## Dispatch plan

**Wave 6** runs as three forked subagents, each in its own subsession,
each writing its own transcript per §5 of the protocol. All three
are Track Ω (docs + ops inventory / proposal) — they touch
non-overlapping files in non-overlapping repos, so they run safely
in parallel without merge-conflict risk. None ship code that
runs on OHM in this wave; each produces a feature branch with a
draft + a recommendation block for the operator to review.

| Sub | Brief | Repo(s) touched | Output |
| --- | --- | --- | --- |
| **M.1** | #19 CONTRIBUTING guides bundled with #21 Part B (SPEC.md analytics chapter + CONTRIBUTING analytics checklist) | `rfc-app` (CONTRIBUTING.md + SPEC.md) + `ohm-rfc` (CONTRIBUTING.md) | Feature branches in both repos with drafts; ready for operator PR review |
| **M.2** | #17 inventory of every repo under `git.wiggleverse.org` + `git.benstull.org` with current name, canonical location, mirror status, and proposed name. Drafts a move-proposal doc. No renames executed. | `ohm-infra` (write proposal here) + read-only across all Wiggleverse / benstull repos | A `REPO-NAMING-PROPOSAL.md` (or similar) for operator to approve |
| **M.3** | #20 deliverability + #18 SMTP/webhook hardening combined inventory. Reads current DNS posture, rfc-app `backend/app/email_*.py`, ohm-infra SMTP/webhook config, Gitea webhook auth surfaces. Drafts a hardened-shape runbook + a rfc-app template-hygiene minor proposal. | `ohm-infra` (runbook) + read-only on `rfc-app` (drafts the proposal as a doc, NOT as code changes) | A hardening runbook + an rfc-app template-hygiene proposal doc |

**Why no rfc-app code release this wave**: every Wave 6 candidate
either (a) is docs / inventory / proposal that lands as a PR the
operator merges, no version bump, or (b) requires operator-led
gestures (DNS edits, counsel review, rename execution) that can't
happen autonomously. Wave 6 is the "set up Wave 7" wave —
deliverables here become the operator's plate, and Wave 7 picks up
the rfc-app minors (template hygiene, pro-consent copy) and the
ops moves (DNS, renames) that fall out.

**Dispatch contract for each subagent** (per §5.2):
1. Pre-assigned subsession ID named in the dispatch prompt.
2. Subagent MUST write `~/git/ohm-infra/SESSION-M.<n>-TRANSCRIPT-<start>--<end>.md`
   before returning a report.
3. Subagent does NOT push code that ships on OHM. Branches only;
   operator merges.
4. Subagent does NOT execute operator-led gestures (no DNS edits,
   no repo renames, no SMTP provider config changes).
5. Subagent reports back with: branch + commit SHAs, the draft's
   one-paragraph PR description, any §19.2 candidates surfaced.

---

## Turn-by-turn arc

### Arc 1 — Opening + Wave 6 plan + parallel dispatch

Operator handed off the wheel at 05:20 PST with a thorough brief
explicitly listing Wave 6 candidates and the new §5 subsession-
transcript dispatch shape. Read SESSION-PROTOCOL.md and ROADMAP.md
end-to-end. Identified six Wave-6-eligible items; three blocked
or operator-only (#22 / #21 Part A / #1), three dispatchable now
(#19 + #21B / #17 / #20+#18). Bundling rationale: #21 Part B
explicitly rides #19's CONTRIBUTING PRs per the roadmap; #18 and
#20 explicitly ship together because the same DNS edits serve both.

Pre-state snapshot above. Then opened this transcript at
`SESSION-0013.0-TRANSCRIPT-2026-05-28T05-26--INPROGRESS.md` and
dispatched the three subagents in parallel at 2026-05-28T05-30 PST.

Each subagent's dispatch prompt named:
- the subsession ID (M.1 / M.2 / M.3)
- the transcript filename to write at session end
- the binding constraints (never-secret-bytes; no-operator-led-
  gestures; no force-push / `--amend` / `git config`)
- the read-only inventory boundary (no DNS edits, no repo
  renames, no flotilla deploy, no SMTP provider changes)
- the deliverable shape and the report-back format

Dispatch summary:

| Sub | agent state | Scope |
| --- | --- | --- |
| **M.1** | running (background) | `rfc-app/CONTRIBUTING.md` + `rfc-app/SPEC.md` analytics chapter + `ohm-rfc/CONTRIBUTING.md`. Feature branches in both repos. |
| **M.2** | running (background) | `REPO-NAMING-PROPOSAL.md` — inventory + move proposal across `git.wiggleverse.org` + `git.benstull.org`. No renames executed. |
| **M.3** | running (background) | `EMAIL-AND-WEBHOOK-HARDENING-RUNBOOK.md` + `RFC-APP-EMAIL-HYGIENE-PROPOSAL.md`. Read-only DNS / flotilla-overlay / rfc-app code inventory. No DNS or SMTP changes. |

Driver waits for each completion notification, folds the report
+ subsession-transcript reference into Arc 2 / Arc 3 / Arc 4 as
they return. Cut-state table written after all three are in.

### Arc 2 — M.2 returns: #17 repo naming inventory

M.2 completed at 2026-05-28T05-37 PST (~7 min). Full detail:
`SESSION-0013.2-TRANSCRIPT-2026-05-28T05-30--2026-05-28T05-37.md`.

**Inventory totals:** 9 repos on `git.wiggleverse.org` (4 under
`ben.stull/`, 5 under `wiggleverse/`); 3 on `git.benstull.org`
(under `benstull/`: 1 real sha-synced mirror, 1 stub, 1 empty);
9 local-only working dirs catalogued in the proposal.

**Surfaced friction not in roadmap #17:**
- Three-meta-repos collision (`wiggleverse/meta` +
  `wiggleverse/ohm-meta` + `wiggleverse/rfc-0001-human` —
  the monorepo `ohm-rfc` has won in practice; three repos for
  one job).
- Push-mirror description drift between `ben.stull/rfc-app` and
  `benstull/rfc-app`.
- A `ben` username alias on git.wiggleverse.org creating a
  1-hop redirect chain alongside the canonical `ben.stull`.
- `~/git/ohm-infra/` is a plain directory, not a git repo —
  matches what the driver caught at session open. M.2 surfaces
  it as a §19.2 candidate (see below).

**Top moves proposed (execution order from the proposal doc):**
1. Archive `wiggleverse/meta` (vestigial; zero-risk warmup).
2. Archive `wiggleverse/ohm-meta` + `wiggleverse/rfc-0001-human`
   (requires Q1 operator confirm — per-RFC repos vs. monorepo).
3. Transfer `ben.stull/ohm-rfc` → `wiggleverse/ohm-rfc` — medium
   risk; affects flotilla SPEC (~10 places), 4 test files
   hardcode `ben/ohm-rfc`, and flotilla's SQLite registry stores
   it as `pin_source_repo` + `corpus_repo` — **operator must run a
   flotilla update post-move or the next deploy chases a 2-hop
   Gitea redirect.**
4. Transfer `ben.stull/rfc-app` → `wiggleverse/rfc-app` — largest
   blast radius. Flotilla SPEC §3.1 has a *conceptual* paragraph
   explaining why rfc-app gets a benstull mirror — that rationale
   dissolves under org-namespace and needs rewriting. Good news:
   no shipped frontend artifacts reference these URLs, so end-
   users see nothing.
5. Archive `benstull/rfc-app-flotilla` stub (empty placeholder
   that contradicts SPEC §3.1's "no benstull mirror for flotilla"
   rule).

**§19.2 candidates surfaced by M.2:**
1. Stop relying on Gitea path aliases — resolve canonical paths
   at registry-update time so flotilla's SQLite registry doesn't
   silently chase Gitea redirects after a transfer.
2. Concrete `flotilla-core` reservation slot (the
   `benstull/rfc-app-flotilla` stub repo is an accidental artifact
   of this need).
3. Promote `~/git/ohm-infra/` to a real git repo — currently a
   plain directory; transcripts publish to the remote
   `wiggleverse/ohm-session-history` but the runbooks, proposals,
   and protocol doc that live in ohm-infra have no version
   control.

**Open questions for the operator** (the proposal lists six;
the load-bearing ones):
- **Q1**: per-RFC repos vs monorepo — settles whether step 2 above
  happens at all, and may reshape the proposal entirely.
- **Q2**: mirror destination after rfc-app moves.
- **Q3**: fate of the `benstull/rfc-app-flotilla` stub (the
  proposal recommends archive; operator confirm).
- **Q4**: promote ohm-infra/ to a real repo (yes? then where?).
- **Q5**: remove the `ben` username alias.
- **Q6**: backup ohm-infra/ before execution session begins.

**Artifacts written by M.2:**
- `~/git/ohm-infra/REPO-NAMING-PROPOSAL.md` (~33 KB).
- `~/git/ohm-infra/SESSION-0013.2-TRANSCRIPT-2026-05-28T05-30--2026-05-28T05-37.md`.

Zero remote git operations. No Gitea admin actions. Clean
inventory + proposal pass per scope.

### Arc 3 — M.3 returns: #20 + #18 deliverability & webhook hardening

M.3 completed at 2026-05-28T05-58 PST (~28 min). Full detail:
`SESSION-0013.3-TRANSCRIPT-2026-05-28T05-30--2026-05-28T05-58.md`.

**Load-bearing finding (top of operator's plate)**: `SMTP_HOST`
and `SMTP_USER` are MISSING from the live flotilla overlay. Only
`SMTP_PORT=587`, `SMTP_STARTTLS=1`, `EMAIL_FROM`, `EMAIL_FROM_NAME`
are present. Either the bootstrap was reshaped post-run and these
dropped, OR they live on the VM out-of-band via systemd override.
**If neither holds, every OHM email path is silently failing
right now** (OTC, beta-access notifications, per-RFC owner invites,
admin-create user invites — all four paths added across v0.7.0 →
v0.17.0). Operator disambiguates next session via
`gcloud ssh ohm-app` + `systemctl show ohm-app.service | grep Environment`.

**Current DNS posture (`wiggleverse.org`, literal values):**
- SPF: `v=spf1 include:_spf.google.com ~all` (soft-fail)
- DMARC: `v=DMARC1; p=none; rua=mailto:ben@wiggleverse.org`
  (monitoring-only, no enforcement)
- DKIM: present at `google._domainkey.wiggleverse.org`
  (2048-bit Workspace key)
- MX: standard Google Workspace `aspmx.l.google.com` family

**SMTP provider identified:** Google Workspace SMTP relay
(`smtp-relay.gmail.com`) — inferred from bootstrap + DKIM + MX
(no direct confirmation since `SMTP_HOST` is missing from the
overlay).

**Top 3 deployment-side hardening gestures (priority order):**
1. **Delete stale `wiggleverse/meta` Gitea hook** — still active,
   points at `http://rfc.wiggleverse.org/api/webhooks/gitea`
   (host was deprovisioned 2026-05-27, plain HTTP). 5-second
   `curl DELETE`. Trivial cleanup.
2. **Disambiguate the SMTP_HOST gap** (see load-bearing finding
   above). Highest user impact if email is actually silently
   broken.
3. **DMARC Phase A** — extend the existing `p=none` record with
   `pct=100; sp=quarantine; adkim=s; aspf=s`, collect `rua` data
   for 1 week, flip to `p=quarantine`. Conservative enforcement
   ramp.

**Top 3 framework-side changes (target rfc-app v0.18.0):**
1. **`build_envelope` shared helper** — adds `Date`, `Message-ID`,
   `Auto-Submitted: auto-generated`, `List-Unsubscribe` +
   `List-Unsubscribe-Post` (per RFC 8058), and
   `multipart/alternative` text+html to all four email paths
   (`email_otc.py`, `email_invite.py`, `email.py:_deliver`,
   `email.py:_send_bundle`). Today: NONE of these headers are
   set; bodies are plain-text-only; Gmail's one-click-unsubscribe
   enforcement is unsatisfied for bulk-adjacent watcher
   notifications.
2. **Make `GITEA_WEBHOOK_SECRET` required, not optional.**
   `config.py:75` is `_optional`; `webhooks.py:43` silently
   accepts unsigned POSTs when empty. Insecure-by-default for any
   deployment that forgets the binding. Add `RFC_APP_INSECURE_WEBHOOKS=1`
   dev-bypass for local dev only.
3. **`outbound_emails` audit table + write on every send.**
   Today failures log-and-forget; operator has zero visibility
   without grepping VM logs. The audit table seam doubles as a
   place to wire bounce-handling later.

**Webhook posture audit:** signature verification is correct
(`hmac.compare_digest`, HMAC-SHA-256 over raw body) **when the
secret is set** — and on OHM it is. Two structural issues:
(a) silent-accept-when-unset default; (b) no replay-window
(currently mitigated by idempotent cache refreshes, so not yet
exploited but worth a §19.2 candidate or follow-up rfc-app minor).

M.3 also noted: `wiggleverse/rfc-0001-human` has NO Gitea webhook
configured (the only published RFC's content changes only reach
OHM via the reconciler). Operator confirms whether this is
intentional. Bot lacks admin on `ben.stull/ohm-rfc` (the pin
source), so M.3 couldn't self-check that one.

**§19.2 candidates surfaced by M.3 (5):**
1. rfc-app: `GITEA_WEBHOOK_SECRET` should be required.
2. rfc-app: outbound-email observability seam (audit table).
3. flotilla: `overlay show` doesn't surface expected-but-missing
   keys; needs `overlay validate` verb.
4. flotilla: lacks a `webhook list/check` verb (generic, would
   land in `flotilla-core` after extraction per §19.2).
5. rfc-app: From-identity spec to bind `EMAIL_FROM` consumers +
   DMARC + Workspace allowed-senders to one source.

**Artifacts written by M.3:**
- `~/git/ohm-infra/EMAIL-AND-WEBHOOK-HARDENING-RUNBOOK.md` (~21 KB).
- `~/git/ohm-infra/RFC-APP-EMAIL-HYGIENE-PROPOSAL.md` (~9 KB).
- `~/git/ohm-infra/SESSION-0013.3-TRANSCRIPT-2026-05-28T05-30--2026-05-28T05-58.md`.

Read-only DNS / flotilla-overlay / rfc-app code inventory. Zero
DNS edits, zero overlay set, zero code commits. Clean per scope.

### Arc 4 — M.1 returns (with a stall): #19 + #21B docs landed

M.1 hit the stream watchdog at 600s-no-progress and the agent was
killed before it could return its report. Its own transcript says
end-time 05:40 PST — meaning it wrote the transcript ~10 minutes
into its run, then spent another ~50 minutes in a "sanity check
before reporting back" phase that produced no streamable output and
got watchdog-killed.

Per protocol §5.5 the driver MAY write a stub if a subagent fails to
produce a transcript. M.1 did produce a transcript (full 460-line
detailed shape, all the synthesis the driver needs); the stall was
on the report-back-to-driver step. Driver chose **no re-dispatch**:
all deliverables landed cleanly on disk, both feature branches
pushed to canonical (rfc-app to origin + benstull, ohm-rfc to
origin), tree clean. Recovery was: read M.1's transcript directly
+ switch the two repos back to `main` (M.1 left them on the feature
branches) + fold synthesis into this transcript.

**Pre-state addendum surfaced by M.1's own pre-state**: ohm-rfc
`main` was at `8cce148` when M.1 opened at 05:30, but this
driver's pre-state at 05:26 (above) recorded ohm-rfc `main` at
`47cb2f9`. The operator pushed `8cce148 ROADMAP: +#23 (rename
transcripts SESSION-<letter> → SESSION-NNNN.M)` to ohm-rfc/main in
the 4-minute gap between session-open and M.1 dispatch. Recorded
honestly here per the protocol's no-curation rule — this is the
Session-L "don't batch roadmap captures to end-of-session" lesson
applied in real time. **Roadmap item #23 now exists** (transcript-
filename rename) and is captured on ohm-rfc/main outside of any
Session-M subagent's scope.

**M.1 deliverables (all on feature branches, none merged):**

| | |
| --- | --- |
| `rfc-app` `feature/contributing-and-spec-analytics` | `213f686 docs: CONTRIBUTING.md + SPEC.md §21 analytics chapter`. Pushed to `origin` + `benstull`. |
| `ohm-rfc` `feature/contributing-roadmap-guide` | `f26fae4 CONTRIBUTING: add roadmap-change guide + operator-only boundary`. Pushed to `origin`. |

`rfc-app/CONTRIBUTING.md` is new, ~370 lines, covering: PR shape,
strict-descending CHANGELOG convention, RFC 2119 `Upgrade steps:`
discipline, §19.2 candidate practice, test-coverage expectations
characterized from the real ~250-test backend suite, the operator-
only-gestures boundary (with the hard no-secret-bytes rule cited
from Session 0011.0 Arc 6), and the #21 Part B analytics-instrumentation
checklist as a dedicated section. Worked-example links to Sessions
E (clean small release), I (deploy-fault recovery), K (multi-feature
wave + paused-on-secret), L (squash-merge across three parallel
features + #21 Part C inline). Links to the repo root not specific
transcript filenames — anticipating the #23 rename.

`rfc-app/SPEC.md` gets new §21 "Analytics instrumentation and
identity", ~440 lines, structured §21.1 event-taxonomy → §21.2
required prop families → §21.3 autocapture-friendly DOM patterns
→ §21.4 session-replay masking → §21.5 consent-gate contract →
§21.6 identity lifecycle (#21 Part C) → §21.6.1 set vs setOnce
taxonomy → §21.7 cohort-shape implications → §21.8 secret-vs-public
framing → §21.9 §19.2 candidates → §21.10 open question. RFC 2119
keywords used precisely throughout. The starting v0.17.0 taxonomy
(13 events) laid out verbatim.

**Placement decision the driver should weigh in on:** M.1 settled
inline on placing the new chapter as §21 (after §20 versioning)
rather than §16 (the naively-correct insertion-after-§15
notifications). Rationale: §19.2 is a load-bearing project noun
(in CLAUDE.md, SESSION-PROTOCOL.md §3, 12 published transcripts,
historical commit messages) — renumbering would cascade across all
of those. §15 now carries a forward-pointer naming §21 as the peer
cross-cutting analytics chapter. The driver agrees with M.1's call:
the right §16 numbering belongs to a future cross-repo cleanup
session, NOT a docs-PR. Flagged as §19.2 candidate #1 from M.1
(see below).

`ohm-rfc/CONTRIBUTING.md` extends the existing 130-line RFC-content
guide (preserved verbatim) with a new "Proposing a change to the
OHM roadmap" section (~80 added lines). Covers: roadmap-change
kinds, ohm-session-history pointer + worked-example links into
Sessions J/K/L roadmap reshuffles, PR-submission gesture with the
**anonymous-PR open question SETTLED inline** (see Arc 5 below),
the operator-only-gestures boundary phrased with the "I'm the only
one that gets to yolo" quote, the two-layer rule.

### Arc 5 — Anonymous-PR question settlement (M.1 inline)

M.1 probed `https://git.wiggleverse.org/user/sign_up` and got HTTP
200 with body containing `<p>Registration is disabled. Please
contact your site administrator.</p>`. Definitive answer.

The ohm-rfc CONTRIBUTING text settles on two contributor paths:
Issue-first (default; the operator or next driver session turns it
into a roadmap entry or opens discussion in the issue thread), or
request-account-from-operator (for repeat contributors). Phrased so
the operator can edit down to one path if registration policy
flips later.

### Arc 6 — Recovery + finalize

Driver switched rfc-app + ohm-rfc back to `main` (M.1 left them on
feature branches; `git checkout main` on each — clean, "up to date
with origin/main"). Marked M.1 task completed with a description
recording the stall + recovery decision. Wrote this turn-by-turn,
cut state, §19.2 ledger, operator plate, next-session prompt.
Renamed transcript INPROGRESS → final timestamp. About to publish
all four transcripts (M + M.1 + M.2 + M.3) via
`~/git/ohm-infra/scripts/publish-transcript.sh` per §5.6.

### Arc 7 — Publish-script validator extension (in-scope plumbing)

First attempt to publish M.1 failed:
`filename does not match SESSION-<letter>-TRANSCRIPT[-<start>--<end>].md`.
Investigation revealed a **protocol-vs-reality discrepancy**: both
the operator's brief ("the publish script's filename validator was
extended in Session 0012.0 to accept `SESSION-<letter>.<n>-TRANSCRIPT-…md`")
and SESSION-PROTOCOL.md §5.6 ("The publish script's filename
validator accepts the `SESSION-<letter>.<n>-TRANSCRIPT-…md` form
(extended Session-L, see `scripts/publish-transcript.sh`)") said
the extension was already in place — but the actual regex in
`scripts/publish-transcript.sh` at L127 was still
`^SESSION-[A-Za-z]+-TRANSCRIPT(-${_TS}--${_TS})?\.md$`, and the
header check at L140 only accepted `Transcript` / `transcript`
endings, not `Subsession transcript`. The extension was documented
in Session 0012.0's protocol amendment but never landed in the script.

Driver made minimal-scope edits to fix:
- Filename regex: extended to `^SESSION-[A-Za-z]+(\.[0-9]+)*-TRANSCRIPT(-${_TS}--${_TS})?\.md$` (accepts `.N` and `.N.N` for hypothetical sub-subagents per §5.2's nested convention).
- Header regex: extended to accept `Subsession transcript` and `— Subsession transcript` forms; allow `.N` after the session letter.
- Updated the comment block at L123 and L131-135 to document the subsession form.
- Updated the error message at L128 to name `[.<n>]` explicitly.

Dry-ran M.1 to verify both validators pass against
`# Session 0013.1 — Subsession transcript`. Header OK. Then published
M.1 → M.2 → M.3 → M serially.

**Note on scope**: this script edit is in-scope plumbing for §5 being
operational at all — Session 0013.0 is the first session under §5, so the
validator gap had to be fixed before the wave could complete. The
change is small (regex extensions + comment/message updates only,
no logic changes), commented in the script, and the dry-run gesture
confirmed correctness before the real push. The script lives in
`~/git/ohm-infra/`, which is **not under version control** per M.2's
§19.2 candidate #7 — so this script change is on local disk only
with no history. That candidate is now more salient.

This driver-surfaced issue is captured below as §19.2 candidate #14
(reconcile protocol amendments with actual code changes — the
amendment should fail review if the code change isn't also present).

### Arc 8 — Post-publish close (operator-confirmed at ~07:00 PST)

After Arc 7's validator extension + serial publish of all four
transcripts (M.1 @ `7c7c478` → M.2 @ `5dad1b2` → M.3 @ `2007cf3`
→ M @ `61698af`), driver re-published M with the Arc 7 + §19.2
candidate #14 + script-edit-disclosure additions (final commit
`6248f88` on `wiggleverse/ohm-session-history` `main`).

Operator (returned ~07:00 PST, not asleep) confirmed close and
asked for: (1) the next-session prompt surfaced verbatim in chat
for easy copy, (2) the transcript updated with this final
exchange. Driver replied with the prompt printed in full (no
substantive change from the version embedded in this transcript
below in the "Prompt the operator can paste…" section; reproduced
verbatim for ergonomics), then added this Arc 8 + re-published
per the Session-L "transcript is the final session step" feedback
memory ("re-finalize if more work happens after").

Final state of this session matches the cut-state table below.
No code shipped to OHM. Two docs feature branches and three
ohm-infra/-local proposal/runbook docs are the deliverables that
become Wave 7's plate.

---

## Cut state (end of session, 2026-05-28T06-50 PST)

| | |
| --- | --- |
| `rfc-app` | `main` unchanged at `1456c8b` (Release 0.17.0). New branch `feature/contributing-and-spec-analytics` at `213f686` pushed to `origin` + `benstull` — awaiting operator PR review. Local tree on `main`, clean. |
| `ohm-rfc` | `main` advanced from `47cb2f9` → `8cce148` mid-session (operator pushed #23). New branch `feature/contributing-roadmap-guide` at `f26fae4` pushed to `origin` — awaiting operator PR review. `.rfc-app-version` unchanged at `0.17.0`. Local tree on `main`, clean. |
| `ohm-rfc-app-flotilla` | unchanged at `65c0e55`. v1.0.1. |
| OHM live | unchanged from Session 0012.0 cut — `deploys.id=21`, v0.17.0. No deploy gesture this session. |
| `ohm-infra` (local dir) | 4 new files this session: this transcript + M.1/M.2/M.3 subsessions. Plus 2 inventory/runbook docs from M.2 (`REPO-NAMING-PROPOSAL.md`) and 2 from M.3 (`EMAIL-AND-WEBHOOK-HARDENING-RUNBOOK.md`, `RFC-APP-EMAIL-HYGIENE-PROPOSAL.md`). |

| Wave 6 ledger | Status |
| --- | --- |
| #19 CONTRIBUTING guides (rfc-app + ohm-rfc) | 🟡 drafts pushed on feature branches; awaiting operator PR review + merge |
| #21 Part B SPEC.md §21 chapter + CONTRIBUTING checklist | 🟡 drafted on rfc-app feature branch (rides #19's commit); awaiting operator review |
| #17 Repo naming + location alignment (inventory + proposal) | 🟡 proposal doc written at `~/git/ohm-infra/REPO-NAMING-PROPOSAL.md`; six open questions for operator; execution session deferred |
| #20 Email deliverability inventory | 🟡 runbook written; one load-bearing finding (SMTP_HOST possibly missing from overlay); awaiting operator disambiguation + DNS edits |
| #18 SMTP relay + Gitea webhook hardening inventory | 🟡 runbook same as #20 (bundled); webhook code-side proposal in `RFC-APP-EMAIL-HYGIENE-PROPOSAL.md`; awaiting future rfc-app v0.18.0 minor + ops gestures |
| #21 Part A audit | ⏸ blocked — needs ~1 week of Amplitude data; v0.17.0 is <12h old |
| #22 Pro-analytics-consent copy | ⏸ blocked — waiting on operator-drafted + counsel-reviewed copy |
| #1 VM rename | ⏸ operator-led ops gesture |
| #23 Transcript filename rename (`SESSION-<letter>` → `SESSION-NNNN.M`) | ✅ added to ROADMAP (ohm-rfc/main `8cce148`, operator pushed mid-session) |

No rfc-app release shipped this wave; that's Wave 6's intended shape
(set-up wave — deliverables become Wave 7's plate). The plate is
heavy and clearly-named.

---

## §19.2 candidates surfaced this session

From **M.1**:
1. **SPEC.md §16..§20 renumbering vs. §21-at-end placement.** The
   chapter went in as §21 not §16 to preserve the §19.2 / §19.3
   cross-reference graph; a future cleanup session can do the
   numerically-clean §16 insertion + cascade across CLAUDE.md
   files + SESSION-PROTOCOL.md + 12 transcripts + auto-memory.
2. **Centralized email-hash helper** (SHA-256 of normalized lower-
   cased email for #12-style per-RFC `target_email` hashing).
3. **Property-shape CI lint** for `track(` / `identify(` callsites
   (allowlist of prop keys, denylist of PII names).
4. **Frontend test runner** (no `vitest`/`jest`/`playwright` today;
   the CONTRIBUTING file documents the gap honestly).

From **M.2**:
5. **Stop relying on Gitea path aliases** — resolve canonical paths
   at registry-update time so flotilla's SQLite registry doesn't
   silently chase Gitea redirects after a transfer.
6. **Concrete `flotilla-core` reservation slot** — the
   `benstull/rfc-app-flotilla` stub is an accidental artifact of
   this latent need.
7. **Promote `~/git/ohm-infra/` to a real git repo** — currently a
   plain directory; transcripts publish to the remote but runbooks,
   proposals, and the protocol doc have no local version control.

From **M.3**:
8. rfc-app: `GITEA_WEBHOOK_SECRET` should be **required**, not
   `_optional`. Insecure-by-default for any deployment that forgets
   the binding.
9. rfc-app: **outbound-email observability seam** (`outbound_emails`
   audit table + write on every send). Today failures log-and-
   forget; doubles as bounce-handling hook later.
10. flotilla: `overlay show` doesn't surface expected-but-missing
    keys; needs an `overlay validate` verb. (The session's own
    headline finding — possibly-missing SMTP_HOST — would have been
    a one-command detection if this existed.)
11. flotilla: lacks a `webhook list/check` verb (generic — would
    land in `flotilla-core` after §19.2 extraction).
12. rfc-app: **From-identity spec** to bind `EMAIL_FROM` consumers
    + DMARC + Workspace allowed-senders to one source.

Driver-surfaced (this transcript):
13. **Subagent stream-watchdog stall pattern.** M.1 finished its
    work + transcript at minute ~10 but spent ~50 more minutes in a
    quiet "sanity check" phase before the watchdog killed it. The
    session-protocol §5.3 instruction says transcript-write happens
    BEFORE report-back — that's load-bearing, since the report-back
    is where the agent dies. Worth a protocol clarification:
    "transcript first; report-back is the only thing after; do NOT
    add a 'final sanity check' phase between — sanity-check
    findings go into the transcript." Or alternatively, a
    documented expectation that the driver always re-verifies
    deliverables on disk and doesn't depend on the report-back text
    alone.
14. **Protocol amendments must land with the code change.** Session 0012.0
    documented in SESSION-PROTOCOL.md §5.6 that the publish script's
    validator was extended to accept `SESSION-<letter>.<n>-TRANSCRIPT-…md`,
    and the Session 0013.0 brief repeated the claim. Reality at session
    open: the script's regex hadn't been modified. Driver had to
    extend it mid-Session-M to publish the subsession transcripts at
    all (see Arc 7). Worth a discipline: an amendment to the
    protocol that claims a code change MUST also include the code
    change in the same merge (or be marked "pending implementation"
    in the protocol text). Plus: amendment review should diff the
    protocol claim against the named code surface and reject if
    they don't match. This is a Session 0013.0 lesson; would land in
    SESSION-PROTOCOL.md §6 ("when the protocol is unclear") as a
    sub-paragraph.

---

## What lands on the operator's plate

In rough priority order (driver's read):

### URGENT — investigate before any further Wave 7 work

1. **Disambiguate the SMTP_HOST / SMTP_USER overlay gap** (M.3
   finding). Run `gcloud ssh ohm-app` + `systemctl show
   ohm-app.service | grep Environment` to check whether they're set
   via systemd override. If not, OHM has been silently dropping
   every email (OTC, beta-access notifications, per-RFC invites,
   admin-create invites) — meaning v0.7.0 / v0.9.0 / v0.16.0 /
   v0.17.0's user-facing email features are degraded since deploy.
   Send a manual test through the admin "Create user + invite"
   flow as a probe.

### HIGH — review + merge the docs PRs

2. **rfc-app PR** for `feature/contributing-and-spec-analytics` at
   `213f686`. Suggested PR description in
   `SESSION-0013.1-TRANSCRIPT-…md` What-the-driver-needs-to-know #6.
   Open on:
   - `https://git.wiggleverse.org/ben.stull/rfc-app/pulls/new/feature/contributing-and-spec-analytics`
   - `https://git.benstull.org/benstull/rfc-app/pulls/new/feature/contributing-and-spec-analytics`
   Operator decides §21-vs-§16 placement — driver concurs with M.1's
   §21 call (don't break the §19.2 cross-reference graph).

3. **ohm-rfc PR** for `feature/contributing-roadmap-guide` at
   `f26fae4`. Description also in M.1's transcript. Open on:
   - `https://git.wiggleverse.org/ben.stull/ohm-rfc/pulls/new/feature/contributing-roadmap-guide`

### MEDIUM — schedule + decide

4. **#17 repo naming proposal** at `~/git/ohm-infra/REPO-NAMING-PROPOSAL.md`.
   Six open questions in the proposal that need operator input
   before the execution session can be scoped. Q1 (per-RFC repos
   vs monorepo) may reshape the proposal entirely; resolve first.

5. **#20 + #18 deliverability runbook** at
   `~/git/ohm-infra/EMAIL-AND-WEBHOOK-HARDENING-RUNBOOK.md`.
   Once #1 above is settled, this is the priority-ordered list of
   operator gestures. Top three:
   (a) `curl DELETE` the stale `wiggleverse/meta` Gitea webhook
   pointing at deprovisioned `http://rfc.wiggleverse.org`;
   (b) DMARC Phase A (`pct=100; sp=quarantine; adkim=s; aspf=s`);
   (c) confirm `wiggleverse/rfc-0001-human` has no webhook (M.3
   noted absence — intentional or oversight?).

6. **rfc-app v0.18.0 minor** is now the natural next release —
   carries the framework-side hardening from M.3:
   `build_envelope` shared helper (Date/Message-ID/Auto-Submitted/
   List-Unsubscribe headers + multipart text+html); required
   `GITEA_WEBHOOK_SECRET`; `outbound_emails` audit table.
   Proposal doc at `~/git/ohm-infra/RFC-APP-EMAIL-HYGIENE-PROPOSAL.md`.

### LOW / informational

7. **#23 transcript filename rename** is on ROADMAP now (operator
   pushed `8cce148`). This session is the last to use the
   `SESSION-<letter>` convention before #23 ships; future sessions
   will name as `SESSION-NNNN.M` per the new spec.

8. **Stale Session-L INPROGRESS file** at
   `~/git/ohm-infra/SESSION-0012.0-TRANSCRIPT-2026-05-28T04-22--INPROGRESS.md`
   (4.3 KB). Final L transcript is published; this is a placeholder
   that can be deleted. Driver did NOT delete (operator's call).

9. **Three rfc-app worktrees from Session 0012.0** still on disk:
   `~/git/rfc-app-v0.15.0-amplitude/`, `~/git/rfc-app-v0.16.0-owner-invite/`,
   `~/git/rfc-app-v0.17.0-admin-create-user/`. All shipped + merged.
   Cleanup via `git worktree remove` at the operator's convenience.

10. **§19.2 candidate #13** (subagent stream-watchdog stall pattern)
    is a candidate protocol clarification for SESSION-PROTOCOL.md §5
    — see the candidates list above.

11. **`scripts/publish-transcript.sh` was edited mid-session** to
    actually extend the filename + header validators for the §5
    subsession form (Session 0012.0 documented the extension but never
    landed it). The script lives in `~/git/ohm-infra/` which is
    not version-controlled, so the change has no commit history.
    Driver-applied diff: regex `^SESSION-[A-Za-z]+-TRANSCRIPT…`
    → `^SESSION-[A-Za-z]+(\.[0-9]+)*-TRANSCRIPT…`; header regex
    extended to accept `Subsession transcript` and `— Subsession
    transcript` endings plus `.N` after the letter. Worked
    correctly for all three subsession transcripts. Captured as
    §19.2 candidate #14 (protocol amendments must land with the
    code change). M.2's §19.2 candidate #7 (promote ohm-infra/ to
    a real git repo) becomes more salient — this script change is
    not under version control.

---

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

```
You are the OHM roadmap driver. The previous session (Session 0013.0)
ran Wave 6 as three Track Ω inventory/draft subsessions in parallel
(M.1 / M.2 / M.3) under the new §5 subsession-transcript convention.

**What Session 0013.0 produced:**
- Two docs feature branches awaiting your PR review:
  - rfc-app `feature/contributing-and-spec-analytics` at `213f686`
    (CONTRIBUTING.md + SPEC.md §21 analytics chapter, lands #19 +
    #21 Part B; placed §21 not §16 to preserve the §19.2 cross-
    reference graph — driver concurs with M.1's call)
  - ohm-rfc `feature/contributing-roadmap-guide` at `f26fae4`
    (CONTRIBUTING extension covering roadmap changes + operator-
    only boundary)
- Three new docs on disk at `~/git/ohm-infra/`:
  - `REPO-NAMING-PROPOSAL.md` (#17 inventory + proposal; six open
    questions need your input)
  - `EMAIL-AND-WEBHOOK-HARDENING-RUNBOOK.md` (#18 + #20 deployment-
    side gestures, prioritized)
  - `RFC-APP-EMAIL-HYGIENE-PROPOSAL.md` (#18 + #20 framework-side
    minor proposal — target rfc-app v0.18.0)

**URGENT to investigate FIRST**: M.3 found `SMTP_HOST` + `SMTP_USER`
missing from the live flotilla overlay. If they aren't being set
via systemd override on the VM, every OHM email path (OTC,
beta-access notifications, per-RFC invites, admin-create invites
— v0.7.0/v0.9.0/v0.16.0/v0.17.0) has been silently dropping mail
since the relevant deploy. First gesture: `gcloud ssh ohm-app` +
`systemctl show ohm-app.service | grep Environment` to check. If
broken, send a probe through the "admin create user + invite"
flow.

**Wave 7 candidates (priority order):**
1. **Investigate + fix SMTP gap** (above) — possibly an ops-only
   gesture if systemd override is sufficient; possibly an rfc-app
   minor if the email-send code needs a defensive change.
2. **Operator reviews + merges the two docs PRs from Session 0013.0**
   (no version bump for either; lands as no-bump docs commits).
3. **rfc-app v0.18.0** — framework-side hardening from
   `RFC-APP-EMAIL-HYGIENE-PROPOSAL.md`: `build_envelope` shared
   helper (Date/Message-ID/Auto-Submitted/List-Unsubscribe headers
   + multipart text+html for all four email paths); make
   `GITEA_WEBHOOK_SECRET` required (no longer `_optional`);
   `outbound_emails` audit table. Add `RFC_APP_INSECURE_WEBHOOKS=1`
   dev-bypass for local dev only.
4. **#17 execution session** — needs Q1 (per-RFC repos vs monorepo)
   answered first; then operator runs the moves per
   `REPO-NAMING-PROPOSAL.md`.
5. **#20 deployment-side gestures** — DMARC Phase A (`pct=100;
   sp=quarantine; adkim=s; aspf=s`), then ramp to `p=quarantine`
   after a week of `rua` data; delete stale `wiggleverse/meta`
   Gitea hook pointing at deprovisioned rfc.wiggleverse.org.
6. **#22** — operator drafts + counsel-reviews the pro-analytics-
   consent copy; subagent wires it in once approved.
7. **#21 Part A** — earliest reasonable target ~2026-06-04 (one
   week of Amplitude data post-v0.17.0). Defer until then.
8. **#1 VM rename** — operator-led ops gesture, schedule a
   maintenance window.
9. **#23 transcript filename rename** — on ROADMAP at `8cce148`;
   pick a session to execute the rename across all 13 published
   transcripts + the publish script's filename validator.

**Hard rules carried forward from Sessions K + L + M:**
- Never EVER ask the operator to paste secret bytes into the
  conversation. Always give the operator-run gesture (`pbpaste |
  flotilla secret set <deployment> <KEY>`). Public bundle-embedded
  values (VITE_AMPLITUDE_API_KEY, VITE_TURNSTILE_SITE_KEY) are fine
  in-conversation via `flotilla overlay set`.
- Subagent stream-watchdog stalls (Session 0013.0 new): subagents
  MUST write their transcript BEFORE returning a report (already
  in §5.3) — and SHOULD NOT add a "final sanity check" phase
  between transcript-write and report-back (where M.1 died).
  Driver should always re-verify deliverables on disk rather
  than depend on the report-back text alone.
- §5 subsession-transcript convention is binding from Session 0013.0
  onward.

**Cut state at handoff:**
- rfc-app `main` at `1456c8b` (v0.17.0)
- ohm-rfc `main` at `8cce148` (#23 added by operator mid-Session-M)
- ohm-rfc-app-flotilla `main` at `65c0e55` (v1.0.1)
- ohm-rfc `.rfc-app-version` = `0.17.0`
- OHM live: deploys.id=21, v0.17.0
- Two feature branches awaiting your PR review (above)
- Next session letter: **N**

Read `~/git/ohm-infra/SESSION-PROTOCOL.md` and
`/Users/benstull/projects/wiggleverse/ohm-rfc/ROADMAP.md`
end-to-end. Then pick Wave 7's shippable scope.
```


---

(Cut state, §19.2 candidates, operator plate, and next-session
prompt all written at session-end.)
