Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a053ff-1d6f-7fe2-ac1c-a6eb40a42a0c
1280 lines
52 KiB
Python
1280 lines
52 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_credential as fc
|
|
|
|
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, title=None):
|
|
import uuid as _u
|
|
self.id = _u.uuid4()
|
|
self.slug = slug
|
|
self.status = status
|
|
# The title is a derived field the reset syncs; the fixture carried no
|
|
# `title` at all, so it could not have caught the drift that left
|
|
# cust-wp-0010 describing another workplan's work.
|
|
self.title = title
|
|
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,
|
|
task_rows=None,
|
|
task_collision_rows=None,
|
|
):
|
|
self._repo = repo
|
|
self.rows = list(rows)
|
|
self._foreign = list(foreign or [])
|
|
self._slug_clash = list(slug_clash or [])
|
|
self._task_rows = task_rows
|
|
self._task_collision_rows = list(task_collision_rows 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
|
|
# 1 resolves the repo, 2 loads its workplans, 3 is the identifier
|
|
# lookup, 4 the slug lookup. On the update-only path (no creates)
|
|
# the 3rd call is the existing-task load instead.
|
|
update_only = (
|
|
self._task_rows is not None
|
|
and not self._foreign
|
|
and not self._slug_clash
|
|
)
|
|
if self._calls == 2:
|
|
payload = rows
|
|
elif update_only and self._calls == 3:
|
|
payload = self._task_rows
|
|
elif update_only and self._calls == 4:
|
|
payload = self._task_collision_rows
|
|
elif self._calls == 3:
|
|
payload = self._foreign
|
|
elif self._calls == 4:
|
|
payload = self._slug_clash
|
|
elif self._calls == 5:
|
|
payload = self._task_collision_rows
|
|
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 == []
|
|
|
|
|
|
class TestUnreadableIsNotMissing:
|
|
"""STATE-WP-0084-T01.
|
|
|
|
A repository central is not permitted to read and a repository whose
|
|
records no longer derive authorise opposite things. Every test here exists
|
|
to keep the retirement path unreachable from an answer that cannot support
|
|
it — by construction, not by the reset happening to fail first.
|
|
"""
|
|
|
|
@pytest.mark.parametrize(
|
|
"stderr",
|
|
[
|
|
"fatal: could not read Username for 'https://forgejo.coulomb.social'",
|
|
"remote: Invalid username or password.\nfatal: Authentication failed",
|
|
"fatal: could not read Username for 'https://f': terminal prompts disabled",
|
|
"fatal: repository 'https://forgejo.coulomb.social/rapp-openbao.git' not found",
|
|
"fatal: unable to access '...': The requested URL returned error: 403",
|
|
],
|
|
)
|
|
def test_permission_shaped_failures_are_classified_unreadable(self, stderr, monkeypatch):
|
|
def boom(*a, **k):
|
|
raise fp.ForgeDeriveError(stderr)
|
|
|
|
monkeypatch.setattr(fp, "_run_git", boom)
|
|
with pytest.raises(fp.ForgeUnreadableError):
|
|
fp.derive_from_forge("rapp-openbao")
|
|
|
|
def test_a_genuine_fault_stays_a_plain_error(self, monkeypatch):
|
|
def boom(*a, **k):
|
|
raise fp.ForgeDeriveError("fatal: early EOF\nfatal: index-pack failed")
|
|
|
|
monkeypatch.setattr(fp, "_run_git", boom)
|
|
with pytest.raises(fp.ForgeDeriveError) as exc:
|
|
fp.derive_from_forge("demo")
|
|
assert not isinstance(exc.value, fp.ForgeUnreadableError)
|
|
|
|
def test_unreadable_is_a_derive_error_so_old_callers_still_catch_it(self):
|
|
assert issubclass(fp.ForgeUnreadableError, fp.ForgeDeriveError)
|
|
|
|
def test_a_checkout_without_workplans_cannot_evidence_absence(self, tmp_path):
|
|
(tmp_path / "bare").mkdir()
|
|
p = fp.derive_from_checkout(tmp_path / "bare", "bare", "abc")
|
|
assert p.workplans == []
|
|
assert p.records_source_present is False
|
|
assert p.retirement_eligible is False
|
|
|
|
def test_a_real_checkout_can(self, tmp_path):
|
|
p = fp.derive_from_checkout(_repo(tmp_path), "demo", "abc")
|
|
assert p.records_source_present is True
|
|
assert p.retirement_eligible is True
|
|
|
|
def test_the_diff_withholds_stale_rather_than_computing_it(self):
|
|
derived = fp.DerivedProjection(
|
|
repo_slug="demo", commit="c0ffee", records_source_present=False
|
|
)
|
|
hub = [{"id": "11111111-1111-1111-1111-111111111111", "slug": "demo-wp-0001",
|
|
"status": "active", "backing_relative_path": "workplans/a.md"}]
|
|
d = fp.diff_against_hub(derived, hub, {})
|
|
assert d.stale == []
|
|
assert d.would_remove == 0
|
|
assert d.stale_withheld
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_an_empty_source_cannot_retire_even_when_acknowledged(self):
|
|
derived = fp.DerivedProjection(
|
|
repo_slug="demo", commit="c0ffee", records_source_present=False
|
|
)
|
|
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=derived, acknowledge_retirements=True
|
|
)
|
|
assert out.status == "refused"
|
|
assert out.retired == []
|
|
assert row.projection_retired_at is None
|
|
assert out.refused[0]["reason"].startswith("source produced no records")
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_an_unreadable_repository_reports_unreadable_not_error(self, monkeypatch):
|
|
def boom(*a, **k):
|
|
raise fp.ForgeUnreadableError("rapp-openbao could not be read from the forge")
|
|
|
|
monkeypatch.setattr(fp, "derive_from_forge", boom)
|
|
session = _FakeSession(repo=_Repo(), rows=[])
|
|
out = await fp.reset_repository_projection(session, "rapp-openbao")
|
|
assert out.status == "unreadable"
|
|
assert out.retired == [] and out.created == [] and out.updated == []
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_the_fleet_keeps_unreadable_out_of_the_error_bucket(self, monkeypatch):
|
|
async def fake(session, slug, **kw):
|
|
if slug == "private":
|
|
out = fp.ResetOutcome(repo_slug=slug, commit="", status="unreadable")
|
|
out.refused.append({"reason": "repository could not be read from the forge",
|
|
"slug": slug, "detail": "could not read Username"})
|
|
return out
|
|
if slug == "broken":
|
|
raise RuntimeError("index-pack failed")
|
|
out = fp.ResetOutcome(repo_slug=slug, commit="c0ffee", status="applied")
|
|
out.updated.append("A-WP-0001")
|
|
return out
|
|
|
|
monkeypatch.setattr(fp, "reset_repository_projection", fake)
|
|
sessions = [_FakeSession(repo=_Repo(), rows=[]) for _ in range(3)]
|
|
outcome = await fp.reset_fleet_projection(
|
|
TestFleetReset._Factory(sessions), ["ok", "private", "broken"]
|
|
)
|
|
assert list(outcome.unreadable) == ["private"]
|
|
assert list(outcome.errors) == ["broken"]
|
|
assert "private" not in outcome.results
|
|
d = outcome.to_dict()
|
|
assert d["unreadable_count"] == 1 and d["errored"] == 1
|
|
assert d["repositories"] == 3
|
|
|
|
|
|
class TestForgeCredential:
|
|
"""Optional forge read credential (STATE-WP-0084-T03).
|
|
|
|
Absent is a valid state: a hub with no credential must still derive every
|
|
public repository. The credential must never reach argv, a log, or an
|
|
exception — the places a secret leaks without anyone deciding to leak it.
|
|
"""
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _clear_cache(self):
|
|
"""The resolved credential is cached for 5 minutes in production.
|
|
|
|
That cache is deliberate — a fleet reset of 121 repositories must not
|
|
authenticate to OpenBao 121 times — so tests clear it rather than
|
|
disable it, and exercise the same code path production uses.
|
|
"""
|
|
fc.reset_cache()
|
|
yield
|
|
fc.reset_cache()
|
|
|
|
def test_absent_credential_is_none_not_empty_string(self, monkeypatch):
|
|
monkeypatch.delenv(fc.TOKEN_ENV, raising=False)
|
|
monkeypatch.delenv(fc.TOKEN_FILE_ENV, raising=False)
|
|
assert fc.forge_read_token() is None
|
|
|
|
def test_a_file_is_preferred_over_the_environment(self, tmp_path, monkeypatch):
|
|
"""Kubernetes rotates a mounted file without a redeploy."""
|
|
f = tmp_path / "token"
|
|
f.write_text("from-file\n", encoding="utf-8")
|
|
monkeypatch.setenv(fc.TOKEN_ENV, "from-env")
|
|
monkeypatch.setenv(fc.TOKEN_FILE_ENV, str(f))
|
|
assert fc.forge_read_token() == "from-file"
|
|
|
|
def test_an_unreadable_token_file_does_not_fall_back_silently(self, monkeypatch):
|
|
"""Falling back to a stale env value would hide a broken mount."""
|
|
monkeypatch.setenv(fc.TOKEN_FILE_ENV, "/nonexistent/token")
|
|
monkeypatch.setenv(fc.TOKEN_ENV, "from-env")
|
|
assert fc.forge_read_token() is None
|
|
|
|
def test_openbao_is_the_last_resort_not_the_first(self, tmp_path, monkeypatch):
|
|
"""A file or env value must not trigger a network call."""
|
|
f = tmp_path / "token"
|
|
f.write_text("local", encoding="utf-8")
|
|
monkeypatch.setenv(fc.TOKEN_FILE_ENV, str(f))
|
|
monkeypatch.setattr(
|
|
fc, "_from_openbao", lambda: pytest.fail("OpenBao consulted unnecessarily")
|
|
)
|
|
assert fc.forge_read_token() == "local"
|
|
|
|
def test_openbao_failure_resolves_to_absent_not_an_exception(self, monkeypatch):
|
|
"""A hub that cannot reach OpenBao must still derive public repos.
|
|
|
|
Raising here would turn "nine repositories are unreadable" into "the
|
|
whole derivation pass failed" — the outcome STATE-WP-0084-T01 exists to
|
|
prevent.
|
|
"""
|
|
monkeypatch.delenv(fc.TOKEN_FILE_ENV, raising=False)
|
|
monkeypatch.delenv(fc.TOKEN_ENV, raising=False)
|
|
monkeypatch.setenv(fc.OPENBAO_ADDR_ENV, "https://openbao.invalid")
|
|
monkeypatch.setenv(fc.SECRET_PATH_ENV, "kv/data/forge")
|
|
monkeypatch.setenv(fc.OPENBAO_ROLE_ENV, "state-hub-forge-derivation")
|
|
monkeypatch.setenv(fc.OPENBAO_JWT_PATH_ENV, "/nonexistent/sa-token")
|
|
assert fc.forge_read_token() is None
|
|
|
|
def test_openbao_unwraps_kv_v2(self, tmp_path, monkeypatch):
|
|
jwt = tmp_path / "sa"
|
|
jwt.write_text("jwt-value", encoding="utf-8")
|
|
monkeypatch.delenv(fc.TOKEN_FILE_ENV, raising=False)
|
|
monkeypatch.delenv(fc.TOKEN_ENV, raising=False)
|
|
monkeypatch.setenv(fc.OPENBAO_ADDR_ENV, "https://openbao.test")
|
|
monkeypatch.setenv(fc.SECRET_PATH_ENV, "kv/data/forge")
|
|
monkeypatch.setenv(fc.OPENBAO_ROLE_ENV, "state-hub-forge-derivation")
|
|
monkeypatch.setenv(fc.OPENBAO_JWT_PATH_ENV, str(jwt))
|
|
|
|
class R:
|
|
def __init__(self, payload):
|
|
self._p = payload
|
|
|
|
def raise_for_status(self):
|
|
return None
|
|
|
|
def json(self):
|
|
return self._p
|
|
|
|
class C:
|
|
def __enter__(self):
|
|
return self
|
|
|
|
def __exit__(self, *a):
|
|
return False
|
|
|
|
def post(self, url, json):
|
|
assert json["jwt"] == "jwt-value"
|
|
assert json["role"] == "state-hub-forge-derivation"
|
|
return R({"auth": {"client_token": "bao-token"}})
|
|
|
|
def get(self, url, headers):
|
|
assert headers["X-Vault-Token"] == "bao-token"
|
|
return R({"data": {"data": {"token": "forge-secret"}}})
|
|
|
|
monkeypatch.setattr(fc.httpx, "Client", lambda **kw: C())
|
|
assert fc.forge_read_token() == "forge-secret"
|
|
|
|
def test_credential_never_appears_in_argv(self, monkeypatch):
|
|
"""`-c http.extraHeader=` would put the token in every ps listing."""
|
|
seen = {}
|
|
|
|
class P:
|
|
returncode = 0
|
|
stdout = ""
|
|
stderr = ""
|
|
|
|
def fake_run(cmd, **kw):
|
|
seen["cmd"] = cmd
|
|
seen["env"] = kw.get("env") or {}
|
|
return P()
|
|
|
|
monkeypatch.setattr(fp.subprocess, "run", fake_run)
|
|
fp._run_git("clone", "url", "dir", token="s3cret")
|
|
assert not any("s3cret" in part for part in seen["cmd"])
|
|
assert seen["env"]["GIT_CONFIG_COUNT"] == "1"
|
|
assert "s3cret" not in seen["env"]["GIT_CONFIG_KEY_0"]
|
|
|
|
def test_credential_is_redacted_from_failures(self, monkeypatch):
|
|
class P:
|
|
returncode = 128
|
|
stdout = ""
|
|
stderr = "fatal: auth failed using s3cret"
|
|
|
|
monkeypatch.setattr(fp.subprocess, "run", lambda *a, **k: P())
|
|
with pytest.raises(fp.ForgeDeriveError) as exc:
|
|
fp._run_git("clone", token="s3cret")
|
|
assert "s3cret" not in str(exc.value) and "***" in str(exc.value)
|
|
|
|
def test_no_credential_still_runs(self, monkeypatch):
|
|
class P:
|
|
returncode = 0
|
|
stdout = "ok"
|
|
stderr = ""
|
|
seen = {}
|
|
|
|
def fake_run(cmd, **kw):
|
|
seen["env"] = kw.get("env") or {}
|
|
return P()
|
|
|
|
monkeypatch.setattr(fp.subprocess, "run", fake_run)
|
|
assert fp._run_git("status") == "ok"
|
|
assert "GIT_CONFIG_COUNT" not in seen["env"]
|
|
|
|
|
|
class TestRekeyIsNotRename:
|
|
"""A changed identifier is a new record, not a moved file.
|
|
|
|
The ad-hoc requalification (CUST-WP-0066) keeps the filename by design, so
|
|
for those records a re-key never changes the path. Matching on path there
|
|
cannot distinguish the two cases, and choosing rename updates a row whose
|
|
UUID still encodes the old identifier — leaving the file and the hub
|
|
disagreeing about what the record is called.
|
|
"""
|
|
|
|
class _Row:
|
|
def __init__(self, id, slug, path):
|
|
self.id = id
|
|
self.slug = slug
|
|
self.backing_relative_path = path
|
|
self.projection_retired_at = None
|
|
self.status = "finished"
|
|
|
|
def test_a_derived_row_is_not_matched_by_path(self):
|
|
"""uuid5 means identity is a function of the identifier."""
|
|
import uuid as _u
|
|
old = self._Row(
|
|
_u.UUID(fp.derived_record_uuid("ADHOC-2026-08-25")),
|
|
"adhoc-2026-08-25",
|
|
"workplans/ADHOC-2026-08-25.md",
|
|
)
|
|
assert fp._identity_is_derived(old)
|
|
|
|
def test_a_legacy_row_keeps_path_matching(self):
|
|
"""v4 rows predate derived identity; there the identifier is a label."""
|
|
import uuid as _u
|
|
legacy = self._Row(_u.uuid4(), "repo-wp-0001", "workplans/REPO-WP-0001.md")
|
|
assert not fp._identity_is_derived(legacy)
|
|
|
|
def test_the_two_uuids_actually_differ(self):
|
|
"""Guards the premise: if they were equal the distinction is moot."""
|
|
assert fp.derived_record_uuid("ADHOC-2026-08-25") != fp.derived_record_uuid(
|
|
"CUST-WP-ADHOC-2026-08-25"
|
|
)
|
|
|
|
@pytest.mark.parametrize(
|
|
"slug",
|
|
[
|
|
"adhoc-2026-07-02", # grandfathered daily ad-hoc
|
|
"cust-wp-adhoc-2026-08-25", # qualified daily ad-hoc
|
|
"net-kingdom-adhoc-2026-07-02", # repo-name-qualified legacy form
|
|
"adhoc-llmc-2026-06-02", # another legacy qualification
|
|
"rcluster-wp-0007", # ordinary workplan
|
|
"cust-wp-0066-t01", # task
|
|
],
|
|
)
|
|
def test_identifier_slugs_are_recognised(self, slug):
|
|
"""A slug that claims to be an identifier is one, whatever its UUID.
|
|
|
|
25 of 44 ad-hoc rows are legacy v4, so a UUID-version test alone would
|
|
leave them path-matched and silently diverged from their files.
|
|
"""
|
|
assert fp._slug_is_identifier(slug)
|
|
|
|
@pytest.mark.parametrize(
|
|
"slug",
|
|
["three-phoenix-ha-cluster", "testdrive-jsui-publication", "state-hub-v0.1", ""],
|
|
)
|
|
def test_title_slugs_keep_path_matching(self, slug):
|
|
"""Hub-first rows never claimed an identifier; the path is their only link."""
|
|
assert not fp._slug_is_identifier(slug)
|
|
|
|
|
|
class TestRetirementReleasesTheIdentifier:
|
|
"""`slug` is unique table-wide, so retirement must free it.
|
|
|
|
Retirement that only sets a timestamp leaves the identifier locked to a
|
|
record nothing derives, and the repository that owns it can never claim it.
|
|
"""
|
|
|
|
def test_the_identifier_is_released(self):
|
|
from datetime import datetime, timezone
|
|
when = datetime(2026, 8, 28, tzinfo=timezone.utc)
|
|
assert fp._tombstone_slug("repo-wp-0001", when) == "repo-wp-0001@retired-20260828"
|
|
|
|
def test_re_retiring_does_not_stack_marks(self):
|
|
"""Otherwise the 100-char column overflows after a few passes."""
|
|
from datetime import datetime, timezone
|
|
a = fp._tombstone_slug("repo-wp-0001", datetime(2026, 8, 28, tzinfo=timezone.utc))
|
|
b = fp._tombstone_slug(a, datetime(2026, 9, 1, tzinfo=timezone.utc))
|
|
assert b == "repo-wp-0001@retired-20260901"
|
|
assert b.count(fp.RETIRED_SLUG_MARK) == 1
|
|
|
|
def test_a_long_slug_stays_within_the_column(self):
|
|
from datetime import datetime, timezone
|
|
out = fp._tombstone_slug("x" * 140, datetime(2026, 8, 28, tzinfo=timezone.utc))
|
|
assert len(out) <= 100
|
|
|
|
class _Retired:
|
|
def __init__(self, slug, when, id=None):
|
|
import uuid as _u
|
|
self.id = id or _u.uuid4()
|
|
self.slug = slug
|
|
self.projection_retired_at = when
|
|
self.projection_retired_reason = "x"
|
|
self.backing_relative_path = None
|
|
self.status = "finished"
|
|
|
|
def test_rows_retired_before_the_stamp_existed_are_repaired(self):
|
|
"""`stale` excludes already-retired rows, so nothing else revisits them.
|
|
|
|
The 32 rows retired earlier today still hold their identifiers; without
|
|
this they would block the owning repository forever.
|
|
"""
|
|
from datetime import datetime, timezone
|
|
when = datetime(2026, 8, 28, tzinfo=timezone.utc)
|
|
row = self._Retired("repo-wp-0001", when)
|
|
assert fp.RETIRED_SLUG_MARK not in row.slug
|
|
row.slug = fp._tombstone_slug(row.slug, row.projection_retired_at)
|
|
assert row.slug == "repo-wp-0001@retired-20260828"
|
|
|
|
def test_an_already_stamped_row_is_left_alone(self):
|
|
"""Otherwise every reset rewrites the date and churns the row."""
|
|
from datetime import datetime, timezone
|
|
row = self._Retired(
|
|
"repo-wp-0001@retired-20260828", datetime(2026, 8, 28, tzinfo=timezone.utc)
|
|
)
|
|
assert fp.RETIRED_SLUG_MARK in row.slug
|
|
|
|
def test_a_release_is_committed_even_when_the_repo_refuses(self):
|
|
"""The fleet driver commits only on `applied` and rolls back otherwise.
|
|
|
|
Moving the release ahead of the refusal returns achieves nothing if the
|
|
rollback then discards it — which is the state railiance-cluster and
|
|
inter-hub were in.
|
|
"""
|
|
import inspect
|
|
src = inspect.getsource(fp.reset_fleet_projection)
|
|
assert 'result.status == "applied" or result.released' in src
|
|
|
|
|
|
class TestUuidMatchWins:
|
|
"""A row whose UUID is uuid5 of the identifier IS that record.
|
|
|
|
`testdrive-jsui-publication` and `three-phoenix-ha-cluster` carry legacy
|
|
title slugs while their UUIDs are derived from MARKITECT-WP-0002 and
|
|
RCLUSTER-WP-0007. The slug-identifier rule alone reads them as re-keys and
|
|
proposes retiring records that were correct all along.
|
|
"""
|
|
|
|
def test_a_title_slugged_row_is_still_its_derived_record(self):
|
|
import uuid as _u
|
|
rid = "MARKITECT-WP-0002"
|
|
row_id = _u.UUID(fp.derived_record_uuid(rid))
|
|
# The slug says one thing, the UUID says another; the UUID is identity.
|
|
assert not fp._slug_is_identifier("testdrive-jsui-publication")
|
|
assert str(row_id) == fp.derived_record_uuid(rid)
|
|
|
|
def test_uuid_match_is_checked_before_the_rekey_rules(self):
|
|
import inspect
|
|
src = inspect.getsource(fp.reset_repository_projection)
|
|
uuid_at = src.index("want_by_uuid.get(str(row.id))")
|
|
rekey_at = src.index("_identity_is_derived(row) or _slug_is_identifier")
|
|
assert uuid_at < rekey_at, "UUID match must precede the re-key heuristics"
|
|
|
|
|
|
class TestTaskIdentityIsQualified:
|
|
"""A bare `T01` is unique within its workplan, not in the fleet.
|
|
|
|
Unqualified, `uuid5("T01")` is the same UUID for every workplan that has a
|
|
T01 — llm-connect's 91 task blocks derive 49 distinct UUIDs, and creating
|
|
its workplans dies on `duplicate key value violates unique constraint
|
|
"tasks_pkey"`.
|
|
"""
|
|
|
|
BODY = """
|
|
```task
|
|
id: T01
|
|
status: done
|
|
```
|
|
|
|
```task
|
|
id: T02
|
|
status: todo
|
|
```
|
|
"""
|
|
|
|
def test_short_ids_are_qualified_by_their_workplan(self):
|
|
a = fp._parse_tasks(self.BODY, "LLM-WP-0001")
|
|
b = fp._parse_tasks(self.BODY, "LLM-WP-0002")
|
|
assert [t.record_id for t in a] == ["LLM-WP-0001-T01", "LLM-WP-0001-T02"]
|
|
assert {t.uuid for t in a}.isdisjoint({t.uuid for t in b})
|
|
|
|
def test_an_already_qualified_id_is_left_alone(self):
|
|
body = "```task\nid: LLM-WP-0009-T03\nstatus: todo\n```"
|
|
assert fp._parse_tasks(body, "LLM-WP-0001")[0].record_id == "LLM-WP-0009-T03"
|
|
|
|
def test_it_agrees_with_the_backfill(self):
|
|
"""Both must apply the same rule or they disagree on a task's name."""
|
|
from api.services.task_record_id_backfill import qualify_task_id
|
|
got = fp._parse_tasks(self.BODY, "LLM-WP-0001")[0].record_id
|
|
assert got == qualify_task_id("T01", "LLM-WP-0001")
|
|
|
|
def test_uuid_follows_the_qualified_id(self):
|
|
t = fp._parse_tasks(self.BODY, "LLM-WP-0001")[0]
|
|
assert t.uuid == fp.derived_record_uuid("LLM-WP-0001-T01")
|
|
|
|
|
|
class TestRetirementIsNotUndoneByPath:
|
|
"""A retired row is matched by UUID or not at all.
|
|
|
|
Releasing the identifier removes the evidence that made the row a re-key:
|
|
the tombstoned slug is no longer an identifier, and a legacy row is not
|
|
UUID-derived, so both guards fall through to path matching — which matches
|
|
the very file the row was retired for and resurrects it beside the correct
|
|
record created from that same file.
|
|
"""
|
|
|
|
def test_a_tombstoned_slug_is_not_an_identifier(self):
|
|
"""This is the signal the tombstone destroys."""
|
|
assert fp._slug_is_identifier("adhoc-2026-06-01")
|
|
assert not fp._slug_is_identifier("adhoc-2026-06-01@retired-20260827")
|
|
|
|
def test_retired_rows_are_skipped_before_path_matching(self):
|
|
import inspect
|
|
src = inspect.getsource(fp.reset_repository_projection)
|
|
skip_at = src.index("if row.projection_retired_at is not None:")
|
|
path_at = src.index("if bp and _path_key(bp) in want_paths:")
|
|
assert skip_at < path_at, "retired rows must be skipped before path matching"
|
|
|
|
def test_uuid_match_still_precedes_the_skip(self):
|
|
"""A record that genuinely returns must still be un-retired."""
|
|
import inspect
|
|
src = inspect.getsource(fp.reset_repository_projection)
|
|
assert src.index("want_by_uuid.get(str(row.id))") < src.index(
|
|
"if row.projection_retired_at is not None:"
|
|
)
|
|
|
|
def test_a_row_that_derives_again_is_not_tombstoned(self):
|
|
"""Releasing it would change the slug out from under the matching.
|
|
|
|
The row would then fail to match its own file and never be un-retired —
|
|
which is how five rows ended up tombstoned but not retired.
|
|
"""
|
|
import inspect
|
|
src = inspect.getsource(fp.reset_repository_projection)
|
|
rel = src.index("outcome.released.append(before)")
|
|
guard = src.index('in want or str(r.id) in want_by_uuid')
|
|
assert guard < rel, "the release must exempt records the forge still derives"
|
|
|
|
def test_a_stamped_slug_counts_as_retired_even_if_the_flag_is_clear(self):
|
|
"""Otherwise a resurrected row path-matches its file and reports noop forever."""
|
|
import inspect
|
|
src = inspect.getsource(fp.reset_repository_projection)
|
|
assert "projection_retired_at is None and RETIRED_SLUG_MARK in" in src
|
|
|
|
def test_a_resurrected_row_becomes_stale_rather_than_being_skipped(self):
|
|
"""Skipping it outright is what left six rows reporting noop forever.
|
|
|
|
`stale` is computed from `matched`, so a row that never enters `matched`
|
|
is invisible: not retired, so not stale; path-matching its own file, so
|
|
never reported.
|
|
"""
|
|
import inspect
|
|
src = inspect.getsource(fp.reset_repository_projection)
|
|
zombie = src.index("projection_retired_at is None and RETIRED_SLUG_MARK in")
|
|
tail = src[zombie:zombie + 700]
|
|
assert "matched[key] = row" in tail, "a resurrected row must enter `matched`"
|
|
|
|
|
|
class TestCompetingClaims:
|
|
"""Two rows can claim one record; the weaker must not vanish.
|
|
|
|
`cust-wp-0010` claimed CUST-WP-0010 by its own identifier while
|
|
`workstream-lifecycle-documentation` claimed it by path. Assigning into
|
|
`matched` unconditionally let the later one win and dropped the other
|
|
silently — never matched, so never stale, so never reported by any pass.
|
|
"""
|
|
|
|
def test_claims_are_ranked_not_overwritten(self):
|
|
import inspect
|
|
src = inspect.getsource(fp.reset_repository_projection)
|
|
assert "def _claim(" in src
|
|
assert "displaced.append" in src
|
|
|
|
def test_a_displaced_row_becomes_stale(self):
|
|
import inspect
|
|
src = inspect.getsource(fp.reset_repository_projection)
|
|
stale_at = src.index("stale = [")
|
|
assert "displaced" in src[stale_at:stale_at + 400]
|
|
|
|
def test_identifier_beats_path(self):
|
|
"""A row naming the record outranks one that merely shares its file."""
|
|
import inspect
|
|
src = inspect.getsource(fp.reset_repository_projection)
|
|
assert src.index("UUID_MATCH, SLUG_MATCH, PATH_MATCH, PREFIX_MATCH = 0, 1, 2, 3") > 0
|
|
# Strength is ordered strongest-first, so a lower number wins.
|
|
assert "if strength < held:" in src
|
|
|
|
|
|
class TestTitleIsDerivedToo:
|
|
"""The title is a derived field and must follow the file.
|
|
|
|
Not syncing it left `cust-wp-0010` correctly identified and correctly backed
|
|
while describing a different workplan's work.
|
|
"""
|
|
|
|
def test_title_is_synced(self):
|
|
import inspect
|
|
src = inspect.getsource(fp.reset_repository_projection)
|
|
assert "row.title = w.title.strip()" in src
|
|
|
|
def test_an_empty_derived_title_does_not_blank_a_real_one(self):
|
|
"""Three activity-core files parse to no title; blanking is worse than stale."""
|
|
import inspect
|
|
src = inspect.getsource(fp.reset_repository_projection)
|
|
i = src.index("row.title = w.title.strip()")
|
|
guard = src[max(0, i - 200):i]
|
|
assert "w.title and w.title.strip()" in guard
|
|
|
|
|
|
class TestTitleSyncBehaviour:
|
|
"""Exercised against the fake session rather than asserted on source."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_a_drifted_title_is_corrected(self):
|
|
row = _Row(slug="demo-wp-0001", status="active",
|
|
path="workplans/a.md", title="Some Other Workplan")
|
|
session = _FakeSession(repo=_Repo(), rows=[row])
|
|
out = await fp.reset_repository_projection(
|
|
session, "demo",
|
|
derived=TestReset()._derived(("DEMO-WP-0001", "active", "workplans/a.md")),
|
|
)
|
|
assert out.status == "applied"
|
|
assert row.title != "Some Other Workplan"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_an_empty_derived_title_leaves_the_existing_one(self):
|
|
row = _Row(slug="demo-wp-0001", status="active",
|
|
path="workplans/a.md", title="Real Title")
|
|
d = TestReset()._derived(("DEMO-WP-0001", "active", "workplans/a.md"))
|
|
d.workplans[0].title = None
|
|
session = _FakeSession(repo=_Repo(), rows=[row])
|
|
await fp.reset_repository_projection(session, "demo", derived=d)
|
|
assert row.title == "Real Title"
|
|
|
|
|
|
class _TaskRow:
|
|
def __init__(self, record_id, status="todo", title="t", workplan_id=None):
|
|
import uuid as _u
|
|
self.id = _u.uuid4()
|
|
self.record_id = record_id
|
|
self.status = status
|
|
self.title = title
|
|
self.priority = "medium"
|
|
self.workplan_id = workplan_id
|
|
|
|
|
|
class TestExistingWorkplanTasks:
|
|
"""Tasks of an existing workplan are matched by record_id (CUST-WP-0068-T09)."""
|
|
|
|
def _derived(self, *tasks):
|
|
dts = [
|
|
fp.DerivedTask(
|
|
record_id=tid,
|
|
uuid=fp.derived_record_uuid(tid),
|
|
title=title,
|
|
status=st,
|
|
priority="medium",
|
|
)
|
|
for tid, title, st in tasks
|
|
]
|
|
return fp.DerivedProjection(
|
|
repo_slug="demo",
|
|
commit="c0ffee",
|
|
workplans=[
|
|
fp.DerivedWorkplan(
|
|
record_id="DEMO-WP-0001",
|
|
uuid=fp.derived_record_uuid("DEMO-WP-0001"),
|
|
title="DEMO-WP-0001",
|
|
status="active",
|
|
relative_path="workplans/a.md",
|
|
archived=False,
|
|
tasks=dts,
|
|
)
|
|
],
|
|
)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_creates_a_missing_identified_task(self):
|
|
row = _Row(slug="demo-wp-0001", status="active", path="workplans/a.md")
|
|
session = _FakeSession(repo=_Repo(), rows=[row], task_rows=[])
|
|
out = await fp.reset_repository_projection(
|
|
session, "demo",
|
|
derived=self._derived(("DEMO-WP-0001-T01", "Do it", "todo")),
|
|
)
|
|
assert out.created_tasks == ["DEMO-WP-0001-T01"]
|
|
assert session.added
|
|
assert session.added[0].record_id == "DEMO-WP-0001-T01"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_cancels_an_open_task_the_file_no_longer_derives(self):
|
|
row = _Row(slug="demo-wp-0001", status="active", path="workplans/a.md")
|
|
stale = _TaskRow("DEMO-WP-0001-T09", status="todo", workplan_id=row.id)
|
|
session = _FakeSession(repo=_Repo(), rows=[row], task_rows=[stale])
|
|
out = await fp.reset_repository_projection(
|
|
session, "demo",
|
|
derived=self._derived(("DEMO-WP-0001-T01", "Do it", "todo")),
|
|
)
|
|
assert stale.status.value == "cancel" or stale.status == "cancel" or str(stale.status).endswith("cancel")
|
|
assert "DEMO-WP-0001-T09" in out.cancelled_tasks
|
|
assert session.deleted == []
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_sets_record_id_when_the_derived_uuid_already_exists(self):
|
|
row = _Row(slug="demo-wp-0001", status="active", path="workplans/a.md")
|
|
existing = _TaskRow(None, status="done", title="Clear the stale hub rows", workplan_id=row.id)
|
|
derived = self._derived(("DEMO-WP-0001-T01", "Clear the stale hub rows", "done"))
|
|
existing.id = __import__("uuid").UUID(derived.workplans[0].tasks[0].uuid)
|
|
session = _FakeSession(repo=_Repo(), rows=[row], task_rows=[existing])
|
|
out = await fp.reset_repository_projection(session, "demo", derived=derived)
|
|
assert out.created_tasks == []
|
|
assert existing.record_id == "DEMO-WP-0001-T01"
|
|
assert session.added == []
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_leaves_unidentified_tasks_alone(self):
|
|
row = _Row(slug="demo-wp-0001", status="active", path="workplans/a.md")
|
|
orphan = _TaskRow(None, status="todo", title="legacy", workplan_id=row.id)
|
|
session = _FakeSession(repo=_Repo(), rows=[row], task_rows=[orphan])
|
|
await fp.reset_repository_projection(
|
|
session, "demo",
|
|
derived=self._derived(("DEMO-WP-0001-T01", "Do it", "todo")),
|
|
)
|
|
assert orphan.status == "todo"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_duplicate_identity_inside_current_workplan_is_refused(self):
|
|
row = _Row(slug="demo-wp-0001", status="active", path="workplans/a.md")
|
|
first = _TaskRow("DEMO-WP-0001-T01", workplan_id=row.id)
|
|
second = _TaskRow("demo-wp-0001-t01", workplan_id=row.id)
|
|
session = _FakeSession(repo=_Repo(), rows=[row], task_rows=[first, second])
|
|
out = await fp.reset_repository_projection(
|
|
session,
|
|
"demo",
|
|
derived=self._derived(("DEMO-WP-0001-T01", "Do it", "todo")),
|
|
)
|
|
assert out.status == "refused"
|
|
assert out.refused[0]["reason"].startswith("current workplan contains duplicate")
|
|
assert session.added == []
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_duplicate_stale_history_inside_current_workplan_does_not_block(self):
|
|
row = _Row(slug="demo-wp-0001", status="active", path="workplans/a.md")
|
|
desired = _TaskRow("DEMO-WP-0001-T01", status="todo", workplan_id=row.id)
|
|
stale_a = _TaskRow("DEMO-WP-0001-T09", status="done", workplan_id=row.id)
|
|
stale_b = _TaskRow("demo-wp-0001-t09", status="done", workplan_id=row.id)
|
|
session = _FakeSession(
|
|
repo=_Repo(), rows=[row], task_rows=[desired, stale_a, stale_b]
|
|
)
|
|
out = await fp.reset_repository_projection(
|
|
session,
|
|
"demo",
|
|
derived=self._derived(("DEMO-WP-0001-T01", "Do it", "todo")),
|
|
)
|
|
assert out.status in {"applied", "noop"}
|
|
assert out.refused == []
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_derived_task_uuid_held_elsewhere_is_refused_before_insert(self):
|
|
row = _Row(slug="demo-wp-0001", status="active", path="workplans/a.md")
|
|
derived = self._derived(("DEMO-WP-0001-T01", "Do it", "todo"))
|
|
holder = _TaskRow("DEMO-WP-0001-T01", workplan_id=__import__("uuid").uuid4())
|
|
holder.id = __import__("uuid").UUID(derived.workplans[0].tasks[0].uuid)
|
|
session = _FakeSession(
|
|
repo=_Repo(),
|
|
rows=[row],
|
|
task_rows=[],
|
|
task_collision_rows=[holder],
|
|
)
|
|
out = await fp.reset_repository_projection(session, "demo", derived=derived)
|
|
assert out.status == "refused"
|
|
assert out.refused[0]["reason"].startswith("derived task identifier already belongs")
|
|
assert session.added == []
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_tasks_on_retired_copy_do_not_block_current_projection(self):
|
|
from datetime import datetime, timezone
|
|
|
|
current = _Row(slug="demo-wp-0001", status="active", path="workplans/a.md")
|
|
current.id = __import__("uuid").UUID(fp.derived_record_uuid("DEMO-WP-0001"))
|
|
retired = _Row(
|
|
slug="demo-wp-0001@retired-20260831",
|
|
status="finished",
|
|
path="workplans/a.md",
|
|
)
|
|
retired.projection_retired_at = datetime.now(tz=timezone.utc)
|
|
task = _TaskRow("DEMO-WP-0001-T01", status="todo", workplan_id=current.id)
|
|
session = _FakeSession(repo=_Repo(), rows=[current, retired], task_rows=[task])
|
|
out = await fp.reset_repository_projection(
|
|
session,
|
|
"demo",
|
|
derived=self._derived(("DEMO-WP-0001-T01", "Do it", "todo")),
|
|
)
|
|
assert out.status in {"applied", "noop"}
|
|
assert out.refused == []
|
|
|
|
|
|
def test_diff_reports_duplicate_current_task_identity_as_ambiguous():
|
|
derived = TestExistingWorkplanTasks()._derived(
|
|
("DEMO-WP-0001-T01", "Do it", "todo")
|
|
)
|
|
workplan_id = derived.workplans[0].uuid
|
|
hub_workplans = [
|
|
{
|
|
"id": workplan_id,
|
|
"slug": "demo-wp-0001",
|
|
"status": "active",
|
|
"backing_relative_path": "workplans/a.md",
|
|
}
|
|
]
|
|
hub_tasks = {
|
|
workplan_id: [
|
|
{
|
|
"id": "11111111-1111-4111-8111-111111111111",
|
|
"record_id": "DEMO-WP-0001-T01",
|
|
"title": "a",
|
|
"status": "todo",
|
|
},
|
|
{
|
|
"id": "22222222-2222-4222-8222-222222222222",
|
|
"record_id": "demo-wp-0001-t01",
|
|
"title": "b",
|
|
"status": "todo",
|
|
},
|
|
]
|
|
}
|
|
diff = fp.diff_against_hub(derived, hub_workplans, hub_tasks)
|
|
assert diff.clean is False
|
|
assert diff.to_dict()["counts"]["ambiguous"] == 1
|
|
assert diff.ambiguous[0]["source"] == "hub"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_reset_refuses_duplicate_task_identity_in_forge_projection():
|
|
first = TestExistingWorkplanTasks()._derived(
|
|
("SHARED-WP-0001-T01", "First", "todo")
|
|
).workplans[0]
|
|
second = fp.DerivedWorkplan(
|
|
record_id="DEMO-WP-0002",
|
|
uuid=fp.derived_record_uuid("DEMO-WP-0002"),
|
|
title="Second",
|
|
status="active",
|
|
relative_path="workplans/b.md",
|
|
archived=False,
|
|
tasks=[
|
|
fp.DerivedTask(
|
|
record_id="SHARED-WP-0001-T01",
|
|
uuid=fp.derived_record_uuid("SHARED-WP-0001-T01"),
|
|
title="Second claim",
|
|
status="todo",
|
|
priority="medium",
|
|
)
|
|
],
|
|
)
|
|
derived = fp.DerivedProjection(
|
|
repo_slug="demo", commit="c0ffee", workplans=[first, second]
|
|
)
|
|
session = _FakeSession(repo=_Repo(), rows=[])
|
|
out = await fp.reset_repository_projection(session, "demo", derived=derived)
|
|
assert out.status == "refused"
|
|
assert any(item.get("source") == "forge" for item in out.refused)
|
|
assert session.added == []
|