fix(backfill): source task identities from the forge, not a workstation
The first implementation took local filesystem paths. Central has no workstation checkout and must not depend on one: ADR-012 decision 1 makes the forge the projection source, and a backfill reading someone's laptop would reintroduce the exact coupling that ADR removes. Surfaced concretely — central's postgres is not reachable from the workstation (only the API tunnel, which is HTTP), so the local-path variant cannot reach the database it needs to update, while the pod can clone the forge and already holds the connection. A repository that cannot be cloned contributes nothing rather than reducing what the rest can identify. Refs STATE-WP-0083-T06 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 2583210@bnt-lap001 Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
This commit is contained in:
parent
de853fc9e6
commit
cdd5cef373
1 changed files with 75 additions and 0 deletions
|
|
@ -114,3 +114,78 @@ async def backfill_task_record_ids(
|
||||||
if not dry_run:
|
if not dry_run:
|
||||||
row.record_id = record_id
|
row.record_id = record_id
|
||||||
return report
|
return report
|
||||||
|
|
||||||
|
|
||||||
|
async def backfill_from_forge(
|
||||||
|
session: Any,
|
||||||
|
repo_slugs: list[str],
|
||||||
|
*,
|
||||||
|
forge_base: str | None = None,
|
||||||
|
dry_run: bool = True,
|
||||||
|
) -> BackfillReport:
|
||||||
|
"""Backfill from repositories cloned out of the forge.
|
||||||
|
|
||||||
|
The local-path variant above needs a workstation checkout, which central
|
||||||
|
does not have and should not depend on: `ADR-012` decision 1 makes the forge
|
||||||
|
the projection source, and a backfill sourced from someone's laptop would
|
||||||
|
reintroduce exactly the coupling that ADR removes.
|
||||||
|
|
||||||
|
Central can clone the forge directly, so it reads the pairing from the same
|
||||||
|
place it derives everything else.
|
||||||
|
"""
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
from api.services.forge_projection import (
|
||||||
|
DEFAULT_FORGE_BASE,
|
||||||
|
ForgeDeriveError,
|
||||||
|
_run_git,
|
||||||
|
)
|
||||||
|
|
||||||
|
base = forge_base or DEFAULT_FORGE_BASE
|
||||||
|
report = BackfillReport()
|
||||||
|
pairs: dict[str, str] = {}
|
||||||
|
|
||||||
|
for slug in repo_slugs:
|
||||||
|
url = f"{base.rstrip('/')}/{slug}.git"
|
||||||
|
with tempfile.TemporaryDirectory(prefix=f"backfill-{slug}-") as tmp:
|
||||||
|
try:
|
||||||
|
_run_git("clone", "--depth", "1", "--quiet", url, tmp)
|
||||||
|
except (ForgeDeriveError, Exception):
|
||||||
|
# A repository that cannot be read contributes nothing. It must
|
||||||
|
# not silently reduce what the rest can identify.
|
||||||
|
continue
|
||||||
|
repo_pairs, repo_report = collect_pairs([Path(tmp)])
|
||||||
|
report.scanned_files += repo_report.scanned_files
|
||||||
|
report.conflicts.extend(repo_report.conflicts)
|
||||||
|
for uid, rid in repo_pairs.items():
|
||||||
|
prior = pairs.get(uid)
|
||||||
|
if prior and prior != rid:
|
||||||
|
report.conflicts.append({"uuid": uid, "first": prior, "second": rid})
|
||||||
|
continue
|
||||||
|
pairs[uid] = rid
|
||||||
|
|
||||||
|
report.pairs_found = len(pairs)
|
||||||
|
if not pairs:
|
||||||
|
return report
|
||||||
|
|
||||||
|
from sqlalchemy import select
|
||||||
|
|
||||||
|
from api.models.task import Task
|
||||||
|
|
||||||
|
rows = list((await session.execute(select(Task))).scalars())
|
||||||
|
by_id = {str(r.id): r for r in rows}
|
||||||
|
for uid, rid in pairs.items():
|
||||||
|
row = by_id.get(uid)
|
||||||
|
if row is None:
|
||||||
|
report.unmatched_uuids += 1
|
||||||
|
continue
|
||||||
|
if row.record_id == rid:
|
||||||
|
report.already_set += 1
|
||||||
|
continue
|
||||||
|
if row.record_id and row.record_id != rid:
|
||||||
|
report.conflicts.append({"uuid": uid, "first": row.record_id, "second": rid})
|
||||||
|
continue
|
||||||
|
report.updated += 1
|
||||||
|
if not dry_run:
|
||||||
|
row.record_id = rid
|
||||||
|
return report
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue