Add gated changeset application and graph controls
This commit is contained in:
parent
3c15e26283
commit
78335c8973
20 changed files with 1813 additions and 453 deletions
|
|
@ -6,10 +6,10 @@ import fcntl
|
|||
import json
|
||||
import os
|
||||
import tempfile
|
||||
from collections.abc import Generator
|
||||
from collections.abc import Callable, Generator, Mapping
|
||||
from contextlib import contextmanager
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from typing import Any, cast
|
||||
|
||||
from .changeset_contract import (
|
||||
document_hash,
|
||||
|
|
@ -265,6 +265,68 @@ class ChangesetStore:
|
|||
)
|
||||
return projected, document_hash(document)
|
||||
|
||||
def apply(
|
||||
self,
|
||||
*,
|
||||
changeset_id: str,
|
||||
expected_changeset_hash: str,
|
||||
applier_id: str,
|
||||
application: Callable[
|
||||
[ProjectSnapshot, ProjectSnapshot, tuple[Mapping[str, object], ...]],
|
||||
dict[str, object],
|
||||
],
|
||||
) -> dict[str, object]:
|
||||
"""Apply one exact validated proposal through a project-owned canonical serializer."""
|
||||
|
||||
validate_id(changeset_id, "changeset_id")
|
||||
validate_hash(expected_changeset_hash, "expected_changeset_hash")
|
||||
if self.writer is None or self.writer.writer_id != applier_id:
|
||||
raise DocForgeError(
|
||||
"canonical_application_disabled",
|
||||
"Canonical applier identity is not configured for this store",
|
||||
)
|
||||
with self._lock():
|
||||
snapshot, document, nodes, edges = self._validate_locked(changeset_id)
|
||||
actual_hash = document_hash(document)
|
||||
if actual_hash != expected_changeset_hash:
|
||||
raise DocForgeError(
|
||||
"changeset_conflict",
|
||||
"Changeset changed after the caller approved it",
|
||||
changeset_id=changeset_id,
|
||||
expected=expected_changeset_hash,
|
||||
actual=actual_hash,
|
||||
)
|
||||
if document["creator"] != applier_id:
|
||||
raise DocForgeError(
|
||||
"changeset_owner_conflict",
|
||||
"Canonical applier does not own this changeset",
|
||||
changeset_id=changeset_id,
|
||||
owner=document["creator"],
|
||||
applier=applier_id,
|
||||
)
|
||||
projected = ProjectSnapshot(
|
||||
descriptor=snapshot.descriptor,
|
||||
nodes=tuple(sorted(nodes.values(), key=lambda node: node.node_id)),
|
||||
edges=tuple(Edge(*edge) for edge in sorted(edges)),
|
||||
source_hash=snapshot.source_hash,
|
||||
revision=snapshot.revision,
|
||||
)
|
||||
payload = application(
|
||||
snapshot,
|
||||
projected,
|
||||
tuple(cast(Mapping[str, object], item) for item in document["operations"]),
|
||||
)
|
||||
current = self.project.load()
|
||||
return self._result(
|
||||
current,
|
||||
document,
|
||||
valid=True,
|
||||
applied=True,
|
||||
applied_from_revision=snapshot.revision,
|
||||
applied_from_source_hash=snapshot.source_hash,
|
||||
**payload,
|
||||
)
|
||||
|
||||
def _append(
|
||||
self,
|
||||
changeset_id: str,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue