ad01c0854a
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.
919 lines
46 KiB
Markdown
919 lines
46 KiB
Markdown
# Session L — Transcript
|
||
|
||
> Date: 2026-05-28
|
||
> Goal: Execute Wave 5 of `ohm-rfc/ROADMAP.md` as the autonomous
|
||
> driver per the "Operating instructions for the next session"
|
||
> section. The operator's bedtime prompt asked for three items
|
||
> dispatched in parallel — **#13 Amplitude → v0.15.0** (Track A;
|
||
> originally wave-paused on operator-provided `AMPLITUDE_API_KEY`),
|
||
> **#12 Owner-only invite → v0.16.0** (Track B), and
|
||
> **#16 Admin-create user + invite email → v0.17.0** (Track C
|
||
> extension; layers on top of v0.9.0's `/admin/users` surface).
|
||
> Mid-session, the operator returned to the keyboard and added
|
||
> three roadmap items + a meaningful enlargement of #13's scope.
|
||
>
|
||
> Outcome: **All three Wave-5 features shipped to OHM live.**
|
||
>
|
||
> - **#13 Amplitude shipped as rfc-app v0.15.0 to OHM live**
|
||
> (`deploys.id=19`, all 9 phases green, `/api/health` returns
|
||
> `{"version":"0.15.0","status":"ok"}`). Vendor's recommended
|
||
> `@amplitude/unified` package with `initAll(KEY, { analytics:
|
||
> { autocapture: true }, sessionReplay: { sampleRate: 1 } })`.
|
||
> Bound via `flotilla overlay set ohm-rfc-app
|
||
> VITE_AMPLITUDE_API_KEY=…`, not `secret set` — Amplitude
|
||
> browser keys are bundle-embedded (same nature as
|
||
> `VITE_TURNSTILE_SITE_KEY`). The "wave pause for operator
|
||
> secret-set" the bedtime prompt anticipated didn't materialize
|
||
> in that shape; instead the operator returned mid-session,
|
||
> provisioned the Amplitude project, surfaced the vendor's
|
||
> installation prompt (which contained the public key inline),
|
||
> and the binding settled as overlay. Conversation-layer hard
|
||
> rule respected: the key value was operator-provided
|
||
> voluntarily, not asked for.
|
||
> - **#12 Owner-only invite shipped as rfc-app v0.16.0 to OHM
|
||
> live** (`deploys.id=20`, all 9 phases green, `/api/health`
|
||
> returns `{"version":"0.16.0","status":"ok"}`). Per-RFC
|
||
> `contributor` / `discussant` roles, transactional email,
|
||
> migration `018_rfc_invitations.sql`.
|
||
> - **#16 Admin-create user + invite shipped as rfc-app v0.17.0
|
||
> to OHM live** (`deploys.id=21`, all 9 phases green,
|
||
> `/api/health` returns `{"version":"0.17.0","status":"ok"}`).
|
||
> Admin-driven user provisioning with optional custom message,
|
||
> opaque-token claim flow, migration `019_user_invite_tokens.sql`.
|
||
>
|
||
> Plus three new roadmap items captured mid-session
|
||
> (`ohm-rfc/ROADMAP.md` items #20, #21, #22 — see arc 7 below):
|
||
> email deliverability (DNS + SMTP + template hygiene), Amplitude
|
||
> audit + standing instrumentation discipline (with Part C
|
||
> identity-lifecycle work folded inline into all three v0.15.0 /
|
||
> v0.16.0 / v0.17.0 release commits), and pro-analytics-consent
|
||
> copy on the privacy/cookies opt-in (with legal caveats spelled
|
||
> out so the drafting accounts for 501(c)(3)-planned-not-granted
|
||
> status, "no external sharing" honesty about Amplitude as the
|
||
> processor, and consent-coercion risk).
|
||
>
|
||
> Side effect of the pin jumping from `0.12.0` → `0.15.0` → `0.16.0`
|
||
> → `0.17.0`: OHM now also runs v0.13.0's cookie-consent banner
|
||
> and v0.14.0's `/docs` route, which had been integrated on
|
||
> `main` since Session J but were not currently deployed because
|
||
> the pin had rolled back during Session K's Wave-4 sequence.
|
||
|
||
---
|
||
|
||
## Pre-session state
|
||
|
||
- **rfc-app**: `main` at `b3f1b15` (Release 0.12.0 — Turnstile).
|
||
`VERSION` = `0.12.0`. `frontend/package.json#version` = `0.12.0`.
|
||
Tags shipped through v0.14.0; the v0.x lineage is
|
||
`v0.2.0, .1, .2, .3, v0.3.0, v0.4.0, v0.5.0, v0.6.0, v0.7.0,
|
||
v0.8.0, v0.9.0, v0.10.0, v0.11.0, v0.12.0, v0.13.0, v0.14.0`.
|
||
Last migration on disk: `017_device_trust.sql` (slot 016
|
||
reserved + skipped per Session K).
|
||
- **ohm-rfc**: `.rfc-app-version` = `0.12.0`. The pin was "below"
|
||
v0.13.0 (cookie banner) and v0.14.0 (`/docs`) — those features
|
||
were integrated on rfc-app `main` but not currently deployed
|
||
to OHM. Any Wave-5 pin bump past `0.12.0` would pick up both
|
||
side-effect features; flagged in arc 1 + reconfirmed at the
|
||
arc 6 deploy. ROADMAP main was at `9654cbb` (post-Session-K
|
||
ROADMAP: #10 v0.12.0 shipped row).
|
||
- **OHM live**: serving `v0.12.0` per
|
||
`https://ohm.wiggleverse.org/api/health` returning
|
||
`{"version":"0.12.0","status":"ok"}`. `flotilla deploy log
|
||
ohm-rfc-app` showed last successful deploy as `deploys.id=18`
|
||
(v0.12.0, succeeded, 11:18 UTC) — the post-`TURNSTILE_REQUIRED=true`
|
||
re-deploy from Session K that put abuse defense fail-closed.
|
||
- **ohm-rfc-app-flotilla**: `main` at `65c0e55` (CLAUDE.md
|
||
secret-set gesture correction). No flotilla work expected in
|
||
Wave 5; none performed.
|
||
- **ohm-infra**: most recent transcript is Session K
|
||
(`SESSION-K-TRANSCRIPT-2026-05-28T03-20--2026-05-28T03-55.md`).
|
||
Next letter is L (Session K = previous driver; Session J = the
|
||
parallel docs-feature / v0.14.0 session that ran during
|
||
Session I's wind-down).
|
||
- **Hard rule baked in mid-Session-K**: never EVER ask the operator
|
||
to paste secret bytes into the conversation. Stayed honored
|
||
through this session — the Amplitude key the operator pasted is
|
||
a public bundle-embedded value (same nature as
|
||
`VITE_TURNSTILE_SITE_KEY`), which CLAUDE.md explicitly carves
|
||
out as fine to receive in conversation. The driver did not ask
|
||
for any value; the operator volunteered the vendor's
|
||
installation prompt (which contained the key inline) +
|
||
confirmed it. Reconciliation between the bedtime prompt's
|
||
"wave-pauses on AMPLITUDE_API_KEY" framing and the actual
|
||
vendor-recommended overlay binding is documented as the
|
||
v0.15.0 CHANGELOG's "Caveat — overlay binding" section.
|
||
|
||
---
|
||
|
||
## Turn-by-turn arc
|
||
|
||
The session moves through nine arcs, in order.
|
||
|
||
### Arc 1 — Reconnaissance + dispatch
|
||
|
||
The driver read `~/git/ohm-infra/SESSION-PROTOCOL.md` and
|
||
`/Users/benstull/projects/wiggleverse/ohm-rfc/ROADMAP.md`
|
||
end-to-end, verified pre-session ground truth (rfc-app `main` at
|
||
`b3f1b15`, OHM serves v0.12.0 at `deploys.id=18`, ohm-rfc pin at
|
||
`0.12.0`), and dispatched three subagents in a single message
|
||
with parallel `Agent` tool uses, `subagent_type: general-purpose`,
|
||
running in the background. Each subagent created its own
|
||
`git worktree add` in `/Users/benstull/git/rfc-app-<target-version-
|
||
slug>` (the `Agent` tool's worktree isolation only covers the
|
||
flotilla CWD, not the rfc-app CWD subagents needed — Session K
|
||
lesson). Each prompt was self-contained: full roadmap text,
|
||
target version, reserved migration slot (018 for ν, 019 for ο,
|
||
none for ξ), descending-order CHANGELOG insertion point, hard
|
||
constraints (do NOT tag, do NOT touch the ohm-rfc pin, do NOT
|
||
run `flotilla deploy`, no force-push, no `--amend`, no secret
|
||
bytes, no `git config` edits), and a structured reporting brief.
|
||
|
||
Worktree base discovered via `ls -la /Users/benstull/git/`. One
|
||
ground-truth correction caught early: `~/git/ohm-infra` is NOT a
|
||
git repo locally — it's a working directory the publish script
|
||
clones the remote `wiggleverse/ohm-session-history` into. Session
|
||
K's prompt phrased it correctly; the driver's first `cd
|
||
~/git/ohm-infra && git log` failed and surfaced the right model.
|
||
|
||
### Arc 2 — Operator returns mid-session: Amplitude scope settled
|
||
|
||
While the three subagents were running in the background, the
|
||
operator returned to the keyboard and surfaced the Amplitude
|
||
provisioning:
|
||
|
||
1. "I provisioned an Amplitude instance."
|
||
2. The vendor's installation-wizard prompt (verbatim — it included
|
||
the API key inline as a literal string argument:
|
||
`amplitude.initAll('741709e30574cc8314b83b763f675c32', {
|
||
"analytics": { "autocapture": true }, "sessionReplay":
|
||
{ "sampleRate": 1 } });` — and recommended the
|
||
`@amplitude/unified` package).
|
||
3. "I have the amplitude API key… can set that secret when we're
|
||
ready. Or, if it's not a secret and is really a public thing
|
||
that's in JavaScript, can provide it here."
|
||
|
||
The driver settled three things in one response:
|
||
|
||
- **Secret vs. public**: the Amplitude browser key is bundle-
|
||
embedded by design (visible in dev tools; vendor's own prompt
|
||
shows it as a literal string argument). Same nature as
|
||
`VITE_TURNSTILE_SITE_KEY` from v0.12.0, which is explicitly
|
||
public per CLAUDE.md. The right binding is `flotilla overlay
|
||
set`, not `flotilla secret set`. The roadmap row #13's "new
|
||
secret: AMPLITUDE_API_KEY" wording predated vendor
|
||
consultation; corrected at the v0.15.0 CHANGELOG + the
|
||
shipped-row update.
|
||
- **Scope change**: the vendor recommends `@amplitude/unified`
|
||
(analytics + session replay bundled) instead of the
|
||
`@amplitude/analytics-browser` the dispatch brief specified.
|
||
`sampleRate: 1` (100% session replay) is the vendor default
|
||
— meaningfully larger privacy footprint than events-only. The
|
||
driver surfaced three options to the operator via
|
||
AskUserQuestion: events only / events + replay 10% / events
|
||
+ replay 100% (vendor default). The operator picked **events
|
||
+ session replay @ 100% (vendor default)**.
|
||
- **In-flight subagent**: ξ was already running under the
|
||
pre-vendor brief; no `SendMessage` tool available in this
|
||
harness to redirect it; chosen path is "let ξ finish, driver
|
||
post-corrects on the branch" since the post-correction is
|
||
tight and mechanical (package swap, init-call rewrite,
|
||
CHANGELOG MUST-step rewrite). The wrapper structure, event
|
||
taxonomy, and consent integration ξ would produce were
|
||
brief-correct and would survive the post-correction
|
||
unchanged.
|
||
|
||
### Arc 3 — Operator surfaces roadmap items #20, #21, #22
|
||
|
||
Across three messages while the subagents were running, the
|
||
operator added three new roadmap items:
|
||
|
||
- **#20 Email deliverability** — "do whatever we can to prevent
|
||
invites from going to the spam folder." Captured during arc 5
|
||
before any subagent had returned. Scope spelled out: DNS (SPF,
|
||
DKIM, DMARC, BIMI follow-up), SMTP-provider posture (verified
|
||
sender domain, warm-up), From / Reply-To hygiene (no
|
||
`noreply@`), rfc-app template hygiene (plain-text alternative,
|
||
`List-Unsubscribe` + `List-Unsubscribe-Post` per RFC 8058,
|
||
bounce handling), engagement signals, monitoring (mail-tester,
|
||
Postmaster Tools, DMARC `rua=`). Overlap with #18 (Secure SMTP
|
||
+ Gitea webhook) flagged explicitly; likely shipped together.
|
||
Two-layer rule honored: rfc-app template hygiene is
|
||
framework-wide, DNS + SMTP-provider config + BIMI logo are
|
||
OHM-specific. Sequencing: don't block #12 / #16 on this; the
|
||
real-world inbox/spam data from those first invite blasts
|
||
informs the #20 sweep afterward. Committed to ohm-rfc at
|
||
`2072663`.
|
||
- **#21 Amplitude audit + standing instrumentation discipline** —
|
||
captured after the operator extended Amplitude scope in arc 2.
|
||
Two-part structure: Part A (deep audit of v0.15.0's taxonomy +
|
||
autocapture + replay quality once a week of real data is in)
|
||
+ Part B (standing discipline: SPEC.md analytics chapter,
|
||
rfc-app CONTRIBUTING checklist, optional CI lint).
|
||
Subsequently extended with **Part C** after the operator said:
|
||
"ensure a new user in Amplitude is created when an invite is
|
||
sent and we have a user with the OHM user ID set on that
|
||
event"; "appropriate pattern for setting user properties on
|
||
login + clearing on logout"; "implement Amplitude best
|
||
practices from the very get-go". Part C captures the
|
||
user-identity lifecycle contract (setUserId + identify
|
||
properties on sign-in; setUserProperties on mid-session state
|
||
change; reset on sign-out; identify-BEFORE-track on invite-
|
||
claim so the Amplitude record is created with the OHM user_id
|
||
from the first event rather than as an anonymous device that
|
||
retroactively links). Part C explicitly names what "ships
|
||
inline in Session L" vs. what waits for the data-informed
|
||
audit. Committed to ohm-rfc at `f006d33` (Parts A + B), then
|
||
`1c88f71` (Part C added).
|
||
- **#22 Pro-analytics-consent copy on the privacy/cookies
|
||
opt-in** — captured later: "add language to the privacy /
|
||
cookies opt-in that asks people to consider allowing analytics
|
||
and we promise we won't share it externally — we're a
|
||
non-profit and will only use it to make the experience better
|
||
(if that language is legal)." Legal caveats spelled out in
|
||
the item itself: 501(c)(3) is **planned not granted** per the
|
||
project memory (copy MUST NOT claim "we are a 501(c)(3)"
|
||
today; defensible alternatives are "being formed as", "non-
|
||
commercial project", or neutral "operated by Wiggleverse Org"
|
||
with an explainer); "never shared externally" is literally
|
||
untrue because Amplitude IS an external service (defensible
|
||
framing is "don't sell or trade" + "Amplitude holds it on our
|
||
behalf"); purpose-limitation pledges are enforceable; GDPR /
|
||
CCPA caution about coerced consent; withdrawal must stay easy.
|
||
Drafting flow: operator drafts → counsel reviews → subagent
|
||
wires approved text into banner + `/privacy` + `/cookies`
|
||
(the wiring step is small and mechanical). Committed to
|
||
ohm-rfc at `26b4684`.
|
||
|
||
### Arc 4 — Session ξ returns; driver post-corrects on its branch
|
||
|
||
ξ completed at `0fd8c52` on `feature/v0.15.0-amplitude` (origin +
|
||
benstull). 14 files touched; the nine-event taxonomy (Page
|
||
Viewed, RFC Viewed, User Signed In / Signed Out, RFC Proposed,
|
||
PR Opened, Comment Posted, Beta Access Requested, Admin
|
||
Permission Decision) wired into App.jsx, Login.jsx, ProposeModal,
|
||
PRModal, RFCView, RFCDiscussionPanel, PRView, Admin. Consent gate
|
||
read at `frontend/src/lib/consent.js:91` (getConsent) and `:102`
|
||
(onConsentChange). One env-var-naming flag from ξ that the
|
||
post-correction also resolves: ξ used `VITE_AMPLITUDE_API_KEY` in
|
||
code (Vite prefix convention) but the brief specified
|
||
`AMPLITUDE_API_KEY` in the CHANGELOG MUST-step; the overlay-
|
||
binding correction made the env-var-naming inconsistency moot.
|
||
|
||
Driver post-correction (on the same branch, as a follow-up commit
|
||
`6cfbf69` — no amend of pushed commits):
|
||
- Package: `npm uninstall @amplitude/analytics-browser && npm
|
||
install @amplitude/unified` (resolved to `^1.1.9`).
|
||
- Init call: `mod.init(KEY, undefined, { defaultTracking: false
|
||
}).promise` → `mod.initAll(KEY, { analytics: { autocapture:
|
||
true }, sessionReplay: { sampleRate: 1 } })` with a defensive
|
||
`if (ret && ret.promise) await ret.promise` for unified-build
|
||
variance.
|
||
- CHANGELOG MUST step: secret-set → `flotilla overlay set
|
||
ohm-rfc-app VITE_AMPLITUDE_API_KEY=<key>`. New Caveat sections
|
||
spelled out the binding choice + the session-replay scope
|
||
(with the separate-consent-category future-work flagged as
|
||
§19.2).
|
||
- `frontend/.env.example`: VITE_AMPLITUDE_API_KEY block rewritten
|
||
to describe the overlay binding.
|
||
- Wrapper header comment + warning text updated to reference
|
||
unified + overlay.
|
||
- Wrapper structure, event taxonomy, queue + drain, consent
|
||
integration, anonymize-on-sign-out: all unchanged from ξ's
|
||
work.
|
||
- Frontend build verified green; pushed to origin + benstull
|
||
at `6cfbf69`.
|
||
|
||
### Arc 5 — Sessions ο, then ν return
|
||
|
||
**ο** completed at `41b0c6a` on
|
||
`feature/v0.17.0-admin-create-user`. 33 backend tests total in
|
||
ο's surface (15 new in `test_admin_create_user_invite_vertical.py`).
|
||
Notable decisions: opaque DB token (256-bit CSPRNG, bcrypt-at-
|
||
rest, 7-day TTL constant `INVITE_TOKEN_TTL_DAYS = 7`); immediate-
|
||
send (no admin-review queue); no bulk-invite (deferred); OTC
|
||
skipped on first sign-in (token = proof of email control); no
|
||
`users` table changes (the brief floated `first_sign_in_at IS
|
||
NULL` as the discriminator, but the existing
|
||
`users.last_seen_at` is NOT NULL with `datetime('now')` default
|
||
+ no `first_sign_in_at` column existed — so the discriminator
|
||
became "active row in `user_invite_tokens` joined on
|
||
invited_user_id" instead, and `/api/admin/users` carries a
|
||
new `pending_invite` field via that join); admin self-grant
|
||
refusals (422 self-invite, 409 duplicate, 422 non-owner granting
|
||
owner); new `permission_events` event_kind `'user_invited'` for
|
||
the audit trail; `/api/invites/claim` lives in `main.py`'s
|
||
oauth_router (shares `_set_device_trust_cookie` helper with
|
||
`/auth/otc/verify`); inviter display name reads off the DB row
|
||
fresh (not from the session-cookie cache). Admin.jsx touched
|
||
additively only — button + modal + badge + listing-field
|
||
consumption; existing component shape preserved.
|
||
|
||
**ν** completed last (~30 min vs ο's ~19 and ξ's ~12) at
|
||
`a51beec` on `feature/v0.16.0-owner-invite`. 237 backend tests
|
||
total in ν's surface (18 new in `test_rfc_invitations_vertical.py`;
|
||
6 existing tests in test_pr_flow / test_graduation / test_e2e_smoke
|
||
opted into the new contract via a new `grant_rfc_collaborator`
|
||
helper). Notable: per-RFC roles are `contributor` (PR + discussion)
|
||
or `discussant` (discussion only); re-accept that would lower the
|
||
role is a no-op; the per-RFC gate falls through to the v0.6.0
|
||
platform-granted contract when an RFC has no frontmatter owners
|
||
yet (super-draft pre-§13.1 claim); accept endpoint requires the
|
||
accepting user's email to match the invitee's
|
||
`invitee_email` case-insensitively; platform-grant decision stays
|
||
the admin's call (accepting a per-RFC invite while pending
|
||
surfaces on `/api/admin/users` as the `rfc_invitations` array per
|
||
user — informing, not deciding, the platform grant); email
|
||
dispatch reuses `EmailConfig.from_env()` + `_SENT` like
|
||
`email_otc.py`; send failure does NOT roll back the invite row
|
||
(owner has the token on the listing surface for an out-of-band
|
||
share); regex email validation (`^[^\s@]+@[^\s@]+$`) instead of
|
||
pydantic `EmailStr` to avoid pulling in `email-validator`,
|
||
matching the v0.7.0 OTC body's shape. **ν did NOT touch
|
||
Admin.jsx directly** — the `/api/admin/users` response gained an
|
||
additive `rfc_invitations` array (forward-compat for the existing
|
||
UI), so the Admin.jsx-collision risk with ο that the bedtime
|
||
prompt flagged did not materialize. The conflict moved to
|
||
`backend/app/api_admin.py` (both ν and ο extended different
|
||
response fields + ο added new endpoints) — additive in different
|
||
scopes, hand-reconcilable.
|
||
|
||
### Arc 6 — Integration, with #21 Part C wiring folded inline
|
||
|
||
The driver squash-merged each branch into rfc-app `main` in
|
||
version order, hand-resolving conflicts and adding inline
|
||
Amplitude wiring per #21 Part C "ships inline in Session L":
|
||
|
||
**v0.15.0 integration** — fast-forward squash from `b3f1b15`
|
||
(no conflicts: main was at ξ's base). Driver-side extensions
|
||
folded in BEFORE the release commit:
|
||
|
||
- `analytics.js`: `identify(...)` signature extended to
|
||
`{ user_id, properties? }` — properties apply via a new
|
||
internal `applyProperties(props)` that builds an Amplitude
|
||
`Identify` event with `.set(k, v)` semantics by default and
|
||
`.setOnce(k, v)` semantics when the value is wrapped as
|
||
`['__setOnce__', v]` (the sentinel pattern). New public
|
||
`setUserProperties(properties)` API exposed the same
|
||
property-apply path for mid-session state changes.
|
||
`anonymize()` now clears `_pendingProperties` too so a
|
||
fresh sign-in doesn't carry-over the previous user's
|
||
property cache.
|
||
- `App.jsx`: identify call extended to pass a viewer-derived
|
||
property bag — `role` / `permission_state` / `passcode_set` /
|
||
`device_trusted` (mutable `.set()`), `first_sign_in_at` /
|
||
`account_created_at` (immutable `.setOnce()`). PII discipline
|
||
preserved: no email, no display_name, no gitea_login.
|
||
- CHANGELOG opening summary, Added wrapper bullet, and Added
|
||
user-binding bullet rewritten to reflect the extensions.
|
||
- Committed as `72f8457` "Release 0.15.0: Amplitude Analytics
|
||
+ Session Replay (with #21 Part C identity lifecycle)";
|
||
tag `v0.15.0` pushed origin + benstull.
|
||
|
||
**v0.16.0 integration** — conflicts in VERSION, package.json,
|
||
CHANGELOG. CHANGELOG resolved by reordering to strict-descending
|
||
(0.16.0 above 0.15.0 above 0.14.0) via a small Python script
|
||
that read the conflict markers and emitted the incoming-block-
|
||
first variant. Inline Amplitude wiring per #21 Part C:
|
||
|
||
- EVENTS taxonomy in analytics.js extended with
|
||
`INVITATION_SENT: 'Invitation Sent'` + `INVITATION_ACCEPTED:
|
||
'Invitation Accepted'` (plus `USER_INVITED` + `INVITE_CLAIMED`
|
||
pre-added for v0.17.0).
|
||
- `InvitationsModal.jsx`: fires
|
||
`track(EVENTS.INVITATION_SENT, { rfc_slug, role_in_rfc })`
|
||
on send success.
|
||
- `AcceptInvitation.jsx`: fires
|
||
`identify({ user_id, properties: { invited_at (setOnce),
|
||
last_invited_to_rfc, last_invite_role_in_rfc, claim_method:
|
||
'rfc-invite' } })` BEFORE
|
||
`track(EVENTS.INVITATION_ACCEPTED, { rfc_slug, role_in_rfc })`
|
||
on accept success.
|
||
- CHANGELOG Added section extended with an "Amplitude wiring"
|
||
bullet documenting the calls + the no-PII discipline.
|
||
- Committed as `ee4925b` "Release 0.16.0: owner-only invite for
|
||
per-RFC contribution + discussion (+ #21 Part C Amplitude
|
||
wiring)"; tag `v0.16.0` pushed.
|
||
|
||
**v0.17.0 integration** — conflicts in VERSION, package.json,
|
||
CHANGELOG, App.jsx (two regions — both imports + both routes
|
||
kept), `backend/app/api_admin.py` (two regions — both per-user
|
||
additive fields + ο's new endpoints all kept, resolved by a
|
||
small Python script that emits head-block then incoming-block).
|
||
Full backend pytest run post-merge: **252 tests pass** (matches
|
||
the union of ν's 237 + ο's 15 new). Inline Amplitude wiring per
|
||
#21 Part C:
|
||
|
||
- `Admin.jsx` `CreateUserInviteModal`: fires
|
||
`track(EVENTS.USER_INVITED, { target_user_id, initial_role,
|
||
custom_message_chars })` on `POST /api/admin/users` success.
|
||
`custom_message_chars` is a coarse admin-effort signal
|
||
(0 = template-only, 1+ = personalized).
|
||
- `InviteClaim.jsx`: fires
|
||
`identify({ user_id, properties: { claim_method:
|
||
'admin-invite', invited_at (setOnce), invited_by_admin_id
|
||
(setOnce), initial_role (setOnce) } })` BEFORE
|
||
`track(EVENTS.INVITE_CLAIMED, { invited_by_admin_id,
|
||
initial_role, needs_passcode, trust_device })` on
|
||
`POST /api/invites/claim` success — so the Amplitude user
|
||
record is created with the OHM user_id from the very first
|
||
event the invitee fires, never as an anonymous device that
|
||
retroactively links.
|
||
- Committed as `1456c8b` "Release 0.17.0: admin-create user +
|
||
invite email (+ #21 Part C Amplitude wiring)"; tag `v0.17.0`
|
||
pushed.
|
||
|
||
### Arc 7 — Three serialized deploys to OHM
|
||
|
||
Per the §8.3 single-in-flight-deploy rule and Session K's
|
||
serialization lesson, each version deployed in turn:
|
||
|
||
- **Overlay set**: `flotilla overlay set ohm-rfc-app
|
||
VITE_AMPLITUDE_API_KEY=…` ran first. Confirmed via
|
||
`flotilla overlay show ohm-rfc-app` — the key joined
|
||
`VITE_TURNSTILE_SITE_KEY` and the other non-secret overlay
|
||
values; all five Secret-Manager refs unchanged.
|
||
- **v0.15.0**: pin bumped, committed `c784107`, pushed.
|
||
`flotilla deploy run ohm-rfc-app` → `deploys.id=19`, all 9
|
||
phases green, verify 6.9s. `/api/health` returns
|
||
`{"version":"0.15.0","status":"ok"}`. Side-effect features
|
||
v0.13.0 (cookie banner) + v0.14.0 (`/docs` route) now also
|
||
on production for the first time since the pin had rolled
|
||
back during Session K's Wave-4 sequence.
|
||
- **v0.16.0**: pin bumped, committed `1c2bb26`, pushed.
|
||
`flotilla deploy run ohm-rfc-app` → `deploys.id=20`, all 9
|
||
phases green, verify 2.4s. `/api/health` returns
|
||
`{"version":"0.16.0","status":"ok"}`.
|
||
- **v0.17.0**: pin bumped, committed `dc7a572`, pushed.
|
||
`flotilla deploy run ohm-rfc-app` → `deploys.id=21`, all 9
|
||
phases green, verify 2.4s. `/api/health` returns
|
||
`{"version":"0.17.0","status":"ok"}`.
|
||
|
||
No deploy aborts, no rollbacks, no surprises. The "stop on first
|
||
deploy failure" Wave constraint never engaged.
|
||
|
||
### Arc 8 — ROADMAP strikethroughs
|
||
|
||
`ohm-rfc/ROADMAP.md` rows for #12, #13, #16 struck through with
|
||
release-tag links + `deploys.id` references. #13's note records
|
||
the mid-session binding correction ("originally said `secret
|
||
set AMPLITUDE_API_KEY`; settled as `overlay set
|
||
VITE_AMPLITUDE_API_KEY`") so the historical record is honest
|
||
about the pre-vendor wording. Committed at `47cb2f9`, pushed.
|
||
|
||
### Arc 9 — Protocol amendment: subagent transcripts (Session-L-introduced)
|
||
|
||
After the driver had written what it thought was the final
|
||
Session L transcript, the operator surfaced a series of follow-up
|
||
asks:
|
||
|
||
1. "Prompt for next session pls?" + "and update transcript" —
|
||
straightforward; the next-session prompt was already at the
|
||
bottom of the transcript file, and the driver surfaced it as
|
||
a copy-pasteable block in the conversation.
|
||
2. "Do the forked agents get transcripts captured? If not, let's
|
||
make sure they write their own transcripts out before
|
||
returning to the main session. They can be e.g. SESSION-L.1
|
||
etc so it's clear they're a subsession."
|
||
3. "And the call to the agent should tell it what its transcript
|
||
ID is."
|
||
4. "And then update the transcript again — always do that as a
|
||
final step of a session."
|
||
|
||
The first two are a protocol amendment. The current
|
||
SESSION-PROTOCOL.md §3 said "Driver-only writes" — subagents
|
||
returned report-backs that the driver folded into the single
|
||
session transcript. That shape captures the high-level shape of
|
||
each subagent's work but flattens the detail (mid-flight
|
||
decisions, dead ends, schema choices, micro-corrections,
|
||
test-suite outcomes). The amendment introduces subsession
|
||
transcripts:
|
||
|
||
- **Naming**: `SESSION-<letter>.<n>-TRANSCRIPT-<start>--<end>.md`
|
||
(e.g. `SESSION-M.1-TRANSCRIPT-…md` for the first subagent in
|
||
Session M). Nested ordinals (`.1.1`) supported if a forked
|
||
subagent itself dispatches another.
|
||
- **Dispatch contract**: the driver pre-assigns subsession IDs
|
||
in dispatch order, names the exact filename + the
|
||
before-return write requirement + the transcript skeleton in
|
||
each subagent's prompt.
|
||
- **Driver responsibility**: main transcript references each
|
||
subsession by filename in the cut-state table; driver
|
||
publishes its own + each subsession's transcript via the
|
||
publish script. Stub-write on subagent's behalf if the
|
||
subagent crashed without producing one.
|
||
- **Applies from Session M onward.** Session L itself ran under
|
||
the prior "driver-only" rule because the amendment landed
|
||
mid-session after L's three subagents had already returned;
|
||
no `SESSION-L.1` / `.2` / `.3` transcript files exist or will
|
||
be created retroactively.
|
||
|
||
Changes landed:
|
||
|
||
- `~/git/ohm-infra/SESSION-PROTOCOL.md` — new §5 "Subagent
|
||
transcripts (Session-L amendment)" inserted before what was
|
||
§5 (now renumbered §6 "When the protocol is unclear").
|
||
- `~/git/ohm-infra/scripts/publish-transcript.sh` — filename
|
||
validator regex extended from `[A-Za-z]+` to
|
||
`[A-Za-z]+(\.[0-9]+)*` so subsession filenames pass.
|
||
Dry-run validation confirmed: the parent filename still
|
||
validates, and a hypothetical `SESSION-M.1-TRANSCRIPT-…md`
|
||
would also pass.
|
||
- Session L's next-session prompt (below) updated with the new
|
||
dispatch shape so Session M's driver inherits it.
|
||
|
||
The fourth ask — "update the transcript again — always do that
|
||
as a final step of a session" — was captured as a feedback
|
||
memory at
|
||
`~/.claude/projects/.../memory/feedback_transcript_is_final_step.md`
|
||
so future driver sessions honor the "transcript is the LAST
|
||
thing before publish" discipline. Memory index updated.
|
||
|
||
### Arc 10 — First transcript pass + publish
|
||
|
||
This file's first "final" pass folded in arcs 1–9 + arc 10;
|
||
cut-state reflected the protocol amendment + the publish-script
|
||
edit + the memory captured. Published via
|
||
`~/git/ohm-infra/scripts/publish-transcript.sh` to
|
||
`wiggleverse/ohm-session-history` at commit `b983b7d`.
|
||
|
||
### Arc 11 — Operator reinforced the final-step discipline; re-finalize + re-publish
|
||
|
||
Immediately after the first publish, the operator repeated the
|
||
instruction: "and then update the transcript again — always do
|
||
that as a final step of a session." This is precisely the
|
||
pattern the new feedback memory
|
||
(`feedback_transcript_is_final_step.md`) anticipates: a "finalized"
|
||
transcript that gets new material added after it (in this case,
|
||
the arc-9 protocol-amendment exchange itself + the driver's
|
||
closing summary that surfaced the next-session prompt) is not
|
||
yet final — the discipline is to apply the new material AND
|
||
finalize AGAIN AND re-publish.
|
||
|
||
So this arc 11 captures: the operator reinforced the discipline,
|
||
the driver re-finalized the transcript with this arc included,
|
||
and re-published. The publish script's idempotent-on-identical /
|
||
update-on-changed behavior means the re-publish lands as an
|
||
"Update SESSION-L-TRANSCRIPT-…md" commit on
|
||
`wiggleverse/ohm-session-history`, not a duplicate file. The
|
||
filename's `--<end>` timestamp stays the original
|
||
`2026-05-28T05-17` (the time of the first finalization); the
|
||
re-finalized content acknowledges that the actual session end
|
||
crept a few minutes later (~05-25 PST) as the discipline-
|
||
reinforcement exchange happened.
|
||
|
||
Future driver sessions should expect this pattern to recur:
|
||
"finalization" is not a one-shot gesture. Any operator
|
||
instruction landing after the first publish triggers another
|
||
finalize + re-publish cycle. The discipline is captured for
|
||
Session M onward in the next-session prompt's closing block
|
||
+ the feedback memory.
|
||
|
||
### Arc 12 — Roadmap item #23 surfaced: rename transcripts to numeric form
|
||
|
||
Right after arc 11's re-publish, the operator surfaced another
|
||
ask: "Add renaming transcripts from e.g. SESSION-A to SESSION-0001.0
|
||
etc to the roadmap." This is roadmap item #23 (Track Ω, operator-
|
||
led, one-session operational gesture):
|
||
|
||
- **New convention**: `SESSION-NNNN.M-TRANSCRIPT-<start>--<end>.md`
|
||
where `NNNN` is a zero-padded 4-digit session number and `M` is
|
||
the subagent ordinal (`.0` = main driver, `.1+` = subagents).
|
||
Nested as `.M.N` for sub-subagents.
|
||
- **Mapping**: A → `SESSION-0001.0`, B → `SESSION-0002.0`, …,
|
||
L → `SESSION-0012.0`. Twelve existing transcripts in
|
||
`wiggleverse/ohm-session-history` to rename, plus the local
|
||
`~/git/ohm-infra/` copies.
|
||
- **Surfaces touched** (enumerated in the item): the public repo
|
||
(git mv per file for history preservation), local working dir,
|
||
SESSION-PROTOCOL.md §1 + §5 + worked examples, publish-
|
||
transcript.sh filename validator, flotilla CLAUDE.md
|
||
references, ohm-rfc/ROADMAP.md shipped-row references in the
|
||
table, cross-transcript body links (Session-K-to-Session-I
|
||
etc.).
|
||
- **Open questions enumerated in the item**: git-mv vs.
|
||
add+delete (preserve history); redirect tombstones vs. 404
|
||
(URL breakage for external references); sequencing relative
|
||
to the next-letter-after-L session (most-elegant timing is
|
||
Session M as the rename session itself, so `SESSION-0013.0`
|
||
debuts as the new shape from the start). Decide at session
|
||
time.
|
||
|
||
Committed to ohm-rfc at `8cce148` with a 130-line item
|
||
description + a single-row addition to the version-targets table.
|
||
|
||
### Arc 13 — Re-finalize + re-publish (again)
|
||
|
||
Per the same discipline arc 11 named: the operator's #23 ask
|
||
landed after the first re-publish, so this triggers another
|
||
finalize-and-republish cycle. This arc 12+13 added; cut-state
|
||
table updated to reflect ohm-rfc at `8cce148` and the third
|
||
finalization pass; re-published.
|
||
|
||
If a fourth operator instruction lands after this publish, the
|
||
cycle continues. The discipline holds open-ended.
|
||
|
||
---
|
||
|
||
## Cut state (end of session)
|
||
|
||
| | |
|
||
| --- | --- |
|
||
| rfc-app | `main` at `1456c8b` (Release 0.17.0). Tags through v0.17.0. Last migration on disk: `019_user_invite_tokens.sql`. 252 backend tests pass. |
|
||
| ohm-rfc | `main` at `8cce148` (ROADMAP strike + pin at 0.17.0 + #23 rename-transcripts item added in arc 12). `.rfc-app-version` = `0.17.0`. |
|
||
| OHM live | `deploys.id=21`, v0.17.0, healthy. `/api/health` = `{"version":"0.17.0","status":"ok"}`. Cookie banner (v0.13.0), `/docs` route (v0.14.0), Amplitude analytics + session replay (v0.15.0), per-RFC owner invites (v0.16.0), admin-create user + invite (v0.17.0) all live. |
|
||
| flotilla overlay | `VITE_AMPLITUDE_API_KEY` bound (public). All other overlay / secret bindings unchanged. |
|
||
| ohm-rfc-app-flotilla | `main` at `65c0e55`. Unchanged this session. |
|
||
| ohm-infra (local) | `SESSION-PROTOCOL.md` amended (new §5 "Subagent transcripts"; §5 → §6); `scripts/publish-transcript.sh` regex extended to accept `SESSION-<letter>.<n>-TRANSCRIPT-…md` subsession form. |
|
||
| Auto-memory | New feedback memory: `feedback_transcript_is_final_step.md` (transcript is the LAST step before publish). MEMORY.md index updated. |
|
||
|
||
| Wave 5 ledger | Status |
|
||
| --- | --- |
|
||
| #13 Amplitude (v0.15.0) | ✅ shipped, `deploys.id=19` |
|
||
| #12 Owner invite (v0.16.0) | ✅ shipped, `deploys.id=20` |
|
||
| #16 Admin-create user + invite (v0.17.0) | ✅ shipped, `deploys.id=21` |
|
||
|
||
| New roadmap items captured (Session L) | Status |
|
||
| --- | --- |
|
||
| #20 Email deliverability | 📝 captured, awaiting session |
|
||
| #21 Amplitude audit + standing discipline | 📝 captured. Part C identity-lifecycle work shipped inline in Session L's v0.15.0 + v0.16.0 + v0.17.0 commits. Part A audit waits for ~1 week of real Amplitude data; Part B discipline can start anytime. |
|
||
| #22 Pro-analytics-consent copy on privacy/cookies opt-in | 📝 captured. Waiting on operator-drafted + counsel-reviewed copy. |
|
||
| #23 Rename transcripts `SESSION-<letter>` → `SESSION-NNNN.M` | 📝 captured. One Track-Ω session, operator-led. Mapping A→0001.0 through L→0012.0 spelled out in the item. Surfaces enumerated (12 transcripts + protocol + publish-script + cross-doc refs). |
|
||
|
||
---
|
||
|
||
## §19.2 candidates surfaced
|
||
|
||
1. **`@amplitude/unified` bundle size + lazy import effectiveness**
|
||
— the unified package adds ~150 KB gzipped (vs. ~30 KB for
|
||
analytics-browser only); the wrapper's consent-gated `await
|
||
import('@amplitude/unified')` keeps it off the initial bundle
|
||
for users who haven't opted in, but the post-consent init
|
||
path hasn't been measured for jank. Part of #21 Part A's
|
||
bundle + performance check.
|
||
2. **Session-replay-specific consent category** — v0.13.0's
|
||
cookie banner has a single "analytics" toggle that gates both
|
||
events and full-DOM session recording. Recording has a
|
||
meaningfully larger privacy footprint than event counters;
|
||
splitting the consent into "analytics" vs "session replay"
|
||
categories is the cleaner shape. Captured in #21's Part A +
|
||
§19.2 follow-up.
|
||
3. **CHANGELOG `### Migration` block has no machine-readable
|
||
shape** — three releases in this session said "auto-applied
|
||
on deploy by the existing migration runner" with prose;
|
||
nothing pins which slot was claimed or surfaces a conflict
|
||
if two parallel features claim the same slot. The slot
|
||
coordination this session (016 reserved, 018 → #12, 019 →
|
||
#16) was done by the driver's brief, not enforced by tooling.
|
||
Worth a small lint or CI step.
|
||
4. **Bedtime prompt drift vs. live state** — the bedtime prompt
|
||
said "OHM serves v0.12.0" which was accurate, but did NOT
|
||
mention that v0.13.0 and v0.14.0 features (cookie banner,
|
||
`/docs`) were integrated on `main` but absent from the live
|
||
deploy because the pin had rolled back during Session K.
|
||
Any Wave-5 deploy was always going to bring those features
|
||
back; the driver flagged this in arc 1 + handled it
|
||
transparently, but a session-bootstrap script that emits
|
||
"what is on main but not deployed?" would catch this kind of
|
||
drift before it surprises a future driver. (This is adjacent
|
||
to the Session-I §19.2 candidate around deploy-snapshot
|
||
surfacing.)
|
||
5. **Vendor-prompt-as-truth pattern** — Amplitude's installation
|
||
wizard is an LLM-targeted prompt the vendor publishes for
|
||
their customers to drop into Cursor / Claude Code / etc.
|
||
When the operator pastes it mid-session, the prompt's
|
||
recommendations override the roadmap row's pre-vendor
|
||
guesses (package name, init shape, secret-vs-overlay
|
||
framing). For future vendor integrations (Stripe? Linear?
|
||
Sentry?), a discipline is worth naming: vendor-prompt
|
||
guidance is authoritative for SDK-shape decisions, but
|
||
integration points (consent, identity, PII) stay project-
|
||
sovereign. Could become a §19.2 candidate spec section.
|
||
6. **Subagent re-direction without `SendMessage`** — when the
|
||
operator's mid-session input invalidated ξ's pre-vendor
|
||
brief, the driver had no `SendMessage` tool available to
|
||
redirect the in-flight subagent. The chosen path
|
||
(let-finish, driver-post-corrects) worked here because the
|
||
correction was small and on a fresh-cut branch; in a larger
|
||
correction it would waste subagent work or force a
|
||
`TaskStop` + re-dispatch. Worth exploring whether the
|
||
harness can expose `SendMessage` for background agents, or
|
||
whether the dispatch pattern should pre-commit to "wait for
|
||
green-light from the operator" before doing the more
|
||
invasive work.
|
||
|
||
---
|
||
|
||
## What lands on the operator's plate
|
||
|
||
1. **Amplitude dashboard verification** — vendor's installation
|
||
prompt says "tell the user to start the application and fire
|
||
an event to verify it is being sent to Amplitude." The
|
||
deploy + `/api/health` verification confirmed the version is
|
||
on the box; the **Amplitude-side confirmation that events
|
||
actually arrive** is the operator's gesture (you have the
|
||
dashboard login; the driver does not). Easiest probe per the
|
||
v0.15.0 CHANGELOG: open `https://ohm.wiggleverse.org` in an
|
||
Incognito window, accept analytics on the cookie banner,
|
||
navigate to an RFC, watch the project's live event stream +
|
||
session-replay panel. Expected events: `Page Viewed` (path),
|
||
`RFC Viewed` (rfc_slug, rfc_id), the autocapture-generated
|
||
click events on header links.
|
||
2. **Send a real invite (#16) end-to-end** — the live
|
||
`/admin/users` surface now has the "Create user + invite"
|
||
affordance. A real-world deliverability test is part of the
|
||
#20 input data: send to a gmail address, a yahoo address, a
|
||
protonmail address, watch which ones land in inbox vs. spam.
|
||
3. **Send a real per-RFC invitation (#12)** — same shape; pick
|
||
an RFC, hit "Invitations" in the header strip, send to your
|
||
alternate email.
|
||
4. **Roadmap items #20 / #22 are operator-led**:
|
||
- **#20** needs DNS edits on `wiggleverse.org` (SPF / DKIM /
|
||
DMARC) and SMTP-provider dashboard work. Subagent can
|
||
inventory + propose; the executions are operator-only.
|
||
- **#22** needs operator-drafted copy + counsel review
|
||
before a subagent wires it. The roadmap item spells out
|
||
the legal caveats (501(c)(3) is planned-not-granted, "no
|
||
external sharing" honesty about Amplitude as the
|
||
processor, purpose-limitation enforceability,
|
||
GDPR/CCPA coerced-consent risk).
|
||
5. **§19.2-candidate #6 (subagent re-direction)** is a
|
||
harness-feature request worth raising with the Claude Code
|
||
team if it bites again.
|
||
6. **Item #1 VM rename + #17 inventory + #19 PR coordination
|
||
+ #23 transcript-rename** stay operator territory. #23
|
||
adds one decision-point: do you want Session M to be the
|
||
rename session (so it debuts as `SESSION-0013.0`), or run
|
||
a few more letter-form sessions first and rename in a
|
||
later session?
|
||
|
||
---
|
||
|
||
## Updated next-session prompt
|
||
|
||
```
|
||
You are the OHM roadmap driver. The previous session (Session L)
|
||
shipped rfc-app v0.15.0 (Amplitude Analytics + Session Replay,
|
||
#13, deploys.id=19), v0.16.0 (per-RFC owner invites, #12,
|
||
deploys.id=20), and v0.17.0 (admin-create user + invite email,
|
||
#16, deploys.id=21) to OHM. All of Wave 5 is closed. OHM serves
|
||
v0.17.0 with Amplitude analytics + session replay live (gated by
|
||
the v0.13.0 cookie banner), v0.13.0 cookie banner itself, v0.14.0
|
||
/docs route, per-RFC owner invites, and admin-create user invites
|
||
all in production for the first time. Session L also folded #21
|
||
Part C identity-lifecycle work inline across all three release
|
||
commits — identify({ user_id, properties }) + setUserProperties()
|
||
+ amplitude.reset on sign-out + identify-BEFORE-track on every
|
||
invite-claim path.
|
||
|
||
Read `~/git/ohm-infra/SESSION-PROTOCOL.md` and
|
||
`/Users/benstull/projects/wiggleverse/ohm-rfc/ROADMAP.md`
|
||
end-to-end. The next session letter is M.
|
||
|
||
**New protocol amendment from Session L**: subagents write their
|
||
own transcripts (subsession files like
|
||
`SESSION-M.1-TRANSCRIPT-<start>--<end>.md`) before returning to
|
||
the driver. The driver pre-assigns each subagent's subsession ID
|
||
in the dispatch prompt. The driver's own Session-M transcript
|
||
stays load-bearing for cross-subagent synthesis + cut-state,
|
||
with cross-references to each subsession. See
|
||
SESSION-PROTOCOL.md §5 for the binding form. The publish
|
||
script's filename validator was extended in Session L to accept
|
||
`SESSION-<letter>.<n>-TRANSCRIPT-…md`.
|
||
|
||
**Hard rule (Session K + Session L): never EVER ask the operator
|
||
to paste secret bytes into the conversation.** Session L
|
||
confirmed the corollary: public bundle-embedded values
|
||
(`VITE_AMPLITUDE_API_KEY`, `VITE_TURNSTILE_SITE_KEY`) are fine
|
||
to receive in conversation and set via `flotilla overlay set` —
|
||
but never the secret-half of a public/secret pair, and never
|
||
something whose nature you're unsure about. When in doubt,
|
||
default to the operator-run gesture (`pbpaste | flotilla secret
|
||
set …` for secrets; `flotilla overlay set DEPLOYMENT KEY=VALUE`
|
||
for non-secrets).
|
||
|
||
Wave 6 candidates (per Session L's roadmap state):
|
||
- **#20 Email deliverability** (Track Ω + rfc-app template
|
||
hygiene) — high priority now that two invite paths ship and
|
||
the recipient's first contact with OHM is an email; spam
|
||
foldering is silent invite failure. Likely one combined ops
|
||
session for DNS + SMTP-provider posture + template hygiene
|
||
(note: overlaps with #18; ship together).
|
||
- **#22 Pro-analytics-consent copy on the privacy/cookies
|
||
opt-in** — small frontend-only rfc-app minor; depends on
|
||
operator-drafted + counsel-reviewed copy in hand. Legal
|
||
caveats spelled out in the item (501(c)(3) is planned, not
|
||
granted; "no external sharing" honesty about Amplitude as the
|
||
processor; purpose-limitation pledges are enforceable;
|
||
GDPR/CCPA coerced-consent risk; withdrawal must stay easy).
|
||
- **#21 Part A audit** — waits for ~1 week of real Amplitude
|
||
data, then a deep audit pass on taxonomy, autocapture quality
|
||
(DOM hygiene), session-replay masking, bundle/performance.
|
||
Ships as a rfc-app minor.
|
||
- **#21 Part B discipline** — SPEC.md analytics chapter +
|
||
rfc-app CONTRIBUTING checklist + optional CI lint. Can start
|
||
anytime; ships with #19's CONTRIBUTING PRs.
|
||
- **#18 Secure SMTP relay + Gitea webhook** — likely bundled
|
||
with #20.
|
||
- **#17 Repo naming + location alignment** — operator-led; a
|
||
subagent can inventory + propose, operator executes.
|
||
- **#19 CONTRIBUTING guides + transcript-linked onboarding** —
|
||
Track Ω docs PR; subagent can draft, operator approves +
|
||
merges.
|
||
- **#1 VM rename** — operator-led ops gesture.
|
||
|
||
Dispatch shape (updated for Session M per the Session-L
|
||
amendment):
|
||
1. For each subagent, pre-assign a subsession ID: M.1, M.2, …
|
||
2. In the dispatch prompt, name the subsession ID + tell the
|
||
subagent it MUST write `~/git/ohm-infra/SESSION-M.<n>-TRANSCRIPT-<start>--<end>.md`
|
||
BEFORE returning a report.
|
||
3. The subagent's transcript follows the same shape as a main
|
||
transcript (pre-state, arcs, cut-state, what-the-driver-
|
||
needs). The driver's report-back is a tight summary; the full
|
||
detail lives in the subagent's transcript file.
|
||
4. Each subagent creates its own `git worktree add` in
|
||
`/Users/benstull/git/rfc-app-…`, pushes a feature branch,
|
||
does NOT tag, does NOT touch the ohm-rfc pin, does NOT run
|
||
flotilla deploy.
|
||
5. Driver integrates serially.
|
||
6. Driver publishes its own transcript AND each subsession
|
||
transcript via `~/git/ohm-infra/scripts/publish-transcript.sh`
|
||
(the regex now accepts `SESSION-<letter>.<n>-TRANSCRIPT-…md`).
|
||
|
||
Session-L lessons to apply automatically (in addition to all of
|
||
Session K's):
|
||
- **Vendor prompts can override the roadmap's pre-vendor
|
||
guesses.** When the operator pastes a vendor installation
|
||
prompt mid-session (Amplitude this time; could be Stripe,
|
||
Linear, Sentry next time), the prompt's SDK-shape
|
||
recommendations are authoritative for the SDK choices —
|
||
package name, init signature, sample rates, etc. The roadmap
|
||
row's "new secret: FOO" or "uses bar library" wording was
|
||
best-effort pre-vendor and gets superseded. Integration
|
||
points (consent gates, identity lifecycle, PII discipline)
|
||
stay project-sovereign — the vendor doesn't get to decide
|
||
those. Document the override in the release CHANGELOG +
|
||
strike the ROADMAP row with the corrected framing.
|
||
- **No `SendMessage` available in this harness.** When the
|
||
operator's mid-session input invalidates an in-flight
|
||
subagent's brief, the choices are (a) let it finish and
|
||
driver-post-correct, (b) `TaskStop` and re-dispatch. Pick (a)
|
||
when the correction is small and on a fresh-cut branch; (b)
|
||
when the structural decisions would be wrong. The
|
||
driver-post-correction is a new commit on the same branch
|
||
(no amend, no force-push), pushed to both remotes.
|
||
- **Squash-merge integration with hand-resolved CHANGELOG**
|
||
works well for parallel-feature waves. Use a small Python
|
||
script for the conflict resolution when the block is large
|
||
(~150+ lines) — git's conflict markers are accurate but
|
||
manual editing is error-prone at scale.
|
||
- **#21 Part C is a standing pattern**: every new
|
||
identity-meaningful surface MUST call `identify({ user_id,
|
||
properties })` (not just `setUserId`), and any new claim/
|
||
sign-in/invite-accept path MUST call identify BEFORE the
|
||
first track event so the Amplitude record is created with
|
||
the OHM user_id from the first event rather than as an
|
||
anonymous device that retroactively links. SPEC chapter to
|
||
codify is part of #21 Part B.
|
||
- **Pin-jump side effects**: when the pin bumps past versions
|
||
that were integrated-but-undeployed (in Session L: v0.13.0
|
||
cookie banner + v0.14.0 /docs both went live as a side
|
||
effect of the v0.15.0 → v0.17.0 sequence), flag this to the
|
||
operator at the deploy step. Not a blocker; just transparency.
|
||
- **CHANGELOG slot 016 stays reserved + skipped**. v0.15.0
|
||
used no schema migration; #12 took slot 018, #16 took 019.
|
||
Next free: 016, 020, 021, ….
|
||
- **Login.jsx remains contested**: Session L's v0.15.0
|
||
Amplitude wiring added `User Signed In` calls at the three
|
||
verify paths (otc / passcode / trust-device). Future
|
||
auth-touching releases need to preserve v0.11.0 + v0.12.0 +
|
||
v0.15.0 injections.
|
||
- **Admin.jsx contested too**: v0.15.0 added Admin Permission
|
||
Decision firing; v0.17.0 added Create-user-invite modal +
|
||
pending-invite badge. Future admin-page work layers on top
|
||
additively.
|
||
- **api_admin.py contested**: v0.16.0 added `rfc_invitations[]`
|
||
per-user; v0.17.0 added `pending_invite` per-user + new
|
||
endpoints. Both additive; the per-user dict has two new
|
||
fields side-by-side. Future admin-API work should follow
|
||
the same additive shape and not restructure the response
|
||
envelope.
|
||
- **Roadmap items added mid-session**: #20 email deliverability,
|
||
#21 Amplitude audit + standing discipline (Parts A + B + C),
|
||
#22 pro-analytics-consent copy on the privacy/cookies opt-in.
|
||
All three pushed to ohm-rfc as their own commits during the
|
||
session. Don't batch roadmap captures to end-of-session — push
|
||
promptly so the operator sees them on the gitea side without
|
||
waiting.
|
||
|
||
End-of-session: write `~/git/ohm-infra/SESSION-M-TRANSCRIPT-…md`
|
||
(your main transcript) AND ensure each subagent's subsession
|
||
transcript was written. Per Session-L feedback: **transcript is
|
||
the LAST step** — finish all other work first, then finalize the
|
||
transcript, then publish all of them via
|
||
`~/git/ohm-infra/scripts/publish-transcript.sh`. If the operator
|
||
surfaces new instructions after the transcript is "finalized,"
|
||
it isn't yet final — apply the new work, finalize again, then
|
||
publish.
|
||
```
|