§22 M3-backend Plan B (write path, propose): project-scoped propose (v0.38.0)

A new entry can be proposed into a specific project; it lands in that project's
content repo and shows under that project's proposals. A non-default project is
no longer read-only.

- api.py: POST /api/projects/{pid}/rfcs/propose (propose body extracted into a
  project-parameterized helper; unscoped /api/rfcs/propose kept as default
  compat). Slug uniqueness, idea-PR reservation, landing state, and the
  proposed_use_cases row scoped to the target project. GET
  /api/projects/{pid}/proposals.
- cache.py: refresh_meta_pulls loops every project's content_repo, stamping
  cached_prs.project_id; projects.content_repo(pid) helper.
- frontend: proposeRFC(projectId,…)/listProposals(projectId); ProposeModal
  takes projectId; App resolves current project from the /p/<id>/ URL; Catalog
  lists that project's proposals.
- tests: test_project_scoped_propose.py (lands scoped + gated 404). 447 backend
  + 11 Vitest green; clean build.

Known limitation: branch/PR/graduation edit flows + default-id re-stamp not yet
scoped (next slice). Per docs/superpowers/specs/2026-06-04-m3-backend-planb-design.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ben Stull
2026-06-04 07:02:19 -07:00
parent 455ef33b29
commit fec51bdbb6
11 changed files with 205 additions and 34 deletions
+9
View File
@@ -31,6 +31,15 @@ def default_content_repo(config: Config) -> str | None:
return row["content_repo"] if row and row["content_repo"] else None
def content_repo(project_id: str) -> str | None:
"""The content repo for a specific project (§22.3). None if unknown/unset.
The per-project successor to `default_content_repo` for the write path."""
row = db.conn().execute(
"SELECT content_repo FROM projects WHERE id = ?", (project_id,)
).fetchone()
return row["content_repo"] if row and row["content_repo"] else None
def project_initial_state(project_id: str) -> str:
"""§22.4b landing state for new entries in a project. Defaults to
'super-draft' for an unknown/unset row (the safe, today's-flow default)."""