Add deterministic incremental adapter assembly
This commit is contained in:
parent
09c09300b1
commit
cd54cae71d
6 changed files with 224 additions and 38 deletions
|
|
@ -12,6 +12,7 @@ from pathlib import Path
|
|||
from mcp.shared.memory import create_connected_server_and_client_session
|
||||
|
||||
from docforge.adapter_contract import (
|
||||
AdapterAssembly,
|
||||
AdapterEdge,
|
||||
AdapterManifest,
|
||||
AdapterNode,
|
||||
|
|
@ -182,6 +183,56 @@ class IncrementalLoader:
|
|||
)
|
||||
|
||||
|
||||
class OverlappingIncrementalLoader(IncrementalLoader):
|
||||
def extract_source(self, source: AdapterSource) -> AdapterSourceProjection:
|
||||
self.extract_calls.append(source.source_id)
|
||||
content = self.sources[source.source_id]
|
||||
shared = Node(
|
||||
node_id="guide.shared",
|
||||
title="Shared",
|
||||
family="guide",
|
||||
authority="derived",
|
||||
status="active",
|
||||
tags=("guide",),
|
||||
summary=f"Evidence selected from {source.source_id}.",
|
||||
content=content,
|
||||
source_path=source.source_path,
|
||||
source_anchor=None,
|
||||
content_hash=self._hash(content),
|
||||
)
|
||||
return AdapterSourceProjection(
|
||||
source_id=source.source_id,
|
||||
fingerprint=source.fingerprint,
|
||||
nodes=(AdapterNode(shared),),
|
||||
edges=(),
|
||||
)
|
||||
|
||||
def assemble_projection(
|
||||
self,
|
||||
manifest: AdapterManifest,
|
||||
contributions: tuple[AdapterSourceProjection, ...],
|
||||
) -> AdapterAssembly:
|
||||
selected = min(contributions, key=lambda item: item.source_id)
|
||||
return AdapterAssembly(
|
||||
AdapterProjection(
|
||||
project_id=manifest.project_id,
|
||||
title=manifest.title,
|
||||
adapter_id=manifest.adapter_id,
|
||||
adapter_version=manifest.adapter_version,
|
||||
root=manifest.root,
|
||||
revision=manifest.revision,
|
||||
source_hash=manifest.source_hash,
|
||||
nodes=selected.nodes,
|
||||
edges=(),
|
||||
)
|
||||
)
|
||||
|
||||
def load_projection(self) -> AdapterProjection:
|
||||
manifest = self.load_manifest()
|
||||
contributions = tuple(self.extract_source(source) for source in manifest.sources)
|
||||
return self.assemble_projection(manifest, contributions).projection
|
||||
|
||||
|
||||
class AdapterContractTests(unittest.TestCase):
|
||||
def projection(self, root: Path) -> AdapterProjection:
|
||||
foundation = Node(
|
||||
|
|
@ -426,6 +477,38 @@ class AdapterContractTests(unittest.TestCase):
|
|||
self.assertEqual("ok", equivalent["status"])
|
||||
self.assertEqual(1, equivalent["node_count"])
|
||||
|
||||
def test_incremental_adapter_can_assemble_overlapping_source_evidence(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory).resolve()
|
||||
loader = OverlappingIncrementalLoader(root)
|
||||
project = AdapterProject(loader, cache_root=root / ".cache" / "overlap")
|
||||
index = ProjectIndex(project)
|
||||
|
||||
first = index.build()
|
||||
self.assertEqual(2, first["build"]["reparsed_sources"])
|
||||
self.assertEqual(1, first["node_count"])
|
||||
self.assertEqual(
|
||||
"Foundation content.",
|
||||
index.get_node("guide.shared")["node"]["content"],
|
||||
)
|
||||
|
||||
loader.extract_calls.clear()
|
||||
warm = index.build()
|
||||
self.assertEqual(2, warm["build"]["cache_hits"])
|
||||
self.assertEqual([], loader.extract_calls)
|
||||
|
||||
loader.sources["guide.workflow"] = "Changed overlapping evidence."
|
||||
loader.extract_calls.clear()
|
||||
changed = index.build()
|
||||
self.assertEqual(["guide.workflow"], loader.extract_calls)
|
||||
self.assertEqual(1, changed["build"]["reparsed_sources"])
|
||||
self.assertEqual(1, changed["node_count"])
|
||||
self.assertEqual(
|
||||
"Foundation content.",
|
||||
index.get_node("guide.shared")["node"]["content"],
|
||||
)
|
||||
self.assertEqual("ok", project.verify_incremental_equivalence()["status"])
|
||||
|
||||
def test_artifact_comparison_is_complete_and_byte_exact(self) -> None:
|
||||
reference = (
|
||||
ShadowArtifact("manual", b"same"),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue