fix(registry): clean content_repo failure in hygiene; retire META_REPO from .env.example; clarity + idempotency test

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ben Stull
2026-06-03 23:28:08 -07:00
parent cecc6c0b41
commit 48fd6f9675
4 changed files with 38 additions and 21 deletions
+10 -11
View File
@@ -9,19 +9,18 @@ GITEA_URL=http://localhost:3000
GITEA_BOT_USER=rfc-bot
GITEA_BOT_TOKEN=
# The Gitea org or user that owns the meta repo and every RFC repo
# the bot will create on graduation.
# The Gitea org or user that owns every RFC repo the bot will create on
# graduation.
GITEA_ORG=wiggleverse
META_REPO=meta
# --- Multi-project registry (§22) ---
# The registry repo (under GITEA_ORG) the framework reads to learn which
# projects this deployment hosts. Optional through Slice M1: the registry
# mirror lands in M3, and until then the single default project's content
# repo is taken from META_REPO above. The repo's name is the deployment's
# choice. When the mirror lands, REGISTRY_REPO becomes required and
# supersedes META_REPO.
# REGISTRY_REPO=wiggleverse-registry
# §22.2 — the project registry repo (REQUIRED). The framework reads
# `projects.yaml` at its root to learn which projects exist. The repo name is
# the deployment's choice; the app fails to start if this is unset.
REGISTRY_REPO=registry
# §22.13 — optional id for the bootstrap/default project. Reserved for the
# Plan B re-stamp; leave unset in Plan A (the default project id stays
# `default`). When set, it must match an `id` in projects.yaml.
# DEFAULT_PROJECT_ID=ohm
# --- OAuth (Gitea) ---
# In Gitea: Site Administration → Applications → Add OAuth2 Application.
+8 -1
View File
@@ -299,7 +299,14 @@ async def _delete_branch_via_bot(
log.warning("hygiene: cannot delete %s/%s — slug missing from cache", slug, branch)
return False
if not rfc["repo"]:
owner, repo = config.gitea_org, (projects_mod.default_content_repo(config) or "")
repo = projects_mod.default_content_repo(config)
if not repo:
log.warning(
"hygiene: default project has no content_repo; skipping branch delete for %s/%s",
slug, branch,
)
return False
owner = config.gitea_org
elif "/" in rfc["repo"]:
owner, repo = rfc["repo"].split("/", 1)
else:
+8 -3
View File
@@ -80,16 +80,21 @@ def make_router(config: Config, gitea: Gitea) -> APIRouter:
payload = {}
repo_full = (payload.get("repository") or {}).get("full_name") or ""
registry_full = f"{config.gitea_org}/{config.registry_repo}"
meta_repo = projects_mod.default_content_repo(config)
meta_full = f"{config.gitea_org}/{meta_repo}" if meta_repo else None
content_repo = projects_mod.default_content_repo(config)
if not content_repo:
log.warning("webhook: default project content_repo is unknown; corpus refresh skipped")
content_full = f"{config.gitea_org}/{content_repo}" if content_repo else None
try:
if repo_full == registry_full:
# §22.2: a registry-repo push re-mirrors the projects table.
# Tolerate a malformed projects.yaml (keep last-good rows); let a
# transport error bubble to the outer 500 so an unreachable Gitea
# on a registry push is loud rather than silently dropped.
try:
await registry_mod.refresh_registry(config, gitea)
except registry_mod.RegistryError:
log.exception("registry webhook: invalid projects.yaml; keeping last-good")
elif meta_full and (repo_full == meta_full or not repo_full):
elif content_full and (repo_full == content_full or not repo_full):
await cache.refresh_meta_repo(config, gitea)
await cache.refresh_meta_branches(config, gitea)
await cache.refresh_meta_pulls(config, gitea)
@@ -110,15 +110,21 @@ def test_registry_mirror_is_idempotent(app_with_fake_gitea):
"""Re-running the registry mirror is safe — it upserts (overwrites) the
projects row from projects.yaml each time without raising. M3 retirement
of seed_default_project: the registry mirror is now the sole authority."""
from app import db
import asyncio
from app import db, registry as registry_mod
app, _ = app_with_fake_gitea
with TestClient(app):
# After startup the row should have content_repo from projects.yaml.
got = db.conn().execute(
"SELECT content_repo FROM projects WHERE id = 'default'"
).fetchone()["content_repo"]
assert got == "meta"
before = db.conn().execute("SELECT COUNT(*) AS n FROM projects").fetchone()["n"]
cfg = app.state.config
gitea = app.state.gitea
asyncio.run(registry_mod.refresh_registry(cfg, gitea))
after = db.conn().execute("SELECT COUNT(*) AS n FROM projects").fetchone()["n"]
assert after == before
row = db.conn().execute(
"SELECT content_repo FROM projects WHERE id='default'"
).fetchone()
assert row["content_repo"] == "meta"
def test_registry_repo_config_wired(app_with_fake_gitea, monkeypatch):