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

Establish Milestone 0 compatibility and quality gates

This commit is contained in:
Andraxion 2026-07-29 03:12:30 -04:00
parent 15a913003c
commit 8ebb78a71d
15 changed files with 1114 additions and 27 deletions

View file

@ -47,6 +47,7 @@ from docforge.models import (
RenderConfig,
RenderView,
)
from docforge.viewer_manager import ViewerManagerClient
from docforge.visualization import VisualizationIndexSnapshot
@ -236,6 +237,13 @@ class OverlappingIncrementalLoader(IncrementalLoader):
return self.assemble_projection(manifest, contributions).projection
class NonLogicIncrementalLoader(IncrementalLoader):
"""Exercise non-AST incremental caching without publishing function Logic."""
def extract_source(self, source: AdapterSource) -> AdapterSourceProjection:
return replace(super().extract_source(source), logic=())
class AdapterContractTests(unittest.TestCase):
def projection(self, root: Path) -> AdapterProjection:
foundation = Node(
@ -561,6 +569,60 @@ class AdapterContractTests(unittest.TestCase):
self.assertEqual("adapter_policy_forbids_logic", captured.exception.code)
self.assertFalse(project.descriptor.index_path.exists())
def test_no_ast_index_accepts_legacy_and_non_logic_incremental_adapters(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory).resolve()
legacy = AdapterProject(
Loader(self.projection(root)),
cache_root=root / ".cache" / "legacy-no-ast",
)
legacy_index = ProjectIndex(legacy, allow_logic=False)
self.assertEqual(2, legacy_index.build()["node_count"])
self.assertEqual(
"guide.workflow",
legacy_index.get_node("guide.workflow")["node"]["node_id"],
)
with tempfile.TemporaryDirectory() as directory:
root = Path(directory).resolve()
loader = NonLogicIncrementalLoader(root)
project = AdapterProject(
loader,
cache_root=root / ".cache" / "incremental-no-ast",
)
index = ProjectIndex(project, allow_logic=False)
first = index.build()
self.assertEqual(2, first["build"]["reparsed_sources"])
loader.extract_calls.clear()
second = index.build()
self.assertEqual([], loader.extract_calls)
self.assertEqual(2, second["build"]["cache_hits"])
self.assertEqual(0, second["build"]["reparsed_sources"])
self.assertEqual(0, second["logic_projection_count"])
def test_no_ast_rejects_preexisting_logic_index_and_viewer_snapshot(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory).resolve()
project = AdapterProject(
IncrementalLoader(root),
cache_root=root / ".cache" / "preexisting-logic",
)
ProjectIndex(project).build()
preserved = ProjectIndex(project, allow_logic=False)
with self.assertRaises(DocForgeError) as checked:
preserved.check(verify_rows=False)
self.assertEqual("adapter_policy_forbids_logic", checked.exception.code)
with self.assertRaises(DocForgeError) as viewed:
ViewerManagerClient(
preserved,
state_path=root / ".cache" / "viewer-state.json",
).start()
self.assertEqual("adapter_policy_forbids_logic", viewed.exception.code)
def test_fast_incremental_reads_reverify_a_changed_index_file(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory).resolve()

View file

@ -81,7 +81,11 @@ class DocForgeMcpTests(unittest.IsolatedAsyncioTestCase):
async def test_no_ast_binding_preserves_adapter_and_blocks_logic(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = self.copy_fixture("alpha", Path(directory))
ProjectIndex(Project.open(root)).build()
project = Project.open(root)
ProjectIndex(project).build()
service = DocForgeService(project, no_ast=True)
self.assertIs(service.index, service.application.index)
self.assertFalse(service.application.index.allow_logic)
async with create_connected_server_and_client_session(
create_server(root, no_ast=True), raise_exceptions=True
) as session:

View file

@ -0,0 +1,270 @@
from __future__ import annotations
import argparse
import hashlib
import importlib
import json
import shutil
import subprocess
import sys
import tempfile
import tomllib
import unittest
from pathlib import Path
from jsonschema import Draft202012Validator
import docforge
from docforge.changeset_contract import canonical_bytes, document_hash
from docforge.changesets import ChangesetStore
from docforge.cli import _parser
from docforge.index import ProjectIndex
from docforge.mcp_server import (
ALL_TOOLS,
APPLICATION_TOOLS,
PROPOSAL_TOOLS,
READ_TOOLS,
SERVER_VERSION,
DocForgeService,
)
from docforge.project import Project
ROOT = Path(__file__).resolve().parents[1]
FIXTURES = ROOT / "tests" / "fixtures"
SCHEMAS = ROOT / "schemas"
PUBLIC_IMPORTS = {
"docforge": (
"CanonicalApplier",
"CanonicalApplicationService",
"DocForgeError",
"GenericCanonicalApplier",
"Project",
),
"docforge.adapter_contract": (
"AdapterAssembly",
"AdapterEdge",
"AdapterImplementation",
"AdapterLoader",
"AdapterManifest",
"AdapterNode",
"AdapterProject",
"AdapterProjection",
"AdapterProjectSettings",
"AdapterSource",
"AdapterSourceProjection",
"IncrementalAdapterAssembler",
"IncrementalAdapterLoader",
),
"docforge.application": (
"CanonicalApplier",
"CanonicalApplicationService",
"GenericCanonicalApplier",
),
"docforge.index": ("ProjectIndex",),
"docforge.mcp_server": (
"create_project_server",
"create_read_only_server",
"create_server",
),
"docforge.models": (
"Edge",
"LogicEdge",
"LogicNode",
"LogicProjection",
"Node",
"ProjectDescriptor",
"ProjectService",
"ProjectSnapshot",
),
"docforge.render_contract": (
"GenericHtmlRenderer",
"PreparedRender",
"Renderer",
"renderer_for",
),
}
EXPECTED_ENTRY_POINTS = {
"docforge": "docforge.cli:main",
"docforge-mcp": "docforge.mcp_server:main",
"docforge-viewer-manager": "docforge.viewer_manager:main",
}
EXPECTED_CLI_COMMANDS = {
"apply",
"backlinks",
"build",
"check",
"context",
"dependencies",
"filter",
"impact",
"info",
"onboard",
"preview",
"reindex",
"render",
"render-status",
"search",
"show",
"sync",
"validate",
"validate-index",
"visualization-status",
"visualization-stop",
"visualize",
}
EXPECTED_MCP_TOOLS = {
"docforge_abandon_changeset",
"docforge_apply_changeset",
"docforge_backlinks",
"docforge_bootstrap",
"docforge_create_changeset",
"docforge_dependencies",
"docforge_filter_nodes",
"docforge_get_changeset",
"docforge_get_changeset_diff",
"docforge_get_context",
"docforge_get_contract",
"docforge_get_logic",
"docforge_get_node",
"docforge_impact",
"docforge_list_changesets",
"docforge_preview_changeset",
"docforge_project_info",
"docforge_propose_node_create",
"docforge_propose_node_delete",
"docforge_propose_node_move",
"docforge_propose_node_update",
"docforge_propose_relationship_update",
"docforge_rebase_changeset",
"docforge_register_changes",
"docforge_render_status",
"docforge_search",
"docforge_stop_visualization",
"docforge_sync",
"docforge_validate_changeset",
"docforge_validate_project",
"docforge_visualization_status",
"docforge_visualize",
}
class PublicContractTests(unittest.TestCase):
def copy_fixture(self, destination: Path) -> Path:
root = destination / "alpha"
shutil.copytree(FIXTURES / "alpha", root)
return root
@staticmethod
def schema(name: str) -> dict[str, object]:
return json.loads((SCHEMAS / name).read_text(encoding="utf-8"))
def test_distribution_version_entry_points_and_imports_are_stable(self) -> None:
project = tomllib.loads((ROOT / "pyproject.toml").read_text(encoding="utf-8"))["project"]
self.assertEqual("docforge", project["name"])
self.assertEqual(docforge.__version__, project["version"])
self.assertEqual(docforge.__version__, SERVER_VERSION)
scripts = project["scripts"]
for name, target in EXPECTED_ENTRY_POINTS.items():
self.assertEqual(target, scripts[name])
for module_name, names in PUBLIC_IMPORTS.items():
module = importlib.import_module(module_name)
for name in names:
with self.subTest(module=module_name, name=name):
self.assertTrue(hasattr(module, name))
def test_cli_and_mcp_names_remain_additively_compatible(self) -> None:
parser = _parser()
commands = next(
action for action in parser._actions if isinstance(action, argparse._SubParsersAction)
)
self.assertLessEqual(EXPECTED_CLI_COMMANDS, set(commands.choices))
self.assertLessEqual(
EXPECTED_MCP_TOOLS,
set((*ALL_TOOLS, *APPLICATION_TOOLS)),
)
self.assertEqual((*READ_TOOLS, *PROPOSAL_TOOLS), ALL_TOOLS)
completed = subprocess.run(
[sys.executable, "-m", "docforge.mcp_server", "--help"],
cwd=ROOT,
check=True,
capture_output=True,
text=True,
)
self.assertIn("--project-root", completed.stdout)
self.assertIn("--no-ast", completed.stdout)
def test_published_schemas_validate_their_current_contract_examples(self) -> None:
for path in sorted(SCHEMAS.glob("*.json")):
with self.subTest(schema=path.name):
Draft202012Validator.check_schema(json.loads(path.read_text(encoding="utf-8")))
descriptor = tomllib.loads(
(FIXTURES / "alpha" / ".docforge" / "project.toml").read_text(encoding="utf-8")
)
Draft202012Validator(self.schema("project.schema.json")).validate(descriptor)
with tempfile.TemporaryDirectory() as directory:
root = self.copy_fixture(Path(directory))
project = Project.open(root)
snapshot = project.load()
node = snapshot.nodes[0]
node_metadata = {
"schema_version": 1,
"id": node.node_id,
"title": node.title,
"family": node.family,
"authority": node.authority,
"status": node.status,
"tags": list(node.tags),
"summary": node.summary,
}
Draft202012Validator(self.schema("node.schema.json")).validate(node_metadata)
edge = snapshot.edges[0]
Draft202012Validator(self.schema("edge.schema.json")).validate(
{
"source_id": edge.source_id,
"relation": edge.relation,
"target_id": edge.target_id,
}
)
store = ChangesetStore(project, "alpha-editor")
store.create("public-contract")
changeset = json.loads(
(root / ".docforge" / "changesets" / "public-contract.json").read_text(
encoding="utf-8"
)
)
Draft202012Validator(self.schema("changeset.schema.json")).validate(changeset)
service = DocForgeService(project)
ProjectIndex(project).build()
success = service.project_info()
error = service.invoke(lambda: service.index.get_node("missing.public-contract-node"))
result_validator = Draft202012Validator(self.schema("result.schema.json"))
result_validator.validate(success)
result_validator.validate(error)
def test_changeset_hash_is_exact_canonical_json_sha256(self) -> None:
document = {
"schema_version": 1,
"changeset_id": "hash-contract",
"project_id": "alpha-docs",
"root_fingerprint": "0" * 16,
"base_revision": "unversioned",
"base_source_hash": "1" * 64,
"creator": "alpha-editor",
"operations": [],
}
expected = hashlib.sha256(canonical_bytes(document)).hexdigest()
self.assertEqual(expected, document_hash(document))
if __name__ == "__main__":
unittest.main()