The reset raised IntegrityError on net-kingdom: its ADHOC-2026-08-23 derives to an identifier another repository already holds — the collision CUST-WP-0066 documents, where two repositories created the same daily identifier on the same day. Derivation is deterministic, so the clash is real rather than incidental. It now checks, before creating anything, whether a derived identifier belongs to another repository, and refuses naming both the record and the holder. A refusal is something the caller can rule on; a constraint violation is a stack trace. Acknowledging retirements deliberately does not authorise a collision. Those are different decisions — one says the work is gone, the other says take an identifier another repository owns — and conflating them would let a routine acknowledgement smuggle an identity change through. Refs STATE-WP-0083-T03 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
301 lines
12 KiB
Python
301 lines
12 KiB
Python
"""Deriving a projection from the forge (STATE-WP-0083-T01).
|
|
|
|
The properties that matter are identity and determinism: a forge-derived
|
|
projection must compute the same record identities as repo-manager, and the same
|
|
commit must always yield the same projection. Without both, the reset in T03
|
|
cannot be verified against anything.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from api.services import forge_projection as fp
|
|
|
|
|
|
def test_identity_matches_the_fleet_derivation():
|
|
"""Same namespace as ADR-007, so overlay and forge agree on identity."""
|
|
assert fp.derived_record_uuid("CUST-WP-0067") == "16249302-2767-55df-aec0-d92c2751c225"
|
|
assert fp.derived_record_uuid("CUST-WP-0067-T01") == "f3608db4-20a5-58fb-a965-885eb14858af"
|
|
|
|
|
|
def _repo(tmp_path: Path) -> Path:
|
|
root = tmp_path / "demo"
|
|
(root / "workplans" / "archived").mkdir(parents=True)
|
|
(root / "workplans" / "DEMO-WP-0001-a.md").write_text(
|
|
"---\nid: DEMO-WP-0001\ntype: workplan\ntitle: \"First\"\nstatus: active\n---\n\n"
|
|
"## Do the thing\n\n```task\nid: DEMO-WP-0001-T01\nstatus: todo\npriority: high\n```\n\n"
|
|
"## Do the other\n\n```task\nid: DEMO-WP-0001-T02\nstatus: done\npriority: low\n```\n",
|
|
encoding="utf-8",
|
|
)
|
|
(root / "workplans" / "archived" / "260101-DEMO-WP-0002-b.md").write_text(
|
|
"---\nid: DEMO-WP-0002\ntype: workplan\ntitle: \"Second\"\nstatus: finished\n---\n\n# b\n",
|
|
encoding="utf-8",
|
|
)
|
|
(root / "workplans" / "NOTES.md").write_text(
|
|
"---\nid: NOT-A-WORKPLAN\ntype: note\n---\n\n# not a workplan\n", encoding="utf-8"
|
|
)
|
|
return root
|
|
|
|
|
|
def test_derives_workplans_tasks_and_archived_flag(tmp_path):
|
|
p = fp.derive_from_checkout(_repo(tmp_path), "demo", "abc123")
|
|
assert [w.record_id for w in p.workplans] == ["DEMO-WP-0001", "DEMO-WP-0002"]
|
|
first, second = p.workplans
|
|
assert first.status == "active" and first.archived is False
|
|
assert second.archived is True
|
|
assert p.task_count == 2
|
|
assert [t.record_id for t in first.tasks] == ["DEMO-WP-0001-T01", "DEMO-WP-0001-T02"]
|
|
|
|
|
|
def test_ignores_files_that_are_not_workplans(tmp_path):
|
|
"""Selection is by `type: workplan`; anything else is not this hub's business."""
|
|
p = fp.derive_from_checkout(_repo(tmp_path), "demo", "abc123")
|
|
assert all(w.record_id != "NOT-A-WORKPLAN" for w in p.workplans)
|
|
|
|
|
|
def test_task_titles_fall_back_to_the_preceding_heading(tmp_path):
|
|
p = fp.derive_from_checkout(_repo(tmp_path), "demo", "abc123")
|
|
titles = [t.title for t in p.workplans[0].tasks]
|
|
assert titles == ["Do the thing", "Do the other"]
|
|
|
|
|
|
def test_identifiers_are_derived_not_read_from_the_file(tmp_path):
|
|
"""A forge projection must not inherit whatever id a file happens to carry."""
|
|
root = _repo(tmp_path)
|
|
f = root / "workplans" / "DEMO-WP-0001-a.md"
|
|
f.write_text(
|
|
f.read_text(encoding="utf-8").replace(
|
|
"status: active",
|
|
'status: active\nstate_hub_workstream_id: "00000000-0000-4000-8000-000000000000"',
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
p = fp.derive_from_checkout(root, "demo", "abc123")
|
|
assert p.workplans[0].uuid == fp.derived_record_uuid("DEMO-WP-0001")
|
|
assert p.workplans[0].uuid != "00000000-0000-4000-8000-000000000000"
|
|
|
|
|
|
def test_same_input_yields_identical_output(tmp_path):
|
|
root = _repo(tmp_path)
|
|
assert fp.derive_from_checkout(root, "demo", "abc").to_dict() == \
|
|
fp.derive_from_checkout(root, "demo", "abc").to_dict()
|
|
|
|
|
|
def test_missing_workplans_directory_is_empty_not_an_error(tmp_path):
|
|
(tmp_path / "bare").mkdir()
|
|
p = fp.derive_from_checkout(tmp_path / "bare", "bare", "abc")
|
|
assert p.workplans == [] and p.commit == "abc"
|
|
|
|
|
|
def test_clone_failure_is_reported_not_swallowed(monkeypatch):
|
|
def boom(*a, **k):
|
|
raise fp.ForgeDeriveError("repository not found")
|
|
monkeypatch.setattr(fp, "_run_git", boom)
|
|
with pytest.raises(fp.ForgeDeriveError, match="not found"):
|
|
fp.derive_from_forge("nope")
|
|
|
|
|
|
class TestReset:
|
|
"""Applying the reset (STATE-WP-0083-T03).
|
|
|
|
The properties worth guarding are the refusals, not the happy path. A reset
|
|
that quietly retires a record someone still needs is worse than one that
|
|
does nothing.
|
|
"""
|
|
|
|
@staticmethod
|
|
def _derived(*records):
|
|
wps = []
|
|
for rid, status, path in records:
|
|
wps.append(
|
|
fp.DerivedWorkplan(
|
|
record_id=rid, uuid=fp.derived_record_uuid(rid), title=rid,
|
|
status=status, relative_path=path, archived=False, tasks=[],
|
|
)
|
|
)
|
|
return fp.DerivedProjection(repo_slug="demo", commit="c0ffee", workplans=wps)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_refuses_retirement_unless_acknowledged(self, monkeypatch):
|
|
"""A record that stops deriving may mean a deleted file — or a wrong branch."""
|
|
session = _FakeSession(
|
|
repo=_Repo(),
|
|
rows=[_Row(slug="demo-wp-0001", status="active", path="workplans/a.md")],
|
|
)
|
|
out = await fp.reset_repository_projection(
|
|
session, "demo", derived=self._derived() # forge has nothing
|
|
)
|
|
assert out.status == "refused"
|
|
assert out.refused and out.refused[0]["slug"] == "demo-wp-0001"
|
|
assert out.retired == []
|
|
assert session.committed is False
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_retires_when_acknowledged(self):
|
|
row = _Row(slug="demo-wp-0001", status="active", path="workplans/a.md")
|
|
session = _FakeSession(repo=_Repo(), rows=[row])
|
|
out = await fp.reset_repository_projection(
|
|
session, "demo", derived=self._derived(), acknowledge_retirements=True
|
|
)
|
|
assert out.status == "applied" and out.retired == ["demo-wp-0001"]
|
|
assert row.projection_retired_at is not None
|
|
assert row.projection_retired_reason == fp.RETIRE_REASON
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_retirement_is_not_deletion(self):
|
|
"""Hub-native records reference workplans with RESTRICT; the row survives."""
|
|
row = _Row(slug="demo-wp-0001", status="active", path="workplans/a.md")
|
|
session = _FakeSession(repo=_Repo(), rows=[row])
|
|
await fp.reset_repository_projection(
|
|
session, "demo", derived=self._derived(), acknowledge_retirements=True
|
|
)
|
|
assert row in session.rows
|
|
assert session.deleted == []
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_unretires_a_record_that_derives_again(self):
|
|
from datetime import datetime, timezone
|
|
row = _Row(slug="demo-wp-0001", status="active", path="workplans/a.md")
|
|
row.projection_retired_at = datetime.now(tz=timezone.utc)
|
|
row.projection_retired_reason = fp.RETIRE_REASON
|
|
session = _FakeSession(repo=_Repo(), rows=[row])
|
|
out = await fp.reset_repository_projection(
|
|
session, "demo",
|
|
derived=self._derived(("DEMO-WP-0001", "active", "workplans/a.md")),
|
|
)
|
|
assert out.status == "applied"
|
|
assert row.projection_retired_at is None
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_unregistered_repository_is_refused_not_created(self):
|
|
session = _FakeSession(repo=None, rows=[])
|
|
out = await fp.reset_repository_projection(session, "nope", derived=self._derived())
|
|
assert out.status == "refused"
|
|
assert "not registered" in out.refused[0]["reason"]
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_records_the_commit_it_derived_from(self):
|
|
row = _Row(slug="demo-wp-0001", status="proposed", path="workplans/a.md")
|
|
session = _FakeSession(repo=_Repo(), rows=[row])
|
|
await fp.reset_repository_projection(
|
|
session, "demo",
|
|
derived=self._derived(("DEMO-WP-0001", "active", "workplans/a.md")),
|
|
)
|
|
assert row.derived_from_commit == "c0ffee"
|
|
assert row.status == "active"
|
|
|
|
|
|
class _Repo:
|
|
def __init__(self):
|
|
import uuid as _u
|
|
self.id = _u.uuid4()
|
|
self.topic_id = None
|
|
|
|
|
|
class _Row:
|
|
def __init__(self, slug, status, path):
|
|
import uuid as _u
|
|
self.id = _u.uuid4()
|
|
self.slug = slug
|
|
self.status = status
|
|
self.backing_relative_path = path
|
|
self.backing_filename = path.rsplit("/", 1)[-1]
|
|
self.backing_archived = False
|
|
self.projection_retired_at = None
|
|
self.projection_retired_reason = None
|
|
self.derived_from_commit = None
|
|
|
|
|
|
class _FakeSession:
|
|
"""Stands in for AsyncSession: enough to prove intent without a database."""
|
|
|
|
def __init__(self, repo, rows, foreign=None):
|
|
self._repo = repo
|
|
self.rows = list(rows)
|
|
self._foreign = list(foreign or [])
|
|
self.added = []
|
|
self.deleted = []
|
|
self.committed = False
|
|
self._calls = 0
|
|
|
|
async def execute(self, *_a, **_k):
|
|
self._calls += 1
|
|
repo, rows = self._repo, self.rows
|
|
# 1st call resolves the repo, 2nd loads its workplans, 3rd is the
|
|
# foreign-identifier lookup.
|
|
payload = rows if self._calls == 2 else self._foreign
|
|
|
|
class R:
|
|
def scalar_one_or_none(self_inner):
|
|
return repo
|
|
|
|
def scalars(self_inner):
|
|
return iter(payload)
|
|
|
|
return R()
|
|
|
|
def add(self, obj):
|
|
self.added.append(obj)
|
|
|
|
def delete(self, obj):
|
|
self.deleted.append(obj)
|
|
|
|
async def flush(self):
|
|
return None
|
|
|
|
async def commit(self):
|
|
self.committed = True
|
|
|
|
|
|
class TestCollisionRefusal:
|
|
"""A colliding identifier must refuse, not raise (STATE-WP-0083-T03).
|
|
|
|
net-kingdom's ADHOC-2026-08-23 derives to an identifier another repository
|
|
already holds — the case CUST-WP-0066 documents. The reset previously failed
|
|
on a database constraint, which tells the caller nothing they can act on.
|
|
"""
|
|
|
|
@staticmethod
|
|
def _derived(rid="DEMO-WP-0001"):
|
|
return fp.DerivedProjection(
|
|
repo_slug="demo", commit="c0ffee",
|
|
workplans=[fp.DerivedWorkplan(
|
|
record_id=rid, uuid=fp.derived_record_uuid(rid), title=rid,
|
|
status="active", relative_path="workplans/a.md",
|
|
archived=False, tasks=[])],
|
|
)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_refuses_when_the_identifier_belongs_elsewhere(self):
|
|
derived = self._derived()
|
|
foreign = _Row(slug="held-by-someone-else", status="finished", path="workplans/x.md")
|
|
foreign.id = __import__("uuid").UUID(derived.workplans[0].uuid)
|
|
session = _FakeSession(repo=_Repo(), rows=[], foreign=[foreign])
|
|
out = await fp.reset_repository_projection(session, "demo", derived=derived)
|
|
assert out.status == "refused"
|
|
assert out.refused[0]["reason"].startswith("derived identifier already belongs")
|
|
assert out.refused[0]["held_by_slug"] == "held-by-someone-else"
|
|
assert out.created == [] and session.added == []
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_acknowledging_retirements_does_not_authorise_a_collision(self):
|
|
"""Different decision, different authorisation."""
|
|
derived = self._derived()
|
|
foreign = _Row(slug="held-by-someone-else", status="finished", path="workplans/x.md")
|
|
foreign.id = __import__("uuid").UUID(derived.workplans[0].uuid)
|
|
session = _FakeSession(repo=_Repo(), rows=[], foreign=[foreign])
|
|
out = await fp.reset_repository_projection(
|
|
session, "demo", derived=derived, acknowledge_retirements=True
|
|
)
|
|
assert out.status == "refused"
|
|
assert session.added == []
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_no_collision_still_creates(self):
|
|
derived = self._derived()
|
|
session = _FakeSession(repo=_Repo(), rows=[], foreign=[])
|
|
out = await fp.reset_repository_projection(session, "demo", derived=derived)
|
|
assert out.status == "applied" and out.created == ["DEMO-WP-0001"]
|