§22 S1: thread collection_id through backend + update tests (N=1 unchanged, 454 green)
- collections.py resolution helpers (default_collection_id, type, initial_state) - registry mirror writes project grouping fields + default-collection corpus fields - auth.project_of_rfc joins collections; project_member_role reads memberships - cache/api_*/funder writers+readers re-keyed to collection_id (cached_prs + denormalised project_id tags unchanged); api_deployment reads type/initial_state from the default collection - projects.py restamp detects bootstrap via collections; initial_state via collection - tests updated to the three-tier schema; test_migration_028 retired (superseded by 029) - add @S1 acceptance test (collection grain + N=1 serving) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+27
-10
@@ -337,24 +337,41 @@ def project_visibility(project_id: str) -> str:
|
||||
|
||||
|
||||
def project_member_role(user: SessionUser | None, project_id: str) -> str | None:
|
||||
"""The user's *explicit* §22.6 project_members role in this project, or
|
||||
None. This is the stored row only — it does not fold in the deployment tier
|
||||
or the implicit-on-public baseline (those live in the helpers below)."""
|
||||
"""The user's *explicit* §22.6 membership role at this project, or None.
|
||||
|
||||
§22 three-tier (S1): M2's `project_members` rows migrated into the unified
|
||||
`memberships` table at the project's default collection (and the project
|
||||
tier is freshly grantable). The unified `{owner, contributor}` roles are
|
||||
mapped back to the legacy `project_admin`/`project_contributor` strings the
|
||||
S1 project-grain authz still speaks; the four-layer scope resolver lands in
|
||||
S3. Reads the stored grant only (project OR default-collection scope) — it
|
||||
does not fold in the deployment tier or the implicit-on-public baseline."""
|
||||
if user is None:
|
||||
return None
|
||||
from . import collections as collections_mod
|
||||
cid = collections_mod.default_collection_id(project_id)
|
||||
row = db.conn().execute(
|
||||
"SELECT role FROM project_members WHERE project_id = ? AND user_id = ?",
|
||||
(project_id, user.user_id),
|
||||
"SELECT role FROM memberships "
|
||||
"WHERE user_id = ? AND ((scope_type = 'project' AND scope_id = ?) "
|
||||
" OR (scope_type = 'collection' AND scope_id = ?)) "
|
||||
"ORDER BY CASE role WHEN 'owner' THEN 0 ELSE 1 END LIMIT 1",
|
||||
(user.user_id, project_id, cid),
|
||||
).fetchone()
|
||||
return row["role"] if row else None
|
||||
if row is None:
|
||||
return None
|
||||
return "project_admin" if row["role"] == "owner" else "project_contributor"
|
||||
|
||||
|
||||
def project_of_rfc(rfc_slug: str) -> str:
|
||||
"""The project an RFC belongs to (`cached_rfcs.project_id`). Falls back to
|
||||
the default project when the slug isn't cached or the column is unset — the
|
||||
same N=1 default migration 026 backfills."""
|
||||
"""The project an RFC belongs to, via its collection
|
||||
(`cached_rfcs.collection_id` -> `collections.project_id`, §22 three-tier).
|
||||
Falls back to the default project when the slug isn't cached — the same N=1
|
||||
default migration 026 backfills."""
|
||||
row = db.conn().execute(
|
||||
"SELECT project_id FROM cached_rfcs WHERE slug = ?", (rfc_slug,)
|
||||
"SELECT c.project_id AS project_id "
|
||||
"FROM cached_rfcs r JOIN collections c ON c.id = r.collection_id "
|
||||
"WHERE r.slug = ?",
|
||||
(rfc_slug,),
|
||||
).fetchone()
|
||||
if row is None:
|
||||
return DEFAULT_PROJECT_ID
|
||||
|
||||
Reference in New Issue
Block a user