§22 S3: scope-role enforcement + collection-grain visibility — v0.42.0 (@S3) #1
@@ -19,9 +19,11 @@
|
|||||||
> the live `/p/<project>/e/<slug>` URLs; v0.37.0/0.38.0 shipped per-project
|
> the live `/p/<project>/e/<slug>` URLs; v0.37.0/0.38.0 shipped per-project
|
||||||
> read + propose. Inserting the third tier is therefore an **evolution of a
|
> read + propose. Inserting the third tier is therefore an **evolution of a
|
||||||
> shipped system**, not a revision of unshipped designs. The migration strategy
|
> shipped system**, not a revision of unshipped designs. The migration strategy
|
||||||
> (Part E) is **pending re-decision** with correct facts; the structural model
|
> (Part E) was **re-decided on these corrected facts** (session 0072): a new
|
||||||
> (Parts A–D) is unaffected. Target release: a further pre-1.0 minor with
|
> **migration 029** adds a *collection* grain *beneath* today's project, plus a
|
||||||
> breaking changes + upgrade steps (§20.2).
|
> breaking `/p/<project>/e/<slug>` → `/p/<project>/c/<collection>/e/<slug>` URL
|
||||||
|
> change with 308s. The structural model (Parts A–D) is unaffected. Target
|
||||||
|
> release: a further pre-1.0 minor with breaking changes + upgrade steps (§20.2).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -41,9 +43,9 @@ the deployment level is unchanged.
|
|||||||
|
|
||||||
⚠️ The two-tier model is **already shipped** (v0.39.0): migration 028 rebuilt
|
⚠️ The two-tier model is **already shipped** (v0.39.0): migration 028 rebuilt
|
||||||
the slug PK to `(project_id, slug)`, and `/p/<project>/e/<slug>` URLs are live
|
the slug PK to `(project_id, slug)`, and `/p/<project>/e/<slug>` URLs are live
|
||||||
(v0.35.0). So inserting the third tier evolves a shipped system — see the
|
(v0.35.0). So inserting the third tier evolves a shipped system — see Part E
|
||||||
corrected Part E for the real migration cost (a new migration 029 + a breaking
|
for the decided strategy (a new migration 029 adding a collection grain beneath
|
||||||
URL change with 308s), pending re-decision.
|
today's project, + a breaking URL change with 308s).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -479,83 +481,91 @@ Applied in place when §22 is rewritten; listed here as the change surface.
|
|||||||
|
|
||||||
# Part E — Revised slicing plan (the roadmap re-slot)
|
# Part E — Revised slicing plan (the roadmap re-slot)
|
||||||
|
|
||||||
> ⚠️ **PENDING RE-DECISION (session 0072).** The strategy below assumed Plan B
|
**Strategy (session 0072, decided on corrected facts).** The two-tier model is
|
||||||
> and M3-frontend were unshipped. They are shipped (v0.35.0–0.39.0). The "fold
|
shipped end-to-end (v0.39.0): migration 028 keyed entries `(project_id, slug)`;
|
||||||
> into not-yet-shipped work" framing is void; the actual work is a **new
|
v0.35.0 shipped `/p/<project>/` routing + live `/p/<project>/e/<slug>` URLs;
|
||||||
> migration 029** that evolves the shipped two-tier schema to three tiers, plus
|
v0.37.0/0.38.0 shipped per-project read + propose. Inserting the third tier is
|
||||||
> a **breaking `/p/<project>/e/<slug>` → `/p/<project>/c/<collection>/e/<slug>`
|
therefore an **evolution of a shipped system**. The chosen mapping **adds a
|
||||||
> URL change with 308s**. The two live mapping options (relabel today's
|
collection grain *beneath* today's project** — the shipped `projects` table
|
||||||
> project → collection + insert a group tier above, vs. add a collection
|
stays the grouping tier (it already owns `content_repo`, where §A.2 wants it),
|
||||||
> sub-grain beneath today's project) are being put back to the operator. The
|
a new `collections` table holds the per-corpus fields, and entries re-key to the
|
||||||
> text below is retained only as the structural target, not the sequencing.
|
finer `(collection_id, slug)`. The migrated default keeps every deployment
|
||||||
|
running; the new `/c/<collection>/` URL segment is added with 308s off the
|
||||||
|
now-legacy two-tier URLs.
|
||||||
|
|
||||||
Strategy (ORIGINAL, premise now false): fold the third tier into the
|
- **Landed, unchanged (v0.39.0):** M1–M2, M3-backend Plan A **and** Plan B
|
||||||
not-yet-shipped work rather than ship two-tier and migrate again. M1, M2, and
|
(read+propose, mig 028), M3-frontend (`/p/<project>/` routing), §22.13
|
||||||
M3-backend Plan A are additive and already merged (v0.39.0); they keep running.
|
re-stamp. None of this is rebuilt; it is *evolved* by migration 029 below.
|
||||||
The tier goes in at the **first breaking migration (Plan B / 028)** and the
|
|
||||||
**first public routing (M3-frontend)**, before either ships.
|
|
||||||
|
|
||||||
- **Landed, unchanged (v0.39.0):** M1 (additive `project_id` spine, mig 026),
|
- **N1 — migration 029: add the collection grain.** A new
|
||||||
M2 (authz resolver), M3-backend Plan A (registry mirror, APIs, propose /
|
`029_collections.sql` that: (1) **adds a `collections` table** —
|
||||||
mark-reviewed, mig 027). These read fine under the new model: today's single
|
`(id, project_id, type, subfolder, initial_state, visibility, name,
|
||||||
`projects` row is re-read as the **default collection** after the §E migration
|
registry_sha)`; (2) **moves the per-corpus fields** (`type`, `initial_state`,
|
||||||
relabels it.
|
visibility) from the shipped `projects` table **down** to `collections`,
|
||||||
|
leaving `projects` as `(id, content_repo, visibility, name, tagline, theme,
|
||||||
|
enabled_models, …)`; (3) **creates one default collection per existing
|
||||||
|
project** (id `default`, `subfolder` = repo root, inheriting that project's
|
||||||
|
`type`/`initial_state`); (4) **re-keys every entry-scoped table** from
|
||||||
|
`(project_id, slug)` to `(collection_id, slug)` via the same SQLite
|
||||||
|
rebuild procedure migration 028 used (the `028_project_scoped_keys.sql`
|
||||||
|
pattern — `__new` table, copy, drop, rename, FK-off + `foreign_key_check`),
|
||||||
|
stamping the default `collection_id`; (5) **generalizes `project_members` →
|
||||||
|
`memberships(scope_type ∈ {project, collection}, scope_id, …)`**, migrating
|
||||||
|
existing rows to `scope_type='collection'` on the default collection and
|
||||||
|
collapsing the role enum to `{owner, contributor}` (§B.3).
|
||||||
|
|
||||||
- **M3-backend Plan B → "insert the project tier + re-key to per-collection"**
|
- **N2 — backend collection scoping.** Thread `collection_id` through the
|
||||||
(revised). Migration 028 now: (1) add the `projects` (grouping) table with
|
resolution gates that migration 028/Plan B threaded `project_id` through
|
||||||
`content_repo`; (2) rename the landed `projects` table to `collections`, drop
|
(`app/auth.py`, `app/projects.py`, `app/cache.py`, the `api_*` writers);
|
||||||
its `content_repo`, add `project_id` + `subfolder`; (3) re-key every
|
generalize the §22.7 union to the four-layer resolver (§B.2); add the
|
||||||
entry-scoped `project_id` → `collection_id`; (4) the slug-PK rebuild targets
|
`GET /api/projects/:id/collections` and
|
||||||
`(collection_id, slug)`; (5) generalize `project_members` → the polymorphic
|
`GET /api/projects/:id/collections/:cid` surfaces; scope propose/serve/PR
|
||||||
`memberships` table and collapse the role enum (§B.3); (6) the §22.13 default
|
endpoints to `(project, collection)`.
|
||||||
project wraps the default collection (§A.6). One breaking migration, one slug
|
|
||||||
rebuild.
|
|
||||||
|
|
||||||
- **M3-backend Plan B+ — manifests & in-app create.** Teach the mirror to read
|
- **N3 — manifests & in-app create.** Teach the registry mirror to read
|
||||||
`.collection.yaml`; add the bot-commit-wrapped **create-project** and
|
`.collection.yaml` from each project's content repo; add the
|
||||||
**create-collection** endpoints; mirror new collections.
|
bot-commit-wrapped **create-collection** (project Owner / RFC Contributor)
|
||||||
|
and **create-project** (global Owner) endpoints (§A.2); mirror new
|
||||||
|
collections into `collections` rows.
|
||||||
|
|
||||||
- **M3-frontend → three-tier routing.** `/p/<project>/c/<collection>/e/<slug>`;
|
- **N4 — frontend three-tier routing.** Add the `/c/<collection>/` segment:
|
||||||
the deployment directory at `/`, the project (collection) directory at
|
`/p/<project>/c/<collection>/e/<slug>`; the project (collection) directory at
|
||||||
`/p/<project>/`; the single-project / single-collection redirects (§A.3); the
|
`/p/<project>/`; the single-collection redirect (§A.3); **308** the shipped
|
||||||
308s off `/rfc/<slug>`; runtime branding at both project and collection.
|
`/p/<project>/e/<slug>` → `/p/<project>/c/<default>/e/<slug>`; per-collection
|
||||||
|
chrome layered under the existing per-project chrome.
|
||||||
|
|
||||||
- **M4 — second-collection + second-project acceptance.** End-to-end pass: a
|
- **N5 — roles surface (this pass's scope).** The `{owner, contributor}` ×
|
||||||
second collection in a project, and a second project, each with disjoint
|
`{global, project, collection}` grants (Part B) with the invite surfaces and
|
||||||
membership, full propose→graduate lifecycle under the per-collection slug
|
empty states of Part C as acceptance tests. (Richer roles deferred, §B.1.)
|
||||||
namespace.
|
|
||||||
|
|
||||||
- **M5 — type modules (unchanged in intent).** Per-type frontmatter + surfaces,
|
- **N6 — type modules / membership lifecycle / hardening.** Carries the original
|
||||||
now selected on the **collection's** `type`.
|
§22 M5–M7 forward, now selecting on the **collection's** `type`: per-type
|
||||||
|
frontmatter + surfaces; request-to-join + cross-collection inbox; per-collection
|
||||||
|
`enabled_models`; the registry + manifest format in `docs/DEPLOYMENTS.md`;
|
||||||
|
two-project / multi-collection e2e; the §20.4 changelog + upgrade-steps; the
|
||||||
|
SPEC merge (Part A applied, Part D amendments in place).
|
||||||
|
|
||||||
- **M6 — membership lifecycle.** Invite + request-to-join at all three scopes
|
## E.1 (= §A.6) Migration — the default collection (the N=1 case)
|
||||||
(Part C.2); the cross-collection inbox; the roster admin surface. The Part C
|
|
||||||
scenarios are this slice's acceptance tests.
|
|
||||||
|
|
||||||
- **M7 — hardening + SPEC merge.** Per-collection `enabled_models`; the registry
|
A deployment on the shipped two-tier schema (v0.39.0) is migrated by 029 so it
|
||||||
+ manifest format in `docs/DEPLOYMENTS.md`; two-project / multi-collection e2e;
|
keeps running unchanged:
|
||||||
the §20.4 changelog + upgrade-steps; the SPEC merge (Part A applied, Part D
|
|
||||||
amendments in place).
|
|
||||||
|
|
||||||
## E.1 (= §A.6) Migration — the default project and default collection
|
1. The existing `projects` row **stays as the project** (it already owns
|
||||||
|
`content_repo` and its config-derived `id` from §22.13 step 1).
|
||||||
|
2. A **default collection** (`id='default'`, `subfolder` = repo root) is created
|
||||||
|
per project, inheriting that project's `type` / `initial_state` / visibility;
|
||||||
|
those fields are then dropped from `projects`.
|
||||||
|
3. Every entry-scoped `project_id` row is re-keyed with the default
|
||||||
|
`collection_id` (PK `(project_id, slug)` → `(collection_id, slug)`).
|
||||||
|
4. `project_members` rows migrate to `memberships(scope_type='collection')` on
|
||||||
|
the default collection, role-collapsed (§B.3).
|
||||||
|
5. **308 redirects:** the shipped `/p/<project>/e/<slug>` →
|
||||||
|
`/p/<project>/c/<default>/e/<slug>`, and the pre-multi-project `/rfc/<slug>`
|
||||||
|
/ `/proposals/<n>` → their `/p/<project>/c/<default>/…` equivalents.
|
||||||
|
|
||||||
A pre-three-tier deployment (already on v0.39.0's single `projects` row, or
|
Until a second collection is added, the deployment is functionally identical to
|
||||||
pre-multi-project) is migrated so it keeps running:
|
before, with one extra path segment. This is the §20.4 upgrade-steps content for
|
||||||
|
the release.
|
||||||
1. The landed single `projects` row becomes the **default collection**
|
|
||||||
(`type=document`, its `initial_state`/`unreviewed` preserved).
|
|
||||||
2. A **default project** is generated to wrap it: `content_repo` moves from the
|
|
||||||
(old project / now collection) onto the project; `id` is the config-derived
|
|
||||||
slug already re-stamped in §22.13 step 1 (v0.39.0).
|
|
||||||
3. Every entry-scoped `project_id` is re-keyed to the default `collection_id`.
|
|
||||||
4. M2's `project_members` rows migrate to `memberships(scope_type='collection')`
|
|
||||||
on the default collection, role-collapsed (§B.3).
|
|
||||||
5. `/rfc/<slug>` and `/proposals/<n>` 308-redirect to
|
|
||||||
`/p/<default-project>/c/<default-collection>/…`.
|
|
||||||
|
|
||||||
Until a second project or collection is added, the deployment is functionally
|
|
||||||
identical to before, with two extra path segments. This is the §20.4
|
|
||||||
upgrade-steps content for the release.
|
|
||||||
|
|
||||||
## E.2 Scope of the first implementation pass
|
## E.2 Scope of the first implementation pass
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user