mirror of
https://github.com/langgenius/dify.git
synced 2026-11-13 06:41:07 +08:00
647 lines
25 KiB
Python
647 lines
25 KiB
Python
"""One-shot Code physical evidence under the existing request ownership lock.
|
|
|
|
No remote I/O, payload storage, recovery, capacity release or production factory.
|
|
The local transaction seam delegates loading to OwnedNativeTestLoader._load after
|
|
setting PostgreSQL lock/statement timeouts. Pool checkout and commit response
|
|
are not covered by those per-statement limits. Any uncertain return refuses I/O.
|
|
"""
|
|
|
|
from collections.abc import Callable, Generator
|
|
from contextlib import contextmanager
|
|
from datetime import datetime
|
|
from math import isfinite
|
|
from time import monotonic
|
|
from typing import Never, Protocol
|
|
|
|
from pydantic import TypeAdapter
|
|
from sqlalchemy import inspect, or_, select, text
|
|
from sqlalchemy.orm import Session
|
|
|
|
from models.dify_builder_native_code import NativeCodePhysicalOperation
|
|
from models.dify_builder_native_tests import (
|
|
NativeDispatch,
|
|
NativeEffectAttempt,
|
|
NativeEffectDispatchClaim,
|
|
NativeEffectOperation,
|
|
NativeInvocationState,
|
|
NativeTestScope,
|
|
)
|
|
from runtime_contracts.builder.execution import (
|
|
BuilderExecutionPolicyError,
|
|
canonical_digest,
|
|
)
|
|
from runtime_contracts.builder.native.contracts import (
|
|
Counter,
|
|
DispatchPermit,
|
|
EffectIntent,
|
|
NativeTestContextRef,
|
|
OperationKey,
|
|
)
|
|
from services.dify_builder.native_testing.authorization import (
|
|
EffectAuthorizationService,
|
|
)
|
|
from services.dify_builder.native_testing.repository import (
|
|
OwnedNativeTest,
|
|
OwnedNativeTestLoader,
|
|
utc_now,
|
|
)
|
|
from services.dify_builder.native_testing.sandbox.contracts import (
|
|
CodeAcquisitionOwner,
|
|
CodeAcquisitionRef,
|
|
CodePhysicalRef,
|
|
CodeScopeSpec,
|
|
JobID,
|
|
UUIDString,
|
|
)
|
|
|
|
_MUTABLE = frozenset(
|
|
{
|
|
"scope_create_acked",
|
|
"job_reserved",
|
|
"job_id",
|
|
"input_may_have_been_sent",
|
|
"observed_output_offset",
|
|
"ambiguous",
|
|
"destroyed_at",
|
|
}
|
|
)
|
|
|
|
|
|
def _refuse() -> Never:
|
|
raise BuilderExecutionPolicyError("native_code_physical_conflict")
|
|
|
|
|
|
def _validate(model, value):
|
|
return model.model_validate(value.model_dump(mode="python"), strict=True)
|
|
|
|
|
|
class CodeIdentity(Protocol):
|
|
@property
|
|
def operation_ref(self) -> str: ...
|
|
@property
|
|
def scope_id(self) -> str: ...
|
|
@property
|
|
def generation(self) -> int: ...
|
|
|
|
|
|
class NativeCodePhysicalRepository:
|
|
def __init__(
|
|
self,
|
|
loader: OwnedNativeTestLoader,
|
|
*,
|
|
acquisition_owner: CodeAcquisitionOwner,
|
|
transaction_deadline: Callable[[bool], float],
|
|
):
|
|
if not isinstance(acquisition_owner, CodeAcquisitionOwner) or not callable(acquisition_owner.check_current):
|
|
raise BuilderExecutionPolicyError("native_code_acquisition_owner_required")
|
|
if not callable(transaction_deadline):
|
|
raise BuilderExecutionPolicyError("native_code_transaction_deadline_required")
|
|
self._transaction_deadline = transaction_deadline
|
|
self._deadline_caps: dict[bool, float] = {}
|
|
self.loader = loader
|
|
self.acquisition_owner = acquisition_owner
|
|
self.authorization = EffectAuthorizationService(loader)
|
|
# Delivery-local retained arguments only; never a durable retry recipe.
|
|
self._bindings: dict[str, tuple[EffectIntent, DispatchPermit]] = {}
|
|
self._attempted_scopes: set[str] = set()
|
|
self._attempted_jobs: set[str] = set()
|
|
self._attempted_inputs: set[str] = set()
|
|
|
|
def _remaining_ms(self, *, settlement: bool) -> int:
|
|
"""Origin-worker local deadline; never extend an observed operation budget.
|
|
|
|
True selects cleanup allowance, not the expired execution permit. Pool
|
|
checkout, RBAC and commit still need their separate whole-wall gate.
|
|
"""
|
|
deadline = self._transaction_deadline(settlement)
|
|
if type(deadline) not in (int, float) or not isfinite(deadline):
|
|
_refuse()
|
|
deadline = min(deadline, self._deadline_caps.get(settlement, deadline))
|
|
self._deadline_caps[settlement] = deadline
|
|
remaining = min(1000, int((deadline - monotonic()) * 1000))
|
|
if remaining <= 0:
|
|
_refuse()
|
|
return remaining
|
|
|
|
@contextmanager
|
|
def _transaction(
|
|
self,
|
|
*,
|
|
ref: NativeTestContextRef,
|
|
acquisition: CodeAcquisitionRef,
|
|
settlement: bool,
|
|
deadline: datetime | None = None,
|
|
) -> Generator[tuple[Session, OwnedNativeTest, NativeTestScope]]:
|
|
ref = _validate(NativeTestContextRef, ref)
|
|
acquisition = _validate(CodeAcquisitionRef, acquisition)
|
|
if (ref.request_id, ref.context_digest) != (
|
|
acquisition.ref.request_id,
|
|
acquisition.ref.context_digest,
|
|
):
|
|
_refuse()
|
|
self.acquisition_owner.check_current(acquisition)
|
|
self._remaining_ms(settlement=settlement)
|
|
if not settlement:
|
|
self.loader.load(ref=ref) # RBAC/RPC closes before the writer.
|
|
self.acquisition_owner.check_current(acquisition)
|
|
self._remaining_ms(settlement=settlement)
|
|
with self.loader.session_factory.begin() as session:
|
|
if session.get_bind().dialect.name != "postgresql":
|
|
raise BuilderExecutionPolicyError("native_code_postgresql_required")
|
|
remaining = self._remaining_ms(settlement=settlement)
|
|
if deadline is not None:
|
|
remaining = min(remaining, int((deadline - utc_now()).total_seconds() * 1000))
|
|
if remaining <= 0:
|
|
_refuse()
|
|
# Transaction-local settings precede the initial request FOR UPDATE.
|
|
session.execute(
|
|
text("SELECT set_config('lock_timeout', :limit, true), set_config('statement_timeout', :limit, true)"),
|
|
{"limit": f"{remaining}ms"},
|
|
)
|
|
owned, scope = self.loader._load(session, ref=ref, lock=True, settlement=settlement)
|
|
if not settlement:
|
|
remaining = min(
|
|
remaining,
|
|
int((scope.expires_at - utc_now()).total_seconds() * 1000),
|
|
)
|
|
if remaining <= 0:
|
|
_refuse()
|
|
session.execute(
|
|
text(
|
|
"SELECT set_config('lock_timeout', :limit, true), set_config('statement_timeout', :limit, true)"
|
|
),
|
|
{"limit": f"{remaining}ms"},
|
|
)
|
|
yield session, owned, scope
|
|
if not settlement and utc_now() >= min(deadline or scope.expires_at, scope.expires_at):
|
|
_refuse()
|
|
self.acquisition_owner.check_current(acquisition)
|
|
self._remaining_ms(settlement=settlement)
|
|
self.acquisition_owner.check_current(acquisition)
|
|
|
|
def _chain(
|
|
self,
|
|
*,
|
|
session: Session,
|
|
owned: OwnedNativeTest,
|
|
acquisition: CodeAcquisitionRef,
|
|
operation_id: str,
|
|
attempt_id: str,
|
|
settlement: bool,
|
|
):
|
|
operation = session.get(NativeEffectOperation, operation_id)
|
|
attempt = session.get(NativeEffectAttempt, attempt_id)
|
|
claim = session.scalar(
|
|
select(NativeEffectDispatchClaim).where(NativeEffectDispatchClaim.operation_id == operation_id)
|
|
)
|
|
owner = (owned.tenant_id, owned.ref.request_id, owned.ref.invocation_id)
|
|
if operation is None or attempt is None or claim is None:
|
|
_refuse()
|
|
for item in (operation, attempt, claim):
|
|
if item is None or (item.tenant_id, item.request_id, item.invocation_id) != owner:
|
|
_refuse()
|
|
if attempt.operation_id != operation.id or (
|
|
claim.operation_id,
|
|
claim.attempt_id,
|
|
) != (operation.id, attempt.id):
|
|
_refuse()
|
|
dispatch = session.get(NativeDispatch, acquisition.root_dispatch_id)
|
|
states = {
|
|
state.invocation_id: state
|
|
for state in session.scalars(
|
|
select(NativeInvocationState).where(
|
|
NativeInvocationState.tenant_id == owned.tenant_id,
|
|
NativeInvocationState.request_id == owned.ref.request_id,
|
|
)
|
|
)
|
|
}
|
|
state = states.get(owned.ref.invocation_id)
|
|
selected, visited = state, set()
|
|
while selected is not None:
|
|
if selected.invocation_id in visited or (selected.tenant_id, selected.request_id) != owner[:2]:
|
|
_refuse()
|
|
visited.add(selected.invocation_id)
|
|
if selected.parent_invocation_id is None:
|
|
break
|
|
selected = states.get(selected.parent_invocation_id)
|
|
if (
|
|
state is None
|
|
or selected is None
|
|
or selected.invocation_id != acquisition.ref.invocation_id
|
|
or dispatch is None
|
|
or (
|
|
dispatch.tenant_id,
|
|
dispatch.request_id,
|
|
dispatch.invocation_id,
|
|
dispatch.generation,
|
|
)
|
|
!= (*owner[:2], selected.invocation_id, acquisition.generation)
|
|
or selected.generation != acquisition.generation
|
|
or selected.cleanup_complete
|
|
or dispatch.state not in ({"claimed", "unknown", "completed", "cancelled"} if settlement else {"claimed"})
|
|
):
|
|
_refuse()
|
|
if not settlement and (dispatch.claim_expires_at is None or dispatch.claim_expires_at <= utc_now()):
|
|
_refuse()
|
|
return operation, attempt, claim, state
|
|
|
|
@staticmethod
|
|
def _snapshot(row: NativeCodePhysicalOperation, ref: NativeTestContextRef) -> CodePhysicalRef:
|
|
data = {attribute.key: attribute.value for attribute in inspect(row).attrs}
|
|
acquisition = CodeAcquisitionRef(
|
|
ref=ref.model_copy(update={"invocation_id": data.pop("root_invocation_id")}),
|
|
root_dispatch_id=data.pop("host_dispatch_id"),
|
|
generation=data.pop("host_generation"),
|
|
acquisition_id=data.pop("host_acquisition_id"),
|
|
)
|
|
data.pop("request_id")
|
|
data.pop("invocation_id")
|
|
return CodePhysicalRef(ref=ref, acquisition=acquisition, **data)
|
|
|
|
def reserve_scope(
|
|
self,
|
|
*,
|
|
ref: NativeTestContextRef,
|
|
key: OperationKey,
|
|
permit: DispatchPermit,
|
|
acquisition: CodeAcquisitionRef,
|
|
spec: CodeScopeSpec,
|
|
intent: EffectIntent,
|
|
payload_digest: str,
|
|
profile_digest: str,
|
|
physical_deadline: datetime,
|
|
) -> CodePhysicalRef:
|
|
ref = _validate(NativeTestContextRef, ref)
|
|
key = _validate(OperationKey, key)
|
|
permit = _validate(DispatchPermit, permit)
|
|
intent = _validate(EffectIntent, intent)
|
|
spec = _validate(CodeScopeSpec, spec)
|
|
acquisition = _validate(CodeAcquisitionRef, acquisition)
|
|
if (
|
|
type(physical_deadline) is not datetime
|
|
or physical_deadline.tzinfo is None
|
|
or not 0 < (physical_deadline - utc_now()).total_seconds() <= 30
|
|
):
|
|
_refuse()
|
|
if intent.key != key or key.invocation_id != ref.invocation_id or intent.kind != "code":
|
|
_refuse()
|
|
admitted = {
|
|
"scope_spec_digest": canonical_digest(spec.native.model_dump(mode="json")),
|
|
"environment_digest": spec.environment_digest,
|
|
"base_snapshot_ref": spec.native.base_snapshot_ref,
|
|
"base_snapshot_digest": spec.base_snapshot_digest,
|
|
"payload_digest": payload_digest,
|
|
"profile_digest": profile_digest,
|
|
}
|
|
if not isinstance(intent.target, dict) or intent.target.get("code_physical") != admitted:
|
|
_refuse()
|
|
if permit.operation_id in self._attempted_scopes:
|
|
_refuse()
|
|
# Even a rolled-back/uncertain writer cannot be retried by this delivery.
|
|
# Committed row uniqueness remains the cross-instance durable fence.
|
|
self._attempted_scopes.add(permit.operation_id)
|
|
deadline = min(
|
|
physical_deadline,
|
|
spec.native.deadline,
|
|
datetime.fromisoformat(permit.expires_at),
|
|
)
|
|
with self._transaction(ref=ref, acquisition=acquisition, settlement=False, deadline=deadline) as (
|
|
session,
|
|
owned,
|
|
scope,
|
|
):
|
|
attempt = self.authorization._validate_dispatch_locked(
|
|
session=session, owned=owned, scope=scope, intent=intent, permit=permit
|
|
)
|
|
operation, current_attempt, claim, state = self._chain(
|
|
session=session,
|
|
owned=owned,
|
|
acquisition=acquisition,
|
|
operation_id=permit.operation_id,
|
|
attempt_id=permit.attempt_id,
|
|
settlement=False,
|
|
)
|
|
if (
|
|
current_attempt.id != attempt.id
|
|
or spec.native.capability_digest != intent.capability_digest
|
|
or key.occurrence_path != (attempt.execution_id,)
|
|
):
|
|
_refuse()
|
|
root = session.get(NativeDispatch, acquisition.root_dispatch_id)
|
|
# _chain verified this exact root and its live claim. Logical intent
|
|
# keeps its original ceiling; only this row owns the admitted wire spec.
|
|
if root is None or root.claim_expires_at is None:
|
|
_refuse()
|
|
deadline = min(deadline, attempt.expires_at, scope.expires_at, root.claim_expires_at)
|
|
if not 0 < (deadline - utc_now()).total_seconds() <= spec.native.limits.wall_seconds:
|
|
_refuse()
|
|
wire = spec.native.model_copy(update={"deadline": deadline})
|
|
admitted["scope_spec_digest"] = canonical_digest(wire.model_dump(mode="json"))
|
|
TypeAdapter(UUIDString).validate_python(attempt.execution_id)
|
|
existing = session.scalar(
|
|
select(NativeCodePhysicalOperation.id).where(
|
|
or_(
|
|
NativeCodePhysicalOperation.operation_id == operation.id,
|
|
NativeCodePhysicalOperation.attempt_id == attempt.id,
|
|
NativeCodePhysicalOperation.effect_claim_id == claim.id,
|
|
NativeCodePhysicalOperation.scope_id == spec.native.scope_id,
|
|
)
|
|
)
|
|
)
|
|
if existing is not None:
|
|
_refuse()
|
|
row = NativeCodePhysicalOperation(
|
|
tenant_id=owned.tenant_id,
|
|
actor_id=owned.actor_id,
|
|
request_id=ref.request_id,
|
|
invocation_id=ref.invocation_id,
|
|
operation_id=operation.id,
|
|
attempt_id=attempt.id,
|
|
effect_claim_id=claim.id,
|
|
host_dispatch_id=acquisition.root_dispatch_id,
|
|
host_acquisition_id=acquisition.acquisition_id,
|
|
root_invocation_id=acquisition.ref.invocation_id,
|
|
host_generation=acquisition.generation,
|
|
invocation_generation=state.generation,
|
|
native_execution_id=attempt.execution_id,
|
|
operation_digest=operation.operation_digest,
|
|
intent_digest=operation.intent_digest,
|
|
scope_id=spec.native.scope_id,
|
|
scope_generation=spec.native.generation,
|
|
scope_expires_at=deadline,
|
|
**admitted,
|
|
)
|
|
result = self._snapshot(row, ref)
|
|
session.add(row)
|
|
session.flush()
|
|
if utc_now() >= deadline:
|
|
_refuse()
|
|
self._bindings[result.id] = (intent, permit)
|
|
return result
|
|
|
|
def _physical_row(self, *, session, owned, scope, physical, settlement, intent=None, permit=None):
|
|
physical = _validate(CodePhysicalRef, physical)
|
|
row = session.get(NativeCodePhysicalOperation, physical.id)
|
|
if row is None:
|
|
_refuse()
|
|
actual = self._snapshot(row, owned.ref)
|
|
for name in CodePhysicalRef.model_fields:
|
|
if name not in _MUTABLE and dict(actual)[name] != dict(physical)[name]:
|
|
_refuse()
|
|
if (row.tenant_id, row.actor_id, row.request_id, row.invocation_id) != (
|
|
owned.tenant_id,
|
|
owned.actor_id,
|
|
owned.ref.request_id,
|
|
owned.ref.invocation_id,
|
|
):
|
|
_refuse()
|
|
operation, attempt, claim, state = self._chain(
|
|
session=session,
|
|
owned=owned,
|
|
acquisition=physical.acquisition,
|
|
operation_id=row.operation_id,
|
|
attempt_id=row.attempt_id,
|
|
settlement=settlement,
|
|
)
|
|
if (
|
|
claim.id,
|
|
state.generation,
|
|
attempt.execution_id,
|
|
operation.operation_digest,
|
|
operation.intent_digest,
|
|
) != (
|
|
row.effect_claim_id,
|
|
row.invocation_generation,
|
|
row.native_execution_id,
|
|
row.operation_digest,
|
|
row.intent_digest,
|
|
):
|
|
_refuse()
|
|
if not settlement:
|
|
if intent is None or permit is None:
|
|
binding = self._bindings.get(row.id)
|
|
if binding is None:
|
|
_refuse()
|
|
intent, permit = binding
|
|
intent = _validate(EffectIntent, intent)
|
|
permit = _validate(DispatchPermit, permit)
|
|
if (permit.operation_id, permit.attempt_id) != (
|
|
row.operation_id,
|
|
row.attempt_id,
|
|
):
|
|
_refuse()
|
|
self.authorization._validate_dispatch_locked(
|
|
session=session, owned=owned, scope=scope, intent=intent, permit=permit
|
|
)
|
|
if row.scope_expires_at <= utc_now() or row.ambiguous or row.destroyed_at is not None:
|
|
_refuse()
|
|
return row
|
|
|
|
@contextmanager
|
|
def _mutation(self, physical, *, settlement, intent=None, permit=None):
|
|
physical = _validate(CodePhysicalRef, physical)
|
|
deadline = None if settlement else physical.scope_expires_at
|
|
if permit is not None and deadline is not None:
|
|
deadline = min(deadline, datetime.fromisoformat(permit.expires_at))
|
|
with self._transaction(
|
|
ref=physical.ref,
|
|
acquisition=physical.acquisition,
|
|
settlement=settlement,
|
|
deadline=deadline,
|
|
) as (session, owned, scope):
|
|
row = self._physical_row(
|
|
session=session,
|
|
owned=owned,
|
|
scope=scope,
|
|
physical=physical,
|
|
settlement=settlement,
|
|
intent=intent,
|
|
permit=permit,
|
|
)
|
|
if not settlement and (row.ambiguous or row.destroyed_at is not None):
|
|
_refuse()
|
|
yield row
|
|
session.flush()
|
|
|
|
def ack_scope(self, *, physical: CodePhysicalRef, scope_id: str, generation: int) -> None:
|
|
if type(generation) is not int or (scope_id, generation) != (
|
|
physical.scope_id,
|
|
physical.scope_generation,
|
|
):
|
|
_refuse()
|
|
with self._mutation(physical, settlement=False) as row:
|
|
if row.scope_create_acked:
|
|
_refuse()
|
|
row.scope_create_acked = True
|
|
|
|
def assert_claim(self, *, physical: CodePhysicalRef, intent: EffectIntent, permit: DispatchPermit) -> None:
|
|
with self._mutation(physical, settlement=False, intent=intent, permit=permit):
|
|
pass
|
|
|
|
def reserve_dispatch(
|
|
self,
|
|
*,
|
|
physical: CodePhysicalRef,
|
|
intent: EffectIntent,
|
|
permit: DispatchPermit,
|
|
payload_digest: str,
|
|
profile_digest: str,
|
|
) -> None:
|
|
if physical.id in self._attempted_jobs:
|
|
_refuse()
|
|
self._attempted_jobs.add(physical.id)
|
|
with self._mutation(physical, settlement=False, intent=intent, permit=permit) as row:
|
|
if (
|
|
not row.scope_create_acked
|
|
or row.job_reserved
|
|
or (payload_digest, profile_digest) != (row.payload_digest, row.profile_digest)
|
|
):
|
|
_refuse()
|
|
row.job_reserved = True
|
|
|
|
def associate_job(
|
|
self,
|
|
*,
|
|
physical: CodePhysicalRef,
|
|
intent: EffectIntent,
|
|
permit: DispatchPermit,
|
|
job_id: str,
|
|
) -> None:
|
|
job_id = TypeAdapter(JobID).validate_python(job_id)
|
|
if physical.id in self._attempted_inputs:
|
|
_refuse()
|
|
self._attempted_inputs.add(physical.id)
|
|
with self._mutation(physical, settlement=False, intent=intent, permit=permit) as row:
|
|
if not row.scope_create_acked or not row.job_reserved or row.job_id is not None:
|
|
_refuse()
|
|
row.job_id = job_id
|
|
row.input_may_have_been_sent = True
|
|
|
|
def mark_unknown(self, *, physical: CodePhysicalRef) -> None:
|
|
with self._mutation(physical, settlement=True) as row:
|
|
if row.destroyed_at is not None and not row.ambiguous:
|
|
_refuse()
|
|
row.ambiguous = True
|
|
|
|
def observe_output(
|
|
self,
|
|
*,
|
|
physical: CodePhysicalRef,
|
|
job_id: str,
|
|
expected_offset: int,
|
|
next_offset: int,
|
|
) -> None:
|
|
TypeAdapter(Counter).validate_python(expected_offset)
|
|
TypeAdapter(Counter).validate_python(next_offset)
|
|
with self._mutation(physical, settlement=True) as row:
|
|
if (
|
|
row.destroyed_at is not None
|
|
or row.job_id != job_id
|
|
or row.job_id is None
|
|
or row.observed_output_offset != expected_offset
|
|
or next_offset < expected_offset
|
|
):
|
|
_refuse()
|
|
row.observed_output_offset = next_offset
|
|
|
|
def ack_destroyed(self, *, physical: CodePhysicalRef) -> None:
|
|
"""Record the trusted caller's ACK for this exact scope, never release capacity."""
|
|
with self._mutation(physical, settlement=True) as row:
|
|
if row.destroyed_at is None:
|
|
row.destroyed_at = utc_now()
|
|
|
|
def load_for_settlement(
|
|
self,
|
|
*,
|
|
ref: NativeTestContextRef,
|
|
operation_id: str,
|
|
acquisition: CodeAcquisitionRef,
|
|
) -> CodePhysicalRef:
|
|
operation_id = TypeAdapter(UUIDString).validate_python(operation_id)
|
|
with self._transaction(ref=ref, acquisition=acquisition, settlement=True) as (
|
|
session,
|
|
owned,
|
|
scope,
|
|
):
|
|
row = session.scalar(
|
|
select(NativeCodePhysicalOperation).where(
|
|
NativeCodePhysicalOperation.operation_id == operation_id,
|
|
NativeCodePhysicalOperation.tenant_id == owned.tenant_id,
|
|
NativeCodePhysicalOperation.request_id == ref.request_id,
|
|
NativeCodePhysicalOperation.invocation_id == ref.invocation_id,
|
|
)
|
|
)
|
|
if row is None:
|
|
_refuse()
|
|
result = self._snapshot(row, ref)
|
|
if result.acquisition != acquisition:
|
|
_refuse()
|
|
self._physical_row(
|
|
session=session,
|
|
owned=owned,
|
|
scope=scope,
|
|
physical=result,
|
|
settlement=True,
|
|
)
|
|
return result
|
|
|
|
def bind(self, *, physical: CodePhysicalRef, intent: EffectIntent, permit: DispatchPermit):
|
|
self.assert_claim(physical=physical, intent=intent, permit=permit)
|
|
return BoundCodeDurableOwner(
|
|
self,
|
|
_validate(CodePhysicalRef, physical),
|
|
_validate(EffectIntent, intent),
|
|
_validate(DispatchPermit, permit),
|
|
)
|
|
|
|
|
|
class BoundCodeDurableOwner:
|
|
"""Structural NativeCodeDurableOwner signatures; reviewed adapter stays independent."""
|
|
|
|
def __init__(self, repository, physical, intent, permit):
|
|
self.repository, self.physical, self.intent, self.permit = (
|
|
repository,
|
|
physical,
|
|
intent,
|
|
permit,
|
|
)
|
|
|
|
def _identity(self, identity: CodeIdentity) -> None:
|
|
if type(identity.generation) is not int or (
|
|
identity.operation_ref,
|
|
identity.scope_id,
|
|
identity.generation,
|
|
) != (
|
|
self.physical.operation_id,
|
|
self.physical.scope_id,
|
|
self.physical.scope_generation,
|
|
):
|
|
_refuse()
|
|
|
|
def assert_claim(self, identity: CodeIdentity) -> None:
|
|
self._identity(identity)
|
|
self.repository.assert_claim(physical=self.physical, intent=self.intent, permit=self.permit)
|
|
|
|
def reserve_dispatch(self, identity: CodeIdentity, *, payload_digest: str, profile_digest: str) -> None:
|
|
self._identity(identity)
|
|
self.repository.reserve_dispatch(
|
|
physical=self.physical,
|
|
intent=self.intent,
|
|
permit=self.permit,
|
|
payload_digest=payload_digest,
|
|
profile_digest=profile_digest,
|
|
)
|
|
|
|
def associate_job(self, identity: CodeIdentity, job_id: str) -> None:
|
|
self._identity(identity)
|
|
self.repository.associate_job(
|
|
physical=self.physical,
|
|
intent=self.intent,
|
|
permit=self.permit,
|
|
job_id=job_id,
|
|
)
|
|
|
|
def mark_unknown(self, identity: CodeIdentity) -> None:
|
|
self._identity(identity)
|
|
self.repository.mark_unknown(physical=self.physical)
|