# Session 0033.0 — Transcript

> Date: 2026-05-28
> Start: 2026-05-28T19-44 (PST implied) · End: 2026-05-28T20-14
> Goal: Advance the OHM roadmap by one disjoint item, parallel-safe.
> Picked the recommended item: **#28 Parts 2 + 3** — offer-to-create-an-RFC
> for strong-candidate terms, and offer-to-contribute-to-a-pending-RFC.
>
> Outcome: **rfc-app v0.29.0 shipped + deployed + verified live on
> ohm.wiggleverse.org (deploys.id=40).** All three #28 parts are now live.
> One additive migration (024, `contribution_requests`); no config /
> overlay / secret / nginx / systemd change. 374 backend tests green (9
> new); frontend builds clean. Merged as PR #3 (`8a94e26`), tagged
> `v0.29.0`, pushed origin + benstull mirror; ohm-rfc pin 0.28.0 → 0.29.0;
> ROADMAP #28 row struck through (all three parts shipped).

---

## Launch prompt

OHM roadmap driver session, parallel-safe. Claim ID first; read
SESSION-PROTOCOL §7 + ohm-rfc/ROADMAP.md. State at hand-off (end of 0031):
OHM at v0.27.0; flotilla v1.2.0 merged+tagged. Recommended item: **#28
Parts 2 + 3** → target v0.28.0 (possibly split). Avoid conflicting with
session 0032 (then running) and v0.27.0's fresh security surfaces. Hard
rules: never ask for secret bytes; fresh worktree+venv for rfc-app builds;
coordinate the serialized deploy lock + version slot; FF-merge via gitea
API; finalize+publish transcript LAST, handoff prompt before publish.

---

## Plan / TODO

- [x] Claim session ID (→ 0033), read SESSION-PROTOCOL §7 + ROADMAP.
- [x] Reconcile stale brief state vs reality (0032 finished; v0.28.0 shipped).
- [x] Explore the #28 surfaces (scanner, propose flow, inbox, invite, roles, Haiku).
- [x] Isolated rfc-app worktree + venv off main; baseline tests green.
- [x] Part 2 — create-RFC offers (scanner candidate bucket + propose pre-fill + gating).
- [x] Part 3 — contribute-to-pending (scanner pending bucket + request flow + inbox actions + accept→invite + decline).
- [x] Tests (9 new), full backend suite, frontend build.
- [x] CHANGELOG + VERSION + frontend mirror.
- [x] Commit → PR #3 → FF-merge → tag v0.29.0 → push origin + benstull.
- [x] ohm-rfc pin bump → deploy → verify live.
- [x] ROADMAP #28 row struck through; worktree cleaned up.
- [x] Finalize + publish transcript (this file).

---

## Pre-session state vs. reality (the brief was stale)

The launch brief said OHM was live at **v0.27.0** and that session 0032
was *actively running*; it also implied v0.28.0 was un-tagged/un-deployed.
By session start the picture had moved:

- **0032 had finished** (transcript `…18-52--19-13`). Its work: IAP-only
  SSH hardening, flotilla v1.2.0 re-verify, orphaned-db cleanup, and
  rfc-app **v0.28.0** (audit I3 HTML-email guard + I4 async Turnstile)
  opened as PR #2. The only `--INPROGRESS` placeholders left were stale
  **0026 / 0027** (their security work already shipped as v0.27.0 — the
  brief said to ignore them).
- The operator had since **merged, tagged, AND deployed v0.28.0**: live
  `/api/health` returned `version=0.28.0`, the ohm-rfc pin was `0.28.0`,
  and tag `v0.28.0` existed on main. This removed the version-slot
  ambiguity the brief worried about — there was no un-tagged predecessor
  to reconcile, so this session simply took the next slot, **v0.29.0**.

Claimed session **0033** via `claim-session-id.sh --start
2026-05-28T19-44` (push to ohm-session-history is the atomic claim).

---

## Scope decisions (documented in code, CHANGELOG, and ROADMAP)

Two interpretive calls shaped the build; both were made to match Part 1's
conservative, false-positive-averse philosophy and were written into the
code docstrings so they aren't re-litigated later:

1. **"Pending RFC" = a super-draft** (`cached_rfcs.state='super-draft'`),
   not a pre-merge idea PR. A super-draft is in `cached_rfcs` (so the #12
   `rfc_invitations` FK the accept path reuses resolves), is owned
   (`owners_json` — routes the request, names the owner), and has a
   contribution surface to open. Pre-merge idea PRs satisfy none of those
   cleanly, so they're deliberately out of scope — a documented future
   extension.
2. **Part 2 candidate heuristic = multi-word #27-taxonomy tags with no
   defining RFC.** Multi-word is the same guard Part 1 uses for titles; a
   single common tag word (`identity`) would turn every prose occurrence
   into an affordance. The roadmap sanctions "a heuristic OR the Haiku
   path"; the cheap deterministic tag heuristic was chosen, with
   capitalized-phrase mining / repeated-term detection / the #27 Haiku
   (`ANTHROPIC_API_KEY`) pathway noted as sanctioned-but-deferred
   extensions (mirroring how Part 1 deferred the curated canonical-terms
   list).

---

## Turn-by-turn arc

### Arc 1 — Claim + orient
Claimed 0033. Read SESSION-PROTOCOL §7, ROADMAP (#28 detail + operating
instructions), and 0032's transcript. Confirmed live OHM = v0.28.0, pin =
0.28.0, deploy lock free, no competing active sessions.

### Arc 2 — Explore (two parallel Explore agents)
Mapped the surfaces #28 Parts 2+3 touch: the role model (`require_contributor`
gates propose; `permission_state='granted'` is "has create rights"); the
RFC lifecycle (idea-PR-open → super-draft → active); the notification /
§15 inbox system (`_emit_one` chokepoint, `render_summary`, kinds, the
recipient-scoped inbox); and the #12 owner-invite flow (`rfc_invitations`
+ `rfc_collaborators`, accept writes the membership row). Verified
migrations top out at 023 → **024 free**.

### Arc 3 — Backend build (isolated worktree + venv)
Took a git worktree off main on branch
`feature/v0.29.0-pr-rfc-create-contribute` with its own `.venv`; baseline
63 tests green. Then:
- **`rfc_links.py`** rewritten to a three-bucket scanner (`Term` namedtuple
  + `kind` ∈ active/pending/candidate; precedence active > pending >
  candidate then longest-match-first). Kept backward-compat with the legacy
  `(key, slug, title)` 3-tuple so all Part 1 unit tests pass untouched.
  `build_index` now also gathers super-draft terms (with resolved owner
  display) and candidate tags.
- **`notify.py`** — `fan_out_contribution_request` (one actionable
  notification per owner) + `notify_contribution_decided`; three new
  `render_summary` branches; the inbox list item now carries `extras`
  (recipient-scoped, so safe) for inline who/why/use-case.
- **`api_invitations.py`** — extracted the invite issue path into one
  reusable `issue_invitation(...)` chokepoint shared by the manual
  endpoint and Part 3's accept.
- **`api_contributions.py`** (new) — the GET target + request + accept +
  decline endpoints; accept fires `issue_invitation` (role contributor),
  reusing an existing pending invite if one exists.
- **migration 024** — additive `contribution_requests` table (partial
  unique index = one open request per (RFC, user)).
- Registered the new router in `api.py`.

### Arc 4 — Frontend build
`LinkedText.jsx` renders the two new segment kinds as query-param-routed
affordances (no prop-threading from deep in comment lists): `rfc-candidate`
→ `?propose=<term>` (gated on create rights), `rfc-pending` → `?contribute=
<slug>&term=<term>` (gated on signed-in). `App.jsx` reads the params and
opens `ProposeModal` (new `initialTitle`) / the new `ContributeRequestForm`
(3-field). `Inbox.jsx` gained a `ContributionRequestRow` — the first
actionable inbox kind — showing who/why/use-case with Accept/Decline.
`api.js` + CSS additions. `viewer`/`canCreate` threaded into the three
LinkedText call sites (PRView ×2, RFCDiscussionPanel). Frontend builds
clean.

### Arc 5 — Tests
3 scanner-bucket unit tests + 6 end-to-end (PR-view segment surfacing;
request→accept→invite→notify; decline; owner-cannot-self-request;
active-RFC-rejected; eligibility). **Full backend suite 374 passing.**

### Arc 6 — Release + deploy
VERSION + frontend mirror → 0.29.0; CHANGELOG entry with RFC-2119 upgrade
steps. Committed; pushed branch; opened **PR #3** via gitea API; FF-merged
(`8a94e26`); tagged `v0.29.0`; pushed tag + main to **origin and the
benstull mirror** (the VM fetches rfc-app from the mirror — tag must be
there before deploy). Bumped ohm-rfc pin 0.28.0 → 0.29.0, pushed. Ran
`flotilla deploy ohm-rfc-app` — all 9 steps OK, **deploys.id=40**, verify
4.6s. Live checks: `/api/health` = `0.29.0`; the new
`/api/rfcs/{slug}/contribution-target` route returns the handler's
`{"detail":"RFC not found"}` (route wired); `GET /api/rfcs` still 200.

### Arc 7 — Record + cleanup
ROADMAP #28 row struck through (all three parts shipped, with the
deploys.id=40 + v0.29.0 link + the scope decisions); committed + pushed to
ohm-rfc. Worktree removed; shared `/Users/benstull/git/rfc-app` checkout
fast-forwarded to the merge.

---

## Friction / notes

- The brief's pre-state was stale (OHM was already on 0.28.0, 0032 done).
  Verifying live state + tags first — rather than trusting the brief —
  avoided a phantom version-slot reconciliation.
- No secret bytes were needed or requested (this release adds no secret/
  config). Hard rule held trivially.
- `issue_invitation` extraction is a small §19.2-style consolidation: the
  manual invite endpoint and the new accept path now share one chokepoint,
  so the dup-guard / token / email stay identical.

---

## Cut state (end of session)

- **OHM live:** `https://ohm.wiggleverse.org/api/health` → `version=0.29.0`,
  `status=ok`. Deploy lock free.
- **rfc-app:** `main` = `8a94e26` (PR #3 merge); tag `v0.29.0` on origin +
  benstull. 374 backend tests green.
- **ohm-rfc:** pin `.rfc-app-version` = `0.29.0`; ROADMAP #28 struck
  through. `main` pushed (`62929b2`).
- **deploys.id=40** records the v0.29.0 deploy.

---

## Operator-plate (manual gestures, if any)

**None.** v0.29.0 needed no secret/config/overlay/nginx/systemd change;
the additive migration 024 ran automatically during the deploy. Nothing is
waiting on the operator.
