# Session 0013.1 — Subsession transcript

> Parent: `SESSION-0013.0-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 0012.0
  (`SESSION-0012.0-TRANSCRIPT-2026-05-28T04-22--2026-05-28T05-17.md`) +
  Session 0013.0's main transcript in-progress
  (`SESSION-0013.0-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-0011.0-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-0012.0-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 0012.0 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 0011.0)
  and v0.15.0's two (Session 0012.0) 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 0011.0 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-0013.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 0011.0/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-0013.1-TRANSCRIPT-2026-05-28T05-30--2026-05-28T05-40.md`
  (this file)
