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 typing import Protocol, runtime_checkable
|
|||
from .adapter_validation import (
|
||||
source_payload,
|
||||
source_projection,
|
||||
validate_logic_projection,
|
||||
validate_manifest,
|
||||
validate_projection,
|
||||
validate_source_projection,
|
||||
|
|
@ -136,6 +137,14 @@ class AdapterSourceProjection:
|
|||
logic: tuple[LogicProjection, ...] = ()
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class AdapterAssembly:
|
||||
"""One finalized graph and Logic set assembled from cached source contributions."""
|
||||
|
||||
projection: AdapterProjection
|
||||
logic: tuple[LogicProjection, ...] = ()
|
||||
|
||||
|
||||
class AdapterLoader(Protocol):
|
||||
"""Load one current, deterministic, project-confined adapter projection."""
|
||||
|
||||
|
|
@ -151,6 +160,17 @@ class IncrementalAdapterLoader(AdapterLoader, Protocol):
|
|||
def extract_source(self, source: AdapterSource) -> AdapterSourceProjection: ...
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class IncrementalAdapterAssembler(Protocol):
|
||||
"""Optionally normalize overlapping source evidence into one final projection."""
|
||||
|
||||
def assemble_projection(
|
||||
self,
|
||||
manifest: AdapterManifest,
|
||||
contributions: tuple[AdapterSourceProjection, ...],
|
||||
) -> AdapterAssembly: ...
|
||||
|
||||
|
||||
ProposalValidator = Callable[
|
||||
[
|
||||
ProjectSnapshot,
|
||||
|
|
@ -503,43 +523,78 @@ class AdapterProject:
|
|||
validate_source_projection(source, contribution)
|
||||
contributions.append(contribution)
|
||||
cache_records.append(cache_record)
|
||||
projection = 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=tuple(
|
||||
if isinstance(loader, IncrementalAdapterAssembler):
|
||||
assembly = loader.assemble_projection(manifest, tuple(contributions))
|
||||
projection = assembly.projection
|
||||
logic_projections = tuple(
|
||||
sorted(
|
||||
(node for contribution in contributions for node in contribution.nodes),
|
||||
key=lambda item: item.node.node_id,
|
||||
assembly.logic,
|
||||
key=lambda projection: projection.owner_node_id,
|
||||
)
|
||||
),
|
||||
edges=tuple(
|
||||
sorted(
|
||||
(edge for contribution in contributions for edge in contribution.edges),
|
||||
key=lambda item: (
|
||||
item.edge.source_id,
|
||||
item.edge.relation,
|
||||
item.edge.target_id,
|
||||
),
|
||||
)
|
||||
),
|
||||
)
|
||||
validate_projection(projection)
|
||||
logic_projections = tuple(
|
||||
sorted(
|
||||
(logic for contribution in contributions for logic in contribution.logic),
|
||||
key=lambda projection: projection.owner_node_id,
|
||||
)
|
||||
else:
|
||||
projection = 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=tuple(
|
||||
sorted(
|
||||
(node for contribution in contributions for node in contribution.nodes),
|
||||
key=lambda item: item.node.node_id,
|
||||
)
|
||||
),
|
||||
edges=tuple(
|
||||
sorted(
|
||||
(edge for contribution in contributions for edge in contribution.edges),
|
||||
key=lambda item: (
|
||||
item.edge.source_id,
|
||||
item.edge.relation,
|
||||
item.edge.target_id,
|
||||
),
|
||||
)
|
||||
),
|
||||
)
|
||||
logic_projections = tuple(
|
||||
sorted(
|
||||
(logic for contribution in contributions for logic in contribution.logic),
|
||||
key=lambda projection: projection.owner_node_id,
|
||||
)
|
||||
)
|
||||
identity = (
|
||||
projection.project_id,
|
||||
projection.adapter_id,
|
||||
projection.adapter_version,
|
||||
projection.root,
|
||||
)
|
||||
if (
|
||||
identity != manifest.identity()
|
||||
or projection.title != manifest.title
|
||||
or projection.revision != manifest.revision
|
||||
or projection.source_hash != manifest.source_hash
|
||||
):
|
||||
raise DocForgeError(
|
||||
"invalid_adapter",
|
||||
"Incremental assembly changed the manifest-bound project identity",
|
||||
)
|
||||
validate_projection(projection)
|
||||
owners = [projection.owner_node_id for projection in logic_projections]
|
||||
if len(owners) != len(set(owners)):
|
||||
raise DocForgeError(
|
||||
"invalid_adapter", "A primary graph node may own only one logic projection"
|
||||
)
|
||||
node_ids = {item.node.node_id for item in projection.nodes}
|
||||
for logic_projection in logic_projections:
|
||||
validate_logic_projection(logic_projection)
|
||||
if logic_projection.owner_node_id not in node_ids:
|
||||
raise DocForgeError(
|
||||
"invalid_adapter",
|
||||
"A Logic projection owner must exist in the assembled primary graph",
|
||||
owner_node_id=logic_projection.owner_node_id,
|
||||
)
|
||||
stable = loader.load_manifest()
|
||||
validate_manifest(stable)
|
||||
if stable != manifest:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue