from __future__ import annotations import subprocess from pathlib import Path import httpx import pytest from repo_manager.identifiers import ( _projection_migration_preflight, derive_work_record_uuid, load_fleet_namespace, migrate_repository_identifier_files, migrate_repository_projection, plan_identifier_migration, plan_identifier_migration_batch, scan_live_identifier_collisions, verify_identifier_migration_batch, verify_identifier_migration_plan, ) def test_declared_fleet_namespace_is_helixforge() -> None: assert load_fleet_namespace() == "helixforge" def test_projection_preflight_rejects_unproven_assignment() -> None: result = _projection_migration_preflight( [{"action": "assign", "record_id": "ONE-WP-0001"}], ["http://hub.test"], ) assert result["ok"] is False assert result["errors"] == [ { "scope": "ONE-WP-0001", "reason": "projection-aware approval does not yet support UUID assignments", } ] def test_projection_preflight_accepts_mixed_convergent_states(monkeypatch) -> None: mappings = [ { "action": "replace", "kind": "workplan", "record_id": "ONE-WP-0001", "current_uuid": "11111111-1111-4111-8111-111111111111", "derived_uuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", }, { "action": "replace", "kind": "task", "record_id": "ONE-WP-0001-T01", "current_uuid": "22222222-2222-4222-8222-222222222222", "derived_uuid": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", }, ] def projection_get(self, url): value = str(url) present = value.endswith( (mappings[0]["current_uuid"], mappings[1]["derived_uuid"]) ) return httpx.Response(200 if present else 404, request=httpx.Request("GET", url)) monkeypatch.setattr(httpx.Client, "get", projection_get) result = _projection_migration_preflight(mappings, ["http://hub.test"]) assert result["ok"] is True projection = result["projections"][0] assert projection["mode"] == "mixed_convergence" assert projection["state_counts"]["legacy_source"] == 1 assert projection["state_counts"]["derived_target"] == 1 def test_projection_migration_client_requires_primary_and_exact_seal(tmp_path: Path) -> None: repo = tmp_path / "one" path = repo / "workplans" / "one.md" _workplan(path, "ONE-WP-0001", "active") subprocess.run(["git", "init"], cwd=repo, check=True, capture_output=True) subprocess.run(["git", "add", "."], cwd=repo, check=True, capture_output=True) subprocess.run( [ "git", "-c", "user.name=Test", "-c", "user.email=test@example.com", "commit", "-m", "seed", ], cwd=repo, check=True, capture_output=True, ) _push_fixture_to_upstream(repo, tmp_path / "migration-remote.git") plan = plan_identifier_migration(tmp_path, "helixforge") def handler(request: httpx.Request) -> httpx.Response: if request.url.path == "/state/health": return httpx.Response( 200, json={ "status": "ok", "instance_role": "primary", "instance_label": "railiance01", }, ) assert request.url.path == "/identifier-migrations/repositories/one/apply" assert request.headers["idempotency-key"].endswith(plan["plan_sha256"]) return httpx.Response( 200, json={"schema": "state-hub.identifier-migration-apply.v1"} ) result = migrate_repository_projection( plan, repo_slug="one", confirm_plan_sha256=plan["plan_sha256"], api_base="http://hub.test", transport=httpx.MockTransport(handler), ) assert result["ok"] is True assert result["direction"] == "forward" with pytest.raises(ValueError, match="confirm-plan-sha256"): migrate_repository_projection( plan, repo_slug="one", confirm_plan_sha256="0" * 64, api_base="http://hub.test", transport=httpx.MockTransport(handler), ) def _workplan(path: Path, identifier: str, status: str, task_status: str = "todo") -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text( f"""--- id: {identifier} title: Test status: {status} --- ## Task ```task id: {identifier}-T01 status: {task_status} ``` """, encoding="utf-8", ) def test_uuid_derivation_is_stable_and_namespace_scoped() -> None: first = derive_work_record_uuid("helixforge", "RMGR-WP-0005") assert str(first) == "6dcf854e-9229-569d-90f0-2d9df235a61d" assert derive_work_record_uuid("helixforge", "RMGR-WP-0005") == first assert derive_work_record_uuid("client-a", "RMGR-WP-0005") != first with pytest.raises(ValueError): derive_work_record_uuid("Client A", "RMGR-WP-0005") def test_preflight_blocks_live_collision_but_ignores_archived_history(tmp_path: Path) -> None: one = tmp_path / "one" two = tmp_path / "two" _workplan(one / "workplans" / "one.md", "SHARED-WP-0001", "active") _workplan(two / "workplans" / "two.md", "SHARED-WP-0001", "ready") report = scan_live_identifier_collisions(tmp_path) assert report["ok"] is False assert "SHARED-WP-0001" in report["collisions"] _workplan(two / "workplans" / "two.md", "SHARED-WP-0001", "archived") report = scan_live_identifier_collisions(tmp_path) assert report["ok"] is True def test_migration_plan_preserves_mapping_and_is_atomic_per_repo(tmp_path: Path) -> None: one = tmp_path / "one" two = tmp_path / "two" _workplan(one / "workplans" / "one.md", "ONE-WP-0001", "active") _workplan(two / "workplans" / "two.md", "TWO-WP-0001", "ready", task_status="done") report = plan_identifier_migration(tmp_path, "fleet-a") assert report["ok"] is True assert report["apply_policy"] == "all-or-nothing per repository" assert report["totals"] == { "repositories": 2, "eligible": 2, "skipped": 0, "records": 3, "replace": 0, "assign": 3, "unchanged": 0, } one_plan = next(item for item in report["repositories"] if item["repo"] == "one") assert one_plan["atomic_unit"] is True assert {item["record_id"] for item in one_plan["mappings"]} == { "ONE-WP-0001", "ONE-WP-0001-T01", } assert all(item["current_uuid"] is None for item in one_plan["mappings"]) assert len(report["plan_sha256"]) == 64 assert report["generated_at"].endswith("Z") def test_migration_plan_skips_entire_repo_affected_by_collision(tmp_path: Path) -> None: one = tmp_path / "one" two = tmp_path / "two" _workplan(one / "workplans" / "one.md", "SHARED-WP-0001", "active") _workplan(one / "workplans" / "safe.md", "ONE-WP-0002", "active") _workplan(two / "workplans" / "two.md", "SHARED-WP-0001", "ready") report = plan_identifier_migration(tmp_path, "fleet-a") assert report["ok"] is False assert report["totals"]["skipped"] == 2 one_plan = next(item for item in report["repositories"] if item["repo"] == "one") assert one_plan["eligible"] is False assert any(item["record_id"] == "ONE-WP-0002" for item in one_plan["mappings"]) assert any(item["reason"] == "live identifier collision" for item in one_plan["blockers"]) def test_migration_verification_detects_tampering_and_source_drift(tmp_path: Path) -> None: repo = tmp_path / "one" path = repo / "workplans" / "one.md" _workplan(path, "ONE-WP-0001", "active") subprocess.run(["git", "init"], cwd=repo, check=True, capture_output=True) subprocess.run(["git", "add", "."], cwd=repo, check=True, capture_output=True) subprocess.run( ["git", "-c", "user.name=Test", "-c", "user.email=test@example.com", "commit", "-m", "seed"], cwd=repo, check=True, capture_output=True, ) plan = plan_identifier_migration(tmp_path, "helixforge") assert verify_identifier_migration_plan(plan)["ok"] is True assert verify_identifier_migration_plan(plan, repo_slug="one")["ok"] is True assert verify_identifier_migration_plan(plan, repo_slug="missing")["ok"] is False path.write_text(path.read_text(encoding="utf-8") + "\nchanged\n", encoding="utf-8") verification = verify_identifier_migration_plan(plan) assert verification["ok"] is False assert any( error["reason"] == "authoritative source changed after planning" for error in verification["errors"] ) plan["namespace"] = "tampered" assert any( error["reason"] == "plan SHA-256 mismatch" for error in verify_identifier_migration_plan(plan)["errors"] ) def test_identifier_file_migration_dry_run_forward_and_reverse(tmp_path: Path) -> None: repo = tmp_path / "one" path = repo / "workplans" / "one.md" _workplan(path, "ONE-WP-0001", "active") old_workplan = "11111111-1111-4111-8111-111111111111" old_task = "22222222-2222-4222-8222-222222222222" original = path.read_text(encoding="utf-8") original = original.replace( "status: active\n---", f'status: active\nstate_hub_workstream_id: "{old_workplan}"\n---', ).replace( "status: todo\n```", f'status: todo\nstate_hub_task_id: "{old_task}"\n```', ) path.write_text(original, encoding="utf-8") plan = plan_identifier_migration(tmp_path, "helixforge") seal = plan["plan_sha256"] report = migrate_repository_identifier_files( plan, repo_slug="one", confirm_plan_sha256=seal, ) assert report["executed"] is False assert report["replacements"] == 2 assert path.read_text(encoding="utf-8") == original report = migrate_repository_identifier_files( plan, repo_slug="one", confirm_plan_sha256=seal, execute=True, ) assert report["executed"] is True migrated = path.read_text(encoding="utf-8") assert str(derive_work_record_uuid("helixforge", "ONE-WP-0001")) in migrated assert str(derive_work_record_uuid("helixforge", "ONE-WP-0001-T01")) in migrated assert old_workplan not in migrated assert old_task not in migrated report = migrate_repository_identifier_files( plan, repo_slug="one", confirm_plan_sha256=seal, direction="reverse", execute=True, ) assert report["direction"] == "reverse" assert path.read_text(encoding="utf-8") == original def test_identifier_file_migration_requires_exact_plan_confirmation(tmp_path: Path) -> None: repo = tmp_path / "one" _workplan(repo / "workplans" / "one.md", "ONE-WP-0001", "active") plan = plan_identifier_migration(tmp_path, "helixforge") with pytest.raises(ValueError, match="confirm-plan-sha256"): migrate_repository_identifier_files( plan, repo_slug="one", confirm_plan_sha256="0" * 64, ) def _push_fixture_to_upstream(repo: Path, remote: Path) -> None: subprocess.run(["git", "init", "--bare", remote], check=True, capture_output=True) subprocess.run(["git", "remote", "add", "origin", str(remote)], cwd=repo, check=True) subprocess.run(["git", "push", "-u", "origin", "HEAD:main"], cwd=repo, check=True) def test_migration_batch_plan_pins_clean_synchronized_repo( tmp_path: Path, monkeypatch ) -> None: repo = tmp_path / "one" path = repo / "workplans" / "one.md" _workplan(path, "ONE-WP-0001", "active") path.write_text( path.read_text(encoding="utf-8") .replace( "status: active\n---", 'status: active\nstate_hub_workstream_id: "11111111-1111-4111-8111-111111111111"\n---', ) .replace( "status: todo\n```", 'status: todo\nstate_hub_task_id: "22222222-2222-4222-8222-222222222222"\n```', ), encoding="utf-8", ) subprocess.run(["git", "init"], cwd=repo, check=True, capture_output=True) subprocess.run(["git", "add", "."], cwd=repo, check=True, capture_output=True) subprocess.run( ["git", "-c", "user.name=Test", "-c", "user.email=test@example.com", "commit", "-m", "seed"], cwd=repo, check=True, capture_output=True, ) _push_fixture_to_upstream(repo, tmp_path / "remote.git") plan = plan_identifier_migration(tmp_path, "helixforge") current_uuids = { mapping["current_uuid"] for mapping in plan["repositories"][0]["mappings"] } def projection_get(self, url): status = 200 if any(str(url).endswith(str(value)) for value in current_uuids) else 404 return httpx.Response(status, request=httpx.Request("GET", url)) monkeypatch.setattr(httpx.Client, "get", projection_get) batch = plan_identifier_migration_batch( plan, repo_slugs=["one"], projection_api_bases=["http://hub-one.test", "http://hub-two.test"], ) assert batch["ok"] is True assert batch["ready_for_approval"] is True assert batch["apply_authorized"] is False assert batch["approval_required"] is True assert batch["projection_api_bases"] == [ "http://hub-one.test", "http://hub-two.test", ] assert batch["repositories"][0]["projection_preflight"]["ok"] is True assert batch["repositories"][0]["git_preflight"]["upstream"] == "origin/main" assert len(batch["batch_sha256"]) == 64 def projection_drift(self, url): return httpx.Response(404, request=httpx.Request("GET", url)) monkeypatch.setattr(httpx.Client, "get", projection_drift) verification = verify_identifier_migration_batch(batch, plan=plan) assert verification["ok"] is False assert verification["ready_for_decision"] is False def test_migration_batch_plan_rejects_projection_gap(tmp_path: Path, monkeypatch) -> None: repo = tmp_path / "one" path = repo / "workplans" / "one.md" _workplan(path, "ONE-WP-0001", "active") path.write_text( path.read_text(encoding="utf-8") .replace( "status: active\n---", 'status: active\nstate_hub_workstream_id: "11111111-1111-4111-8111-111111111111"\n---', ) .replace( "status: todo\n```", 'status: todo\nstate_hub_task_id: "22222222-2222-4222-8222-222222222222"\n```', ), encoding="utf-8", ) subprocess.run(["git", "init"], cwd=repo, check=True, capture_output=True) subprocess.run(["git", "add", "."], cwd=repo, check=True, capture_output=True) subprocess.run( ["git", "-c", "user.name=Test", "-c", "user.email=test@example.com", "commit", "-m", "seed"], cwd=repo, check=True, capture_output=True, ) _push_fixture_to_upstream(repo, tmp_path / "remote.git") plan = plan_identifier_migration(tmp_path, "helixforge") def projection_get(self, url): return httpx.Response(404, request=httpx.Request("GET", url)) monkeypatch.setattr(httpx.Client, "get", projection_get) batch = plan_identifier_migration_batch( plan, repo_slugs=["one"], projection_api_bases=["http://hub.test"], ) assert batch["ok"] is False assert batch["ready_for_approval"] is False assert batch["repositories"][0]["projection_preflight"]["ok"] is False assert any("neither legacy nor derived" in error["reason"] for error in batch["errors"]) def test_migration_batch_plan_rejects_dirty_or_duplicate_scope(tmp_path: Path) -> None: repo = tmp_path / "one" path = repo / "workplans" / "one.md" _workplan(path, "ONE-WP-0001", "active") subprocess.run(["git", "init"], cwd=repo, check=True, capture_output=True) subprocess.run(["git", "add", "."], cwd=repo, check=True, capture_output=True) subprocess.run( ["git", "-c", "user.name=Test", "-c", "user.email=test@example.com", "commit", "-m", "seed"], cwd=repo, check=True, capture_output=True, ) _push_fixture_to_upstream(repo, tmp_path / "remote.git") plan = plan_identifier_migration(tmp_path, "helixforge") path.write_text(path.read_text(encoding="utf-8") + "\ndrift\n", encoding="utf-8") batch = plan_identifier_migration_batch(plan, repo_slugs=["one", "one"]) assert batch["ok"] is False assert batch["ready_for_approval"] is False assert any(error["reason"] == "repository list contains duplicates" for error in batch["errors"]) assert any(error["reason"] == "worktree is not clean" for error in batch["errors"]) def test_migration_batch_verification_repeats_preflight_and_detects_tampering( tmp_path: Path, ) -> None: repo = tmp_path / "one" path = repo / "workplans" / "one.md" _workplan(path, "ONE-WP-0001", "active") subprocess.run(["git", "init"], cwd=repo, check=True, capture_output=True) subprocess.run(["git", "add", "."], cwd=repo, check=True, capture_output=True) subprocess.run( ["git", "-c", "user.name=Test", "-c", "user.email=test@example.com", "commit", "-m", "seed"], cwd=repo, check=True, capture_output=True, ) _push_fixture_to_upstream(repo, tmp_path / "remote.git") plan = plan_identifier_migration(tmp_path, "helixforge") batch = plan_identifier_migration_batch(plan, repo_slugs=["one"]) assert verify_identifier_migration_batch(batch, plan=plan)["ok"] is True batch["totals"]["records"] = 999 verification = verify_identifier_migration_batch(batch, plan=plan) assert verification["ok"] is False assert any(error["reason"] == "batch SHA-256 mismatch" for error in verification["errors"])