#30: per-session folder layout + README + sessions.json
Restructures the flat repo into one folder per session, with a top-level README.md (the "about sessions" page, also rendered at /docs/sessions/about by rfc-app v0.19.0+) and a sessions.json title manifest keyed by 4-digit NNNN. Filenames inside each folder retain the full SESSION-NNNN.M-TRANSCRIPT-… form (no trim of the SESSION- prefix). Legacy letter-form transcripts, if any are still at the root, are not moved — the folder convention applies to the numeric form only. Session 0017.0; see SESSION-PROTOCOL.md §1 amendment for the binding shape.
This commit is contained in:
@@ -0,0 +1,803 @@
|
||||
# Session M — 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 L 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 L's three-deploy
|
||||
Wave 5 finish at 05:17 PST and is sleeping). Session L's main
|
||||
transcript is at
|
||||
`~/git/ohm-infra/SESSION-L-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 L). 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-L-TRANSCRIPT-2026-05-28T04-22--INPROGRESS.md`
|
||||
(4.3 KB) sits next to Session L'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 M's work.
|
||||
- Three rfc-app worktrees from Session L 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-M-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-M.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-M.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-M.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-M.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 K 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 L 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 L'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 M.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 M 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 L 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 L
|
||||
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 M 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 M 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-M.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-L-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 L** 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 L 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 M)
|
||||
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 M 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 M**
|
||||
(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 M 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 M
|
||||
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.)
|
||||
@@ -0,0 +1,460 @@
|
||||
# Session M.1 — Subsession transcript
|
||||
|
||||
> Parent: `SESSION-M-TRANSCRIPT-2026-05-28T05-26--<end>.md`
|
||||
> Date: 2026-05-28
|
||||
> Goal: Draft `rfc-app/CONTRIBUTING.md`, the `rfc-app/SPEC.md` analytics chapter,
|
||||
> and the `ohm-rfc/CONTRIBUTING.md` roadmap-guide extension — landing roadmap
|
||||
> items #19 (CONTRIBUTING guides + transcript-linked onboarding) and #21 Part B
|
||||
> (standing analytics instrumentation discipline). Push feature branches; do
|
||||
> NOT tag, do NOT bump VERSION, do NOT move the pin, do NOT deploy.
|
||||
>
|
||||
> Outcome: **All three deliverables shipped on feature branches; both branches
|
||||
> pushed to canonical remotes; one open question settled inline.**
|
||||
>
|
||||
> - `rfc-app feature/contributing-and-spec-analytics` at `213f686` —
|
||||
> pushed to `origin` (git.wiggleverse.org) + `benstull`
|
||||
> (git.benstull.org). One commit: CONTRIBUTING.md (new, ~370 lines) +
|
||||
> SPEC.md §21 (new chapter, ~470 lines including the new §15 forward-
|
||||
> pointer to §21).
|
||||
> - `ohm-rfc feature/contributing-roadmap-guide` at `f26fae4` — pushed
|
||||
> to `origin` (git.wiggleverse.org, the only remote). One commit:
|
||||
> CONTRIBUTING.md extended with a new "Proposing a change to the OHM
|
||||
> roadmap" section (~80 added lines on top of the existing 130-line
|
||||
> RFC-content guide).
|
||||
> - Open question on Gitea anonymous-PR posture: **settled inline** —
|
||||
> `https://git.wiggleverse.org/user/sign_up` returns "Registration is
|
||||
> disabled. Please contact your site administrator." The
|
||||
> ohm-rfc CONTRIBUTING text reflects this with two paths (Issue-first
|
||||
> default + request-account for repeat contributors) and is phrased so
|
||||
> the operator can update if registration policy changes.
|
||||
|
||||
---
|
||||
|
||||
## Pre-subsession state
|
||||
|
||||
- **rfc-app**: `main` at `1456c8b` (Release 0.17.0). Tags through
|
||||
v0.17.0. No CONTRIBUTING.md existed. `SPEC.md` 4302 lines, chapter
|
||||
sequence §1..§20.
|
||||
- **ohm-rfc**: `main` at `8cce148`. `.rfc-app-version` = `0.17.0`.
|
||||
`CONTRIBUTING.md` existed (130 lines, RFC-content guide); no
|
||||
roadmap-change section.
|
||||
- **ohm-infra (local)**: most recent transcripts Session L
|
||||
(`SESSION-L-TRANSCRIPT-2026-05-28T04-22--2026-05-28T05-17.md`) +
|
||||
Session M's main transcript in-progress
|
||||
(`SESSION-M-TRANSCRIPT-2026-05-28T05-26--INPROGRESS.md`).
|
||||
- **Branches at start**: both repos on `main`, both clean working trees.
|
||||
|
||||
---
|
||||
|
||||
## Turn-by-turn arc
|
||||
|
||||
### Arc 1 — Reconnaissance
|
||||
|
||||
Read in order: `ohm-rfc/ROADMAP.md` (#19 at L405-456, #21 at L549-731,
|
||||
particularly Part B at L625-655 and Part C at L657-731);
|
||||
`ohm-rfc/CONTRIBUTING.md` (existing 130-line file — RFC-content
|
||||
oriented, no roadmap-change section); `ohm-infra/SESSION-PROTOCOL.md`
|
||||
(§1 naming, §2 transcript shape, §5 subagent-transcript amendment —
|
||||
binding for this subsession);
|
||||
`ohm-infra/SESSION-K-TRANSCRIPT-…md` (whole file — clean-small-
|
||||
release + multi-feature-wave + paused-on-secret patterns; the
|
||||
Arc-6 hard-rule incident around secret bytes);
|
||||
`ohm-infra/SESSION-L-TRANSCRIPT-…md` (whole file — squash-merge
|
||||
integration with #21 Part C identity-lifecycle wiring folded inline
|
||||
across three releases; the addendum cycles around "transcript is the
|
||||
LAST step" discipline);
|
||||
`rfc-app/SPEC.md` selectively (top, §15 around L2259-2732, §16 + §17
|
||||
boundaries, §19 around L3183, §20 ending around L4132-4302); the §15
|
||||
→ §16 transition was the candidate insertion-point for the new
|
||||
analytics chapter;
|
||||
`rfc-app/CHANGELOG.md` opening + the v0.17.0 / v0.16.0 / v0.15.0
|
||||
entries (lines 1-413) for the strict-descending convention and
|
||||
RFC 2119 keyword usage examples;
|
||||
`rfc-app/frontend/src/lib/analytics.js` end-to-end (the v0.15.0
|
||||
wrapper extended in Session L with `{ user_id, properties }` identify
|
||||
signature, `applyProperties` with `__setOnce__` sentinel,
|
||||
`setUserProperties`, anonymize-clears-cache);
|
||||
`rfc-app/frontend/src/App.jsx` lines 40-90 (the App-level identify
|
||||
call with viewer-derived `set` + `setOnce` property bag);
|
||||
`rfc-app/frontend/src/components/InviteClaim.jsx` lines 40-90 (the
|
||||
v0.17.0 identify-BEFORE-track pattern for the admin-create invite
|
||||
claim).
|
||||
|
||||
### Arc 2 — Placement decision for the SPEC chapter (settled inline)
|
||||
|
||||
The roadmap (and the dispatch brief) said "Place AFTER §15
|
||||
notifications" and "numbering becomes §16 or whatever's next
|
||||
available." The straightforward read is "insert as §16 and renumber
|
||||
existing §16..§20 to §17..§21." A grep for `§16` through `§20`
|
||||
references in SPEC.md surfaced 50+ cross-references across the file,
|
||||
many of them to `§19.2` and `§19.3` specifically.
|
||||
|
||||
§19.2 specifically is a load-bearing project noun: it's the
|
||||
defer-decisions queue, named that way in CLAUDE.md, in
|
||||
`SESSION-PROTOCOL.md` §3, in every session transcript ("§19.2
|
||||
candidates surfaced"), and in commit messages going back to Slice 5.
|
||||
Renumbering §19 → §20 would invalidate every external reference and
|
||||
require coordinated edits across at least: rfc-app CLAUDE.md,
|
||||
ohm-rfc-app-flotilla CLAUDE.md, ohm-infra SESSION-PROTOCOL.md, all
|
||||
12 published session transcripts, the auto-memory files. None of
|
||||
those are within the scope of this subsession's docs-PR.
|
||||
|
||||
The "or whatever's next available" hedge in the brief covers this
|
||||
case. **Settled inline:** place the new chapter as §21 (after §20
|
||||
versioning), with a forward-pointer added at the end of §15's preamble
|
||||
naming §21 as the peer cross-cutting analytics chapter. The new
|
||||
chapter's opening explicitly states the placement reasoning so a
|
||||
future reader knows why §21 isn't between §15 and §16. This is a
|
||||
deliberate deferral of the "right" numbering to a future cleanup
|
||||
session that scopes the cross-repo renumber properly — flagged in the
|
||||
report-back to the driver.
|
||||
|
||||
### Arc 3 — `rfc-app/CONTRIBUTING.md` draft
|
||||
|
||||
Branched `rfc-app` at `feature/contributing-and-spec-analytics` off
|
||||
`origin/main` at `1456c8b`. Drafted CONTRIBUTING.md (~370 lines)
|
||||
covering, in order:
|
||||
|
||||
- Project framing — what rfc-app is, what a deployment is, the
|
||||
difference between framework-PR and content-PR.
|
||||
- "How the project actually evolves" with explicit
|
||||
wiggleverse/ohm-session-history pointer + worked-example links
|
||||
into Sessions E (clean small release), I (recovery from deploy
|
||||
fault), K (multi-feature wave + paused-on-secret), L (squash-merge
|
||||
across three parallel features + #21 Part C wiring inline). The
|
||||
session links are to the repo root rather than specific
|
||||
transcript filenames so they don't break when transcripts get
|
||||
renamed via roadmap #23.
|
||||
- Contribution flow — Issue-first for structural changes, branch
|
||||
naming convention (matched against the driver session's
|
||||
`feature/v<target>-<slug>` shape), explicit "do not bump VERSION,
|
||||
do not tag, do not deploy, do not touch any .rfc-app-version pin."
|
||||
- CHANGELOG strict-descending convention with a worked entry
|
||||
template (mirroring v0.15.0/v0.16.0/v0.17.0 actual shapes) and
|
||||
the operator-fills-version-at-merge-time note.
|
||||
- RFC 2119 keyword discipline per SPEC §20.4 — MUST / MUST NOT /
|
||||
SHOULD / SHOULD NOT / MAY — with the cross-version-upgrade
|
||||
composition rule cited.
|
||||
- §19.2 candidate discipline — explicit "don't scope-creep, name
|
||||
the deferral." Cited v0.11.0's three new candidates (Session K)
|
||||
and v0.15.0's two (Session L) as worked examples.
|
||||
- Test-coverage expectations characterized from the actual
|
||||
~250-test backend suite. Named specific vertical files
|
||||
(`test_admin_create_user_invite_vertical.py`, etc.) and the
|
||||
`PYTHONPATH=. .venv/bin/pytest -q` ergonomic (still §19.2). Noted
|
||||
that there is no frontend test runner today — the discipline is
|
||||
build-clean + backend HTTP contract coverage.
|
||||
- Analytics instrumentation checklist (the #21 Part B CONTRIBUTING
|
||||
artifact bullet): (1) named events, (2) autocapture-friendly DOM,
|
||||
(3) replay masking, (4) PR description discipline. Plus a
|
||||
pointer to SPEC §21.6 identity-lifecycle contract for any new
|
||||
claim/sign-in/invite path.
|
||||
- Operator-only gestures explicitly enumerated, with the hard
|
||||
no-secret-bytes-in-conversation rule named (Session K Arc 6
|
||||
origin cited).
|
||||
- "When in doubt" closer.
|
||||
|
||||
The CONTRIBUTING file uses MIT License as the rfc-app `LICENSE`
|
||||
shape; checked `ls rfc-app/LICENSE` showed it exists.
|
||||
|
||||
### Arc 4 — `rfc-app/SPEC.md` §21 chapter draft
|
||||
|
||||
Appended new §21 "Analytics instrumentation and identity" (~440
|
||||
lines) at the end of SPEC.md (after §20.7), structured as:
|
||||
|
||||
- §21 preamble — what this chapter codifies, framework-neutral
|
||||
posture, the placement-as-§21 reasoning (the cross-ref-preservation
|
||||
rationale from Arc 2).
|
||||
- §21.1 event-taxonomy conventions — Title Case "Subject Verb",
|
||||
stability + dashboard-name-match, opaque-ids-only-in-props,
|
||||
casing consistency. The starting v0.17.0 taxonomy (13 events)
|
||||
laid out verbatim.
|
||||
- §21.2 required prop families — navigation / auth-state / authored-
|
||||
action / admin-action / invite-side / claim-side. The hashed
|
||||
target_email convention (SHA-256 of normalized lowercased email)
|
||||
named for the #12 per-RFC invite case where the invitee isn't
|
||||
yet a user.
|
||||
- §21.3 autocapture-friendly DOM patterns — stable visible text,
|
||||
aria-label on icon-only buttons, `data-amp-track-*` for repeated
|
||||
rows, `data-amp-track-suppress` for noise surfaces, with a worked
|
||||
HTML example.
|
||||
- §21.4 session-replay masking — credentials MUST mask, PII SHOULD
|
||||
mask, privacy-policy alignment MUST hold, selective redaction
|
||||
preferred over whole-form. The `.amp-mask` / `data-amp-mask`
|
||||
convention named with the qualifier "whichever the wrapper's SDK
|
||||
version uses" (acknowledging that the Amplitude SDK's masking
|
||||
attribute name has varied across versions; the wrapper's bootstrap
|
||||
comment is the live source of truth).
|
||||
- §21.5 consent-gate contract — five MUSTs: pre-consent no init/no
|
||||
network/no recording; denied→granted init at consent moment;
|
||||
granted→denied setOptOut(true) within one tick; no double-init on
|
||||
re-grant; build-time env-var resolution with one-warn no-op when
|
||||
unset.
|
||||
- §21.6 identity lifecycle (per #21 Part C) — sign-in /
|
||||
state-change / sign-out / invite-claim / inviter-side
|
||||
identification, each with MUST language. The User-Signed-Out
|
||||
track-BEFORE-anonymize ordering called out as load-bearing.
|
||||
- §21.6.1 set vs setOnce taxonomy — explicit classification rule
|
||||
with worked examples on each side (role / permission_state /
|
||||
passcode_set / device_trusted = set; first_sign_in_at /
|
||||
account_created_at / invited_by_admin_id / invited_at /
|
||||
claim_method = setOnce).
|
||||
- §21.7 cohort-shape implications (informative) — four worked
|
||||
questions the dashboard can now answer with the conventions:
|
||||
admin-invite vs OTC funnel by week; passcode-set rate in first
|
||||
session by claim path; per-RFC owner-invited-contributor counts;
|
||||
invite-send-to-claim gap by inviter.
|
||||
- §21.8 secret-vs-public framing — overlay binding for the public
|
||||
Amplitude key, secret binding for the Turnstile siteverify
|
||||
secret half. The pbpaste-pipe canonical gesture for OHM-shape
|
||||
deployments named.
|
||||
- §21.9 §19.2 candidates surfaced — four (session-replay-specific
|
||||
consent category; bundle-size budget measurement; property-shape
|
||||
CI lint; centralized email-hash helper).
|
||||
- §21.10 open question — consent-category split timing tied to the
|
||||
next privacy-policy revision.
|
||||
|
||||
Then added a forward-pointer paragraph at the end of §15's
|
||||
preamble (the "section commits the surface..." paragraph) noting
|
||||
§21 as the peer cross-cutting analytics chapter and explicitly
|
||||
saying the two surfaces don't overlap (notifications =
|
||||
signals-of-others-acting; analytics = observability of use).
|
||||
|
||||
The chapter uses RFC 2119 keywords precisely throughout. No
|
||||
"should probably" / "might want to" weasel-words inside MUST/
|
||||
SHOULD steps. Cited frontend/src/lib/analytics.js, App.jsx,
|
||||
InviteClaim.jsx, AcceptInvitation.jsx as worked-example
|
||||
references.
|
||||
|
||||
### Arc 5 — `ohm-rfc/CONTRIBUTING.md` extension
|
||||
|
||||
Branched `ohm-rfc` at `feature/contributing-roadmap-guide` off
|
||||
`origin/main` at `8cce148`. Strategy: extend the existing
|
||||
130-line RFC-content guide with a new section rather than create a
|
||||
competing file. The existing sections (RFC discussions, RFC PR
|
||||
format, RFC numbering, values, license) are preserved verbatim;
|
||||
the roadmap-change section appends after the License block as a new
|
||||
"Proposing a change to the OHM roadmap" heading + subsections.
|
||||
|
||||
Section structure:
|
||||
|
||||
- What kinds of roadmap changes are welcome (5 bullets — add,
|
||||
reshape, claim a version slot, strike-through on ship,
|
||||
§19.2 candidate at the OHM layer).
|
||||
- How OHM actually evolves — wiggleverse/ohm-session-history
|
||||
pointer + worked-example links into Sessions J (mid-build
|
||||
version-slot reshuffle when docs-feature took v0.14.0),
|
||||
K (mid-session capture of #16/#17/#18/#19), L (mid-build
|
||||
capture of #20/#21/#22 + Part C wiring + #23).
|
||||
- How to submit a roadmap PR — including the now-settled Gitea
|
||||
registration-disabled question.
|
||||
- The operator-only gestures — phrased with the operator's "I'm
|
||||
the only one that gets to yolo" quote. Six bullets: flotilla
|
||||
deploy, .rfc-app-version pin bump, secret-set, overlay-set on
|
||||
OHM live, repo rename, Gitea account provisioning, PR merge.
|
||||
Hard no-secret-bytes-in-conversation rule named.
|
||||
- The two-layer rule — framework change → upstream rfc-app PR;
|
||||
OHM-specific change → here.
|
||||
- "When you're not sure" closer.
|
||||
|
||||
### Arc 6 — Open question on Gitea registration posture, settled
|
||||
|
||||
Per the dispatch brief, probed git.wiggleverse.org:
|
||||
|
||||
- `https://git.wiggleverse.org/api/v1/version` returned
|
||||
`{"version":"1.26.2"}` — confirming the instance is up.
|
||||
- `https://git.wiggleverse.org/user/sign_up` returned HTTP 200 but
|
||||
the body contained `<p>Registration is disabled. Please contact
|
||||
your site administrator.</p>` — definitive answer.
|
||||
|
||||
Registration is **disabled**, meaning outside contributors without a
|
||||
Gitea account cannot directly open PRs. The ohm-rfc CONTRIBUTING
|
||||
text settles on two 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). The text is phrased so the operator can
|
||||
edit it down to one path if they choose to flip registration policy
|
||||
later.
|
||||
|
||||
The rfc-app CONTRIBUTING file doesn't carry this question
|
||||
explicitly — its operator-gestures section already covers
|
||||
"contributors do not have direct push access," and the Issue-first
|
||||
path is the default mode for PRs the operator hasn't expected.
|
||||
|
||||
### Arc 7 — Commits + push
|
||||
|
||||
`rfc-app`:
|
||||
- Staged CONTRIBUTING.md (new) + SPEC.md (modified).
|
||||
- Committed `213f686` "docs: CONTRIBUTING.md + SPEC.md §21 analytics
|
||||
chapter" with a HEREDOC commit body naming the deliverables and
|
||||
the placement-as-§21 reasoning. Identity defaulted to
|
||||
`Ben Stull <benstull@mac.lan>` (matches the operator's prior
|
||||
commits on this repo; no git config edit). Pushed to both remotes:
|
||||
`origin` (git.wiggleverse.org) and `benstull` (git.benstull.org)
|
||||
with `-u` flag; both succeeded.
|
||||
|
||||
`ohm-rfc`:
|
||||
- Staged CONTRIBUTING.md (modified).
|
||||
- Committed `f26fae4` "CONTRIBUTING: add roadmap-change guide +
|
||||
operator-only boundary" with a HEREDOC commit body. Same
|
||||
operator identity. Pushed to `origin` (the only remote on this
|
||||
repo) with `-u`; succeeded.
|
||||
|
||||
No tags, no VERSION bump, no `.rfc-app-version` pin move, no
|
||||
`flotilla deploy`. All operator-only gestures untouched.
|
||||
|
||||
### Arc 8 — Transcript
|
||||
|
||||
Wrote this subsession transcript at
|
||||
`~/git/ohm-infra/SESSION-M.1-TRANSCRIPT-2026-05-28T05-30--2026-05-28T05-40.md`
|
||||
per the §5 subagent-transcript convention. Parent reference in the
|
||||
header. The driver publishes this alongside its own Session-M
|
||||
transcript via `~/git/ohm-infra/scripts/publish-transcript.sh`.
|
||||
|
||||
---
|
||||
|
||||
## Cut state (end of subsession)
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| `rfc-app` | `feature/contributing-and-spec-analytics` at `213f686`; pushed to `origin` + `benstull`. `main` unchanged at `1456c8b`. Nothing tagged. |
|
||||
| `ohm-rfc` | `feature/contributing-roadmap-guide` at `f26fae4`; pushed to `origin`. `main` unchanged at `8cce148`. `.rfc-app-version` unchanged at `0.17.0`. |
|
||||
| OHM live | unchanged — `deploys.id=21`, v0.17.0. No deploy gesture performed. |
|
||||
| `ohm-rfc-app-flotilla` | unchanged at `65c0e55`. |
|
||||
| Operator-only gestures | none touched (no tag, no version bump, no pin move, no deploy, no secret-set). |
|
||||
|
||||
| Subsession M.1 ledger | Status |
|
||||
| --- | --- |
|
||||
| #19 rfc-app CONTRIBUTING.md | drafted on feature branch, pushed (origin + benstull) |
|
||||
| #19 ohm-rfc CONTRIBUTING.md (extension) | drafted on feature branch, pushed (origin) |
|
||||
| #21 Part B SPEC.md §21 analytics chapter | drafted on feature branch, pushed (origin + benstull); rides the rfc-app CONTRIBUTING commit |
|
||||
| #19 anonymous-PR open question | settled — registration disabled on the Gitea instance; documented inline with two contributor paths |
|
||||
|
||||
---
|
||||
|
||||
## §19.2 candidates surfaced
|
||||
|
||||
1. **SPEC.md §16..§20 renumbering vs. §21-at-end placement.**
|
||||
The roadmap brief's "place AFTER §15" naturally implies insert-
|
||||
as-§16-and-shift-§16..§20-down, but renumbering would cascade
|
||||
through 50+ cross-references, including `§19.2` (the project's
|
||||
defer-decisions noun, used in CLAUDE.md, the session protocol,
|
||||
12 published transcripts, and historical commit messages). This
|
||||
subsession chose the lower-risk placement-as-§21 with a §15
|
||||
forward-pointer, and documented the choice in the new chapter's
|
||||
preamble. A future cleanup session that wants to do the
|
||||
numerically-clean §16 placement needs to scope: rfc-app SPEC
|
||||
renumber + rfc-app CLAUDE.md update + ohm-rfc-app-flotilla
|
||||
CLAUDE.md cross-ref update + ohm-infra SESSION-PROTOCOL.md
|
||||
reference sweep + all 12 published transcript header notes (in
|
||||
wiggleverse/ohm-session-history) + the auto-memory
|
||||
feedback files. None of those are within this subsession's
|
||||
docs-PR scope; flagged for operator's call.
|
||||
|
||||
2. **Centralized email-hash helper.** §21.2 specifies SHA-256 of
|
||||
the normalized lower-cased email as the hashing function for
|
||||
the per-RFC `target_email` fingerprint, but no helper exists in
|
||||
the framework today. A small `frontend/src/lib/hash.js` or
|
||||
`backend/app/hash.py` that centralizes normalization + hashing
|
||||
would make the contract enforceable rather than relying on
|
||||
per-callsite re-implementation. Captured in SPEC §21.9 as well.
|
||||
|
||||
3. **Property-shape CI lint.** The conventions in SPEC §21.1 /
|
||||
§21.2 are review-enforced today. A small lint (grep `track(` /
|
||||
`identify(` callsites with a property-key allowlist + a PII-name
|
||||
denylist) would catch drift mechanically. Captured in SPEC
|
||||
§21.9.
|
||||
|
||||
4. **Frontend test runner.** rfc-app/frontend has no test runner
|
||||
today (no `vitest`, no `jest`, no `playwright` — only `dev` /
|
||||
`build` / `preview` scripts). The discipline that the
|
||||
CONTRIBUTING file documents (backend vertical tests carry the
|
||||
regression boundary; frontend ships build-clean) is honest about
|
||||
the current state but is itself a §19.2 candidate worth flagging
|
||||
for a future session — a small `vitest` setup + a few smoke
|
||||
tests against the wrapper modules (analytics.js, consent.js, api.js)
|
||||
would catch the kind of structural breakage a frontend-only
|
||||
release would otherwise discover at deploy time.
|
||||
|
||||
---
|
||||
|
||||
## What the driver needs to know
|
||||
|
||||
1. **Both branches are pushed and ready for PR.** The driver/operator can
|
||||
open PRs by visiting:
|
||||
- `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`
|
||||
(the mirror; merge here too if the operator's release flow
|
||||
normally syncs both — Session K/L precedent is that operator
|
||||
pushes tags to both but PR-opens on one and merges the merge-
|
||||
commit to both. The operator's call.)
|
||||
- `https://git.wiggleverse.org/ben.stull/ohm-rfc/pulls/new/feature/contributing-roadmap-guide`
|
||||
2. **The SPEC chapter is §21, not §16.** This is a deliberate
|
||||
docs-PR-scope-only choice (Arc 2 / §19.2 candidate #1 above).
|
||||
The new chapter's preamble explains the placement to readers.
|
||||
If the operator prefers the §16 insertion with full renumber,
|
||||
that's a separate larger PR that touches CLAUDE.md files and
|
||||
transcript headers across multiple repos — too large for this
|
||||
subsession.
|
||||
3. **Anonymous-PR question settled inline.** Registration is
|
||||
disabled on git.wiggleverse.org (confirmed via the sign-up
|
||||
page). The ohm-rfc CONTRIBUTING text is phrased to update
|
||||
easily if the operator changes that posture later.
|
||||
4. **No operator gestures were performed.** No tag, no VERSION
|
||||
bump, no pin move, no deploy, no secret-set, no overlay-set.
|
||||
Both branches are drafts ready for operator review + merge at
|
||||
the operator's chosen cadence.
|
||||
5. **No secret bytes in either commit body or in this transcript.**
|
||||
The hard rule was honored throughout. The SPEC chapter and both
|
||||
CONTRIBUTING files reference secrets by name only
|
||||
(`SMTP_PASSWORD`, `AMPLITUDE_API_KEY`, `CLOUDFLARE_TURNSTILE_SECRET`).
|
||||
6. **Suggested PR descriptions** (operator can paste verbatim into
|
||||
the new-PR form):
|
||||
- `rfc-app` PR: "Lands roadmap item #19 (CONTRIBUTING +
|
||||
transcript-linked onboarding) and #21 Part B (standing analytics
|
||||
instrumentation discipline) as a single docs commit. Adds
|
||||
CONTRIBUTING.md (~370 lines) covering PR shape, CHANGELOG
|
||||
strict-descending convention, RFC 2119 upgrade-steps discipline,
|
||||
§19.2 candidate practice, test-coverage expectations from the
|
||||
real ~250-test backend suite, the operator-only-gestures
|
||||
boundary, and the #21 Part B analytics-instrumentation checklist
|
||||
(named events / autocapture-friendly DOM / replay masking /
|
||||
PR-description discipline). Adds SPEC.md §21 'Analytics
|
||||
instrumentation and identity' (~440 lines) codifying event
|
||||
taxonomy, required prop families, autocapture DOM patterns,
|
||||
session-replay masking conventions, the consent-gate contract,
|
||||
the #21 Part C identity lifecycle (identify-with-properties on
|
||||
sign-in; setUserProperties on state change; reset on sign-out;
|
||||
identify-BEFORE-track on invite-claim), the set-vs-setOnce
|
||||
taxonomy with classification rule, cohort-shape implications,
|
||||
and four new §19.2 candidates. Placed as §21 rather than §16 to
|
||||
preserve the load-bearing §19.2 / §19.3 cross-reference graph;
|
||||
reasoning in the chapter preamble. §15 carries a new forward-
|
||||
pointer naming §21 as the peer cross-cutting analytics chapter.
|
||||
No code change; no VERSION bump; rides a future release or
|
||||
lands as a no-bump docs commit at operator discretion."
|
||||
- `ohm-rfc` PR: "Lands roadmap item #19's OHM-side artifact.
|
||||
Extends the existing 130-line RFC-content CONTRIBUTING.md with
|
||||
a new 'Proposing a change to the OHM roadmap' section (~80
|
||||
lines) covering roadmap-change kinds, the
|
||||
wiggleverse/ohm-session-history pointer with worked-example
|
||||
links into Sessions J/K/L, the PR-submission gesture (with the
|
||||
open question on Gitea registration settled inline:
|
||||
git.wiggleverse.org currently has registration disabled, so
|
||||
contributors choose between Issue-first and request-account
|
||||
paths), the explicit operator-only-gestures boundary with the
|
||||
'I'm the only one that gets to yolo' quote, and the two-layer
|
||||
rule. No version target."
|
||||
|
||||
---
|
||||
|
||||
## Files written
|
||||
|
||||
- `/Users/benstull/git/rfc-app/CONTRIBUTING.md` (new)
|
||||
- `/Users/benstull/git/rfc-app/SPEC.md` (modified — new §21 chapter
|
||||
+ §15 forward-pointer paragraph)
|
||||
- `/Users/benstull/projects/wiggleverse/ohm-rfc/CONTRIBUTING.md`
|
||||
(modified — new "Proposing a change to the OHM roadmap"
|
||||
section appended after the License block)
|
||||
- `/Users/benstull/git/ohm-infra/SESSION-M.1-TRANSCRIPT-2026-05-28T05-30--2026-05-28T05-40.md`
|
||||
(this file)
|
||||
@@ -0,0 +1,276 @@
|
||||
# Session M.2 — Transcript (subsession)
|
||||
|
||||
> Parent: SESSION-M-TRANSCRIPT-2026-05-28T05-26--INPROGRESS.md
|
||||
> Date: 2026-05-28
|
||||
> Goal: Roadmap item #17 step 1 (inventory) + step 2 (propose) — survey every repo on `git.wiggleverse.org` and `git.benstull.org`, draft the move proposal as a single document for the operator to review. **Step 3 (execute) is explicitly out of scope.**
|
||||
>
|
||||
> Outcome: **`REPO-NAMING-PROPOSAL.md` written to `~/git/ohm-infra/`. 12 repos inventoried across the two hosts plus 9 local-only working dirs documented. 8 moves proposed (3 archives, 2 namespace transfers, 3 operator-decision items). 6 open questions surfaced. Zero git operations performed against any remote.**
|
||||
|
||||
This is a subsession of Session M (the OHM driver session). The driver
|
||||
dispatched M.2 with a tight read-only-inventory + single-deliverable
|
||||
brief. Subsession boundaries follow the OHM session protocol §1
|
||||
(letter+number scheme — M.2 is the second subsession of M).
|
||||
|
||||
---
|
||||
|
||||
## Pre-session state
|
||||
|
||||
The driver's hand-off named:
|
||||
|
||||
- 9 known wiggleverse.org repos (driver named 3; M.2 inventoried the
|
||||
rest via Gitea explore endpoint).
|
||||
- 3 known benstull.org repos (same).
|
||||
- Roadmap item #17 ("Repo naming + location alignment") names the
|
||||
framing: rfc-app should be in `wiggleverse/`, not personal namespace;
|
||||
mirror convention undocumented; the `ohm-app` legacy VM (tracked
|
||||
separately as roadmap #1, out of scope for M.2).
|
||||
- Hard constraint: subsession publishes its transcript to
|
||||
`wiggleverse/ohm-session-history` (public). No secret bytes.
|
||||
- Hard constraint: no Gitea admin actions, no transfers, no edits to
|
||||
anything but the proposal document itself.
|
||||
|
||||
Operator's working tree (parent session M's pre-state): clean.
|
||||
Active OHM deployment: v0.17.0 (deploys.id=21). Pin: 0.17.0. Healthy.
|
||||
|
||||
---
|
||||
|
||||
## Turn-by-turn arc
|
||||
|
||||
### Arc 1 — Read roadmap #17 + scout local checkouts
|
||||
|
||||
Read `~/projects/wiggleverse/ohm-rfc/ROADMAP.md` lines 314+ for
|
||||
item #17's full statement. The session-shape it names is:
|
||||
inventory → propose → execute. M.2 owns the first two.
|
||||
|
||||
Listed `~/git/` and `~/projects/wiggleverse/` to map local checkouts:
|
||||
|
||||
- `~/git/`: ohm-infra (not a git repo!), ohm-meta (empty), rfc-app
|
||||
(canonical clone), rfc-app-v0.15/v0.16/v0.17 (per-release worktrees),
|
||||
wiggleverse-meta (clone of `wiggleverse/meta`), benstull-infra
|
||||
(not a repo, just two old transcript MDs).
|
||||
- `~/projects/wiggleverse/`: ohm-rfc (clone), ohm-rfc-app-flotilla
|
||||
(clone), rfc-app-prototype (clone), org/ (not inspected).
|
||||
|
||||
Already a discovered-friction item: `~/git/ohm-infra/` not being a
|
||||
git repo is structurally weird. Filed as §3.4 in the proposal.
|
||||
|
||||
### Arc 2 — Inventory both Gitea hosts
|
||||
|
||||
`curl https://git.wiggleverse.org/explore/repos` and the equivalent
|
||||
on benstull.org returned HTTP 200 HTML for both — anonymous explore
|
||||
works. Parsed out `href="/<owner>/<name>"` links.
|
||||
|
||||
Wiggleverse.org reported 9 repos:
|
||||
- `ben.stull/{benstull,ohm-rfc,rfc-app,rfc-app-prototype}`
|
||||
- `wiggleverse/{meta,ohm-meta,ohm-rfc-app-flotilla,ohm-session-history,rfc-0001-human}`
|
||||
|
||||
Benstull.org reported 3:
|
||||
- `benstull/{docs,rfc-app,rfc-app-flotilla}`
|
||||
|
||||
For each, hit `/api/v1/repos/<owner>/<name>` for metadata (description,
|
||||
created/updated, default branch, size, mirror flag, private flag).
|
||||
All API calls anonymous; all returned 200. No auth needed for this
|
||||
inventory.
|
||||
|
||||
Also hit `/api/v1/repos/<owner>/<name>/commits?limit=1` per repo for
|
||||
the actual last-commit timestamp and message — `updated_at` from the
|
||||
repo metadata wasn't always commit-tied (could be a description edit).
|
||||
|
||||
### Arc 3 — Confirm the push-mirror mechanism
|
||||
|
||||
`ben.stull/rfc-app` and `benstull/rfc-app` both reported commit
|
||||
`1456c8b73f70` at `2026-05-28T05:10:52-07:00` — same sha, same
|
||||
timestamp within seconds. But neither was flagged `mirror: True`
|
||||
in the API. Tried `/api/v1/repos/.../push_mirrors` — returned
|
||||
"user should be an owner or a collaborator with admin write," so
|
||||
the push-mirror config is admin-only-visible.
|
||||
|
||||
Conclusion: the sync mechanism is a push-mirror configured by the
|
||||
operator on the canonical side, invisible to the public read API.
|
||||
Worth a note in the proposal — operator needs to reauthorize it
|
||||
after Move 3.
|
||||
|
||||
Also noticed: the **descriptions** of the canonical and mirror have
|
||||
drifted apart ("The Wiggleverse RFC framework app — see PHILOSOPHY.md
|
||||
and SPEC.md." vs "Open-source RFC platform software"). Filed under
|
||||
§3.3.
|
||||
|
||||
### Arc 4 — Probe URL-redirect handling on Gitea
|
||||
|
||||
The flotilla CHANGELOG L282–283 has a note about `ben/ohm-rfc` being
|
||||
a redirect alias to `ben.stull/ohm-rfc`. Tested:
|
||||
- `/api/v1/repos/ben/ohm-rfc` → HTTP 307
|
||||
- `/api/v1/repos/ben.stull/ohm-rfc` → HTTP 200
|
||||
- Same `307` for `ben/rfc-app`, `ben/benstull`, `ben/rfc-app-prototype`.
|
||||
|
||||
Conclusion: `ben` is an old username that aliases to `ben.stull` via
|
||||
Gitea's user-rename redirect feature. This matters because if Moves
|
||||
3+4 land, the alias chain becomes two hops: `ben → ben.stull →
|
||||
wiggleverse`. Filed as risk in Move 4 and as Q5.
|
||||
|
||||
### Arc 5 — Find URL refs in load-bearing docs
|
||||
|
||||
`grep -nE 'git\.(wiggleverse|benstull)\.org'` against:
|
||||
- `~/git/rfc-app/CLAUDE.md` and `SPEC.md` → zero hits. rfc-app source
|
||||
doesn't reference the hosting URLs. Good — moves don't break
|
||||
shipped framework code.
|
||||
- `~/git/rfc-app/frontend/` → zero hits. The running frontend doesn't
|
||||
embed the canonical URL. Confirms zero shipped-artifact breakage.
|
||||
- `~/projects/wiggleverse/ohm-rfc-app-flotilla/CLAUDE.md`, `SPEC.md`,
|
||||
`README.md`, `CHANGELOG.md`, `docs/operator-guide.md`, `scripts/`,
|
||||
`tests/` → **many hits**. Catalogued line-by-line in the proposal's
|
||||
Moves 3 and 4 sections. Test files hardcode `ben/ohm-rfc` four
|
||||
places. The SPEC has it ~10 places, plus a whole framing paragraph
|
||||
in §3.1 about why rfc-app gets a benstull mirror that **needs
|
||||
conceptual rewriting**, not just URL swapping.
|
||||
- `~/projects/wiggleverse/ohm-rfc/ROADMAP.md` → many hits including
|
||||
the entire §16 release-table (rows 2–13) with tag URLs. Note in
|
||||
proposal: those should probably stay (they record historical state)
|
||||
but Gitea's redirect will keep them resolving.
|
||||
- `~/git/ohm-infra/SESSION-*-TRANSCRIPT-*.md` → many hits. Per session
|
||||
protocol these are immutable. Gitea's redirect handles them. Filed
|
||||
explicitly in the proposal's "do NOT edit historical transcripts"
|
||||
callout.
|
||||
|
||||
### Arc 6 — Inspect overlapping meta-repo content
|
||||
|
||||
`wiggleverse/meta`, `wiggleverse/ohm-meta`, and
|
||||
`wiggleverse/rfc-0001-human` all had READMEs and `rfcs/` dirs.
|
||||
Fetched contents via API.
|
||||
|
||||
- `wiggleverse/meta`: `PHILOSOPHY.md` (12 KB), `CONTRIBUTING.md`
|
||||
(626 B), `rfcs/.gitkeep`, generic README.
|
||||
- `wiggleverse/ohm-meta`: `PHILOSOPHY.md` (12 KB), `CONTRIBUTING.md`
|
||||
(626 B), `rfcs/human.md` (292 B **stub**), README.
|
||||
- `wiggleverse/rfc-0001-human`: `RFC.md` (22 KB, the full body),
|
||||
`.rfc` marker, README that says "canonical text lives here;
|
||||
meta-repo entry is `rfcs/human.md` in ohm-meta."
|
||||
- `ben.stull/ohm-rfc`: `PHILOSOPHY.md` (13.5 KB — superset),
|
||||
`CONTRIBUTING.md` (4.5 KB — superset), `RFC-0001-human.md` (22 KB
|
||||
— **same body** as `rfc-0001-human/RFC.md`), `ROADMAP.md` (61 KB),
|
||||
`CLAUDE.md`, `LICENSE`, `.rfc-app-version`.
|
||||
|
||||
The proposal's §2 Move 2 names the collision: two models
|
||||
("per-RFC repos" vs "monorepo") both exist, monorepo has won in
|
||||
practice. Operator decision Q1 settles whether to archive the per-RFC
|
||||
repos.
|
||||
|
||||
### Arc 7 — Confirm flotilla SQLite registry state
|
||||
|
||||
`sqlite3 ~/.ohm-rfc-app-flotilla/ohm-rfc-app-flotilla.db 'SELECT *
|
||||
FROM deployments;'` returned the ohm-rfc-app row with
|
||||
`pin_source_repo='ben/ohm-rfc'` and `corpus_repo='ben/ohm-rfc'`.
|
||||
|
||||
Important: after Move 4, the operator must run a flotilla command
|
||||
to update these values, or the next deploy chases a two-hop redirect.
|
||||
Flagged in Move 4 cross-repo-cleanup section.
|
||||
|
||||
### Arc 8 — Write the proposal
|
||||
|
||||
Wrote `~/git/ohm-infra/REPO-NAMING-PROPOSAL.md` (~22 KB) covering:
|
||||
|
||||
- §1 inventory: two host tables + local-only working dirs + topology snapshot
|
||||
- §2 move proposal: 8 moves with risk, ordering, URL-breakage callouts, specific paths
|
||||
- §3 discovered friction: 6 items (confirmed roadmap #17's plus 4 new)
|
||||
- §4 open questions: 6 (Q1 RFC-shape, Q2 mirror destination, Q3 stub fate, Q4 ohm-infra-as-repo, Q5 username alias, Q6 backup before)
|
||||
- §5 suggested execution-session shape: pre-flight checklist, ordered execution, Gitea-admin vs git-cli vs edit-text gestures, verify
|
||||
- §6 §19.2 candidates surfaced
|
||||
- §7 summary table
|
||||
|
||||
No git operations against any remote. No edits to anything besides
|
||||
the proposal doc itself.
|
||||
|
||||
---
|
||||
|
||||
## Cut state (end of subsession)
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| `~/git/ohm-infra/REPO-NAMING-PROPOSAL.md` | new file, ~22 KB, M.2's only artifact |
|
||||
| Every other repo (local + remote) | untouched |
|
||||
| OHM live | unchanged (deploys.id=21, v0.17.0, healthy) |
|
||||
| flotilla SQLite registry | unchanged |
|
||||
| Parent session M state | unchanged |
|
||||
|
||||
| M.2 deliverable ledger | Status |
|
||||
| --- | --- |
|
||||
| Read roadmap #17 in full | done |
|
||||
| Inventory wiggleverse.org repos | done (9 found) |
|
||||
| Inventory benstull.org repos | done (3 found) |
|
||||
| Cross-reference local checkouts | done |
|
||||
| Read CLAUDE.md/SPEC.md/ROADMAP.md for URL refs | done |
|
||||
| Write proposal doc | done |
|
||||
| Write transcript | done (this file) |
|
||||
| Publish transcript to ohm-session-history | **driver does this**, not subsession (per protocol §"subagents do not write their own transcripts" — but the brief overrode that and asked M.2 to write one; M.2 wrote, driver publishes) |
|
||||
|
||||
---
|
||||
|
||||
## §19.2 candidates surfaced
|
||||
|
||||
Two surfaced during M.2 (also in proposal §6):
|
||||
|
||||
1. **Stop relying on Gitea path aliases.** flotilla currently leans
|
||||
on the `ben/ohm-rfc` → `ben.stull/ohm-rfc` redirect alias (urllib
|
||||
follows 307s). After Moves 3+4, that chain becomes two hops.
|
||||
Better: resolve canonical paths at `flotilla deployment update`
|
||||
time, store the resolved path in SQLite, never chase aliases at
|
||||
deploy time.
|
||||
2. **flotilla-core extraction reservation as concrete artifact.**
|
||||
The stub repo `git.benstull.org/benstull/rfc-app-flotilla` (Move 5)
|
||||
could either be archived or repurposed as the placeholder for the
|
||||
future `flotilla-core` §19.2 extraction. Operator decision (Q3).
|
||||
|
||||
A third candidate **not** in the proposal but worth a separate item:
|
||||
|
||||
3. **Promote `~/git/ohm-infra/` to a real git repo.** Currently a
|
||||
working dir with no version control on `SESSION-PROTOCOL.md`,
|
||||
the publish script, the transcript-publishing plan, etc. Roadmap
|
||||
#17 scope doesn't cover it but the inventory surfaced it (§3.4).
|
||||
Worth its own roadmap item.
|
||||
|
||||
---
|
||||
|
||||
## What the driver needs to know
|
||||
|
||||
1. **Proposal lives at `~/git/ohm-infra/REPO-NAMING-PROPOSAL.md`**
|
||||
and is ready for operator review.
|
||||
2. **Six open questions** the operator must settle before the
|
||||
execution session. Q1 is structural (monorepo vs per-RFC) and may
|
||||
reshape the proposal entirely.
|
||||
3. **Biggest finding not in roadmap #17:** `wiggleverse/meta`,
|
||||
`wiggleverse/ohm-meta`, and `wiggleverse/rfc-0001-human` are three
|
||||
repos for one job. Monorepo has won in practice; archive the other
|
||||
two if operator confirms.
|
||||
4. **Biggest cross-repo cleanup load:** the flotilla SPEC.md needs
|
||||
~10 URL updates *plus* a conceptual rewrite of the §3.1 paragraph
|
||||
that explains why rfc-app has a benstull mirror (the "personal
|
||||
shape" rationale dissolves once rfc-app moves to org namespace).
|
||||
5. **flotilla test files hardcode `ben/ohm-rfc` in four places**
|
||||
(`test_plan.py`, `test_pin.py`, `test_bringup_replay.py`,
|
||||
`test_deployment.py`). These must update with Move 4.
|
||||
6. **The flotilla SQLite registry** at
|
||||
`~/.ohm-rfc-app-flotilla/ohm-rfc-app-flotilla.db` has
|
||||
`pin_source_repo` and `corpus_repo` both set to `ben/ohm-rfc`.
|
||||
Must be updated post-Move-4 or next deploy chases a redirect.
|
||||
7. **No shipped frontend artifacts reference the canonical URLs.**
|
||||
This is the load-lightening discovery: the moves don't break
|
||||
anything that an OHM end-user sees.
|
||||
8. **No git operations were performed by M.2** against any remote.
|
||||
Read-only inventory, single new file (the proposal), this
|
||||
transcript.
|
||||
|
||||
---
|
||||
|
||||
## Closing handoff to driver
|
||||
|
||||
M.2 is done. The driver returns control to the operator with the
|
||||
proposal ready for review. The next OHM session that wants to
|
||||
execute roadmap #17 picks up by:
|
||||
|
||||
1. Operator reads `REPO-NAMING-PROPOSAL.md`, settles Q1–Q6.
|
||||
2. Operator backs up `~/git/ohm-infra/` (Q6 / §3.4 surfaced) if not
|
||||
yet a real repo.
|
||||
3. Driver session opens, follows the execution shape in proposal §5.
|
||||
|
||||
That execution session is operator-led (per the brief that dispatched
|
||||
M.2: "step 3 is operator-led in a future session").
|
||||
@@ -0,0 +1,311 @@
|
||||
# Session M.3 — Transcript
|
||||
|
||||
> Parent: SESSION-M-TRANSCRIPT-2026-05-28T05-26--<end>.md
|
||||
>
|
||||
> Date: 2026-05-28 (PST)
|
||||
> Start: 2026-05-28T05-30 PST
|
||||
> End: 2026-05-28T05-58 PST
|
||||
> Goal: Inventory OHM's email + Gitea-webhook posture (roadmap items
|
||||
> #18 + #20, bundled) and draft a hardening runbook plus a
|
||||
> framework-side template-hygiene proposal. Read-only only —
|
||||
> no DNS edits, no SMTP-provider changes, no rfc-app code,
|
||||
> no secret rotations, no test emails.
|
||||
>
|
||||
> Outcome: **Two docs written and left on disk for the driver at
|
||||
> `~/git/ohm-infra/EMAIL-AND-WEBHOOK-HARDENING-RUNBOOK.md` and
|
||||
> `~/git/ohm-infra/RFC-APP-EMAIL-HYGIENE-PROPOSAL.md`. Five §19.2
|
||||
> candidates surfaced. Three high-impact deployment-side gestures
|
||||
> and three high-impact framework-side changes prioritized for the
|
||||
> operator. Two surprising findings: SMTP_HOST is missing from the
|
||||
> live flotilla overlay (gap to disambiguate), and a stale Gitea
|
||||
> webhook on `wiggleverse/meta` still points at the deprovisioned
|
||||
> `http://rfc.wiggleverse.org`.**
|
||||
|
||||
---
|
||||
|
||||
## Pre-session state
|
||||
|
||||
- Parent: Session M driver, dispatched M.3 with scope = inventory
|
||||
for items #18 + #20.
|
||||
- `~/git/ohm-infra/` is a plain directory (not a git repo) — driver
|
||||
noted this; M.3 leaves docs on disk for the driver to handle
|
||||
publishing.
|
||||
- `rfc-app` at v0.17.0 on disk; live OHM running v0.17.0 per
|
||||
`flotilla pin check ohm-rfc-app` (deploys.id=21).
|
||||
- Hard constraints from dispatch: no operator-led gestures, no
|
||||
secret bytes in transcript, read-only inventory only.
|
||||
|
||||
---
|
||||
|
||||
## Turn-by-turn arc
|
||||
|
||||
### Arc 1 — DNS inventory + roadmap read
|
||||
|
||||
Ran `dig +short TXT wiggleverse.org`, `dig +short TXT
|
||||
_dmarc.wiggleverse.org`, `dig +short MX wiggleverse.org`, and
|
||||
probed common DKIM selectors (`default`, `mail`, `s1`, `s2`,
|
||||
`selector1`, `k1`, `mandrill`, `smtp`, `google`, etc.).
|
||||
|
||||
Findings:
|
||||
|
||||
- **SPF:** `"v=spf1 include:_spf.google.com ~all"` (soft-fail).
|
||||
- **DMARC:** `"v=DMARC1; p=none; rua=mailto:ben@wiggleverse.org"`
|
||||
(monitoring-only, no enforcement, no `pct=`, no `sp=`).
|
||||
- **DKIM:** present at `google._domainkey.wiggleverse.org` — a
|
||||
2048-bit RSA Workspace key. All other probed selectors empty.
|
||||
- **MX:** `aspmx.l.google.com` family — Google Workspace mailbox.
|
||||
|
||||
Read roadmap items #18 (l.362-403) and #20 (l.458-547) from
|
||||
`~/projects/wiggleverse/ohm-rfc/ROADMAP.md`. Both confirm they
|
||||
should ship together because the same DNS edits serve both. #20
|
||||
names BIMI as a follow-on after DMARC reaches `p=reject`. #18 names
|
||||
"refresh webhook-shared-secret + replay-resistance + ingress
|
||||
restriction" as framework-side work.
|
||||
|
||||
### Arc 2 — Flotilla overlay + secret inventory
|
||||
|
||||
```
|
||||
.venv/bin/ohm-rfc-app-flotilla overlay show ohm-rfc-app
|
||||
.venv/bin/ohm-rfc-app-flotilla secret list ohm-rfc-app
|
||||
```
|
||||
|
||||
Overlay reveals:
|
||||
|
||||
- `SMTP_PORT=587`, `SMTP_STARTTLS=1`, `EMAIL_FROM=notifications@wiggleverse.org`,
|
||||
`EMAIL_FROM_NAME=Wiggleverse` — present.
|
||||
- `SMTP_HOST` — **missing**. `SMTP_USER` — **missing**.
|
||||
- Secret refs include `GITEA_WEBHOOK_SECRET` bound to
|
||||
`wiggleverse-ohm/ohm-rfc-app-gitea-webhook-secret@latest`.
|
||||
|
||||
Greps:
|
||||
|
||||
- `~/projects/wiggleverse/ohm-rfc-app-flotilla/scripts/register-ohm.sh:40`
|
||||
sets `SMTP_HOST=smtp-relay.gmail.com` — but the live overlay
|
||||
doesn't have it. Either the bootstrap script wasn't re-run after
|
||||
a reshape, or SMTP env lives out-of-band on the VM (e.g.,
|
||||
systemd-unit override). This is a finding for the runbook —
|
||||
operator must disambiguate via `gcloud ssh` + `systemctl show
|
||||
ohm-app.service`.
|
||||
|
||||
Confirmed provider = **Google Workspace SMTP relay** (the bootstrap
|
||||
script + the `google._domainkey` DKIM + the MX records align).
|
||||
|
||||
### Arc 3 — Framework email-helper read
|
||||
|
||||
Read all of:
|
||||
|
||||
- `~/git/rfc-app/backend/app/email.py` — §15.4 notification mailer
|
||||
(`_deliver`, `_send_one`, `_send_bundle`, `make_unsubscribe_url`).
|
||||
- `~/git/rfc-app/backend/app/email_otc.py` — v0.7.0 OTC helper.
|
||||
- `~/git/rfc-app/backend/app/email_invite.py` — v0.17.0 admin-create
|
||||
invite helper.
|
||||
|
||||
Built header-presence matrix across all four kinds (OTC / invite /
|
||||
watcher-single / watcher-bundle):
|
||||
|
||||
- `Date`, `Message-ID`, `Auto-Submitted`, `List-Unsubscribe`,
|
||||
`List-Unsubscribe-Post`, `multipart/alternative` — **all
|
||||
absent** on all four paths. Google's SMTP relay adds `Date` /
|
||||
`Message-ID` during ingestion, but the framework doesn't control
|
||||
the format.
|
||||
- Plain-text bodies only. No HTML variant anywhere.
|
||||
- `_deliver` (`email.py:308`) embeds unsubscribe URL in body footer
|
||||
but doesn't set the header.
|
||||
|
||||
Stdlib spot-check: `python3 -c "from email.message import
|
||||
EmailMessage; …"` confirms `set_content` doesn't auto-populate
|
||||
`Date` or `Message-ID`. Inspected `smtplib.SMTP.send_message`
|
||||
source — also doesn't set them.
|
||||
|
||||
### Arc 4 — Gitea-webhook inventory
|
||||
|
||||
Found the handler at `~/git/rfc-app/backend/app/webhooks.py` (78
|
||||
lines). HMAC-SHA-256 over the raw body, `hmac.compare_digest` for
|
||||
constant-time compare. **But:** `webhooks.py:43` gates verification
|
||||
behind `if config.webhook_secret:` — empty secret = no auth. Per
|
||||
`config.py:75`, `webhook_secret` is `_optional`. Insecure default.
|
||||
|
||||
Pulled `gitea-bot-token` from GCP Secret Manager into a shell var
|
||||
(`GITEA_TOKEN=$(gcloud secrets versions access latest …)`), used
|
||||
it for read-only `curl` calls against the Gitea API, immediately
|
||||
`unset` after each call. Never echoed the value to stdout or any
|
||||
file.
|
||||
|
||||
Hook inventory across the wiggleverse org:
|
||||
|
||||
| Repo | Hook | Notes |
|
||||
|---|---|---|
|
||||
| `wiggleverse/ohm-meta` | `https://ohm.wiggleverse.org/api/webhooks/gitea` | active, 12 events — correct |
|
||||
| `wiggleverse/meta` | `http://rfc.wiggleverse.org/api/webhooks/gitea` | **active, plain HTTP, deprovisioned domain** |
|
||||
| `wiggleverse/rfc-0001-human` | (none) | the only published RFC has no hook |
|
||||
| `ben.stull/ohm-rfc` | (bot 403) | can't inventory, operator self-checks |
|
||||
|
||||
The org-level hooks endpoint
|
||||
(`/api/v1/orgs/wiggleverse/hooks`) returned `[]` — no org-wide
|
||||
hooks, all are repo-level.
|
||||
|
||||
Gitea's API exposes hook `config` keys = `['url', 'content_type']`
|
||||
— it does NOT return the configured `secret` field, so I can't
|
||||
confirm from the API side that the secret matches what OHM has.
|
||||
But OHM's cache is current and webhook receiver verifies, so the
|
||||
secrets must align (otherwise everything would 401).
|
||||
|
||||
### Arc 5 — Bounce-handling + observability gap
|
||||
|
||||
Read `~/git/rfc-app/backend/app/api_notifications.py:471-502` —
|
||||
`/api/webhooks/email-bounce` exists, gated on optional
|
||||
`WEBHOOK_EMAIL_BOUNCE_SECRET` per SPEC.md §3667 Slice 8 settlement.
|
||||
But **no provider is wired to POST here** — Google Workspace SMTP
|
||||
relay doesn't natively webhook bounces (it bounces back to the
|
||||
sender mailbox). So today: hard bounces silently retry every
|
||||
`flush_pending` pass.
|
||||
|
||||
All four send helpers catch `Exception`, log via `log.exception`,
|
||||
return `False`. No DB write of the failure, no admin notification.
|
||||
The operator has zero visibility into send failures without
|
||||
grepping VM logs.
|
||||
|
||||
### Arc 6 — Draft the two docs
|
||||
|
||||
Wrote `EMAIL-AND-WEBHOOK-HARDENING-RUNBOOK.md` (~700 lines) with
|
||||
sections: current posture / hardened shape / operator-execution
|
||||
sequence / framework-vs-deployment split / §19.2 candidates.
|
||||
|
||||
Wrote `RFC-APP-EMAIL-HYGIENE-PROPOSAL.md` (~280 lines) with the
|
||||
framework-side minor proposal: `build_envelope` shared helper,
|
||||
webhook handler tightening (mandatory secret), `outbound_emails`
|
||||
audit table, bounce-correlation, suggested 5-slice shipping plan.
|
||||
|
||||
Cross-referenced both docs at each end. Both name files + line
|
||||
numbers for every framework citation.
|
||||
|
||||
### Arc 7 — Transcript
|
||||
|
||||
This file. ohm-infra is not a git repo (driver's check confirmed
|
||||
pre-session), so no commit; driver decides whether to add a remote
|
||||
or just publish the transcripts via `publish-transcript.sh`.
|
||||
|
||||
---
|
||||
|
||||
## Cut state (end of subsession)
|
||||
|
||||
| Surface | State |
|
||||
|---|---|
|
||||
| `~/git/ohm-infra/EMAIL-AND-WEBHOOK-HARDENING-RUNBOOK.md` | written, ~21 KB |
|
||||
| `~/git/ohm-infra/RFC-APP-EMAIL-HYGIENE-PROPOSAL.md` | written, ~9 KB |
|
||||
| `~/git/ohm-infra/SESSION-M.3-TRANSCRIPT-…md` | this file |
|
||||
| OHM live | unchanged (v0.17.0, deploys.id=21) |
|
||||
| DNS / Gitea / SMTP provider | unchanged (read-only inventory only) |
|
||||
| flotilla overlay / secrets | unchanged (read-only `show` + `list` only) |
|
||||
|
||||
---
|
||||
|
||||
## §19.2 candidates surfaced
|
||||
|
||||
1. **rfc-app: `GITEA_WEBHOOK_SECRET` should be required, not optional.**
|
||||
Today the framework silently accepts unsigned POSTs when the env
|
||||
var is empty. Recommend `_required` with documented
|
||||
`RFC_APP_INSECURE_WEBHOOKS=1` dev-bypass.
|
||||
|
||||
2. **rfc-app: outbound-email observability seam.** Add
|
||||
`outbound_emails` table + write on every send. Today failures
|
||||
log-and-forget; the operator has no audit trail.
|
||||
|
||||
3. **flotilla: `overlay show` doesn't surface "expected but missing"
|
||||
keys.** The SMTP_HOST gap is invisible without grepping
|
||||
register-ohm.sh + framework source side-by-side. A per-deployment
|
||||
"required keys" manifest + `flotilla overlay validate` verb would
|
||||
catch this class of regression.
|
||||
|
||||
4. **flotilla: lacks a webhook-inventory verb.** Listing / diffing
|
||||
the webhooks on the org's Gitea repos against the deployment's
|
||||
expected set is a recurring operator gesture. A `flotilla webhook
|
||||
list / check <deployment>` verb would formalize it. Generic
|
||||
enough for a future `flotilla-core` extraction.
|
||||
|
||||
5. **rfc-app: multiple From-identity references should derive from
|
||||
one source.** `EMAIL_FROM` is read in `email.py:82`,
|
||||
`email_otc.py` via cfg, `email_invite.py` via cfg, plus implied
|
||||
by DMARC alignment + Workspace allowed-senders. A spec section
|
||||
binding all surfaces to one source would prevent skew.
|
||||
|
||||
---
|
||||
|
||||
## What lands on the driver's plate
|
||||
|
||||
1. **Decide where to put the two docs.** `~/git/ohm-infra/` is not
|
||||
a git repo. Options: (a) leave as plain files, publish only via
|
||||
`publish-transcript.sh` (which today only handles transcripts);
|
||||
(b) git-init `~/git/ohm-infra/` and add a Gitea remote so
|
||||
ohm-infra becomes a versioned doc repo; (c) move the two docs
|
||||
into `~/projects/wiggleverse/ohm-rfc/` under a `docs/` subdir
|
||||
since they're OHM-deployment-specific. Driver decides.
|
||||
|
||||
2. **Schedule the disambiguation gesture.** The SMTP_HOST gap is
|
||||
the most urgent finding — if the framework is actually falling
|
||||
to stdout-fallback, OHM's emails (OTC, invites, notifications,
|
||||
beta-access) aren't leaving the VM. Operator runs `gcloud ssh
|
||||
ohm-app` + `systemctl show ohm-app.service | grep
|
||||
Environment` next session.
|
||||
|
||||
3. **Schedule the stale-hook deletion.** Five-second gesture; the
|
||||
hook on `wiggleverse/meta` pointing at deprovisioned
|
||||
`http://rfc.wiggleverse.org/api/webhooks/gitea` should go.
|
||||
|
||||
4. **Decide on the From-identity question.** `ohm@wiggleverse.org`
|
||||
(roadmap's suggestion) or keep `notifications@wiggleverse.org`?
|
||||
Pinning this is a prerequisite for the framework-side proposal's
|
||||
`Reply-To` decision.
|
||||
|
||||
5. **Slot the rfc-app email-hygiene minor.** Per the proposal, v0.18.0
|
||||
or v0.19.0; 5 slices; mostly backward-compatible. Operator
|
||||
decides whether to bundle with another minor (e.g., #19's
|
||||
CONTRIBUTING.md docs) or ship solo.
|
||||
|
||||
6. **Confirm `ben.stull/ohm-rfc`'s hooks.** Bot lacks admin so M.3
|
||||
couldn't inventory. Operator self-checks via Gitea web UI.
|
||||
|
||||
---
|
||||
|
||||
## What the driver needs to know (report-back summary)
|
||||
|
||||
**DNS posture (literal values):**
|
||||
- SPF: `v=spf1 include:_spf.google.com ~all` (soft-fail).
|
||||
- DMARC: `v=DMARC1; p=none; rua=mailto:ben@wiggleverse.org`
|
||||
(monitoring-only).
|
||||
- DKIM: present at `google._domainkey.wiggleverse.org` (2048-bit
|
||||
Workspace key).
|
||||
|
||||
**SMTP provider:** Google Workspace SMTP relay
|
||||
(`smtp-relay.gmail.com`) — inferred from bootstrap script + DKIM
|
||||
+ MX. **But SMTP_HOST is missing from the live flotilla overlay**
|
||||
— either the bootstrap was reshaped and these were dropped, or
|
||||
they're set on the VM out-of-band. Operator must disambiguate.
|
||||
|
||||
**Top 3 deployment-side hardening gestures (priority order):**
|
||||
1. Delete stale `wiggleverse/meta` hook (5 seconds, removes
|
||||
plain-HTTP-to-deprovisioned-domain liability).
|
||||
2. Disambiguate SMTP_HOST gap (5 minutes, confirms email actually
|
||||
leaves the VM).
|
||||
3. DMARC Phase A: add `pct=100; sp=quarantine; adkim=s; aspf=s` to
|
||||
the existing `p=none` record; wait 1 week; flip to `p=quarantine`.
|
||||
|
||||
**Top 3 framework-side changes (rfc-app minor, target v0.18.0):**
|
||||
1. `build_envelope` shared helper adds `Date`, `Message-ID`,
|
||||
`Auto-Submitted`, `List-Unsubscribe`, `List-Unsubscribe-Post`,
|
||||
and `multipart/alternative` to all four email paths.
|
||||
2. Make `GITEA_WEBHOOK_SECRET` required (not silently optional).
|
||||
3. `outbound_emails` audit table + bounce-correlation hook.
|
||||
|
||||
**Webhook posture:** signature verification is correct
|
||||
(`hmac.compare_digest`, HMAC-SHA-256 over raw body) **when the
|
||||
secret is set**. On OHM the secret is bound. Two open issues:
|
||||
(a) framework's silent-accept-when-unset insecure default; (b)
|
||||
stale `wiggleverse/meta` hook on a deprovisioned domain.
|
||||
|
||||
**§19.2 candidates surfaced:** 5 (listed above).
|
||||
|
||||
**Doc paths:**
|
||||
- `/Users/benstull/git/ohm-infra/EMAIL-AND-WEBHOOK-HARDENING-RUNBOOK.md`
|
||||
- `/Users/benstull/git/ohm-infra/RFC-APP-EMAIL-HYGIENE-PROPOSAL.md`
|
||||
- `/Users/benstull/git/ohm-infra/SESSION-M.3-TRANSCRIPT-2026-05-28T05-30--2026-05-28T05-58.md`
|
||||
Reference in New Issue
Block a user