Add versioned effective policy
This commit is contained in:
parent
a9a75c5c27
commit
34cd5f74c1
11 changed files with 708 additions and 70 deletions
|
|
@ -17,12 +17,14 @@ from mcp.client.stdio import stdio_client
|
|||
from mcp.shared.memory import create_connected_server_and_client_session
|
||||
|
||||
from docforge.changesets import ChangesetStore
|
||||
from docforge.errors import DocForgeError
|
||||
from docforge.index import ProjectIndex
|
||||
from docforge.mcp_server import (
|
||||
ALL_TOOLS,
|
||||
APPLICATION_TOOLS,
|
||||
CONTENT_WARNING,
|
||||
PROPOSAL_TOOLS,
|
||||
READ_TOOLS,
|
||||
DocForgeService,
|
||||
_create_bound_server,
|
||||
create_server,
|
||||
|
|
@ -103,6 +105,43 @@ class DocForgeMcpTests(unittest.IsolatedAsyncioTestCase):
|
|||
)
|
||||
)
|
||||
|
||||
async def test_explicit_capability_modes_preserve_surfaces_and_fail_closed(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = self.copy_fixture("alpha", Path(directory))
|
||||
ProjectIndex(Project.open(root)).build()
|
||||
async with create_connected_server_and_client_session(
|
||||
create_server(root, capability_mode="read"),
|
||||
raise_exceptions=True,
|
||||
) as session:
|
||||
read_names = tuple(tool.name for tool in (await session.list_tools()).tools)
|
||||
read_bootstrap = await session.call_tool("docforge_bootstrap", {})
|
||||
|
||||
self.assertEqual(READ_TOOLS, read_names)
|
||||
self.assertEqual(
|
||||
"read",
|
||||
read_bootstrap.structuredContent["effective_policy"]["capability_mode"],
|
||||
)
|
||||
self.assertNotIn(
|
||||
"docforge_register_changes",
|
||||
read_bootstrap.structuredContent["recommended_workflow"],
|
||||
)
|
||||
|
||||
async with create_connected_server_and_client_session(
|
||||
create_server(
|
||||
root,
|
||||
"alpha-editor",
|
||||
canonical_applier_id="alpha-editor",
|
||||
capability_mode="application",
|
||||
),
|
||||
raise_exceptions=True,
|
||||
) as session:
|
||||
application_names = tuple(tool.name for tool in (await session.list_tools()).tools)
|
||||
self.assertEqual((*ALL_TOOLS, *APPLICATION_TOOLS), application_names)
|
||||
|
||||
with self.assertRaises(DocForgeError) as unavailable:
|
||||
create_server(root, capability_mode="application")
|
||||
self.assertEqual("capability_unavailable", unavailable.exception.code)
|
||||
|
||||
async def test_factory_diagnostics_are_additive_through_real_mcp(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = self.copy_fixture("alpha", Path(directory))
|
||||
|
|
|
|||
161
tests/test_policy.py
Normal file
161
tests/test_policy.py
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import shutil
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
from jsonschema import Draft202012Validator
|
||||
|
||||
from docforge.application import GenericCanonicalApplier
|
||||
from docforge.errors import DocForgeError
|
||||
from docforge.index import ProjectIndex
|
||||
from docforge.mcp_server import (
|
||||
ALL_TOOLS,
|
||||
APPLICATION_TOOLS,
|
||||
READ_TOOLS,
|
||||
DocForgeService,
|
||||
)
|
||||
from docforge.policy import (
|
||||
POLICY_PRECEDENCE,
|
||||
capability_mode,
|
||||
compose_effective_policy,
|
||||
)
|
||||
from docforge.project import Project
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
FIXTURES = ROOT / "tests" / "fixtures"
|
||||
POLICY_SCHEMA = json.loads((ROOT / "schemas" / "policy.schema.json").read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
class EffectivePolicyTests(unittest.TestCase):
|
||||
def copy_fixture(self, destination: Path) -> Path:
|
||||
root = destination / "alpha"
|
||||
shutil.copytree(FIXTURES / "alpha", root)
|
||||
return root
|
||||
|
||||
def test_policy_schema_and_legacy_adapter_projection_are_exact(self) -> None:
|
||||
standard = compose_effective_policy(
|
||||
selected_mode="proposal",
|
||||
capability_source="factory_default",
|
||||
no_ast=False,
|
||||
diagnostics=False,
|
||||
render_configured=True,
|
||||
application_enabled=False,
|
||||
)
|
||||
preserve = compose_effective_policy(
|
||||
selected_mode="read",
|
||||
capability_source="explicit",
|
||||
no_ast=True,
|
||||
diagnostics=True,
|
||||
render_configured=False,
|
||||
application_enabled=False,
|
||||
)
|
||||
validator = Draft202012Validator(POLICY_SCHEMA)
|
||||
validator.validate(standard.as_dict())
|
||||
validator.validate(preserve.as_dict())
|
||||
self.assertEqual(list(POLICY_PRECEDENCE), preserve.as_dict()["precedence"])
|
||||
self.assertEqual(
|
||||
{
|
||||
"mode": "standard",
|
||||
"ast_analysis": "allowed",
|
||||
"logic_projection": "allowed",
|
||||
"incremental_extraction": "allowed",
|
||||
"adapter_rewrite": "not_requested",
|
||||
},
|
||||
standard.adapter_policy(),
|
||||
)
|
||||
self.assertEqual("preserve-no-ast", preserve.adapter_policy()["mode"])
|
||||
self.assertEqual(["docforge_get_logic"], preserve.adapter_policy()["blocked_tools"])
|
||||
self.assertEqual("off", preserve.as_dict()["logic_indexing"])
|
||||
self.assertEqual("enabled", preserve.as_dict()["profiling"])
|
||||
|
||||
def test_invalid_or_unavailable_capability_fails_closed(self) -> None:
|
||||
with self.assertRaises(DocForgeError) as invalid:
|
||||
capability_mode("admin", default="read")
|
||||
self.assertEqual("invalid_capability_mode", invalid.exception.code)
|
||||
with self.assertRaises(DocForgeError) as unavailable:
|
||||
compose_effective_policy(
|
||||
selected_mode="application",
|
||||
capability_source="explicit",
|
||||
no_ast=False,
|
||||
diagnostics=False,
|
||||
render_configured=True,
|
||||
application_enabled=False,
|
||||
)
|
||||
self.assertEqual("capability_unavailable", unavailable.exception.code)
|
||||
|
||||
def test_modes_preserve_default_surfaces_and_narrow_authority(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = self.copy_fixture(Path(directory))
|
||||
project = Project.open(root)
|
||||
|
||||
read = DocForgeService(
|
||||
project,
|
||||
proposal_writer="alpha-editor",
|
||||
capability_mode_name="read",
|
||||
)
|
||||
proposal = DocForgeService(
|
||||
project,
|
||||
proposal_writer="alpha-editor",
|
||||
capability_mode_name="proposal",
|
||||
)
|
||||
application = DocForgeService(
|
||||
project,
|
||||
proposal_writer="alpha-editor",
|
||||
canonical_applier_id="alpha-editor",
|
||||
canonical_applier=GenericCanonicalApplier(project),
|
||||
capability_mode_name="application",
|
||||
)
|
||||
operator = DocForgeService(
|
||||
project,
|
||||
proposal_writer="alpha-editor",
|
||||
capability_mode_name="operator",
|
||||
)
|
||||
|
||||
self.assertEqual(READ_TOOLS, read.tool_surface)
|
||||
self.assertFalse(read.changesets.access()["enabled"])
|
||||
self.assertEqual(ALL_TOOLS, proposal.tool_surface)
|
||||
self.assertTrue(proposal.changesets.access()["enabled"])
|
||||
self.assertFalse(proposal.application.enabled)
|
||||
self.assertEqual((*ALL_TOOLS, *APPLICATION_TOOLS), application.tool_surface)
|
||||
self.assertTrue(application.application.enabled)
|
||||
self.assertEqual(ALL_TOOLS, operator.tool_surface)
|
||||
self.assertTrue(operator.capabilities()["operator"]["enabled"])
|
||||
|
||||
def test_bootstrap_reuses_synchronized_identity_and_filters_workflow(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = self.copy_fixture(Path(directory))
|
||||
project = Project.open(root)
|
||||
ProjectIndex(project).build()
|
||||
read = DocForgeService(project, capability_mode_name="read")
|
||||
with mock.patch.object(
|
||||
project,
|
||||
"load",
|
||||
side_effect=AssertionError("bootstrap must not reload after synchronization"),
|
||||
):
|
||||
result = read.bootstrap()
|
||||
|
||||
self.assertEqual("ok", result["status"])
|
||||
self.assertEqual(1, result["session_contract"]["schema_version"])
|
||||
self.assertEqual("read", result["effective_policy"]["capability_mode"])
|
||||
self.assertEqual(
|
||||
result["effective_policy"], result["session_contract"]["effective_policy"]
|
||||
)
|
||||
self.assertNotIn("docforge_register_changes", result["recommended_workflow"])
|
||||
self.assertNotIn("docforge_apply_changeset", result["recommended_workflow"])
|
||||
self.assertEqual(
|
||||
"docforge_get_context",
|
||||
result["session_contract"]["recommended_first_operation"]["tool"],
|
||||
)
|
||||
|
||||
proposal = DocForgeService(
|
||||
project,
|
||||
proposal_writer="alpha-editor",
|
||||
capability_mode_name="proposal",
|
||||
).bootstrap()
|
||||
self.assertIn("docforge_register_changes", proposal["recommended_workflow"])
|
||||
self.assertIn("docforge_get_changeset_diff", proposal["recommended_workflow"])
|
||||
self.assertNotIn("docforge_apply_changeset", proposal["recommended_workflow"])
|
||||
|
|
@ -77,6 +77,11 @@ PUBLIC_IMPORTS = {
|
|||
"ProjectService",
|
||||
"ProjectSnapshot",
|
||||
),
|
||||
"docforge.policy": (
|
||||
"EffectivePolicyV1",
|
||||
"capability_mode",
|
||||
"compose_effective_policy",
|
||||
),
|
||||
"docforge.render_contract": (
|
||||
"GenericHtmlRenderer",
|
||||
"PreparedRender",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue