feat: add fast forge work-record reconciliation
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Build and Publish Multi-Context Image / build-and-push (push) Successful in 24s

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a053ff-1d6f-7fe2-ac1c-a6eb40a42a0c
This commit is contained in:
tegwick 2026-08-30 22:38:54 +02:00
parent a65cef02cf
commit 34f5cb3fc3
22 changed files with 799 additions and 162 deletions

View file

@ -630,6 +630,24 @@ def _inject_task_id_frontmatter_list(
# API helpers
# ---------------------------------------------------------------------------
_API_CLIENTS: dict[tuple[str, object], Any] = {}
def _api_client(api_base: str) -> Any:
"""Reuse one keep-alive pool per API/client implementation for this run."""
key = (api_base.rstrip("/"), _httpx.Client)
client = _API_CLIENTS.get(key)
if client is None:
client = _httpx.Client(
base_url=api_base,
timeout=10.0,
follow_redirects=True,
headers=_legacy_meter_headers(),
)
_API_CLIENTS[key] = client
return client
def _api_get(
api_base: str,
path: str,
@ -646,15 +664,9 @@ def _api_get(
last_error: Exception | None = None
for attempt in range(_API_GET_RETRIES):
try:
with _httpx.Client(
base_url=api_base,
timeout=10.0,
follow_redirects=True,
headers=_legacy_meter_headers(),
) as c:
r = c.get(path, params=filtered if filtered else None)
r.raise_for_status()
return r.json()
r = _api_client(api_base).get(path, params=filtered if filtered else None)
r.raise_for_status()
return r.json()
except _httpx.HTTPStatusError as exc:
if exc.response.status_code == 404:
return None
@ -689,15 +701,9 @@ def _api_patch(api_base: str, path: str, body: dict) -> Any:
if not path.endswith("/"):
path += "/"
try:
with _httpx.Client(
base_url=api_base,
timeout=10.0,
follow_redirects=True,
headers=_legacy_meter_headers(),
) as c:
r = c.patch(path, json=body)
r.raise_for_status()
return r.json()
r = _api_client(api_base).patch(path, json=body)
r.raise_for_status()
return r.json()
except Exception as exc:
# Return a sentinel dict so callers can distinguish "API error" from "success"
# and report it rather than silently dropping the fix.
@ -710,15 +716,9 @@ def _api_put(api_base: str, path: str, body: dict) -> Any:
if not path.endswith("/"):
path += "/"
try:
with _httpx.Client(
base_url=api_base,
timeout=30.0,
follow_redirects=True,
headers=_legacy_meter_headers(),
) as c:
r = c.put(path, json=body)
r.raise_for_status()
return r.json()
r = _api_client(api_base).put(path, json=body, timeout=30.0)
r.raise_for_status()
return r.json()
except Exception as exc:
return {"_error": str(exc)}
@ -729,15 +729,9 @@ def _api_post(api_base: str, path: str, body: dict) -> Any:
if not path.endswith("/"):
path += "/"
try:
with _httpx.Client(
base_url=api_base,
timeout=10.0,
follow_redirects=True,
headers=_legacy_meter_headers(),
) as c:
r = c.post(path, json=body)
r.raise_for_status()
return r.json()
r = _api_client(api_base).post(path, json=body)
r.raise_for_status()
return r.json()
except _httpx.HTTPStatusError as exc:
detail = exc.response.text
if len(detail) > 500:
@ -1357,6 +1351,37 @@ def check_repo(
if task_file_id and task_sh_id and task_sh_id not in ("~", "null", "None", "none"):
task_file_id_to_sh_id[task_file_id] = task_sh_id
# Modern hubs expose the entire repo-scoped read model in one response.
# Keep the older per-record calls as a rolling-deploy compatibility path.
projection_snapshot = _api_get(
api_base,
f"/repos/{repo_slug}/work-record-projection/snapshot",
return_error=True,
)
snapshot_available = (
isinstance(projection_snapshot, dict)
and projection_snapshot.get("schema")
== "state-hub.repository-projection-snapshot.v1"
and str(projection_snapshot.get("repo_id")) == repo_id
)
snapshot_workplans_by_id: dict[str, dict] = {}
snapshot_tasks_by_workplan: dict[str, list[dict]] = {}
snapshot_dependencies_by_workplan: dict[str, list[dict]] = {}
if snapshot_available:
for row in projection_snapshot.get("workplans", []):
if isinstance(row, dict) and row.get("id"):
snapshot_workplans_by_id[str(row["id"])] = row
for row in projection_snapshot.get("tasks", []):
if isinstance(row, dict) and row.get("workplan_id"):
snapshot_tasks_by_workplan.setdefault(
str(row["workplan_id"]), []
).append(row)
for row in projection_snapshot.get("dependencies", []):
if isinstance(row, dict) and row.get("from_workplan_id"):
snapshot_dependencies_by_workplan.setdefault(
str(row["from_workplan_id"]), []
).append(row)
# Per-workplan checks
for wp_file, meta, body in workplan_infos:
fname = workplan_display_path(repo_dir, wp_file)
@ -1396,7 +1421,11 @@ def check_repo(
)
continue
ws = _api_get(api_base, f"/workplans/{ws_id}")
ws = (
snapshot_workplans_by_id.get(ws_id)
if snapshot_available
else _api_get(api_base, f"/workplans/{ws_id}")
)
if ws is None:
wp_id = str(meta.get("id", "")).strip()
if wp_id and (
@ -1461,7 +1490,16 @@ def check_repo(
# Continue to check drift even with mismatched repo
tasks = get_tasks_from_workplan(meta, body)
db_tasks = _api_get(api_base, "/tasks", {"workplan_id": ws_id})
db_tasks = (
snapshot_tasks_by_workplan.get(ws_id, [])
if snapshot_available
else _api_get(api_base, "/tasks", {"workplan_id": ws_id})
)
db_tasks_by_id = {
str(row.get("id")): row
for row in db_tasks
if isinstance(row, dict) and row.get("id")
} if isinstance(db_tasks, list) else {}
file_task_statuses = [
str(task.get("status", "")).strip()
for task in tasks
@ -1632,7 +1670,11 @@ def check_repo(
for t in db_tasks:
db_task_by_id[t["id"]] = t
existing_deps = _api_get(api_base, f"/workplans/{ws_id}/dependencies") or []
existing_deps = (
snapshot_dependencies_by_workplan.get(ws_id, [])
if snapshot_available
else _api_get(api_base, f"/workplans/{ws_id}/dependencies") or []
)
existing_dep_keys = set()
if isinstance(existing_deps, list):
for dep in existing_deps:
@ -1718,7 +1760,7 @@ def check_repo(
if t_sh_id:
file_task_sh_ids.add(t_sh_id)
db_task = _api_get(api_base, f"/tasks/{t_sh_id}")
db_task = db_tasks_by_id.get(t_sh_id)
if db_task is None:
if t_id and t_sh_id == _derived_work_record_uuid(t_id):
report.add(
@ -2818,8 +2860,8 @@ def _skip_non_registrar_mint(report: "ConsistencyReport", check_id: str, label:
f"{check_id} skipped: this instance is not the identifier registrar "
f"({label}; do not retry or set STATEHUB_REGISTRAR directly; after "
"committing and pushing file-backed work run once: "
"uv run --project ~/repo-manager rmgr registrar-reconcile "
"--path . --confirm-primary --push; ADR-007 interim / RMGR-WP-0005-T01)"
"uv run --project ~/repo-manager rmgr sync --path . --push; "
"ADR-012 / STATE-WP-0086)"
)
return True