Files
dify/api/services/dify_builder/native_testing/input_binding.py
T
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

85 lines
3.9 KiB
Python

"""Current saved-input identity for Console; never a source of execution authority."""
from sqlalchemy import select
from sqlalchemy.orm import Session, sessionmaker
from core.dify_builder.input_schema import schema_hash, start_schema
from core.dify_builder.models import Actor
from core.dify_builder.state import PcState, is_working
from models.dify_builder import DifyBuilderExecutionRequest, DifyBuilderSession, DifyBuilderSessionCommit
from runtime_contracts.builder.execution import BuilderExecutionPolicyError
from services.dify_builder.execution_policy_service import check_execution_permission, load_execution_owners
from services.dify_builder.native_testing.public_contracts import NativeTestInputBinding
from services.dify_builder.native_testing.repository import native_workflow_revision
def current_input_binding(*, session: Session, actor: Actor, session_id: str) -> NativeTestInputBinding:
"""Validate the exact current commit and complete local ownership chain.
Callers recheck permissions outside SQL; admission repeats this read while
holding its existing session lock. A retained input is never a current one.
"""
builder = session.get(DifyBuilderSession, session_id)
if builder is None:
raise BuilderExecutionPolicyError("native_owner_mismatch")
owner = load_execution_owners(session, actor=actor, app_id=builder.app_id, session_id=session_id)
try:
working = is_working(PcState(builder.current_state))
except ValueError:
raise BuilderExecutionPolicyError("native_session_busy") from None
if working:
raise BuilderExecutionPolicyError("native_session_busy")
commit = session.scalar(
select(DifyBuilderSessionCommit).where(
DifyBuilderSessionCommit.session_id == session_id,
DifyBuilderSessionCommit.version == builder.version,
)
)
input_id = commit.context.get("test_input_ref") if commit is not None else None
if not isinstance(input_id, str) or not input_id:
raise BuilderExecutionPolicyError("native_current_input_unavailable")
owner = load_execution_owners(
session, actor=actor, app_id=builder.app_id, session_id=session_id, test_input_id=input_id
)
if (
owner.test_input is None
or owner.app.mode != "workflow"
or owner.workflow.kind_or_standard != "standard"
or owner.test_input.start_schema_hash
!= schema_hash(start_schema(dict(owner.workflow.graph_dict), owner.app.mode))
):
raise BuilderExecutionPolicyError("native_revision_drift")
active = session.scalar(
select(DifyBuilderExecutionRequest.id)
.where(
DifyBuilderExecutionRequest.tenant_id == actor.tenant_id,
DifyBuilderExecutionRequest.actor_id == actor.account_id,
DifyBuilderExecutionRequest.session_id == session_id,
DifyBuilderExecutionRequest.test_input_id == input_id,
DifyBuilderExecutionRequest.state.in_(("prepared", "running", "waiting")),
)
.limit(1)
)
if active is not None:
raise BuilderExecutionPolicyError("native_session_busy")
return NativeTestInputBinding(
test_input_id=input_id,
expected_version=builder.version,
workflow_id=owner.workflow.id,
graph_revision=native_workflow_revision(owner.workflow),
)
class NativeTestInputBindingService:
def __init__(self, *, session_factory: sessionmaker[Session], actor: Actor):
self.sessions, self.actor = session_factory, actor
def get(self, *, session_id: str) -> NativeTestInputBinding:
with self.sessions() as session:
binding = current_input_binding(session=session, actor=self.actor, session_id=session_id)
builder = session.get(DifyBuilderSession, session_id)
assert builder is not None
app_id = builder.app_id
check_execution_permission(self.actor, app_id, self.sessions)
return binding