Files
session-history/ohm/0076/SESSION-0076.0-TRANSCRIPT-2026-06-05T17-25--2026-06-05T18-13.md
T

173 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Session 0076.0 — Transcript
> App: ohm
> Start: 2026-06-05T17-25 (PST)
> End: 2026-06-05T18-13 (PST)
> Type: coding
> Status: **FINALIZED.**
>
> Driver session — shipped framework release **v0.42.0** (§22 slice S3).
## Launch prompt
`/goal S3` — implement §22 slice S3 (scope-role enforcement, `@S3` =
C1.1C1.8), per `docs/design/2026-06-05-three-tier-projects-collections.md`
Part E. Operator added two requirements mid-session:
1. Collection-level visibility is settable (public or not); a collection
can be "hidden from existence from public" yet visible to contributors
at the project or global scope.
2. Project scope likewise settable; a collection's visibility may only be
set as strict or stricter than its project's.
## Pre-state
`main` at `39ce54f` (S2 / v0.41.0, three mains in sync: origin = benstull =
local). Two-tier shipped @ v0.39.0; S1 (collection grain, mig 029) @ v0.40.0;
S2 (create+navigate a 2nd collection) @ v0.41.0. Backend FastAPI+SQLite under
`backend/`; the §22 three-tier draft spec at
`docs/design/2026-06-05-three-tier-projects-collections.md` (Parts AE,
`@S<n>`-tagged scenarios). S3 was the next slice: the §B.2 four-layer
most-permissive scope-role resolver, every write gate re-checked on the
collection axis.
## Plan
**S3 — scope-role enforcement + collection-grain visibility.** One cohesive
slice (visibility enforcement and the scope resolver share the authz surface).
- **Visibility model.** Rank `public(0) < unlisted(1) < gated(2)`. A
collection's visibility must be `rank ≥` its project's (validated at
create + mirror). Effective read gate = stricter of the two. `gated` =
"hidden from public existence": 404 to anonymous / non-scope-role, omitted
from the directory, but readable + listed for scope-role holders.
- **Scope-role resolver (§B.2).** Four-layer most-permissive union: global
(deployment owner/admin → Owner; explicit `scope_type='global'` grant) →
project membership → collection membership → per-entry. No negative override.
- **Enforcement.** `can_read_collection` / `can_contribute_in_collection`;
viewer-aware directory enumeration; every RFC-grain write helper re-pointed
at the entry's collection; create-collection authorized by project-scope
Owner; implicit-public baseline grandfathered onto the `default` collection
only (N=1 preserved).
- **Schema.** Migration 030: `memberships.scope_type += 'global'`.
- **Tests.** Part C.1 scenarios (`@S3`) + collection-visibility + strictness.
## Session arc
1. **Init.** Claimed session 0076 (no other in-flight). Verified clean pushed
`main` baseline. Read design doc Parts AE (three-tier model, unified roles
§B, the C.1 `@S3` scenarios, the slicing plan).
2. **Explore.** Two parallel Explore agents mapped (a) the test harness — S1/S2
wired as pytest vertical tests, S3 scenarios still prose; the gate runs `cd
backend && pytest`; fixtures `app_with_fake_gitea` / `sign_in_as` /
`provision_user_row`; (b) every authz call site and write gate.
3. **Keystone contradiction found.** §B.1/§B.3 ("deployment contributor =
global RFC Contributor → writes everywhere") contradicts the C.1 "hana"
scenario ("granted account, no scope role → proposes nowhere") AND the M2
implicit-public baseline (`test_multi_project_authz_vertical.py:138`, the
live OHM deployment depends on it). Surfaced the three-way conflict and asked
the operator for the keystone (a binding authz/security call the doc didn't
resolve).
4. **Operator clarification.** Operator declined the multiple-choice and gave
direction in their own words: collection-grain visibility is the primary
control — a collection can be public or "hidden from existence from public"
(visible to project/global-scope contributors), and a collection's
visibility may only be set as strict or stricter than its project's. This
confirmed the scope-role-primary reading and resolved the keystone: a plain
granted account is a granted *account*, not a write-everywhere global role;
"global RFC Contributor" = an explicit `scope_type='global'` grant; the
implicit-public write baseline is grandfathered onto the migration-seeded
`default` collection only (N=1 preserved).
5. **Implement (TDD-ish, suite green at every step).**
- Migration `030_global_scope.sql` (rebuild memberships, `scope_type +=
'global'`) + `test_migration_030_global_scope.py`.
- `auth.py`: `effective_scope_role`, `collection_visibility`,
`effective_collection_visibility`, `can_read_collection` /
`require_collection_readable`, `is_collection_superuser`,
`_has_collection_write_baseline`, `can_contribute_in_collection` /
`can_discuss_in_collection`, `can_create_collection`, `collection_of_rfc`,
`visibility_rank`; generalized `can_read_project` to recognize scope roles;
re-pointed `can_discuss_rfc` / `can_contribute_to_rfc` / `can_invite_to_rfc`
to the collection axis; fixed `project_member_role` (global+project scope,
`default`-collection back-compat only on the real default project via
`_is_default_project` — the old `default_collection_id` conflation misfired
for multi-collection projects).
- Re-pointed entry-scoped superuser checks to the collection grain across
`api.py` (propose gate, mark-reviewed, collection read routes +
`require_collection_readable`), `api_branches.py`, `api_graduation.py`,
`api_prs.py`, `api_discussion.py`, `api_contributions.py`.
- `api_collections.py`: viewer-aware directory enumeration; `get_col` read
gate; create-collection authority widened (§B.1, `can_create_collection`)
+ visibility strictness validation (422 on looser).
- `registry.py`: `_strictest_visibility` clamp at the named-collection mirror.
- Updated two S2 tests to grant explicit scope roles (the new write contract).
- New `test_s3_scope_roles_vertical.py`: all 8 C.1 scenarios (ada/ben/cleo/
dan/eve/fay/gil/hana) + hidden-collection + strictness + create-authority.
- **Full backend suite: 493 passed.**
6. **Release.** VERSION + package.json → 0.42.0; CHANGELOG 0.42.0 entry
(breaking, with upgrade steps); design doc Part E marks S3 shipped + records
the keystone reconciliation. Committed (`c2f5665`).
7. **PR + merge (with a recovered slip).** `git remote -v | head -2` truncated
the remote list and hid `origin`, so the branch was pushed and PR #1 merged
on the **`benstull`** mirror (git.benstull.org) rather than canonical
**`origin`** (git.wiggleverse.org). Caught at finalize; recovered by
fast-forwarding `origin/main` to the same commit (`e6bd69f`). End state:
origin == benstull == local `main`, all at v0.42.0.
## Cut state
- **Shipped: v0.42.0**, `main` at `e6bd69f` (origin = benstull = local, all
aligned). S3 / `@S3` (C1.1C1.8) complete; full backend suite 493 passed.
- Branch `feat/s3-scope-roles-collection-visibility` merged + deleted.
- Frontend untouched (backend-enforcement slice; hidden collections simply
don't appear in API responses the frontend renders).
- **NEXT = S4** — invitation surfaces + role-aware empty states (`@S4` =
C2.1C2.7 + C3.3C3.5). An in-app *edit*-visibility control (post-create) is
a natural S4 add; S3 ships visibility as git-truth + create-time setting +
enforcement.
## Deferred decisions
- **Keystone authz interpretation (low confidence; operator engaged
mid-session and confirmed the visibility half).** The design doc's §B.1/§B.3
("deployment contributor = global RFC Contributor → writes everywhere")
contradicts the C.1 "hana" scenario ("granted account, no scope role →
proposes nowhere") and the locked-in M2 implicit-public baseline. Resolved
as: a plain granted account (`role='contributor'`, no membership) is **a
granted account, not a write-everywhere global role**; "global RFC
Contributor" (cleo) is an **explicit** `scope_type='global'` grant; the
implicit-public write baseline is **grandfathered onto the migration-seeded
`default` collection only** so N=1 OHM loses no capability. Operator's
follow-up (collection-grain visibility, hidden=gated, strictness constraint)
confirmed the scope-role-primary reading. Reinterprets §B.1/§B.3 literally —
flag for SPEC merge (S6).
- **PR target slip (recovered, no harm).** `git remote -v | head -2` truncated
the remote list and hid `origin`, so the S3 branch was pushed and the PR
(#1) merged on the **`benstull`** mirror (git.benstull.org) rather than the
canonical **`origin`** (git.wiggleverse.org:ben.stull/rfc-app) where the S1/S2
PRs (#18/#20) landed. Caught at finalize; recovered by fast-forwarding
`origin/main` to the same commit (`e6bd69f`). End state: `origin` ==
`benstull` == local `main`, all at v0.42.0 — fully consistent, non-destructive.
The S3 PR record lives on benstull #1, not origin. Lesson: never `head`-truncate
`git remote -v` when the canonical target matters.
## Next-session prompt
```
/goal S4 — invitation surfaces + role-aware empty states (@S4 = C2.1C2.7,
C3.3C3.5), per docs/design/2026-06-05-three-tier-projects-collections.md Part E
```
Read `memory/project_rfc_app_multiproject.md` (session-0076 block) first. S4
builds the Owner-only invite UI (grant Owner/RFC Contributor at a scope or any
scope beneath it, §15 notifications, broader-scope-supersedes) on top of the S3
`memberships` table + `auth.effective_scope_role` resolver, plus the role-keyed
create-first-collection / propose-first empty states. **Canonical PR flow is on
`origin` (git.wiggleverse.org) — verify `git remote -v` in full before pushing.**