Commit Graph

9 Commits

Author SHA1 Message Date
Ben Stull 87279fc545 §22 three-tier spec Part E: decided migration-029 strategy (collection grain beneath project)
Operator chose (session 0072) to add the collection grain beneath today's
project: projects table stays the group tier (keeps content_repo), a new
collections table holds the per-corpus fields, entries re-key to
(collection_id, slug), one default collection per project on migrate, breaking
/p/<project>/e/<slug> -> /p/<project>/c/<collection>/e/<slug> with 308s.
Re-sloted slices N1-N6. Correction banners updated from pending -> decided.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 03:46:51 -07:00
Ben Stull 9c0e3b60ac Correct §22 three-tier spec: two-tier model already shipped (v0.39.0)
Re-checked code vs the stale memory: migration 028 (slug PK -> (project_id,
slug)), v0.35.0 /p/<project>/ routing, and v0.37/0.38 per-project read+propose
are all shipped to main. The 'fold into not-yet-shipped Plan B + M3-frontend'
premise is false. Neutralize the wrong claims in §0/§A.3 and flag Part E's
sequencing as pending re-decision; structural model (Parts A-D) unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 03:43:44 -07:00
Ben Stull 31d680be54 §22 three-tier refactor spec: project → RFC collection + unified roles + BDDs
Splits the original §22 two-tier model (deployment → project=corpus) into
three tiers (deployment → project → RFC collection). Project owns one
content repo; collections are typed subfolders declared by .collection.yaml
manifests (git-truth). Reconciles the accumulated role vocabulary onto one
{owner, contributor} enum attached at {global, project, collection}, with
downward additive inheritance and no negative override. Adds BDD scenarios
(Part C) for role usage, invitation, and empty states. Re-slots the roadmap
to fold the tier into the not-yet-shipped Plan B (mig 028) + M3-frontend.

Session 0072 (spec). Revises docs/design/multi-project-spec.md §22.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 03:39:04 -07:00
Ben Stull 1dab24eef0 Merge feat/multi-project (§22 M1+M2) into main
Integrates the multi-project work so far — the §22 design drafts, M1 (the
schema spine), and M2 (project-scoped authorization + the §22.7 resolver) —
on top of the v0.32.0 retire/§13 changes that landed on main meanwhile.

Integration decisions:
- Renumbered the M1 projects migration 025_projects.sql -> 026_projects.sql.
  v0.32.0 shipped 025_retired_state.sql, which rebuilds cached_rfcs and
  predates the project_id column; running projects *after* the retire rebuild
  is required so project_id survives on a fresh database (the runner applies
  *.sql in filename order). Comment/doc references bumped to match.
- Resolved the get_rfc conflict in api.py by composing both gates: compute the
  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 gate coexists with the new retire endpoints + retired
  state).

Full suite 401 passed on a fresh DB.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 04:56:39 -07:00
Ben Stull 503689bf1a feat(projects): M2 — project-scoped authorization + the §22.7 resolver (§22)
Builds the three-tier authorization resolver on M1's schema spine: the
most-permissive union of deployment role (§6.1), project role (§22.6), and
per-RFC authority (§6.3/§12), with the §22.5 visibility gate subtractive on
top (§22.7). Pure app-layer — no migration (M1 shipped the tables), no
behavior change on the public default project. Verifiable on the single
default project by flipping its visibility and granting/revoking roles.

- app/auth.py: the §22.7 resolver primitives — project_visibility,
  project_member_role, project_of_rfc, is_project_superuser, can_read_project,
  effective write/discuss standing (can_contribute_in_project /
  can_discuss_in_project), require_project_readable, visible_project_ids.
  The three per-RFC capability helpers (can_discuss_rfc / can_contribute_to_rfc
  / can_invite_to_rfc) now compose all three tiers, so the ~20 call sites
  inherit M2 unchanged.
- Read pass: the §22.5 visibility gate (404 to non-members) threaded into
  every RFC-resolution helper across api.py / api_branches / api_prs /
  api_graduation / api_discussion / api_contributions / api_invitations, plus
  the catalog + proposals listings filtered by visible_project_ids.
- Write pass: the deep gates (branch read/contribute/owner, PR merge/withdraw/
  edit, graduation, discussion resolve) fold in project_admin via
  is_project_superuser; the "any-contributor" branch mode routes through
  can_contribute_in_project; propose + claim gate on project contribute
  standing. Deployment-level surfaces (admin idea-PR merge/decline, account
  notification-mute) stay deployment-scoped.
- Operator decisions: implicit-on-public (a granted deployment contributor
  keeps its pre-M2 write baseline on a public project, no membership row, so
  the N=1 case stays whole) and preserve-curation (that baseline does not
  override per-RFC owner curation — only an explicit project grant or a
  deployment owner/admin does), keeping the v0.16.0 per-RFC invite contract
  intact on public.
- tests: test_multi_project_authz_vertical.py — 9 vertical assertions on the
  resolver tiers, public-unchanged regression, the gated 404 read gate, the
  gated contribution gate, union + subtractive visibility, and revocation.
  Full suite 390 passed.
- docs: mark Part C M2 "(landed)" with the two operator decisions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 20:02:34 -07:00
Ben Stull ad2ece18fa docs(design): resolve the three M3-blocking decisions (§22 draft)
- Default project id: config-derived slug (DEFAULT_PROJECT_ID > slug of
  deployment name > 'default'); M1's 'default' bootstrap is re-stamped in M3
  before any /p/ URL is public, so it's meaningful (/p/ohm/) and never renamed
  live. §22.13.
- Entry-noun in URLs: generic /p/<project>/e/<slug> for every type; the noun
  (RFC/Spec/Feature) is a type-driven UI label, not in the path. §22.10.
- Existing RFC-NNNN: kept as a frozen, read-only legacy display label in
  frontmatter id (preserves citations); never used for lookup, never assigned
  to new entries. §22.4, §2.3 amended.

Threads through §22.4/22.10/22.13, the §2.3 amendment, and the M3 slice.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 19:03:15 -07:00
Ben Stull 57b2fc5205 docs(design): project types, slug-only identity, initial_state + unreviewed flag (§22 draft)
Extends the §22 multi-project draft along four axes decided this session:

- Project type — each project declares document | specification | bdd
  (immutable, registry-set). One shared propose→branch→PR→graduate engine;
  type only layers entry schema, terminology, and type-specific surfaces
  (spec release-planning, BDD scenarios/coverage). §22.4a.
- Slug-only identity — an entry is (project_id, slug); the per-project
  RFC-NNNN numbering and its allocator are retired. §22.4, §2.3/§13 amended.
- initial_state — per-project landing state for a new entry: super-draft
  (document/spec default) or active (bdd default). §22.4b.
- unreviewed flag — entries that skip straight to active land unreviewed;
  graduation implies review so the normal path is never flagged. Owner
  mark-reviewed clears it; §7 catalog gains an unreviewed filter; mirrored
  into cached_rfcs. §22.4c.

Slicing plan re-sliced six→seven: type/initial_state/unreviewed config ride
M3, type surfaces become M5, membership→M6, hardening→M7.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 18:26:52 -07:00
Ben Stull 6f901e3e2c feat(projects): M1 — the multi-project schema spine (§22)
Lays the additive foundation for hosting N projects per deployment, with
today's single corpus as the N=1 case. No behavior change: the app runs
exactly as before, single project, with the spine underneath.

- migration 025: `projects` + `project_members` tables; seed the `default`
  project (visibility=public, preserving open-by-default); thread
  `project_id NOT NULL DEFAULT 'default'` onto all 19 slug-bearing tables,
  backfilling existing rows. Additive — no table rebuilds; the slug-keyed
  uniqueness/PK rework is enumerated in the migration header and deferred to
  the slice that activates project #2.
- app/projects.py: §22.13 startup backfill of the default project's
  content_repo from META_REPO (idempotent — never clobbers a value the
  future registry mirror sets).
- config: REGISTRY_REPO wired (optional through M1; consumed by the M3
  mirror), documented in .env.example as META_REPO's successor.
- tests: 6 vertical assertions on the spine (seed, backfill, column shape,
  role CHECK, idempotency, config). Full suite 381 passed.
- docs: align Part C M1/M3 boundaries with the landed code (registry mirror
  + redirect move to M3).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 18:14:53 -07:00
Ben Stull 7aba89655d docs(design): sketch multi-project deployments + draft §22 spec & slicing plan
Design for hosting N projects (corpora) per deployment, with today's
single-corpus deployment as the N=1 case. Captures the locked decisions
(git registry, gated-by-default visibility, per-project RFC numbering),
the three-tier role model (deployment / project / per-RFC), and the
forced runtime-branding shift off VITE_APP_NAME.

- multi-project.md: rationale, decisions, operator (flotilla) integration.
- multi-project-spec.md: draft binding §22, in-place amendments to
  §§1,2,5,6,7,8,13,14,17,18,20, and a six-slice (M1-M6) build plan.

Registry lives in a deployment-side repo the framework reads via a new
REGISTRY_REPO env var (META_REPO's multi-project successor); confirmed
against the ohm-rfc-app-flotilla spec — no operator-tooling change needed.

Not yet merged into SPEC.md or versioned; folds in when M1 lands.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 18:05:13 -07:00