Make project MCP workflows self-synchronizing
This commit is contained in:
parent
a30f021a52
commit
73165c9f51
17 changed files with 1124 additions and 56 deletions
|
|
@ -244,6 +244,8 @@ class DocForgeChangesetTests(unittest.TestCase):
|
|||
node_ids = {node.node_id for node in snapshot.nodes}
|
||||
workflow = next(node for node in snapshot.nodes if node.node_id == "guide.workflow")
|
||||
self.assertTrue(result["applied"])
|
||||
self.assertEqual("applied", result["lifecycle"]["status"])
|
||||
self.assertEqual("ok", result["derived_refresh"]["status"])
|
||||
self.assertEqual(
|
||||
[
|
||||
"docs/content/applied.md",
|
||||
|
|
@ -265,8 +267,95 @@ class DocForgeChangesetTests(unittest.TestCase):
|
|||
self.assertTrue((root / ".docforge/cache/index.sqlite3").is_file())
|
||||
self.assertTrue((root / ".docforge/rendered/manual.html").is_file())
|
||||
|
||||
with self.assertRaisesRegex(DocForgeError, "Canonical project changed"):
|
||||
with self.assertRaisesRegex(DocForgeError, "cannot be modified") as closed:
|
||||
service.apply("apply-all", str(final["changeset_hash"]))
|
||||
self.assertEqual("changeset_closed", closed.exception.code)
|
||||
|
||||
def test_abandoned_proposal_releases_overlap_and_stale_work_remains_active(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = self.copy_fixture(Path(directory))
|
||||
project = Project.open(root)
|
||||
store = ChangesetStore(project, "alpha-editor")
|
||||
first = store.register(
|
||||
"first",
|
||||
[
|
||||
{
|
||||
"operation": "update",
|
||||
"node_id": "guide.foundation",
|
||||
"metadata": {"summary": "Abandoned proposal."},
|
||||
"rationale": "Reserve then release this node.",
|
||||
}
|
||||
],
|
||||
)
|
||||
store.abandon(
|
||||
"first",
|
||||
str(first["changeset_hash"]),
|
||||
"The proposal is no longer wanted.",
|
||||
)
|
||||
second = store.register(
|
||||
"second",
|
||||
[
|
||||
{
|
||||
"operation": "update",
|
||||
"node_id": "guide.foundation",
|
||||
"metadata": {"summary": "Replacement proposal."},
|
||||
"rationale": "Verify terminal proposals release conflicts.",
|
||||
}
|
||||
],
|
||||
)
|
||||
workflow = root / "docs/content/workflow.md"
|
||||
workflow.write_text(
|
||||
workflow.read_text(encoding="utf-8") + "\nUnrelated current fact.\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
active = store.list_changesets(include_history=False)
|
||||
stale = store.list_changesets(include_history=False, status="stale")
|
||||
|
||||
self.assertEqual(0, active["count"])
|
||||
self.assertEqual(["second"], [item["changeset_id"] for item in stale["changesets"]])
|
||||
self.assertEqual("stale", stale["changesets"][0]["lifecycle"]["status"])
|
||||
self.assertEqual("ready", second["lifecycle"])
|
||||
|
||||
def test_applied_receipt_survives_a_derived_refresh_failure(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = self.copy_fixture(Path(directory))
|
||||
project = Project.open(root)
|
||||
registered = ChangesetStore(project, "alpha-editor").register(
|
||||
"degraded-refresh",
|
||||
[
|
||||
{
|
||||
"operation": "update",
|
||||
"node_id": "guide.workflow",
|
||||
"metadata": {"summary": "Canonical even if refresh fails."},
|
||||
"rationale": "Separate canonical success from disposable refresh.",
|
||||
}
|
||||
],
|
||||
)
|
||||
service = CanonicalApplicationService(
|
||||
project,
|
||||
applier_id="alpha-editor",
|
||||
applier=GenericCanonicalApplier(project),
|
||||
)
|
||||
with mock.patch.object(
|
||||
service.index,
|
||||
"build",
|
||||
side_effect=DocForgeError("index_failure", "Synthetic derived failure"),
|
||||
):
|
||||
result = service.apply(
|
||||
"degraded-refresh",
|
||||
str(registered["changeset_hash"]),
|
||||
)
|
||||
|
||||
self.assertTrue(result["applied"])
|
||||
self.assertEqual("applied", result["lifecycle"]["status"])
|
||||
self.assertEqual("degraded", result["derived_refresh"]["status"])
|
||||
self.assertEqual("index", result["derived_refresh"]["errors"][0]["component"])
|
||||
with self.assertRaisesRegex(DocForgeError, "cannot be modified"):
|
||||
service.apply(
|
||||
"degraded-refresh",
|
||||
str(registered["changeset_hash"]),
|
||||
)
|
||||
|
||||
def test_relationship_only_update_is_hash_bound_and_does_not_rewrite_node(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue