Files
Charles Yao 231e423431 Integrate validated Builder backend testing and CI repairs
Track backend integration, Ruff cleanup and migration reconciliation in ENG-1271. Retain required copy contracts without implementing frontend interactions.
2026-10-20 15:40:06 -07:00

344 lines
15 KiB
Python

"""Transaction-local native admission, acknowledged fences and unknown quarantine."""
from sqlalchemy import select
from models.dify_builder import DifyBuilderExecutionRequest
from models.dify_builder_native_tests import (
NativeContinuation,
NativeDispatch,
NativeInvocationState,
NativePendingAction,
NativeTestInvocation,
NativeTestScope,
)
from runtime_contracts.builder.execution import BuilderExecutionPolicyError
def validate_pending_fence(rows, *, state, ref, tenant_id, pending_ids):
"""Validate exact current local pending facts, preserving the represented order."""
if (
not isinstance(pending_ids, (tuple, list))
or not pending_ids
or any(type(identity) is not str or not identity for identity in pending_ids)
or len(set(pending_ids)) != len(pending_ids)
or len(rows) != len(pending_ids)
):
raise BuilderExecutionPolicyError("native_pending_mismatch")
by_id = {row.id: row for row in rows}
if set(by_id) != set(pending_ids) or any(
(row.tenant_id, row.request_id, row.invocation_id, row.generation)
!= (tenant_id, ref.request_id, ref.invocation_id, state.generation)
or row.category not in {"approval", "configuration", "interaction"}
for row in rows
):
raise BuilderExecutionPolicyError("native_pending_mismatch")
return tuple(by_id[identity] for identity in pending_ids)
def pending_reason(rows):
"""The actual private reason uses the existing waiting-category precedence."""
categories = {row.category for row in rows}
if not categories or not categories <= {"configuration", "interaction", "approval"}:
raise BuilderExecutionPolicyError("native_pending_mismatch")
return next(reason for reason in ("configuration", "interaction", "approval") if reason in categories)
def validate_pending_history(session, *, ref, tenant_id, state, states=None, dispatch_states=("claimed", "completed")):
"""Retained IDs prove a past acknowledged claim, never a current pause fence.
Root's preservation ruling uses real wait_reason=None to open admission.
Each retained owner must still have its exact saved checkpoint and older
pending generation. Never reconstruct a lost set by scanning a generation.
"""
if states is None:
states = {
row.invocation_id: row
for row in session.scalars(
select(NativeInvocationState).where(
NativeInvocationState.request_id == ref.request_id, NativeInvocationState.tenant_id == tenant_id
)
)
}
ids = state.pending_ids
if (
not state.checkpoint_id
or type(state.generation) is not int
or state.generation <= 0
or type(ids) is not list
or not ids
or any(type(identity) is not str or not identity for identity in ids)
or len(set(ids)) != len(ids)
):
raise BuilderExecutionPolicyError("native_pending_mismatch")
continuations = {
(row.invocation_id, row.checkpoint_id): row
for row in session.scalars(
select(NativeContinuation).where(
NativeContinuation.request_id == ref.request_id, NativeContinuation.tenant_id == tenant_id
)
)
}
path, selected = set(), state.invocation_id
while selected is not None:
owner = states.get(selected)
if selected in path or owner is None or (owner.tenant_id, owner.request_id) != (tenant_id, ref.request_id):
raise BuilderExecutionPolicyError("native_pending_mismatch")
path.add(selected)
selected = owner.parent_invocation_id
claims = session.scalars(
select(NativeDispatch).where(NativeDispatch.request_id == ref.request_id, NativeDispatch.tenant_id == tenant_id)
).all()
if not any(
row.invocation_id in path
and row.generation == state.generation
and row.state in dispatch_states
and (row.tenant_id, row.request_id) == (tenant_id, ref.request_id)
and (row.invocation_id, row.checkpoint_id) in continuations
and continuations[row.invocation_id, row.checkpoint_id].generation < row.generation
and (row.invocation_id != state.invocation_id or row.checkpoint_id == state.checkpoint_id)
for row in claims
):
raise BuilderExecutionPolicyError("native_pending_mismatch")
selected_checkpoint = continuations.get((state.invocation_id, state.checkpoint_id))
if (
selected_checkpoint is None
or selected_checkpoint.generation >= state.generation
or (selected_checkpoint.tenant_id, selected_checkpoint.request_id, selected_checkpoint.invocation_id)
!= (tenant_id, ref.request_id, state.invocation_id)
):
raise BuilderExecutionPolicyError("native_pending_mismatch")
rows = session.scalars(
select(NativePendingAction).where(
NativePendingAction.request_id == ref.request_id,
NativePendingAction.tenant_id == tenant_id,
NativePendingAction.id.in_(ids),
)
).all()
by_id = {row.id: row for row in rows}
if set(by_id) != set(ids) or len(rows) != len(ids):
raise BuilderExecutionPolicyError("native_pending_mismatch")
for row in rows:
owner = states.get(row.invocation_id)
saved = continuations.get((row.invocation_id, owner.checkpoint_id)) if owner is not None else None
if (
owner is None
or saved is None
or row.id not in owner.pending_ids
or (owner.tenant_id, owner.request_id) != (tenant_id, ref.request_id)
or (row.tenant_id, row.request_id, saved.tenant_id, saved.request_id)
!= (tenant_id, ref.request_id, tenant_id, ref.request_id)
or row.category not in {"approval", "configuration", "interaction"}
or row.generation != saved.generation
or saved.generation >= owner.generation
):
raise BuilderExecutionPolicyError("native_pending_mismatch")
descendant, seen = row.invocation_id, set()
while descendant != state.invocation_id:
if descendant in seen or descendant not in states:
raise BuilderExecutionPolicyError("native_pending_mismatch")
seen.add(descendant)
descendant = states[descendant].parent_invocation_id
if descendant is None:
raise BuilderExecutionPolicyError("native_pending_mismatch")
return tuple(by_id[identity] for identity in ids)
def dispatch_readiness(session, *, ref, tenant_id, now, coalesce_self=False):
"""SQL-only physical admission; caller's root lock serializes any write.
Recovery/private reads retain their separate authority. Global generation
changes do not invalidate another branch's current local generation.
Owned wait boundaries may coalesce a validated self fence into an owned wait.
"""
request = session.scalar(
select(DifyBuilderExecutionRequest).where(
DifyBuilderExecutionRequest.id == ref.request_id, DifyBuilderExecutionRequest.tenant_id == tenant_id
)
)
scope = session.scalar(
select(NativeTestScope).where(
NativeTestScope.request_id == ref.request_id, NativeTestScope.tenant_id == tenant_id
)
)
if (
request is None
or (request.id, request.tenant_id, request.context_digest) != (ref.request_id, tenant_id, ref.context_digest)
or scope is None
or (scope.request_id, scope.tenant_id) != (ref.request_id, tenant_id)
or scope.cancelled
or scope.expires_at <= now
):
raise BuilderExecutionPolicyError("native_dispatch_closed")
states = {
state.invocation_id: state
for state in session.scalars(
select(NativeInvocationState).where(
NativeInvocationState.request_id == ref.request_id, NativeInvocationState.tenant_id == tenant_id
)
)
}
active = {
row.invocation_id
for row in session.scalars(
select(NativeTestInvocation).where(
NativeTestInvocation.request_id == ref.request_id, NativeTestInvocation.tenant_id == tenant_id
)
)
if row.active and (row.request_id, row.tenant_id) == (ref.request_id, tenant_id)
}
roots = [state for state in states.values() if state.parent_invocation_id is None]
selected, seen, fence = ref.invocation_id, set(), None
while True:
state = states.get(selected)
if (
selected in seen
or state is None
or selected not in active
or (state.request_id, state.tenant_id) != (ref.request_id, tenant_id)
or state.state != "running"
):
raise BuilderExecutionPolicyError("native_dispatch_closed")
seen.add(selected)
if type(state.pending_ids) is not list or state.wait_reason not in {
None,
"approval",
"configuration",
"interaction",
}:
raise BuilderExecutionPolicyError("native_pending_mismatch")
if state.wait_reason is not None:
if selected != ref.invocation_id or not coalesce_self:
raise BuilderExecutionPolicyError("native_dispatch_fenced")
rows = session.scalars(
select(NativePendingAction).where(
NativePendingAction.tenant_id == tenant_id,
NativePendingAction.request_id == ref.request_id,
NativePendingAction.id.in_(state.pending_ids),
)
).all()
validated = validate_pending_fence(
rows, state=state, ref=ref, tenant_id=tenant_id, pending_ids=state.pending_ids
)
if pending_reason(validated) != state.wait_reason:
raise BuilderExecutionPolicyError("native_pending_mismatch")
fence = next(row for row in validated if row.category == state.wait_reason)
elif state.pending_ids:
validate_pending_history(
session,
ref=ref.model_copy(update={"invocation_id": selected}),
tenant_id=tenant_id,
state=state,
states=states,
)
if state.parent_invocation_id is None:
if len(roots) != 1 or (state.app_id, state.workflow_id) != (request.app_id, request.workflow_id):
raise BuilderExecutionPolicyError("native_dispatch_closed")
return fence
selected = state.parent_invocation_id
def invocation_subtree(session, ref, tenant_id):
states = session.scalars(
select(NativeInvocationState).where(
NativeInvocationState.request_id == ref.request_id, NativeInvocationState.tenant_id == tenant_id
)
).all()
ids = {ref.invocation_id}
while True:
expanded = ids | {state.invocation_id for state in states if state.parent_invocation_id in ids}
if expanded == ids:
return [state for state in states if state.invocation_id in ids]
ids = expanded
def quarantine_unknown(session, *, ref, tenant_id, outcome=None):
"""Caller holds the root request lock. Preserve descendants' saved execution facts.
An unknown descendant blocks ancestor resume separately; only trusted whole
subtree native fencing evidence may restore execution authority.
"""
states = invocation_subtree(session, ref, tenant_id)
ids = {state.invocation_id for state in states}
for state in states:
if state.invocation_id == ref.invocation_id and state.state not in {"completed", "stopped", "cancelled"}:
state.state = "unknown"
if outcome is not None:
state.outcome = outcome
for invocation in session.scalars(
select(NativeTestInvocation).where(
NativeTestInvocation.request_id == ref.request_id,
NativeTestInvocation.tenant_id == tenant_id,
NativeTestInvocation.invocation_id.in_(ids),
)
):
invocation.active = False
for dispatch in session.scalars(
select(NativeDispatch).where(
NativeDispatch.request_id == ref.request_id,
NativeDispatch.tenant_id == tenant_id,
NativeDispatch.invocation_id.in_(ids),
NativeDispatch.state.in_(("queued", "published", "claimed")),
)
):
dispatch.state = "unknown"
def has_unknown_descendant(session, ref, tenant_id):
return any(state.state == "unknown" for state in invocation_subtree(session, ref, tenant_id))
def require_ancestor_authority(session, ref, tenant_id):
"""An invocation's own proof cannot discharge a controlling ancestor quarantine.
Call before external recovery proof and again under the root request lock.
The selected invocation may itself be unknown/inactive during recovery;
every ancestor must still own execution authority. Missing/cyclic lineage
fails closed instead of treating a disconnected child as another root.
"""
states = {
state.invocation_id: state
for state in session.scalars(
select(NativeInvocationState).where(
NativeInvocationState.request_id == ref.request_id, NativeInvocationState.tenant_id == tenant_id
)
)
}
active_ids = set(
session.scalars(
select(NativeTestInvocation.invocation_id).where(
NativeTestInvocation.request_id == ref.request_id,
NativeTestInvocation.tenant_id == tenant_id,
NativeTestInvocation.active.is_(True),
)
)
)
state = states.get(ref.invocation_id)
seen = {ref.invocation_id}
if state is None:
raise BuilderExecutionPolicyError("native_ancestor_quarantined")
while state.parent_invocation_id is not None:
parent_id = state.parent_invocation_id
if parent_id in seen or parent_id not in active_ids:
raise BuilderExecutionPolicyError("native_ancestor_quarantined")
seen.add(parent_id)
state = states.get(parent_id)
if state is None or state.state not in {"running", "waiting", "queued"}:
raise BuilderExecutionPolicyError("native_ancestor_quarantined")
def recoverable_invocation_ids(states, root_invocation_id):
"""Restore only paths whose complete lineage belongs to the recovered subtree.
An independently unknown or completed branch remains inactive, including its
descendants, even when the controlling root's physical fence is proven.
"""
eligible = {state.invocation_id: state for state in states if state.state in {"running", "waiting", "queued"}}
recoverable = {root_invocation_id} if root_invocation_id in eligible else set()
while True:
expanded = recoverable | {
state.invocation_id for state in eligible.values() if state.parent_invocation_id in recoverable
}
if expanded == recoverable:
return recoverable
recoverable = expanded