fix(§22/G-15): make the branch/edit/body subsystem three-tier aware (v0.53.0)
§22 migrated the READ/catalog path to the three-tier (project/collection)
model but the WRITE/branch/body subsystem still hardcoded the default
project's default collection — resolving every meta-resident entry to the
default content repo at rfcs/<slug>.md, ignoring the entry's project (its own
content repo) and collection (a <subfolder>/rfcs/ prefix). An entry outside
the default collection rendered a blank canonical body and every edit/PR/
body-write path hit the wrong file.
- New single resolver: projects.content_repo_for_collection() +
projects.entry_location(config, cid, slug) -> (org, repo, md_path)
(collection -> project -> content_repo + subfolder; falls back to the
default repo for a legacy/unknown collection).
- Every entry write/read path resolves via it: api_branches (body GET + all
branch write paths), api_prs (pr-draft/open/merge/withdraw/review/
resolution-branch), api_graduation (graduate/claim/retire/unretire +
orchestrator + state-flip), api.py mark-reviewed + idea-PR merge/decline/
withdraw + proposal preview, api_metadata. bot.open_metadata_pr and
bot.mark_entry_reviewed gained a file_path param. refresh_meta_branches,
the webhook corpus-refresh dispatch, and the hygiene branch-delete now
span every project's content repo, not just the default.
- New additive collection-scoped body-read routes:
GET /api/projects/{pid}/collections/{cid}/rfcs/{slug}/main and
.../branches/{branch} disambiguate a slug across collections (G-5) and read
the entry's own repo. Slug-only routes kept (now collection-aware via the
cached row). Frontend getRFCMain/getBranch take optional pid+cid; RFCView
threads them (mirrors the v0.52.1 entry-detail fix).
No migration, no config change. Existing default-collection entries
unaffected. Tests: backend resolver + collection-scoped branch/body + graduate-
in-subfolder write path; frontend api unit. backend 677 / frontend 60 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -54,8 +54,13 @@ def _apply_op(entry: Any, op: str, field: str, value: Any) -> Any:
|
||||
def make_router(config: Config, gitea: Gitea, bot: Bot) -> APIRouter:
|
||||
router = APIRouter()
|
||||
|
||||
def _content_repo() -> tuple[str, str]:
|
||||
return config.gitea_org, (projects_mod.default_content_repo(config) or "")
|
||||
def _content_repo(collection_id: str) -> tuple[str, str]:
|
||||
# §22/G-15: the COLLECTION's project content_repo, not the deployment
|
||||
# default — an entry in a non-default project writes its own repo.
|
||||
# Falls back to the default repo for an unknown collection.
|
||||
repo = (projects_mod.content_repo_for_collection(collection_id)
|
||||
or (projects_mod.default_content_repo(config) or ""))
|
||||
return config.gitea_org, repo
|
||||
|
||||
def _md_path(collection_id: str, slug: str) -> str:
|
||||
sub = collections_mod.subfolder_of(collection_id) or ""
|
||||
@@ -83,7 +88,7 @@ def make_router(config: Config, gitea: Gitea, bot: Bot) -> APIRouter:
|
||||
if unknown:
|
||||
raise HTTPException(422, f"Unknown field(s): {', '.join(sorted(unknown))}")
|
||||
|
||||
org, repo = _content_repo()
|
||||
org, repo = _content_repo(collection_id)
|
||||
md_path = _md_path(collection_id, slug)
|
||||
st = await metadata_mod.read_entry_from_git(gitea, org, repo, md_path)
|
||||
if st is None:
|
||||
@@ -144,7 +149,7 @@ def make_router(config: Config, gitea: Gitea, bot: Bot) -> APIRouter:
|
||||
if body.op in ("add", "remove") and fields[body.field].get("type") != "tags":
|
||||
raise HTTPException(422, f"op {body.op} requires a tags field")
|
||||
|
||||
org, repo = _content_repo()
|
||||
org, repo = _content_repo(collection_id)
|
||||
applied: list[str] = []
|
||||
rejected: list[dict[str, str]] = []
|
||||
all_ops: list[dict[str, Any]] = []
|
||||
@@ -195,7 +200,7 @@ def make_router(config: Config, gitea: Gitea, bot: Bot) -> APIRouter:
|
||||
raise HTTPException(404, "Collection not in project")
|
||||
if not auth.is_collection_superuser(viewer, collection_id):
|
||||
raise HTTPException(403, "Owner access required to migrate a collection")
|
||||
org, repo = _content_repo()
|
||||
org, repo = _content_repo(collection_id)
|
||||
subfolder = collections_mod.subfolder_of(collection_id) or ""
|
||||
try:
|
||||
result = await metadata_mod.migrate_collection(
|
||||
|
||||
Reference in New Issue
Block a user