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:
+10
-11
@@ -9,19 +9,18 @@ GITEA_URL=http://localhost:3000
|
|||||||
GITEA_BOT_USER=rfc-bot
|
GITEA_BOT_USER=rfc-bot
|
||||||
GITEA_BOT_TOKEN=
|
GITEA_BOT_TOKEN=
|
||||||
|
|
||||||
# The Gitea org or user that owns the meta repo and every RFC repo
|
# The Gitea org or user that owns every RFC repo the bot will create on
|
||||||
# the bot will create on graduation.
|
# graduation.
|
||||||
GITEA_ORG=wiggleverse
|
GITEA_ORG=wiggleverse
|
||||||
META_REPO=meta
|
|
||||||
|
|
||||||
# --- Multi-project registry (§22) ---
|
# §22.2 — the project registry repo (REQUIRED). The framework reads
|
||||||
# The registry repo (under GITEA_ORG) the framework reads to learn which
|
# `projects.yaml` at its root to learn which projects exist. The repo name is
|
||||||
# projects this deployment hosts. Optional through Slice M1: the registry
|
# the deployment's choice; the app fails to start if this is unset.
|
||||||
# mirror lands in M3, and until then the single default project's content
|
REGISTRY_REPO=registry
|
||||||
# repo is taken from META_REPO above. The repo's name is the deployment's
|
# §22.13 — optional id for the bootstrap/default project. Reserved for the
|
||||||
# choice. When the mirror lands, REGISTRY_REPO becomes required and
|
# Plan B re-stamp; leave unset in Plan A (the default project id stays
|
||||||
# supersedes META_REPO.
|
# `default`). When set, it must match an `id` in projects.yaml.
|
||||||
# REGISTRY_REPO=wiggleverse-registry
|
# DEFAULT_PROJECT_ID=ohm
|
||||||
|
|
||||||
# --- OAuth (Gitea) ---
|
# --- OAuth (Gitea) ---
|
||||||
# In Gitea: Site Administration → Applications → Add OAuth2 Application.
|
# In Gitea: Site Administration → Applications → Add OAuth2 Application.
|
||||||
|
|||||||
@@ -299,7 +299,14 @@ async def _delete_branch_via_bot(
|
|||||||
log.warning("hygiene: cannot delete %s/%s — slug missing from cache", slug, branch)
|
log.warning("hygiene: cannot delete %s/%s — slug missing from cache", slug, branch)
|
||||||
return False
|
return False
|
||||||
if not rfc["repo"]:
|
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"]:
|
elif "/" in rfc["repo"]:
|
||||||
owner, repo = rfc["repo"].split("/", 1)
|
owner, repo = rfc["repo"].split("/", 1)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -80,16 +80,21 @@ def make_router(config: Config, gitea: Gitea) -> APIRouter:
|
|||||||
payload = {}
|
payload = {}
|
||||||
repo_full = (payload.get("repository") or {}).get("full_name") or ""
|
repo_full = (payload.get("repository") or {}).get("full_name") or ""
|
||||||
registry_full = f"{config.gitea_org}/{config.registry_repo}"
|
registry_full = f"{config.gitea_org}/{config.registry_repo}"
|
||||||
meta_repo = projects_mod.default_content_repo(config)
|
content_repo = projects_mod.default_content_repo(config)
|
||||||
meta_full = f"{config.gitea_org}/{meta_repo}" if meta_repo else None
|
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:
|
try:
|
||||||
if repo_full == registry_full:
|
if repo_full == registry_full:
|
||||||
# §22.2: a registry-repo push re-mirrors the projects table.
|
# §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:
|
try:
|
||||||
await registry_mod.refresh_registry(config, gitea)
|
await registry_mod.refresh_registry(config, gitea)
|
||||||
except registry_mod.RegistryError:
|
except registry_mod.RegistryError:
|
||||||
log.exception("registry webhook: invalid projects.yaml; keeping last-good")
|
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_repo(config, gitea)
|
||||||
await cache.refresh_meta_branches(config, gitea)
|
await cache.refresh_meta_branches(config, gitea)
|
||||||
await cache.refresh_meta_pulls(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
|
"""Re-running the registry mirror is safe — it upserts (overwrites) the
|
||||||
projects row from projects.yaml each time without raising. M3 retirement
|
projects row from projects.yaml each time without raising. M3 retirement
|
||||||
of seed_default_project: the registry mirror is now the sole authority."""
|
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
|
app, _ = app_with_fake_gitea
|
||||||
with TestClient(app):
|
with TestClient(app):
|
||||||
# After startup the row should have content_repo from projects.yaml.
|
before = db.conn().execute("SELECT COUNT(*) AS n FROM projects").fetchone()["n"]
|
||||||
got = db.conn().execute(
|
cfg = app.state.config
|
||||||
"SELECT content_repo FROM projects WHERE id = 'default'"
|
gitea = app.state.gitea
|
||||||
).fetchone()["content_repo"]
|
asyncio.run(registry_mod.refresh_registry(cfg, gitea))
|
||||||
assert got == "meta"
|
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):
|
def test_registry_repo_config_wired(app_with_fake_gitea, monkeypatch):
|
||||||
|
|||||||
Reference in New Issue
Block a user