Files
session-history/ohm/0077/SESSION-0077.0-TRANSCRIPT-2026-06-05T23-31--2026-06-06T00-42.md
T

6.9 KiB
Raw Blame History

Session 0077.0 — Transcript

App: ohm Start: 2026-06-05T23-31 (PST) · End: 2026-06-06T00-42 (PST) Type: coding Goal: /goal s4 — ship §22 three-tier slice S4 (invitation surfaces + role-aware empty states). Outcome: S4 shipped @ v0.43.0 (origin main = 2696e64, PR #21 merged via the Gitea API). Completes @S4 (C2.1C2.7 + C3.3C3.5). Backend 504 passed; frontend build green. Non-breaking.

Plan

S4 — Invitation surfaces + role-aware empty states (@S4 = C2.1C2.7, C3.3C3.5), building on S3's effective_scope_role resolver (mig 030).

Backend: (1) auth invite gates; (2) memberships.py grant/revoke/list with the broader-scope-supersedes prune; (3) api_memberships.py GET/POST/DELETE /api/projects/<id>/members (single POST keying on optional collection_id); (4) notify.notify_scope_role_granted + render_summary; (5) viewer capability flags on the project/collection GETs. Frontend: (6) ScopeMembersModal, CreateCollectionModal, role-aware empty states in CollectionDirectory + Catalog. Tests: test_s4_invitations_vertical.py.

Design decision (logged): the C.2 scenarios write a membership row immediately and §15-notify an existing account, so S4 uses a direct grant to an existing user (looked up by email), NOT the per-RFC email-token-accept flow. Inviting a not-yet-account email is out of S4 scope (the admin-create-invite path already covers account provisioning).

Pre-session state

  • §22 three-tier refactor: S1 (mig 029, collection grain) @ v0.40.0; S2 (2nd collection) @ v0.41.0; S3 (scope-role enforcement, mig 030 global scope; collection-grain visibility) @ v0.42.0. origin/main = e6bd69f, clean.
  • The §B.2 four-layer resolver auth.effective_scope_role(user, collection_id) existed (S3); grants were applied administratively (DB / the Owner-authorized create surface) — no invite UI, no role-aware empty states.
  • The roadmap (design doc Part E) named S4 as the next slice. Memory pointer said NEXT = S4.

Turn-by-turn arc

  1. Gate + claim. Classified the /goal s4 open as a coding session; ran wgl-session-coding-init. Peeked (no in-flight ohm sessions), claimed 0077. Verified clean pushed main baseline.
  2. Orient on S4. Read design doc Part E (S4) + the C.2 / C.3 @S4 scenarios. Confirmed shipped state from CHANGELOG/migrations (VERSION 0.42.0, mig 030 present).
  3. Map the surfaces. Two parallel Explore agents mapped the backend (memberships model, effective_scope_role, notify chokepoint _emit_one, the §6 permission_state floor, router structure) and the frontend (CollectionDirectory, Catalog, viewer/session shape, the per-RFC InvitationsModal template, API client conventions).
  4. Key realization. The C.2 scenarios dictate a direct-grant model (membership row written immediately + §15 notify to an existing account), distinct from the per-RFC token-accept flow. Logged the decision.
  5. Backend (TDD). Wrote test_s4_invitations_vertical.py first (the C.2 + C.3-flag spec), then implemented: auth.can_invite_at_project / can_invite_at_collection; new memberships.py (grant + broader-supersedes prune, revoke, list_for_project, user_by_email); new api_memberships.py (registered in api.py); notify.notify_scope_role_granted + render_summary case; viewer capability flags on the two api_collections GETs. 11 S4 tests green; full suite 504 passed. Committed (c9fd1c5).
  6. Frontend. api.js (getCollection, listScopeMembers, grantScopeMember, revokeScopeMember); ScopeMembersModal (Owner-only invite + members/revoke, scope picker bounded to reach); CreateCollectionModal (surfaces the S2 endpoint, previously UI-less); CollectionDirectory rewritten for the role-aware empty states (C3.3/C3.4) + Owner "Members" control; Catalog propose-first empty state (C3.5) gated on can_contribute; small CSS. Build clean. Committed (93cf506).
  7. Release. Bumped VERSION + package.json → 0.43.0, CHANGELOG entry (minor, non-breaking), marked S4 shipped in the design doc. Re-ran the touched suites green. Committed (ff54632), pushed the branch.
  8. PR + merge. Created PR #21 via the Gitea API (wgl-gitea-admin helper), confirmed mergeable, merged (HTTP 200), FF-pulled local main, deleted the branch local + remote. Full suite on merged main: 504 passed.
  9. Memory + finalize. Updated the project memory (S4 shipped, NEXT = S5) and the MEMORY.md index; finalized this transcript.

Cut state (end of session)

Repo Branch SHA State
ben.stull/rfc-app main 2696e64 clean, pushed; PR #21 merged
  • VERSION = 0.43.0 (mirrored in frontend/package.json).
  • New files: backend/app/memberships.py, backend/app/api_memberships.py, backend/tests/test_s4_invitations_vertical.py, frontend/src/components/ScopeMembersModal.jsx, frontend/src/components/CreateCollectionModal.jsx.
  • Touched: backend/app/auth.py, notify.py, api.py, api_collections.py; frontend/src/api.js, App.css, components/CollectionDirectory.jsx, components/Catalog.jsx; CHANGELOG.md, VERSION, the design doc.
  • No schema migration (mig high-water stays 030). Non-breaking — existing authz outcomes unchanged; the new UI is another way to write memberships rows that S3 wrote administratively.

Deferred decisions

The ## Deferred decisions section carried no entries — no low-confidence calls this session. The one real design fork (direct-grant vs. email-token-accept) was clearly resolved by the C.2 scenarios (they write the row immediately and §15-notify an existing account), so it was a decided call, documented in the Plan block, the CHANGELOG, and the design doc — not a low-confidence deferral.

What lands on the operator's plate

  • Nothing blocking. S4 is shipped, merged, and verified.
  • Scope note to confirm at leisure: inviting a not-yet-account email is out of S4 (the grant endpoint 404s an unknown email). The admin-create-invite path provisions accounts; composing the two (invite-by-email that provisions
    • grants) is a candidate for S6's membership lifecycle, not a gap in S4.
  • For the S6 SPEC merge: S3's keystone reinterpretation of §B.1/§B.3 still needs to land in SPEC.md (carried forward, unchanged by S4).
  • Many stale local feature branches exist in the working copy (pre-existing, one checked out in another worktree) — untouched this session; housekeeping candidates whenever convenient.

Prompt the operator can paste into the next session

/goal s5 — in-app create-project + the global directory: the global-Owner
create-project action (bot provisions a Gitea content repo + commits to
projects.yaml), plus the deployment-directory role-aware empty states.
Completes @S5 (C3.1C3.2), per
docs/design/2026-06-05-three-tier-projects-collections.md Part E (S5).