feat(consistency): bootstrap empty repo projections
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a023c0-a0a3-7c03-b395-5a0d2757214d
This commit is contained in:
tegwick 2026-08-22 12:07:08 +02:00
parent 52a7d6bb73
commit 03c7924b7f
3 changed files with 187 additions and 7 deletions

View file

@ -402,6 +402,8 @@ def cmd_fix_consistency(args: argparse.Namespace) -> None:
cmd.append("--remote")
if args.no_writeback:
cmd.append("--no-writeback")
if getattr(args, "bootstrap_empty_projection", False):
cmd.append("--bootstrap-empty-projection")
if args.archive_closed:
cmd.append("--archive-closed")
if args.archive_workplan:
@ -720,6 +722,11 @@ def main() -> None:
fix.add_argument("--remote", action="store_true", help="Pull before fixing; requires --repo or --all")
fix.add_argument("--max-seconds", type=int, default=None, help="Wall-clock budget for --remote --all")
fix.add_argument("--no-writeback", action="store_true", help="Disable DB-to-file status writeback")
fix.add_argument(
"--bootstrap-empty-projection",
action="store_true",
help="Rebuild file UUIDs only after proving the repo projection is empty",
)
fix.add_argument("--archive-closed", action="store_true", help="Archive closed root workplans after fixing")
fix.add_argument("--archive-workplan", default=None, help="Archive only the matching workplan id or filename")
fix.add_argument("--archive-date", default=None, help="YYMMDD archive prefix for --archive-closed")

View file

@ -1154,7 +1154,12 @@ def _check_work_record_index_freshness(
)
def check_repo(api_base: str, repo_slug: str, repo_path_override: str | None = None) -> ConsistencyReport:
def check_repo(
api_base: str,
repo_slug: str,
repo_path_override: str | None = None,
bootstrap_empty_projection: bool = False,
) -> ConsistencyReport:
"""Run all consistency checks for a registered repo."""
repo = _api_get(api_base, f"/repos/{repo_slug}", return_error=True)
if isinstance(repo, dict) and "_error" in repo:
@ -1180,6 +1185,28 @@ def check_repo(api_base: str, repo_slug: str, repo_path_override: str | None = N
repo_path: str = resolve_repo_path(repo, repo_path_override)
report = ConsistencyReport(repo_slug=repo_slug, repo_path=repo_path)
if bootstrap_empty_projection:
projected = _api_get(api_base, "/workplans", {"repo_id": repo_id}, return_error=True)
if not isinstance(projected, list):
report.add(
severity="FAIL",
check_id="C-36",
message="Could not prove that the repository projection is empty",
fixable=False,
)
return report
if projected:
report.add(
severity="FAIL",
check_id="C-36",
message=(
"Empty-projection bootstrap refused: repository already has "
f"{len(projected)} projected workplan(s)"
),
fixable=False,
)
return report
if not repo_path:
report.add(
severity="FAIL", check_id="C-00",
@ -1328,14 +1355,21 @@ def check_repo(api_base: str, repo_slug: str, repo_path_override: str | None = N
ws = _api_get(api_base, f"/workplans/{ws_id}")
if ws is None:
wp_id = str(meta.get("id", "")).strip()
if wp_id and ws_id == _derived_work_record_uuid(wp_id):
if wp_id and (
ws_id == _derived_work_record_uuid(wp_id) or bootstrap_empty_projection
):
# A deterministic file identifier missing from a replaceable
# projection is registration work, not a stale reference.
report.add(
severity="WARN",
check_id="C-06",
message=(
f"Derived workplan {ws_id[:8]}… is absent from this projection"
f"Authoritative workplan {ws_id[:8]}… is absent from this "
+ (
"explicitly empty projection"
if bootstrap_empty_projection
else "projection"
)
),
file_path=fname,
db_id=ws_id,
@ -2755,10 +2789,16 @@ def fix_repo(
repo_slug: str,
repo_path_override: str | None = None,
no_writeback: bool = False,
bootstrap_empty_projection: bool = False,
) -> ConsistencyReport:
"""Run checks then apply all auto-fixable issues. Returns updated report."""
report = check_repo(api_base, repo_slug, repo_path_override)
if any(i.check_id == "C-00" for i in report.failures):
report = check_repo(
api_base,
repo_slug,
repo_path_override,
bootstrap_empty_projection=bootstrap_empty_projection,
)
if any(i.check_id in {"C-00", "C-36"} for i in report.failures):
return report
# RMGR-WP-0002: optional repo-manager reconcile proxy (index rebuild) for pilots
@ -3675,6 +3715,14 @@ def main() -> None:
f"(default: {DEFAULT_REMOTE_ALL_MAX_SECONDS}; 0 disables)")
parser.add_argument("--no-writeback", action="store_true", dest="no_writeback",
help="Disable DB→file status writeback (C-15) while keeping other fixes")
parser.add_argument(
"--bootstrap-empty-projection",
action="store_true",
help=(
"Rebuild authoritative legacy UUIDs only after proving the target "
"repository has zero projected workplans"
),
)
parser.add_argument("--archive-closed", action="store_true",
help="Move closed root workplans to workplans/archived/YYMMDD-*.md")
parser.add_argument("--archive-workplan", metavar="ID_OR_FILE", default=None,
@ -3712,6 +3760,11 @@ def main() -> None:
import os as _os
no_wb = getattr(args, "no_writeback", False)
do_fix = args.fix or args.remote
if args.bootstrap_empty_projection and (not do_fix or args.all or args.remote):
parser.error(
"--bootstrap-empty-projection requires --fix for one local repository "
"and cannot be combined with --all or --remote"
)
# --here: infer slug from git remote URL, then run as single-repo check/fix
if args.here is not None:
@ -3730,7 +3783,15 @@ def main() -> None:
inferred_slug, git_root = inferred
print(f" Detected: {inferred_slug} ({git_root})")
if do_fix:
reports = [fix_repo(args.api_base, inferred_slug, git_root, no_writeback=no_wb)]
reports = [
fix_repo(
args.api_base,
inferred_slug,
git_root,
no_writeback=no_wb,
bootstrap_empty_projection=args.bootstrap_empty_projection,
)
]
else:
reports = [check_repo(args.api_base, inferred_slug, git_root)]
if args.archive_closed:
@ -3792,7 +3853,13 @@ def main() -> None:
reports = [report]
elif do_fix:
reports = [
fix_repo(args.api_base, slug, path_override, no_writeback=no_wb)
fix_repo(
args.api_base,
slug,
path_override,
no_writeback=no_wb,
bootstrap_empty_projection=args.bootstrap_empty_projection,
)
for slug in repo_slugs
]
else:

View file

@ -1272,6 +1272,112 @@ class TestC20DependencyDetection:
class TestC06WorkstreamCreation:
def test_fix_repo_bootstraps_legacy_ids_only_into_empty_projection(
self, tmp_path, monkeypatch
):
repo = tmp_path / "repo"
workplans = repo / "workplans"
workplans.mkdir(parents=True)
workplan_id = "11111111-1111-4111-8111-111111111111"
task_id = "22222222-2222-4222-8222-222222222222"
wp = workplans / "DEMO-WP-0001.md"
wp.write_text(
"---\n"
"id: DEMO-WP-0001\n"
"type: workplan\n"
"title: Demo Workplan\n"
"domain: financials\n"
"repo: demo-repo\n"
"status: ready\n"
f'state_hub_workstream_id: "{workplan_id}"\n'
"---\n\n"
"## Task\n\n"
"```task\n"
"id: DEMO-WP-0001-T01\n"
"status: todo\n"
"priority: high\n"
f'state_hub_task_id: "{task_id}"\n'
"```\n",
encoding="utf-8",
)
created = []
def fake_get(_api_base, path, params=None, **_kwargs):
if path == "/repos/demo-repo":
return {
"id": "repo-1",
"slug": "demo-repo",
"local_path": str(repo),
"host_paths": {},
"domain_slug": "financials",
}
if path == "/topics":
return [{"id": "topic-1", "domain_slug": "financials"}]
if path == "/workplans" and params == {"repo_id": "repo-1"}:
return []
if path == f"/workplans/{workplan_id}":
return None
if path == "/workplans" and params and "slug" in params:
return []
return []
def fake_post(_api_base, path, body):
created.append((path, body))
return body
monkeypatch.setattr("consistency_check._api_get", fake_get)
monkeypatch.setattr("consistency_check._api_post", fake_post)
monkeypatch.setattr("consistency_check._api_patch", lambda *args, **kwargs: {"ok": True})
monkeypatch.setattr("consistency_check._detect_behind_remote", lambda _repo_path: False)
monkeypatch.setattr("consistency_check._detect_ahead_of_remote", lambda _repo_path: 0)
monkeypatch.setattr("consistency_check._write_custodian_brief", lambda *args, **kwargs: False)
monkeypatch.setattr("consistency_check._git_push", lambda _repo_path: (True, "pushed"))
monkeypatch.setenv("STATEHUB_REGISTRAR", "1")
report = fix_repo(
"http://unused",
"demo-repo",
bootstrap_empty_projection=True,
)
assert [body["id"] for path, body in created if path == "/workplans"] == [workplan_id]
assert [body["id"] for path, body in created if path == "/tasks"] == [task_id]
assert any("explicitly empty projection" in issue.message for issue in report.issues)
def test_fix_repo_refuses_empty_bootstrap_when_projection_has_rows(
self, tmp_path, monkeypatch
):
repo = tmp_path / "repo"
(repo / "workplans").mkdir(parents=True)
def fake_get(_api_base, path, params=None, **_kwargs):
if path == "/repos/demo-repo":
return {
"id": "repo-1",
"slug": "demo-repo",
"local_path": str(repo),
"host_paths": {},
"domain_slug": "financials",
}
if path == "/workplans" and params == {"repo_id": "repo-1"}:
return [{"id": "existing"}]
return []
monkeypatch.setattr("consistency_check._api_get", fake_get)
monkeypatch.setattr(
"consistency_check._api_post",
lambda *_args, **_kwargs: pytest.fail("bootstrap must not mutate a non-empty projection"),
)
report = fix_repo(
"http://unused",
"demo-repo",
bootstrap_empty_projection=True,
)
assert len(report.failures) == 1
assert report.failures[0].check_id == "C-36"
def test_fix_repo_rebuilds_missing_projection_from_derived_file_ids(
self, tmp_path, monkeypatch
):