feat: support mixed identifier convergence

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a053ff-1d6f-7fe2-ac1c-a6eb40a42a0c
This commit is contained in:
tegwick 2026-08-31 01:27:21 +02:00
parent 4901b6d623
commit 5789e8c520
5 changed files with 313 additions and 11 deletions

View file

@ -11,6 +11,7 @@ from repo_manager.identifiers import (
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,
@ -38,6 +39,101 @@ def test_projection_preflight_rejects_unproven_assignment() -> None:
]
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": "railliance01",
},
)
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(
@ -335,7 +431,7 @@ def test_migration_batch_plan_rejects_projection_gap(tmp_path: Path, monkeypatch
assert batch["ok"] is False
assert batch["ready_for_approval"] is False
assert batch["repositories"][0]["projection_preflight"]["ok"] is False
assert any("current UUID=200" in error["reason"] for error in batch["errors"])
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: