diff --git a/0029/SESSION-0029.0-TRANSCRIPT-2026-05-28T15-47--2026-05-28T16-29.md b/0029/SESSION-0029.0-TRANSCRIPT-2026-05-28T15-47--2026-05-28T16-29.md
new file mode 100644
index 0000000..98b2f96
--- /dev/null
+++ b/0029/SESSION-0029.0-TRANSCRIPT-2026-05-28T15-47--2026-05-28T16-29.md
@@ -0,0 +1,316 @@
+# Session 0029.0 — Transcript
+
+> Date: 2026-05-28
+> Goal: Advance the OHM roadmap autonomously as a parallel driver session;
+> pick one disjoint item that needs no operator input and doesn't collide
+> with the two in-flight sessions.
+>
+> Outcome: **rfc-app v0.26.0 SHIPPED + DEPLOYED — roadmap #28 Part 1
+> (auto-link accepted RFCs inside PR descriptions + PR/discussion
+> comments). 363 backend tests green, frontend builds clean. Merged to
+> main `698821f`, tagged v0.26.0 (origin + benstull), deployed to OHM as
+> deploys.id=34 — live at ohm.wiggleverse.org/api/health = 0.26.0. The
+> operator authorized merge + deploy mid-session.**
+>
+> Highlights:
+> - Discovered the only buildable rfc-app feature (#28) and scoped it to
+> its clean, non-colliding slice (Part 1).
+> - Deliberate, documented spec deviation: read-time enrichment, not
+> submit-time (§19.3-rule-2 note).
+> - Zero migration (a concurrent session already holds migration 023);
+> XSS-safe structured segments (no `dangerouslySetInnerHTML`).
+
+---
+
+## Pre-session state
+
+- **OHM live**: rfc-app **v0.24.0** on the renamed VM `ohm-rfc-app`
+ (deploys.id stack from Session 0028: v0.22.0→v0.23.0→v0.24.0 landed
+ after the #1 VM-rename blocker cleared). Deploys unblocked, lock free.
+- **ohm-rfc pin** (`.rfc-app-version`): `0.24.0`.
+- **flotilla**: v1.2.0 built + pushed as a PR by Session 0028, NOT merged
+ (operator to open/merge/tag).
+- **Active parallel sessions** at claim time (from `claim-session-id.sh`):
+ **0026** and **0027**, both claimed 2026-05-28T13-46, both `--INPROGRESS`.
+- **rfc-app working dir** (`/Users/benstull/git/rfc-app`): a **shared**
+ checkout, mid-edit on branch `feature/v0.25.0-security-hardening`
+ (created 14:06 today off main/v0.24.0). Uncommitted, unpushed: OTC
+ verify-lockout, in-process rate limiting (`backend/app/ratelimit.py`),
+ HTML/XSS sanitization (`frontend/src/lib/sanitizeHtml.js` +
+ `MarkdownPreview.jsx` / `ProposalView.jsx` / `Editor.jsx`), and
+ **migration 023** (`023_otc_verify_lockout.sql`). This is one of the
+ 0026/0027 sessions' rfc-app side — the v0.25.0 slot is claimed.
+
+---
+
+## Turn-by-turn arc
+
+### Arc 1 — Claim, survey, and the parallel-session hazard
+
+Claimed session ID **0029** via `claim-session-id.sh --start
+2026-05-28T15-47` (placeholder pushed at `286e75f`). Read SESSION-PROTOCOL
+§7; pulled ohm-rfc/ROADMAP.md and read it end to end.
+
+Surveyed the candidate set from the launch prompt against "what can an
+autonomous session actually ship tonight without operator input and
+without colliding with 0026/0027":
+
+- #21 Part A (Amplitude audit) — time-gated to ~2026-06-04 (needs a week
+ of real data). Not now.
+- #22 (pro-consent copy) — blocked on operator-drafted + counsel-reviewed
+ text. Not now.
+- #20 DMARC Phase B — time-gated to ~2026-06-04. #18/#20 ops remainder
+ (stale `wiggleverse/meta` webhook, bounce wiring) is operator/gitea-
+ admin work.
+- #31b / #25-redesign — blocked on operator screenshots.
+- #33 / #34 (name alignment) — operator-led, URL-breaking moves.
+- **#28 (PR cross-references)** — Track B rfc-app feature, deps #3/#7/#12
+ all shipped. The one item buildable with no operator input. Picked.
+
+Then the hazard surfaced: the real rfc-app checkout
+(`/Users/benstull/git/rfc-app`) was a **shared working dir mid-edit** on
+`feature/v0.25.0-security-hardening`. Per the launch prompt's explicit
+warning ("branches alone do NOT isolate concurrent sessions sharing one
+working dir — take a manual git worktree with its own venv"), I created
+an isolated worktree:
+
+```
+git worktree add /Users/benstull/git/rfc-app-s0029 \
+ -b feature/v0.26.0-pr-rfc-links main
+```
+
+…and never touched the shared checkout again. The v0.25.0 slot being
+claimed → I target **v0.26.0**.
+
+### Arc 2 — Scoping #28 to its clean, non-colliding slice
+
+#28 is explicitly "large, its own session" with three parts. I dispatched
+one **read-only Explore agent** to map the PR/comment/RFC surfaces (folded
+into this transcript per §5's driver-fold for read-only helpers — it did
+no commits and warranted no separate `.1` transcript). Key findings:
+
+- **PR descriptions and comments render as plain text** (`PRView.jsx:221`
+ description, `:443` PR conversation; `RFCDiscussionPanel.jsx:282`
+ discussion) — **not** through the `MarkdownPreview` surface the
+ security branch is hardening. **Zero frontend file overlap.**
+- The security branch already created **migration 023**. Decisive signal
+ to make my slice **migration-free** — achievable by enriching at
+ read-time against the live `cached_rfcs` active set rather than
+ persisting links at submit time. That also avoids `main.py` (no new
+ endpoint) and any migration collision.
+- RFC corpus: `SELECT slug, title, rfc_id FROM cached_rfcs WHERE
+ state='active'`. RFC view route is `/rfc/:slug`. Codebase convention
+ for internal nav inside these components is plain `` (e.g.
+ `PRView.jsx:190`), not react-router ``.
+
+Scoped to **Part 1 only** (auto-link existing accepted RFCs), the
+roadmap's recommended "easy win first". Parts 2 + 3 deferred.
+
+### Arc 3 — Implementation (v0.26.0)
+
+Read-time enrichment, migration-free, XSS-safe structured segments:
+
+- **`backend/app/rfc_links.py`** (new): `build_index(conn, exclude_slug=)`
+ → `LinkIndex.segment(text)` → list of `{type:"text"}` /
+ `{type:"rfc", slug, label, title}` segments. Conservative matching to
+ avoid false positives: links only **rfc_id** tokens (`RFC-0001`),
+ **multi-word** titles (whitespace present, e.g. `Open Human Model`), and
+ **hyphenated** slugs (e.g. `open-human-model`). A single common-word
+ title/slug (a hypothetical RFC titled "Human") is **not** linked — that
+ would turn every prose "human" into a link; the roadmap's "curated
+ canonical-terms list" stays an explicit future opt-in. Case-insensitive,
+ word-boundary-anchored, longest-match-wins, self-reference suppressed
+ via `exclude_slug`.
+- **`api_prs.py`** `get_pr()`: builds the index once (exclude current
+ slug), adds `description_segments` and per-comment `text_segments`.
+- **`api_discussion.py`** `get_discussion_thread_messages()`: per-comment
+ `text_segments`.
+- **`frontend/src/components/LinkedText.jsx`** (new): maps segments onto
+ React text nodes + ``. No
+ `dangerouslySetInnerHTML` → XSS-safe by construction, independent of the
+ security branch's `sanitizeHtml.js`. Falls back to raw `text` when
+ segments are absent (older cached responses).
+- Wired into `PRView.jsx` (description + PR conversation bodies) and
+ `RFCDiscussionPanel.jsx` (discussion bodies). `App.css`: a tokenized
+ `.rfc-autolink` rule (accent + dotted underline).
+- `VERSION` → `0.26.0`; `frontend/package.json` → `0.26.0` (the
+ package-lock version field is stale at 0.21.0 across recent releases —
+ left as-is to match convention).
+
+**Deliberate spec deviation** (recorded per §19.3-rule-2): the roadmap row
+says the scan happens "at submit/post time"; I shipped **read-time**
+enrichment. The intent the row actually names — "not as live compose
+preview" — holds (drafts are never scanned). Read-time was chosen for
+correctness (links track the live active set: newly-accepted RFCs start
+linking, withdrawn ones stop), zero migration, and cheapness (the corpus
+is small + cache-resident).
+
+### Arc 4 — Tests, build, ship
+
+- New `backend/tests/test_rfc_links_vertical.py`: 12 tests (9 scanner
+ units covering the gating rules, word boundaries, longest-match,
+ case-insensitivity, casing preservation; 3 end-to-end that PR
+ description + review comment + discussion comment all surface
+ `*_segments`, plus self-reference suppression). Two integration-test
+ fixes mid-flight: the change `original` text must exist verbatim in
+ `SEED_BODY` (else "stale"), and the PR description carries an appended
+ §6.5 `On-behalf-of:` trailer (so assert containment, not equality).
+- **Full backend suite: 363 passed** (351 baseline + 12 new).
+- **Frontend builds clean** (`npm ci` + `npm run build` with a throwaway
+ `.env` providing `VITE_APP_NAME`; the chunk-size warning is pre-existing).
+- Committed `e794523`; pushed `feature/v0.26.0-pr-rfc-links` to **origin**
+ (git.wiggleverse.org) + **benstull** (mirror).
+- ohm-rfc ROADMAP #28 row updated with a "Part 1 built + PR-pushed"
+ status note (commit `0f09403`, pushed to ohm-rfc main) so a future
+ driver doesn't re-pick Part 1.
+
+I left the branch PR-ready and recorded a "built + PR-pushed" status note
+on the ohm-rfc ROADMAP #28 row — initially deferring the merge/deploy to
+the operator per the launch prompt's "coordinate the deploy lock + slot"
+rule.
+
+### Arc 5 — Operator authorized merge + deploy
+
+Mid-session the operator replied: **"go ahead and merge the PR and
+deploy."** So I carried it through, with two safety detours forced by the
+parallel-session environment:
+
+- **Merge.** `main` was not checked out in any worktree (the shared rfc-app
+ checkout holds the security branch with uncommitted work — untouchable).
+ Created a fresh detached worktree on `main`, `git merge --no-ff` the
+ feature branch (merge `698821f`), tagged `v0.26.0` (lightweight, house
+ style), pushed `main` + tag to origin + benstull. Version-slot note:
+ merging v0.26.0 makes the parallel v0.25.0 security session "last to
+ merge" — it renumbers/rebases per the roadmap rule; flagged for the
+ operator.
+- **Deploy.** The canonical flotilla checkout had **uncommitted
+ `deploy.py` changes** — the 0026/0027 secret-redaction WIP on
+ `_PhaseRunner` — and its editable `.venv` runs the working tree. Rather
+ than actuate a production deploy with another session's unmerged code,
+ I deployed from a **clean detached worktree on flotilla `main`
+ (c26f251 = v1.1.0)** with its own venv. The state DB
+ (`~/.ohm-rfc-app-flotilla/`) is a fixed user path, so the worktree saw
+ the registered deployment + deploy log + §8.3 lock.
+- **A scare, resolved.** A `deploy log | tail -12` appeared to show the
+ last *success* as v0.17.0 with three aborted v0.18.0 rows — seemingly
+ contradicting "live at v0.24.0." I stopped and probed ground truth:
+ `deploy status` + a direct curl both returned **0.24.0**. The "missing"
+ rows were a `tail` artifact (the log prints newest-first; `tail` hid
+ ids 26–34). No real discrepancy. (Lesson: probe live health before
+ trusting a truncated log view.)
+- **Deploy.** Dry-run confirmed the plan (target v0.26.0, 7 secret refs
+ resolve incl. the already-bound `ANTHROPIC_API_KEY`, renamed VM
+ `/opt/ohm-rfc-app`, no migration step). Real deploy: all 9 phases ok,
+ **deploys.id=34**, health verify in 7.3s — no SSE-keepalive abort
+ despite deploying with pre-v1.2.0 flotilla (the intermittent failure
+ didn't trigger). Live `/api/health` = `0.26.0`; `pin check` matches.
+- Updated the ohm-rfc ROADMAP #28 row to **Part 1 ✅ SHIPPED + DEPLOYED**
+ (commit `b4d39a8`). Removed all three temporary worktrees.
+
+---
+
+## Cut state (end of session)
+
+| Repo / surface | State |
+| --- | --- |
+| rfc-app | `main` @ `698821f` (merge), tag **v0.26.0** pushed to origin + benstull. 363 tests green, FE builds. Feature commit `e794523`. |
+| ohm-rfc | `main` @ `b4d39a8` — pin bumped to **0.26.0**, ROADMAP #28 Part 1 struck SHIPPED + DEPLOYED. |
+| OHM live | **v0.26.0** on `ohm-rfc-app`, healthy (`/api/health`=0.26.0). **deploys.id=34** succeeded (9/9 phases, verify 7.3s). |
+| Worktrees | All three temporary worktrees (rfc-app feature, rfc-app main-merge, flotilla deploy) removed. |
+
+| #28 ledger | Status |
+| --- | --- |
+| #28 Part 1 (auto-link accepted RFCs) → v0.26.0 | ✅ shipped + deployed (deploys.id=34) |
+| #28 Part 2 (offer-to-create-RFC) | ⏸ not started |
+| #28 Part 3 (offer-to-contribute-to-pending) | ⏸ not started |
+
+---
+
+## §19.2 / process candidates surfaced
+
+1. **Branch-chat auto-linking.** Part 1 enriches PR descriptions, PR
+ review comments, and PR-less discussion comments — but not branch-chat
+ messages (the AI-collaboration surface, `ChatPanel.jsx` +
+ `api_branches.py`). Extending `rfc_links` there is a small follow-up if
+ branch chat warrants it.
+2. **Curated canonical-terms / per-RFC alias list.** The conservative
+ matcher deliberately skips single common-word titles/slugs. The roadmap
+ names a "curated canonical-terms list" for exactly these; it's left as
+ an explicit opt-in (config/content), not guessed at in framework code.
+3. **rfc-app test deps aren't in `requirements.txt`.** `pytest` /
+ `httpx` / `pytest-asyncio` had to be pip-installed into the fresh
+ worktree venv by hand (the shared checkout's venv had them). A
+ `requirements-dev.txt` (or an extras group) would make a clean worktree
+ test-ready in one step. Minor ergonomic gap, like the `PYTHONPATH=.`
+ one CONTRIBUTING already notes.
+
+---
+
+## What lands on the operator's plate
+
+1. **v0.26.0 is shipped + deployed (deploys.id=34) — nothing left here.**
+ Worth a real-world eyeball: open a PR or post a comment on OHM that
+ mentions an accepted RFC (RFC-0001 / "Open Human Model") and confirm
+ the inline auto-link renders to `/rfc/`.
+2. **The parallel v0.25.0 security-hardening session (0026/0027) is now
+ "last to merge."** Because v0.26.0 landed first, that session must
+ rebase onto a `main` that's already at v0.26.0 and **renumber to
+ v0.27.0** (its target slot is taken). Expect a trivial top-of-file
+ CHANGELOG/VERSION resolution; the code regions are disjoint from
+ v0.26.0's (their MarkdownPreview/otc/ratelimit vs. my rfc_links/PRView/
+ discussion).
+3. **The local flotilla `deploy.py` working tree still has the 0026/0027
+ secret-redaction WIP uncommitted** — I deployed from clean `main`
+ (v1.1.0) and left their WIP untouched. Nothing for you unless you want
+ to land it.
+4. (Carried from 0028) flotilla v1.2.0 PR still open + unmerged — the
+ phase-7 SSE pre-drain it carries would have de-risked this deploy's
+ restart phase (which happened to succeed without it).
+5. **Parts 2 + 3 of #28** remain unbuilt — the larger follow-up.
+
+---
+
+## Prompt the operator can paste into the next Claude Code session
+
+```
+OHM roadmap driver session, parallel-safe. Claim your ID first:
+ ~/git/ohm-infra/scripts/claim-session-id.sh --start
+It prints active --INPROGRESS sessions — coordinate. Read SESSION-PROTOCOL §7
++ pull ohm-rfc/ROADMAP.md.
+
+STATE (end of 0029, 2026-05-28): OHM live at rfc-app **v0.26.0** on
+ohm-rfc-app (deploys.id=34, healthy), pin=0.26.0, deploy lock free.
+Session 0029 shipped + DEPLOYED #28 Part 1 (auto-link accepted RFCs in PR
+text + comments). Still built-but-unmerged / in-flight:
+ - flotilla v1.2.0 (Session 0028) — PR open, not merged (its phase-7 SSE
+ pre-drain de-risks deploy restarts; the v0.26.0 deploy succeeded
+ without it).
+ - rfc-app v0.25.0 security-hardening (Sessions 0026/0027) — uncommitted
+ in the shared /Users/benstull/git/rfc-app checkout (+ uncommitted
+ deploy.py redaction WIP in the flotilla checkout). Because v0.26.0
+ merged first, this session is now "last to merge" → it must rebase
+ onto main@v0.26.0 and **renumber to v0.27.0** (slot 0.25.0 superseded).
+ Code regions are disjoint from v0.26.0's.
+
+PICK ONE disjoint item. Open candidates: #28 Parts 2+3 (offer-to-create-RFC;
+offer-to-contribute-to-pending — the larger follow-up, its own session, and
+it will rebase on whatever v0.25.0's MarkdownPreview/sanitize work settled);
+#21 Part A Amplitude audit (real data ~2026-06-04); #22 pro-consent copy
+(operator-drafted + counsel text); #20/#18 ops (DMARC Phase B ~2026-06-04;
+stale wiggleverse/meta webhook still pointing at deprovisioned
+rfc.wiggleverse.org; bounce wiring); #31b/#25-redesign (operator
+screenshots); #33/#34 repo + GCP name alignment (operator-led).
+AVOID shipped/in-flight surfaces: #26/#27/#29/v0.21.0; the v0.25.0 security
+files (otc.py, ratelimit.py, MarkdownPreview/ProposalView/Editor.jsx,
+sanitizeHtml.js, migration 023); and v0.26.0's surfaces (rfc_links.py,
+LinkedText.jsx, get_pr/discussion enrichment).
+
+Hard rules: never ask for secret bytes (give the
+`printf '%s' "$(pbpaste)" | .venv/bin/ohm-rfc-app-flotilla secret set
+ohm-rfc-app ` gesture); coordinate the serialized deploy lock + version
+slot with the operator; if you build in rfc-app, take a manual git worktree
+with its own venv (the shared /Users/benstull/git/rfc-app checkout may be
+mid-edit by another session); subagents write their own §5 subsession
+transcripts; finalize + publish your transcript LAST (deliver the handoff
+prompt before publish). Use branches + PRs. Claim your ID, then go.
+```
diff --git a/0029/SESSION-0029.0-TRANSCRIPT-2026-05-28T15-47--INPROGRESS.md b/0029/SESSION-0029.0-TRANSCRIPT-2026-05-28T15-47--INPROGRESS.md
deleted file mode 100644
index 2500139..0000000
--- a/0029/SESSION-0029.0-TRANSCRIPT-2026-05-28T15-47--INPROGRESS.md
+++ /dev/null
@@ -1,15 +0,0 @@
-# Session 0029.0 — Transcript
-
-> Date: 2026-05-28
-> Start: 2026-05-28T15-47 (PST implied)
-> Status: **PLACEHOLDER — claimed at session start; finalized at session end.**
->
-> This file reserves session ID 0029. The driver replaces this body
-> with the full transcript before publishing, and renames the file to
-> its final SESSION-0029.0-TRANSCRIPT-2026-05-28T15-47--.md form.
-
----
-
-## Launch prompt
-
-_(launch prompt not captured at claim time)_