v0.31.0: meta-only repository topology (ROADMAP #36)

Retire the per-RFC-repo model. RFCs now live in their meta-repo entry
(rfcs/<slug>.md) for their whole life; graduation is an in-place
super-draft → active state flip that keeps the body in the entry — no
repo creation, no body-strip, no five-step transaction, no rollback.

SPEC: §1 topology rewritten (one meta/content repository, no per-RFC
repos) with a deployer-facing "single content repository" framing; §2
(repo: always-null), §3 (active is in-place), §4, §9.8 (handoff
frictions dissolve), and §13 fully rewritten (two-field dialog, "The
flip", §13.6 RFC-0001 fold-back record).

Code: graduation collapses to open+merge one frontmatter PR; branch/PR/
chat dispatch re-keyed on meta-residency (repo IS NULL) so active RFCs
edit on the meta repo exactly as super-drafts do; the two "RFC has no
repo" 409 guards removed; promote-to-branch slug-embeds an active RFC's
auto-branch (edit-<slug>-<hex>) for shared-repo cache attribution;
refresh_meta_branches + hygiene branch-resolution include meta-resident
actives; the §9.8 read-only guard + pre_graduation_history scoped to
legacy per-repo only; dead bot primitives + GraduateDialog repo field +
blocking-PR popover removed. The repo: frontmatter field and the
/blocking-prs endpoint are retained (schema stability / informational).

Tests: graduation suite rewritten to the flip model; e2e + hygiene
updated. Full backend suite 375 passed; frontend builds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ben Stull
2026-05-29 07:11:10 -07:00
parent d581010063
commit 0c972c8af5
15 changed files with 729 additions and 1077 deletions
+15 -11
View File
@@ -603,7 +603,7 @@ def make_router(
repo=repo,
slug=slug,
file_path=_file_path_for(rfc),
is_super_draft=_is_super_draft(rfc),
is_super_draft=_is_meta_resident(rfc),
original_branch=original_branch,
resolution_branch=resolution_branch,
)
@@ -671,32 +671,36 @@ def make_router(
"""Used by the §10 PR-flow read and write paths. Per §17's routing-
collapse rule, a super-draft RFC also routes here — its body-edit
PRs are meta-repo PRs with pr_kind='meta_body_edit', but the API
surface is identical."""
surface is identical. Under the meta-only topology (§1) an active
RFC is meta-resident too (repo is null) — that is normal, not an
error, so there is no per-RFC-repo precondition."""
row = _require_rfc(slug)
if row["state"] not in ("active", "super-draft"):
raise HTTPException(409, f"RFC is {row['state']}")
if row["state"] == "active" and not row["repo"]:
raise HTTPException(409, "RFC has no repo")
return row
def _is_super_draft(rfc) -> bool:
return rfc["state"] == "super-draft"
def _is_meta_resident(rfc) -> bool:
"""Meta-only topology (§1): an entry lives in the meta repo's
`rfcs/<slug>.md` (super-draft or active-in-place) unless it carries
a legacy per-RFC `repo:` — which nothing does after the RFC-0001
fold-back (§13.6). Drives the body/path/repo dispatch below."""
return not rfc["repo"]
def _owner_repo(rfc) -> tuple[str, str]:
if _is_super_draft(rfc):
if _is_meta_resident(rfc):
return config.gitea_org, config.meta_repo
owner, repo = rfc["repo"].split("/", 1)
return owner, repo
def _file_path_for(rfc) -> str:
if _is_super_draft(rfc):
if _is_meta_resident(rfc):
return f"rfcs/{rfc['slug']}.md"
return RFC_FILE_PATH
def _extract_body(rfc, file_contents: str) -> str:
"""For super-draft entries the file on disk is the full
"""For meta-resident entries the file on disk is the full
frontmatter+body envelope; the editable body is entry.body."""
if not _is_super_draft(rfc):
if not _is_meta_resident(rfc):
return file_contents
try:
entry = entry_mod.parse(file_contents)
@@ -760,7 +764,7 @@ def make_router(
return row["original_pr_number"] if row else None
async def _refresh_after_pr_write(rfc) -> None:
if _is_super_draft(rfc):
if _is_meta_resident(rfc):
await cache.refresh_meta_repo(config, gitea)
await cache.refresh_meta_branches(config, gitea)
await cache.refresh_meta_pulls(config, gitea)