REUSE-WP-0019-T01: forge host abstraction + URL migration inventory
Some checks failed
ci / validate-registry (push) Has been cancelled

reuse_surface/forge_host.py: parse/derive/rewrite raw index URLs across
Gitea and Forgejo (handles both the legacy /raw/<branch>/... form and the
canonical /raw/branch/<branch>/... form both forges serve without a 303
redirect). migrate_source_host() verifies the new URL resolves via HTTP
HEAD before writing -- refuses to point a repo at a host it hasn't
actually migrated to.

New CLI: reuse-surface federation migrate-host --repo <slug> --to
<base-url> [--from <check>] [--dry-run] [--no-verify] [--update-hub].

Inventory: cross-referenced sources.yaml against each repo's actual git
origin. 11/61 repos already on Forgejo; found 2 with stale sources.yaml
entries (activity-core, state-hub) despite having migrated. Fixed for
real: local sources.yaml + production hub registration (hub update),
verified against GET /v1/federated post-migration. config-atlas's
WP-0017-T06 303 confirmed NOT a host-transition symptom (already diagnosed
there as something else).

Also fixed two host-agnostic gaps found while inventorying:
registry_update.py and maintain_llm.py only recognized .gitea/workflows/,
missing repos already on .forgejo/workflows/. Fixed a stale copy-paste
example (state-hub's now-wrong old URL) in docs/RegistryFederation.md, and
a pre-existing unrelated port typo (8088 vs the real llm-connect default
8080) in tools/README.md and registry/README.md.

17 new pytest cases (tests/test_forge_host.py), 106 total pass.
Recomposed federated.yaml post-migration: still 61 capabilities.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-07 18:14:30 +02:00
parent 5aaa4c31c9
commit 00b7eab154
11 changed files with 451 additions and 30 deletions

View File

@@ -11,6 +11,7 @@ from jsonschema import Draft202012Validator
from reuse_surface.catalog import write_catalog
from reuse_surface.federation import write_federated_index
from reuse_surface.forge_host import migrate_source_host
from reuse_surface import hub_client
from reuse_surface.graph import check_relations, render_mermaid, write_graph
from reuse_surface.hub_sync import (
@@ -264,6 +265,56 @@ def cmd_federation_compose(args: argparse.Namespace) -> int:
return 0
def cmd_federation_migrate_host(args: argparse.Namespace) -> int:
sources_path = Path(args.sources) if args.sources else DEFAULT_SOURCES_PATH
manifest = load_sources_manifest(sources_path)
sources = manifest.get("sources", [])
reports: list[dict[str, Any]] = []
errors: list[str] = []
for repo in args.repo:
entry = next((s for s in sources if s.get("repo") == repo), None)
if entry is None:
errors.append(f"{repo}: not found in {sources_path}")
continue
if args.from_host and args.from_host not in entry["url"]:
errors.append(
f"{repo}: current URL does not contain --from {args.from_host!r}: {entry['url']}"
)
continue
try:
report = migrate_source_host(sources, repo, args.to, verify=not args.no_verify)
reports.append(report)
except ValueError as exc:
errors.append(str(exc))
for r in reports:
verified = "unverified" if r["verified"] is None else ("verified" if r["verified"] else "FAILED")
print(f"{r['repo']}: {r['old_url']} -> {r['new_url']} ({verified})")
for e in errors:
print(f"error: {e}", file=sys.stderr)
if args.dry_run:
print("(dry-run, no files written)")
return 1 if errors else 0
if reports:
write_sources_manifest(manifest, sources_path)
if args.update_hub:
for r in reports:
try:
status, payload = hub_client.hub_update(
r["repo"], {"url": r["new_url"]}, args.base_url
)
if status != 200:
errors.append(f"{r['repo']}: hub update returned {status}: {payload}")
except ValueError as exc:
errors.append(f"{r['repo']}: hub update skipped: {exc}")
return 1 if errors else 0
def cmd_graph(args: argparse.Namespace) -> int:
warnings = check_relations() if args.check else []
content = render_mermaid()
@@ -750,6 +801,35 @@ def main(argv: list[str] | None = None) -> int:
)
compose.set_defaults(func=cmd_federation_compose)
migrate_host = federation_sub.add_parser(
"migrate-host",
help="rewrite one or more repos' raw index URL to a new forge host (REUSE-WP-0019-T01)",
)
migrate_host.add_argument(
"--repo", action="append", required=True,
help="repo slug to migrate (repeatable)",
)
migrate_host.add_argument(
"--to", required=True,
help="new base URL, e.g. https://forgejo.coulomb.social (or REUSE_SURFACE_FORGE_BASE_URL)",
)
migrate_host.add_argument(
"--from", dest="from_host",
help="sanity check: refuse to migrate a repo whose current URL doesn't contain this",
)
migrate_host.add_argument("--sources", help="path to sources.yaml (default: registry/federation/sources.yaml)")
migrate_host.add_argument("--dry-run", action="store_true", help="show what would change, write nothing")
migrate_host.add_argument(
"--no-verify", action="store_true",
help="skip HTTP-probing the new URL before writing (unsafe -- can point at a repo that hasn't migrated yet)",
)
migrate_host.add_argument(
"--update-hub", action="store_true",
help="also PATCH the production hub registration for each migrated repo",
)
migrate_host.add_argument("--base-url", help="hub service base URL (or REUSE_SURFACE_URL), used with --update-hub")
migrate_host.set_defaults(func=cmd_federation_migrate_host)
query = subparsers.add_parser("query", help="query capability index")
query.add_argument("--discovery-min")
query.add_argument("--availability-min")