add 0065/SESSION-0065.0-TRANSCRIPT-2026-06-03T02-30--2026-06-03T05-12.md + replace placeholder/variant SESSION-0065.0-TRANSCRIPT-2026-06-03T02-30--INPROGRESS.md
This commit is contained in:
@@ -0,0 +1,232 @@
|
||||
# Session 0065.0 — Transcript
|
||||
|
||||
> Date: 2026-06-03
|
||||
> Goal: Build **M2** of the rfc-app §22 multi-project series — project-scoped
|
||||
> authorization + the §22.7 resolver — then finalize (merge to main, publish).
|
||||
>
|
||||
> Outcome: **M2 shipped. The §22.7 three-tier resolver + §22.5 visibility gate
|
||||
> landed across all eight API modules, behavior-preserving on the public default
|
||||
> project (full suite 401 green). Merged to `main` on top of v0.32.0 (retire)
|
||||
> that had advanced main meanwhile — required a migration renumber and a
|
||||
> `get_rfc` conflict resolution. `feat/multi-project` deleted; no release/deploy
|
||||
> (the §22 series releases as one minor at M7).**
|
||||
>
|
||||
> Highlights:
|
||||
> - Two operator design decisions settled mid-build (see Arc 2): *implicit-on-
|
||||
> public* and *preserve-curation*. Both are a deliberate, documented
|
||||
> liberalization of §22.5's literal "grant required on public," chosen to keep
|
||||
> the N=1 case whole and the v0.16.0 per-RFC invite contract intact.
|
||||
> - A latent **migration-numbering collision** surfaced at merge time: M1's
|
||||
> `025_projects.sql` vs v0.32.0's `025_retired_state.sql`. See §19.2 candidate 1.
|
||||
|
||||
---
|
||||
|
||||
## Pre-session state
|
||||
|
||||
- **rfc-app** (`~/git/wiggleverse.org/ben.stull/rfc-app`, origin =
|
||||
`git.wiggleverse.org:ben.stull/rfc-app`): branch `feat/multi-project` carried
|
||||
the §22 design drafts + **M1** (the schema spine, commit `6f901e3`:
|
||||
`projects` / `project_members` tables, `project_id` on all 19 slug-bearing
|
||||
tables, migration `025_projects.sql`). `main` was at `0062510` *locally* but
|
||||
had in fact advanced on origin (see Arc 3). The §22 slicing plan (M1–M7,
|
||||
`docs/design/multi-project-spec.md` Part C) targets **one pre-1.0 minor at
|
||||
M7**; intermediate slices accumulate on the feature branch and bump nothing.
|
||||
- Backend: FastAPI + SQLite (`backend/`), test suite `cd backend && python -m
|
||||
pytest -q` via the local `.venv`. Baseline at session start: **381 passed**.
|
||||
- OHM live: untouched this session (no deploy, no pin bump).
|
||||
|
||||
---
|
||||
|
||||
## Turn-by-turn arc
|
||||
|
||||
### Arc 1 — Locating the work
|
||||
|
||||
"Build RFC App M2" was terse. Traced it from `ohm-infra` references → the
|
||||
rfc-app framework → `docs/design/multi-project-spec.md` §22, Part C slicing
|
||||
plan. M1 was confirmed landed on `feat/multi-project`; **M2** = "Project-scoped
|
||||
authorization + the §22.7 resolver." Surveyed the existing auth machinery
|
||||
(`app/auth.py`: deployment roles §6.1, per-RFC helpers §6.3/§12), the M1 schema
|
||||
(migration 025), and — via an Explore subagent — a full inventory of every
|
||||
RFC-scoped endpoint and its current gate. Key architectural find: every
|
||||
RFC-scoped endpoint funnels through a `_require_rfc*()` helper that does
|
||||
`SELECT ... FROM cached_rfcs WHERE slug = ?`, and M1 already put `project_id` on
|
||||
`cached_rfcs` — a single, uniform hook point for both the resolver and the
|
||||
visibility gate.
|
||||
|
||||
### Arc 2 — Two design forks (operator decisions)
|
||||
|
||||
Entered plan mode. Two genuine forks surfaced that change behavior and so went
|
||||
to the operator via AskUserQuestion:
|
||||
|
||||
1. **Public-write gate.** §22.5/§22.7 literally require an explicit
|
||||
`project_contributor` grant to write *even on a public project*. But the live
|
||||
default project is public and the membership UI isn't due until M6 — a
|
||||
literal reading would break onboarding. **Decision: implicit-on-public** — a
|
||||
granted deployment contributor keeps its pre-M2 write baseline on a public
|
||||
project with no membership row (no backfill; N=1 stays whole).
|
||||
|
||||
2. **Per-RFC curation.** §22.7 says project roles are *supersets* of per-RFC
|
||||
roles, which — combined with implicit-on-public — would erase the v0.16.0
|
||||
per-RFC discussion/contribution invite curation on the public project (and
|
||||
break 4 shipped tests). **Decision: preserve-curation** — the implicit-public
|
||||
baseline does NOT override per-RFC owner curation; only an *explicit* project
|
||||
grant (or deployment owner/admin) does.
|
||||
|
||||
Plan approved. These two decisions are what make M2 behavior-preserving on the
|
||||
public default project while still fully implementing the tier for
|
||||
gated/unlisted projects and explicit grants.
|
||||
|
||||
### Arc 3 — Implementation
|
||||
|
||||
Pure app-layer (no migration — M1 shipped the tables; no version bump —
|
||||
mid-feature-branch).
|
||||
|
||||
- **`app/auth.py`** — the §22.7 resolver primitives: `project_visibility`,
|
||||
`project_member_role`, `project_of_rfc`, `is_project_superuser`,
|
||||
`can_read_project`, `can_contribute_in_project` / `can_discuss_in_project`
|
||||
(with the implicit-public `_has_write_baseline`), `require_project_readable`,
|
||||
`visible_project_ids`. The three existing per-RFC helpers (`can_discuss_rfc` /
|
||||
`can_contribute_to_rfc` / `can_invite_to_rfc`) were rewritten to compose all
|
||||
three tiers (signatures unchanged, so ~20 call sites inherited M2 for free),
|
||||
with the old "no owners yet → open" fallthrough replaced by the
|
||||
curation-preserving baseline.
|
||||
- **Read pass** — threaded `viewer` + `require_project_readable` into every
|
||||
`_require_rfc*` / `_require_super_draft` / `_require_rfc_readable` across
|
||||
`api.py`, `api_branches`, `api_prs`, `api_graduation`, `api_discussion`,
|
||||
`api_contributions`, `api_invitations`; filtered the catalog (`GET /api/rfcs`)
|
||||
and proposals listings by `visible_project_ids`. A gated project's entries now
|
||||
404 to non-members (indistinguishable from absent, §22.5).
|
||||
- **Write pass** — folded `is_project_superuser` (project_admin) into the deep
|
||||
gates (`_can_read_branch`, `_can_contribute`, `_require_branch_owner`,
|
||||
`_can_resolve_thread`, `_can_edit_metadata`, `_capabilities`, `_can_merge`,
|
||||
`_can_withdraw`, `_can_graduate`, discussion `_can_resolve`); routed the
|
||||
branch "any-contributor" mode through `can_contribute_in_project`; gated
|
||||
`propose` + `claim` on project contribute standing. A self-correction mid-pass:
|
||||
initially added `can_contribute_to_rfc` to `pr-draft`/`review`/
|
||||
`resolution-branch`, then reverted it — those were open to any contributor
|
||||
pre-M2, so per-RFC gating there would *tighten* public behavior; the threaded
|
||||
visibility gate is the correct M2 change for them. Deployment-level surfaces
|
||||
(admin idea-PR merge/decline, account notification-mute) left deployment-
|
||||
scoped after audit.
|
||||
- **Tests** — `backend/tests/test_multi_project_authz_vertical.py` (9 vertical
|
||||
assertions: resolver tiers, public-unchanged regression, gated 404 read gate,
|
||||
gated contribution gate, union + subtractive visibility, revocation).
|
||||
- **Docs** — Part C M2 marked "(landed)" with both operator decisions recorded.
|
||||
|
||||
Result: **390 passed** (381 + 9). Committed as `503689b`
|
||||
("feat(projects): M2 — project-scoped authorization + the §22.7 resolver").
|
||||
|
||||
### Arc 4 — Finalize round 1: the diverged-main surprise
|
||||
|
||||
Operator: "you can push/merge." Survey revealed `origin/main` had **advanced**
|
||||
to `34a65e0` (v0.32.0 — the `retired` soft-delete state + §13 retire/unretire),
|
||||
3 commits the branch didn't have; local `main` was stale. A trial merge
|
||||
(`git merge-tree`) reported:
|
||||
|
||||
1. A content conflict in `app/api.py` (`get_rfc` — both sides added
|
||||
`request: Request` and top-of-handler logic).
|
||||
2. A **migration-number collision**: M1's `025_projects.sql` vs v0.32.0's
|
||||
`025_retired_state.sql`. Worse than a name clash — `025_retired_state.sql`
|
||||
*rebuilds* `cached_rfcs` (create-copy-drop-rename to widen a CHECK
|
||||
constraint) and predates `project_id`, so on a fresh DB (filename-sorted
|
||||
order: `projects` < `retired`) the rebuild would **drop `project_id`**,
|
||||
breaking every M2 gate.
|
||||
|
||||
Surfaced this to the operator (who confirmed single-user, land it on main).
|
||||
Resolution, all on `main` via a `--no-ff` merge:
|
||||
- Renamed **`025_projects.sql` → `026_projects.sql`** so projects' `ADD COLUMN`
|
||||
runs *after* the retire rebuild (the runner applies `*.sql` in filename order,
|
||||
tracked in `schema_migrations`); bumped the comment/doc references.
|
||||
- Resolved `get_rfc`: compute `viewer` once, apply the §22.5 visibility gate,
|
||||
then v0.32.0's §13.7 retired-entry owner-only check.
|
||||
- `api_graduation.py` / `api_discussion.py` auto-merged cleanly (M2's threaded
|
||||
viewer/visibility coexists with the new retire endpoints + state).
|
||||
|
||||
Full suite on the merged tree (fresh DB — exercises the migration order):
|
||||
**401 passed**. Merge committed `1dab24e`; pushed `main` + `feat/multi-project`
|
||||
to origin.
|
||||
|
||||
### Arc 5 — Finalize round 2: housekeeping
|
||||
|
||||
Ran the `wgl-coding-session-finalize` flow. Operator chose: delete
|
||||
`feat/multi-project` (local + origin — fully contained in main; M3 branches
|
||||
fresh) and leave the secondary `benstull` remote alone. Done. Updated the
|
||||
project memory note (`project_rfc_app_multiproject.md`) and refreshed it for the
|
||||
deleted branch.
|
||||
|
||||
---
|
||||
|
||||
## Cut state (end of session)
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| rfc-app | `main` @ `1dab24e` (merge: §22 M1+M2 over v0.32.0), pushed to origin. M2 = `503689b`. Working tree clean. |
|
||||
| rfc-app branches | `feat/multi-project` deleted (local + origin). |
|
||||
| Test suite | `backend` 401 passed (fresh DB). |
|
||||
| Version / release | None — §22 releases as one minor at M7; no `VERSION`/`CHANGELOG` change, no tag. |
|
||||
| OHM live | Untouched (no deploy this session). |
|
||||
| ohm-rfc pin | Unchanged. |
|
||||
|
||||
| §22 slice ledger | Status |
|
||||
| --- | --- |
|
||||
| M1 — schema spine | ✅ landed (on main via merge) |
|
||||
| M2 — project-scoped authz + §22.7 resolver | ✅ shipped this session |
|
||||
| M3 — registry mirror + routing + runtime branding | ⏭ next |
|
||||
| M4–M7 | ⏸ pending |
|
||||
|
||||
---
|
||||
|
||||
## §19.2 candidates surfaced
|
||||
|
||||
1. **Migration-number collisions across parallel branches.** M1 and v0.32.0 both
|
||||
grabbed `025_*`; only a manual rename at merge time kept a fresh DB working.
|
||||
The numbering scheme is collision-prone whenever two branches add migrations
|
||||
concurrently. Worth a convention (reserve ranges per workstream, or a
|
||||
monotonic claim like the session-ID gesture) so the next collision isn't
|
||||
silent. The immediate guard: **new migrations must be ≥ 027.**
|
||||
2. **Fold the M2 authz decisions into SPEC at M7.** *implicit-on-public* and
|
||||
*preserve-curation* currently live only in `docs/design/multi-project-spec.md`
|
||||
Part C + `app/auth.py` comments. They are deliberate liberalizations of
|
||||
§22.5's literal text and must be reconciled into the binding SPEC §22 at the
|
||||
M7 merge, not left as drift.
|
||||
3. **Per-endpoint contributor-vs-viewer granularity.** `pr-draft`/`review`/
|
||||
`resolution-branch` are visibility-gated but not contribute-gated (preserving
|
||||
public behavior). On a gated project a project_viewer can therefore reach
|
||||
them. Acceptable for M2; revisit in the M4 acceptance pass if it matters.
|
||||
|
||||
---
|
||||
|
||||
## What lands on the operator's plate
|
||||
|
||||
- Nothing blocking. M2 is on `main` and pushed; no secret, deploy, or gitea
|
||||
admin gesture was required or left open.
|
||||
- Optional: the `sessions.json` title for 0065 (set by this session at publish).
|
||||
- When M3 starts: branch fresh from `main` (it now contains M1+M2+v0.32.0).
|
||||
|
||||
---
|
||||
|
||||
## Prompt the operator can paste into the next Claude Code session
|
||||
|
||||
```
|
||||
Build RFC App M3 (multi-project: registry mirror + routing + runtime branding).
|
||||
This session reserves its own ID — don't trust any number in this prompt.
|
||||
|
||||
Context: ~/git/wiggleverse.org/ben.stull/rfc-app (origin = git.wiggleverse.org,
|
||||
SSH). §22 M1+M2 are merged to main (see ~/.claude memory
|
||||
project_rfc_app_multiproject.md). Read that note, then docs/design/
|
||||
multi-project-spec.md Part C "M3" + §22.2/§22.9/§22.10/§22.13. Branch fresh
|
||||
from main.
|
||||
|
||||
M3 covers: the registry mirror (webhook + reconciler over REGISTRY_REPO
|
||||
populating `projects`), the default-project bootstrap-id re-stamp (default →
|
||||
config-derived slug, before any /p/<id>/ URL is public), the /p/<project>/ route
|
||||
prefix with generic /e/<slug> + 308 redirects off the old corpus-root URLs, GET
|
||||
/api/deployment + /api/projects/:id, the frontend cut from VITE_APP_NAME to
|
||||
runtime config + per-project theme, the deployment directory + project switcher,
|
||||
AND the deferred slug-keyed uniqueness/PK rebuilds enumerated in
|
||||
026_projects.sql's header (must land before a 2nd project can collide).
|
||||
|
||||
New migrations must be numbered >= 027 (M1's projects migration is 026 after a
|
||||
collision with v0.32.0's 025_retired_state). Tests: cd backend && python -m
|
||||
pytest -q via the .venv there. Plan before building.
|
||||
```
|
||||
@@ -1,18 +0,0 @@
|
||||
# Session 0065.0 — Transcript
|
||||
|
||||
> Date: 2026-06-03
|
||||
> Start: 2026-06-03T02-30 (PST implied)
|
||||
> Status: **PLACEHOLDER — claimed at session start; finalized at session end.**
|
||||
>
|
||||
> This file reserves session ID 0065. The driver replaces this body
|
||||
> with the full transcript before publishing, and renames the file to
|
||||
> its final SESSION-0065.0-TRANSCRIPT-2026-06-03T02-30--<end>.md form.
|
||||
|
||||
---
|
||||
|
||||
## Launch prompt
|
||||
|
||||
```
|
||||
Build RFC App M2
|
||||
|
||||
```
|
||||
Reference in New Issue
Block a user