state-hub/tests/test_forge_projection.py
tegwick 6c1262ef6e
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 26s
fix(projection): refuse slug collisions, and record the first fleet pass
The identifier refusal checked id only. slug carries its own unique constraint
across the whole table, so two repositories can derive different identifiers
whose slugs still collide — which left disaster-control raising IntegrityError.

First fleet-wide pass over 121 repositories: 91 applied (737 updated, 8
created), 16 refused covering 64 records, 12 errored. 745 workplans now carry
the commit they derived from, satisfying ADR-012 decision 2 for the first time.

64 is the measured size of the stale-row problem CUST-WP-0068-T09 has waited on.

Eleven of the twelve errors are private repositories the pod cannot clone
anonymously — a real limit on "the forge is the projection source", since their
absence currently looks like an error rather than a policy.

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
2026-08-26 16:39:26 +02:00

419 lines
16 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, slug_clash=None):
self._repo = repo
self.rows = list(rows)
self._foreign = list(foreign or [])
self._slug_clash = list(slug_clash 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.
# 1 resolves the repo, 2 loads its workplans, 3 is the identifier
# lookup, 4 the slug lookup.
if self._calls == 2:
payload = rows
elif self._calls == 3:
payload = self._foreign
elif self._calls == 4:
payload = self._slug_clash
else:
payload = []
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
async def rollback(self):
self.rolled_back = 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"]
class TestFleetReset:
"""The fleet form must be a loop over the repository form (T04).
Its value is entirely in what it does with failure: a wide reset that stops
at the first refusal is one nobody can run, because there is always one
unresolved repository somewhere.
"""
class _Factory:
def __init__(self, sessions):
self._sessions = list(sessions)
def __call__(self):
session = self._sessions.pop(0)
class Ctx:
async def __aenter__(self_inner):
return session
async def __aexit__(self_inner, *a):
return False
return Ctx()
@staticmethod
def _derived(rid):
return fp.DerivedProjection(
repo_slug="x", 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_a_refusal_does_not_stop_the_pass(self, monkeypatch):
calls = []
async def fake(session, slug, **kw):
calls.append(slug)
out = fp.ResetOutcome(repo_slug=slug, commit="c0ffee", status="applied")
if slug == "bad":
out.status = "refused"
out.refused.append({"reason": "would be retired", "slug": "x"})
else:
out.updated.append("A-WP-0001")
return out
monkeypatch.setattr(fp, "reset_repository_projection", fake)
s = [_FakeSession(repo=_Repo(), rows=[]) for _ in range(3)]
res = await fp.reset_fleet_projection(self._Factory(s), ["good", "bad", "also-good"])
assert calls == ["good", "bad", "also-good"]
d = res.to_dict()
assert d["by_status"] == {"applied": 2, "refused": 1}
assert d["totals"]["updated"] == 2
@pytest.mark.asyncio
async def test_an_error_does_not_stop_the_pass(self, monkeypatch):
async def fake(session, slug, **kw):
if slug == "boom":
raise RuntimeError("clone failed")
return fp.ResetOutcome(repo_slug=slug, commit="c", status="noop")
monkeypatch.setattr(fp, "reset_repository_projection", fake)
s = [_FakeSession(repo=_Repo(), rows=[]) for _ in range(3)]
res = await fp.reset_fleet_projection(self._Factory(s), ["a", "boom", "b"])
d = res.to_dict()
assert d["errored"] == 1 and "clone failed" in res.errors["boom"]
assert set(res.results) == {"a", "b"}
@pytest.mark.asyncio
async def test_only_applied_repositories_are_committed(self, monkeypatch):
async def fake(session, slug, **kw):
out = fp.ResetOutcome(repo_slug=slug, commit="c", status="applied")
if slug == "refuser":
out.status = "refused"
else:
out.updated.append("A-WP-0001")
return out
monkeypatch.setattr(fp, "reset_repository_projection", fake)
s = [_FakeSession(repo=_Repo(), rows=[]) for _ in range(2)]
await fp.reset_fleet_projection(self._Factory(s), ["applier", "refuser"])
assert s[0].committed is True
assert s[1].committed is False
class TestSlugCollisionRefusal:
"""slug carries its own unique constraint (STATE-WP-0083-T04).
Checking the identifier alone left disaster-control raising IntegrityError:
two repositories can derive different identifiers whose slugs still collide.
"""
@pytest.mark.asyncio
async def test_refuses_when_the_slug_belongs_elsewhere(self):
derived = fp.DerivedProjection(
repo_slug="demo", commit="c0ffee",
workplans=[fp.DerivedWorkplan(
record_id="REPO-WP-0001", uuid=fp.derived_record_uuid("REPO-WP-0001"),
title="x", status="active", relative_path="workplans/a.md",
archived=False, tasks=[])])
clash = _Row(slug="repo-wp-0001", status="finished", path="workplans/other.md")
session = _FakeSession(repo=_Repo(), rows=[], foreign=[], slug_clash=[clash])
out = await fp.reset_repository_projection(session, "demo", derived=derived)
assert out.status == "refused"
assert out.refused[0]["reason"].startswith("slug already belongs")
assert session.added == []