Add explicit cross-identity proposal acceptance
This commit is contained in:
parent
377cca0531
commit
7b21541ab3
10 changed files with 247 additions and 8 deletions
|
|
@ -17,6 +17,7 @@ from mcp import ClientSession, StdioServerParameters
|
|||
from mcp.client.stdio import stdio_client
|
||||
from mcp.shared.memory import create_connected_server_and_client_session
|
||||
|
||||
from docforge.application import GenericCanonicalApplier
|
||||
from docforge.changesets import ChangesetStore
|
||||
from docforge.errors import DocForgeError
|
||||
from docforge.index import ProjectIndex
|
||||
|
|
@ -29,6 +30,7 @@ from docforge.mcp_server import (
|
|||
SERVER_VERSION,
|
||||
DocForgeService,
|
||||
_create_bound_server,
|
||||
create_project_server,
|
||||
create_server,
|
||||
)
|
||||
from docforge.project import Project, project_root_fingerprint
|
||||
|
|
@ -1446,6 +1448,83 @@ class DocForgeMcpTests(unittest.IsolatedAsyncioTestCase):
|
|||
self.assertEqual("Applied through the gated MCP tool.", workflow.summary)
|
||||
self.assertTrue((root / ".docforge/rendered/manual.html").is_file())
|
||||
|
||||
async def test_project_server_accepts_an_explicit_contributor_without_granting_apply(
|
||||
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")
|
||||
+ """
|
||||
|
||||
[[changesets.writers]]
|
||||
id = "contributor"
|
||||
families = ["guide"]
|
||||
operations = ["update"]
|
||||
""",
|
||||
encoding="utf-8",
|
||||
)
|
||||
project = Project.open(root)
|
||||
ProjectIndex(project).build()
|
||||
async with create_connected_server_and_client_session(
|
||||
create_server(
|
||||
root,
|
||||
"contributor",
|
||||
capability_mode="proposal",
|
||||
),
|
||||
raise_exceptions=True,
|
||||
) as contributor:
|
||||
contributor_tools = tuple(
|
||||
tool.name for tool in (await contributor.list_tools()).tools
|
||||
)
|
||||
registered = await contributor.call_tool(
|
||||
"docforge_register_changes",
|
||||
{
|
||||
"changeset_id": "accepted-contribution",
|
||||
"operations": [
|
||||
{
|
||||
"operation": "update",
|
||||
"node_id": "guide.workflow",
|
||||
"metadata": {"summary": "Accepted through a separate applier."},
|
||||
"rationale": "Prove explicit contributor acceptance over MCP.",
|
||||
}
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
async with create_connected_server_and_client_session(
|
||||
create_project_server(
|
||||
project,
|
||||
proposal_writer="alpha-editor",
|
||||
canonical_applier_id="alpha-editor",
|
||||
canonical_applier=GenericCanonicalApplier(project),
|
||||
accepted_proposal_writers=("contributor",),
|
||||
capability_mode="application",
|
||||
),
|
||||
raise_exceptions=True,
|
||||
) as developer:
|
||||
contract = await developer.call_tool("docforge_get_contract", {})
|
||||
applied = await developer.call_tool(
|
||||
"docforge_apply_changeset",
|
||||
{
|
||||
"changeset_id": "accepted-contribution",
|
||||
"expected_changeset_hash": registered.structuredContent["changeset_hash"],
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(ALL_TOOLS, contributor_tools)
|
||||
self.assertNotIn("docforge_apply_changeset", contributor_tools)
|
||||
self.assertEqual(
|
||||
["contributor"],
|
||||
contract.structuredContent["canonical_application_access"][
|
||||
"accepted_proposal_writers"
|
||||
],
|
||||
)
|
||||
self.assertTrue(applied.structuredContent["applied"])
|
||||
self.assertEqual("contributor", applied.structuredContent["proposal_creator"])
|
||||
self.assertEqual("alpha-editor", applied.structuredContent["applied_by"])
|
||||
|
||||
async def test_stdio_transport_serves_the_same_project_bound_contract(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = self.copy_fixture("beta", Path(directory))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue