1
0
Fork 0
Code Issues Pull requests Projects Releases 2 Packages Wiki Activity Actions Pages

Guarantee bounded mutation receipts

This commit is contained in:
Andraxion 2026-07-29 04:24:06 -04:00
parent 4ae9b31db5
commit 21c4992f9c
7 changed files with 485 additions and 30 deletions

View file

@ -1,5 +1,6 @@
from __future__ import annotations
import json
import os
import shutil
import sys
@ -380,6 +381,122 @@ class DocForgeMcpTests(unittest.IsolatedAsyncioTestCase):
self.assertEqual("result_too_large", payload["error"]["code"])
self.assertNotIn("canonical_paths", payload)
async def test_mutation_overflow_returns_exact_compact_success_receipts(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = self.copy_fixture("alpha", Path(directory))
descriptor = root / ".docforge" / "project.toml"
descriptor.write_text(
descriptor.read_text(encoding="utf-8").replace(
"max_context_tokens = 2000",
"max_context_tokens = 2000\nmax_tool_output_chars = 1600",
),
encoding="utf-8",
)
project = Project.open(root)
ProjectIndex(project).build()
node_hashes = {node.node_id: node.content_hash for node in project.load().nodes}
async with create_connected_server_and_client_session(
create_server(
root,
"alpha-editor",
canonical_applier_id="alpha-editor",
),
raise_exceptions=True,
) as session:
created = await session.call_tool(
"docforge_create_changeset",
{"changeset_id": "compact-mutation"},
)
first = await session.call_tool(
"docforge_propose_node_update",
{
"changeset_id": "compact-mutation",
"expected_changeset_hash": created.structuredContent["changeset_hash"],
"node_id": "guide.workflow",
"expected_content_hash": node_hashes["guide.workflow"],
"metadata": None,
"content": "Updated workflow.\n\n" + ("bounded receipt evidence " * 200),
"relationship_changes": [],
"rationale": "Exercise exact compact append receipts.",
},
)
second = await session.call_tool(
"docforge_propose_node_update",
{
"changeset_id": "compact-mutation",
"expected_changeset_hash": first.structuredContent["changeset_hash"],
"node_id": "guide.foundation",
"expected_content_hash": node_hashes["guide.foundation"],
"metadata": None,
"content": "Updated foundation.\n\n" + ("second exact receipt " * 200),
"relationship_changes": [],
"rationale": "Prove the returned hash supports the next append.",
},
)
applied = await session.call_tool(
"docforge_apply_changeset",
{
"changeset_id": "compact-mutation",
"expected_changeset_hash": second.structuredContent["changeset_hash"],
},
)
for result in (first, second, applied):
payload = result.structuredContent
self.assertEqual("ok", payload["status"])
self.assertTrue(payload["mutation_committed"])
self.assertEqual("receipt", payload["result_mode"])
self.assertLessEqual(
len(json.dumps(payload, sort_keys=True, separators=(",", ":"))),
1600,
)
self.assertNotEqual(
first.structuredContent["changeset_hash"],
second.structuredContent["changeset_hash"],
)
self.assertTrue(applied.structuredContent["applied"])
self.assertEqual(
"applied",
applied.structuredContent["lifecycle"]["status"],
)
self.assertEqual(
"ok",
applied.structuredContent["derived_refresh"]["status"],
)
self.assertIn(
"Updated workflow.",
(root / "docs/content/workflow.md").read_text(encoding="utf-8"),
)
async def test_mutation_preflight_rejects_before_writing(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = self.copy_fixture("alpha", Path(directory))
descriptor = root / ".docforge" / "project.toml"
descriptor.write_text(
descriptor.read_text(encoding="utf-8").replace(
"max_context_tokens = 2000",
"max_context_tokens = 2000\nmax_tool_output_chars = 700",
),
encoding="utf-8",
)
ProjectIndex(Project.open(root)).build()
changeset_id = "must-not-exist-" + ("x" * 100)
async with create_connected_server_and_client_session(
create_server(root, "alpha-editor"),
raise_exceptions=True,
) as session:
result = await session.call_tool(
"docforge_create_changeset",
{"changeset_id": changeset_id},
)
payload = result.structuredContent
self.assertEqual("error", payload["status"])
self.assertEqual("result_too_large", payload["error"]["code"])
self.assertEqual("preflight", payload["error"]["details"]["stage"])
self.assertFalse(payload["error"]["details"]["mutation_committed"])
self.assertFalse((root / f".docforge/changesets/{changeset_id}.json").exists())
async def test_proposal_tools_use_fixed_writer_and_never_change_canonical_content(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = self.copy_fixture("alpha", Path(directory))