|
|
@@ -117,6 +117,8 @@ def test_real_postgres_mysql_minio_polars_cross_source_execution(tmp_path):
|
|
|
sample_crash_correlation_id = new_governance_uid()
|
|
|
receipt_correlation_id = new_governance_uid()
|
|
|
failed_receipt_correlation_id = new_governance_uid()
|
|
|
+ no_run_crash_correlation_id = new_governance_uid()
|
|
|
+ evidence_crash_correlation_id = new_governance_uid()
|
|
|
sql_binding_id = new_governance_uid()
|
|
|
prefix = f"rules/{correlation_id}/"
|
|
|
customer_table = "task5_polars_customers"
|
|
|
@@ -134,6 +136,9 @@ def test_real_postgres_mysql_minio_polars_cross_source_execution(tmp_path):
|
|
|
downstream_plan_id = new_governance_uid()
|
|
|
ledger_jti = None
|
|
|
retry_ledger_jti = None
|
|
|
+ no_run_crash_jti = None
|
|
|
+ evidence_crash_jti = None
|
|
|
+ gateway_candidate_id = None
|
|
|
|
|
|
try:
|
|
|
with postgres.begin() as connection:
|
|
|
@@ -480,10 +485,10 @@ def test_real_postgres_mysql_minio_polars_cross_source_execution(tmp_path):
|
|
|
"""
|
|
|
INSERT INTO public.dataflow_deployments
|
|
|
(id, dataflow_version_id, environment, deployment_config,
|
|
|
- status, activated_at)
|
|
|
+ status)
|
|
|
VALUES (CAST(:id AS uuid),
|
|
|
CAST(:dataflow_version_id AS uuid), 'test',
|
|
|
- '{}'::jsonb, 'active', CURRENT_TIMESTAMP)
|
|
|
+ '{}'::jsonb, 'disabled')
|
|
|
"""
|
|
|
),
|
|
|
{
|
|
|
@@ -702,6 +707,120 @@ def test_real_postgres_mysql_minio_polars_cross_source_execution(tmp_path):
|
|
|
"execution_plan_hash": compiled["plan_hash"],
|
|
|
},
|
|
|
}
|
|
|
+ from app.core.mcp.gateway import SchedulingGateway
|
|
|
+ from app.core.mcp.identity import AgentIdentity
|
|
|
+ from app.core.mcp.persistence import PostgresSchedulingPlanStore
|
|
|
+ from app.runner.api import create_runner_app
|
|
|
+ from app.runner.auth import TaskTokenIssuer, TaskTokenVerifier
|
|
|
+ from app.runner.ledger import PostgresTaskLedger
|
|
|
+ from app.runner.nodes import NodeRegistry
|
|
|
+ from app.runner.rule_evidence import PostgresRuleEvidenceWriter
|
|
|
+
|
|
|
+ class Audit:
|
|
|
+ def __init__(self):
|
|
|
+ self.events = []
|
|
|
+
|
|
|
+ def record(self, event):
|
|
|
+ self.events.append(event)
|
|
|
+
|
|
|
+ class CanaryEngine:
|
|
|
+ def __init__(self):
|
|
|
+ self.calls = []
|
|
|
+
|
|
|
+ def deploy_disabled(self, _definition):
|
|
|
+ return {"revision": "task6-real-gateway"}
|
|
|
+
|
|
|
+ def activate(self, namespace, flow_id):
|
|
|
+ self.calls.append(("activate", namespace, flow_id))
|
|
|
+
|
|
|
+ def execute(self, namespace, flow_id, inputs=None, **_options):
|
|
|
+ self.calls.append(
|
|
|
+ ("execute", namespace, flow_id, dict(inputs or {}))
|
|
|
+ )
|
|
|
+ return {"id": f"task6-canary-{correlation_id}"}
|
|
|
+
|
|
|
+ def deactivate(self, namespace, flow_id):
|
|
|
+ self.calls.append(("deactivate", namespace, flow_id))
|
|
|
+
|
|
|
+ task_secret = "task5-real-http-secret-value-32-bytes"
|
|
|
+ verifier = TaskTokenVerifier(task_secret)
|
|
|
+ gateway_engine = CanaryEngine()
|
|
|
+ gateway_store = PostgresSchedulingPlanStore(platform)
|
|
|
+ gateway = SchedulingGateway(
|
|
|
+ plans=gateway_store,
|
|
|
+ audit=Audit(),
|
|
|
+ engine=gateway_engine,
|
|
|
+ token_issuer=TaskTokenIssuer(task_secret),
|
|
|
+ )
|
|
|
+ gateway_identity = AgentIdentity(
|
|
|
+ subject="task6-real-scheduler",
|
|
|
+ roles=frozenset({"scheduler"}),
|
|
|
+ business_domains=frozenset({"sales"}),
|
|
|
+ environments=frozenset({"test"}),
|
|
|
+ correlation_id=correlation_id,
|
|
|
+ )
|
|
|
+ candidate = gateway.create_candidate_plan(
|
|
|
+ gateway_identity,
|
|
|
+ business_domain="sales",
|
|
|
+ environment="test",
|
|
|
+ workflow_spec={
|
|
|
+ "schema_version": "1.0",
|
|
|
+ "dataflow_uid": dataflow_uid,
|
|
|
+ "name": "Task 6 governed rule canary",
|
|
|
+ "nodes": [node],
|
|
|
+ "edges": [],
|
|
|
+ "parameters": {},
|
|
|
+ },
|
|
|
+ schedule_plan={
|
|
|
+ "schema_version": "1.0",
|
|
|
+ "timezone": "Asia/Shanghai",
|
|
|
+ "triggers": [{"type": "manual"}],
|
|
|
+ "max_concurrency": 1,
|
|
|
+ "conflict_policy": "skip",
|
|
|
+ "timeout_seconds": 600,
|
|
|
+ "retry": {
|
|
|
+ "max_attempts": 1,
|
|
|
+ "delay_seconds": 1,
|
|
|
+ },
|
|
|
+ "backfill": {"max_days": 1, "max_runs": 1},
|
|
|
+ },
|
|
|
+ )
|
|
|
+ gateway_candidate_id = candidate["candidate_id"]
|
|
|
+ with platform.begin() as connection:
|
|
|
+ connection.execute(
|
|
|
+ text(
|
|
|
+ """
|
|
|
+ UPDATE public.dataflow_workflow_versions
|
|
|
+ SET write_authorized = TRUE
|
|
|
+ WHERE id = CAST(:id AS uuid)
|
|
|
+ """
|
|
|
+ ),
|
|
|
+ {"id": gateway_candidate_id},
|
|
|
+ )
|
|
|
+ deployed = gateway.deploy_disabled_version(
|
|
|
+ gateway_identity,
|
|
|
+ candidate_id=gateway_candidate_id,
|
|
|
+ business_domain="sales",
|
|
|
+ environment="test",
|
|
|
+ )
|
|
|
+ assert deployed["deployment_id"] == deployment_id
|
|
|
+ assert deployed["candidate_id"] != deployed["deployment_id"]
|
|
|
+ gateway.run_canary(
|
|
|
+ gateway_identity,
|
|
|
+ candidate_id=gateway_candidate_id,
|
|
|
+ business_domain="sales",
|
|
|
+ environment="test",
|
|
|
+ inputs={},
|
|
|
+ )
|
|
|
+ execution_call = next(
|
|
|
+ call for call in gateway_engine.calls if call[0] == "execute"
|
|
|
+ )
|
|
|
+ task_token = execution_call[3]["dataops_task_tokens"][node["id"]]
|
|
|
+ issued_claims = verifier.verify(task_token, node=node)
|
|
|
+ assert issued_claims.deployment_id == deployment_id
|
|
|
+ assert issued_claims.workflow_version == 1
|
|
|
+ assert issued_claims.environment == "test"
|
|
|
+
|
|
|
executor = RulePlanExecutor(
|
|
|
PostgresRulePlanRepository(platform),
|
|
|
adapters={
|
|
|
@@ -770,24 +889,6 @@ def test_real_postgres_mysql_minio_polars_cross_source_execution(tmp_path):
|
|
|
assert repeated["artifact_ref"] == result["artifact_ref"]
|
|
|
assert "schema_fields" not in result
|
|
|
|
|
|
- from app.runner.api import create_runner_app
|
|
|
- from app.runner.auth import TaskTokenIssuer, TaskTokenVerifier
|
|
|
- from app.runner.ledger import PostgresTaskLedger
|
|
|
- from app.runner.nodes import NodeRegistry
|
|
|
- from app.runner.rule_evidence import PostgresRuleEvidenceWriter
|
|
|
-
|
|
|
- task_secret = "task5-real-http-secret-value-32-bytes"
|
|
|
- verifier = TaskTokenVerifier(task_secret)
|
|
|
- task_token = TaskTokenIssuer(task_secret).issue(
|
|
|
- task_uid=new_governance_uid(),
|
|
|
- dataflow_uid=dataflow_uid,
|
|
|
- deployment_id=deployment_id,
|
|
|
- environment="test",
|
|
|
- workflow_version=1,
|
|
|
- correlation_id=correlation_id,
|
|
|
- node=node,
|
|
|
- write_authorized=True,
|
|
|
- )
|
|
|
ledger_jti = verifier.verify(task_token, node=node).jti
|
|
|
retry_token = TaskTokenIssuer(task_secret).issue(
|
|
|
task_uid=new_governance_uid(),
|
|
|
@@ -802,6 +903,11 @@ def test_real_postgres_mysql_minio_polars_cross_source_execution(tmp_path):
|
|
|
retry_ledger_jti = verifier.verify(
|
|
|
retry_token, node=node
|
|
|
).jti
|
|
|
+ real_evidence_writer = PostgresRuleEvidenceWriter(
|
|
|
+ platform,
|
|
|
+ store,
|
|
|
+ sample_ttl_seconds=900,
|
|
|
+ )
|
|
|
evidenced_executor = RulePlanExecutor(
|
|
|
PostgresRulePlanRepository(platform),
|
|
|
adapters={
|
|
|
@@ -811,17 +917,107 @@ def test_real_postgres_mysql_minio_polars_cross_source_execution(tmp_path):
|
|
|
artifact_ttl_seconds=900,
|
|
|
)
|
|
|
},
|
|
|
- evidence_writer=PostgresRuleEvidenceWriter(
|
|
|
- platform,
|
|
|
- store,
|
|
|
- sample_ttl_seconds=900,
|
|
|
- ),
|
|
|
+ evidence_writer=real_evidence_writer,
|
|
|
)
|
|
|
+ real_ledger = PostgresTaskLedger(platform, lease_seconds=30)
|
|
|
runner_app = create_runner_app(
|
|
|
verifier=verifier,
|
|
|
- ledger=PostgresTaskLedger(platform),
|
|
|
+ ledger=real_ledger,
|
|
|
registry=NodeRegistry({"rule.apply": evidenced_executor}),
|
|
|
)
|
|
|
+ no_run_crash_token = TaskTokenIssuer(task_secret).issue(
|
|
|
+ task_uid=new_governance_uid(),
|
|
|
+ dataflow_uid=dataflow_uid,
|
|
|
+ deployment_id=deployment_id,
|
|
|
+ environment="test",
|
|
|
+ workflow_version=1,
|
|
|
+ correlation_id=no_run_crash_correlation_id,
|
|
|
+ node=node,
|
|
|
+ write_authorized=True,
|
|
|
+ )
|
|
|
+ no_run_claims = verifier.verify(no_run_crash_token, node=node)
|
|
|
+ no_run_crash_jti = no_run_claims.jti
|
|
|
+ evidence_crash_token = TaskTokenIssuer(task_secret).issue(
|
|
|
+ task_uid=new_governance_uid(),
|
|
|
+ dataflow_uid=dataflow_uid,
|
|
|
+ deployment_id=deployment_id,
|
|
|
+ environment="test",
|
|
|
+ workflow_version=1,
|
|
|
+ correlation_id=evidence_crash_correlation_id,
|
|
|
+ node=node,
|
|
|
+ write_authorized=True,
|
|
|
+ )
|
|
|
+ evidence_crash_claims = verifier.verify(
|
|
|
+ evidence_crash_token,
|
|
|
+ node=node,
|
|
|
+ )
|
|
|
+ evidence_crash_jti = evidence_crash_claims.jti
|
|
|
+
|
|
|
+ def task_binding(claims):
|
|
|
+ return {
|
|
|
+ "task_uid": claims.task_uid,
|
|
|
+ "dataflow_uid": claims.dataflow_uid,
|
|
|
+ "deployment_id": claims.deployment_id,
|
|
|
+ "environment": claims.environment,
|
|
|
+ "workflow_version": claims.workflow_version,
|
|
|
+ "correlation_id": claims.correlation_id,
|
|
|
+ "node_id": claims.node_id,
|
|
|
+ "node_type": claims.node_type,
|
|
|
+ "data_source_uid": None,
|
|
|
+ "idempotency_key": "customer_id",
|
|
|
+ }
|
|
|
+
|
|
|
+ assert real_ledger.claim(
|
|
|
+ no_run_crash_jti,
|
|
|
+ task_binding(no_run_claims),
|
|
|
+ expires_at=no_run_claims.expires_at,
|
|
|
+ )
|
|
|
+ assert real_ledger.claim(
|
|
|
+ evidence_crash_jti,
|
|
|
+ task_binding(evidence_crash_claims),
|
|
|
+ expires_at=evidence_crash_claims.expires_at,
|
|
|
+ )
|
|
|
+ real_evidence_writer.start(
|
|
|
+ component_binding_id=component_binding_id,
|
|
|
+ rule_version_id=rule_id,
|
|
|
+ plan_hash=compiled["plan_hash"],
|
|
|
+ correlation_id=evidence_crash_correlation_id,
|
|
|
+ dataflow_uid=dataflow_uid,
|
|
|
+ deployment_id=deployment_id,
|
|
|
+ environment="test",
|
|
|
+ workflow_version=1,
|
|
|
+ node_id=node["id"],
|
|
|
+ lease_owner=evidence_crash_jti,
|
|
|
+ )
|
|
|
+ with platform.begin() as connection:
|
|
|
+ connection.execute(
|
|
|
+ text(
|
|
|
+ """
|
|
|
+ UPDATE public.runner_task_executions
|
|
|
+ SET lease_expires_at =
|
|
|
+ CURRENT_TIMESTAMP - INTERVAL '1 second'
|
|
|
+ WHERE token_jti IN (
|
|
|
+ CAST(:no_run_jti AS uuid),
|
|
|
+ CAST(:evidence_jti AS uuid)
|
|
|
+ )
|
|
|
+ """
|
|
|
+ ),
|
|
|
+ {
|
|
|
+ "no_run_jti": no_run_crash_jti,
|
|
|
+ "evidence_jti": evidence_crash_jti,
|
|
|
+ },
|
|
|
+ )
|
|
|
+ connection.execute(
|
|
|
+ text(
|
|
|
+ """
|
|
|
+ UPDATE public.rule_runs
|
|
|
+ SET lease_expires_at =
|
|
|
+ CURRENT_TIMESTAMP - INTERVAL '1 second'
|
|
|
+ WHERE lease_owner = CAST(:jti AS uuid)
|
|
|
+ """
|
|
|
+ ),
|
|
|
+ {"jti": evidence_crash_jti},
|
|
|
+ )
|
|
|
with runner_app.test_client() as client:
|
|
|
http_result = client.post(
|
|
|
"/v1/tasks/execute",
|
|
|
@@ -847,6 +1043,22 @@ def test_real_postgres_mysql_minio_polars_cross_source_execution(tmp_path):
|
|
|
"parameters": {},
|
|
|
},
|
|
|
)
|
|
|
+ no_run_crash = client.post(
|
|
|
+ "/v1/tasks/execute",
|
|
|
+ json={
|
|
|
+ "task_token": no_run_crash_token,
|
|
|
+ "node": node,
|
|
|
+ "parameters": {},
|
|
|
+ },
|
|
|
+ )
|
|
|
+ evidence_crash = client.post(
|
|
|
+ "/v1/tasks/execute",
|
|
|
+ json={
|
|
|
+ "task_token": evidence_crash_token,
|
|
|
+ "node": node,
|
|
|
+ "parameters": {},
|
|
|
+ },
|
|
|
+ )
|
|
|
assert http_result.status_code == 200, http_result.get_json()
|
|
|
assert http_result.get_json()["output_artifact"] == result[
|
|
|
"artifact_ref"
|
|
|
@@ -861,6 +1073,43 @@ def test_real_postgres_mysql_minio_polars_cross_source_execution(tmp_path):
|
|
|
assert retried.get_json()["output_artifact"] == result[
|
|
|
"artifact_ref"
|
|
|
]
|
|
|
+ assert no_run_crash.status_code == 409
|
|
|
+ assert no_run_crash.get_json() == {
|
|
|
+ "error": "task execution outcome is unknown"
|
|
|
+ }
|
|
|
+ assert evidence_crash.status_code == 409
|
|
|
+ assert evidence_crash.get_json() == {
|
|
|
+ "error": "task execution outcome is unknown"
|
|
|
+ }
|
|
|
+ assert real_ledger.get(no_run_crash_jti).status == "unknown"
|
|
|
+ assert real_ledger.get(evidence_crash_jti).status == "unknown"
|
|
|
+ with platform.connect() as connection:
|
|
|
+ assert connection.execute(
|
|
|
+ text(
|
|
|
+ """
|
|
|
+ SELECT COUNT(*)
|
|
|
+ FROM public.rule_runs
|
|
|
+ WHERE correlation_id =
|
|
|
+ CAST(:correlation_id AS uuid)
|
|
|
+ """
|
|
|
+ ),
|
|
|
+ {"correlation_id": no_run_crash_correlation_id},
|
|
|
+ ).scalar_one() == 0
|
|
|
+ crash_evidence = connection.execute(
|
|
|
+ text(
|
|
|
+ """
|
|
|
+ SELECT status, commit_outcome
|
|
|
+ FROM public.rule_runs
|
|
|
+ WHERE correlation_id =
|
|
|
+ CAST(:correlation_id AS uuid)
|
|
|
+ """
|
|
|
+ ),
|
|
|
+ {"correlation_id": evidence_crash_correlation_id},
|
|
|
+ ).mappings().one()
|
|
|
+ assert dict(crash_evidence) == {
|
|
|
+ "status": "unknown",
|
|
|
+ "commit_outcome": "unknown",
|
|
|
+ }
|
|
|
ledger_record = PostgresTaskLedger(platform).get(ledger_jti)
|
|
|
assert ledger_record is not None
|
|
|
assert ledger_record.status == "success"
|
|
|
@@ -1269,6 +1518,21 @@ def test_real_postgres_mysql_minio_polars_cross_source_execution(tmp_path):
|
|
|
),
|
|
|
{"id": sample_crash_run_id},
|
|
|
).scalar_one() == "ready"
|
|
|
+ with platform.begin() as connection:
|
|
|
+ connection.execute(
|
|
|
+ text(
|
|
|
+ """
|
|
|
+ UPDATE public.rule_violation_samples
|
|
|
+ SET handoff_status = 'unknown'
|
|
|
+ WHERE rule_run_id = CAST(:id AS uuid)
|
|
|
+ """
|
|
|
+ ),
|
|
|
+ {"id": sample_crash_run_id},
|
|
|
+ )
|
|
|
+ sample_reconciliation = evidence_writer.reconcile_samples(
|
|
|
+ limit=10
|
|
|
+ )
|
|
|
+ assert sample_reconciliation["ready"] >= 1
|
|
|
evidence_writer.finish(
|
|
|
sample_crash_run_id,
|
|
|
sample_crash_evidence,
|
|
|
@@ -1411,6 +1675,43 @@ def test_real_postgres_mysql_minio_polars_cross_source_execution(tmp_path):
|
|
|
correlation_id=failed_receipt_correlation_id,
|
|
|
input_binding_id=sql_binding_id,
|
|
|
)
|
|
|
+ with platform.begin() as connection:
|
|
|
+ connection.execute(
|
|
|
+ text(
|
|
|
+ """
|
|
|
+ UPDATE public.rule_sql_staging_receipts
|
|
|
+ SET expires_at =
|
|
|
+ CURRENT_TIMESTAMP - INTERVAL '1 second'
|
|
|
+ WHERE correlation_id IN (
|
|
|
+ CAST(:ready AS uuid),
|
|
|
+ CAST(:failed AS uuid)
|
|
|
+ )
|
|
|
+ """
|
|
|
+ ),
|
|
|
+ {
|
|
|
+ "ready": receipt_correlation_id,
|
|
|
+ "failed": failed_receipt_correlation_id,
|
|
|
+ },
|
|
|
+ )
|
|
|
+ assert evidence_writer.cleanup_sql_staging(limit=10) == 2
|
|
|
+ with platform.connect() as connection:
|
|
|
+ assert connection.execute(
|
|
|
+ text(
|
|
|
+ """
|
|
|
+ SELECT COUNT(*)
|
|
|
+ FROM public.rule_sql_staging_receipts
|
|
|
+ WHERE correlation_id IN (
|
|
|
+ CAST(:ready AS uuid),
|
|
|
+ CAST(:failed AS uuid)
|
|
|
+ )
|
|
|
+ AND status = 'expired'
|
|
|
+ """
|
|
|
+ ),
|
|
|
+ {
|
|
|
+ "ready": receipt_correlation_id,
|
|
|
+ "failed": failed_receipt_correlation_id,
|
|
|
+ },
|
|
|
+ ).scalar_one() == 2
|
|
|
conflict_path = tmp_path / "conflict.parquet"
|
|
|
pl.DataFrame(
|
|
|
{
|
|
|
@@ -1499,6 +1800,18 @@ def test_real_postgres_mysql_minio_polars_cross_source_execution(tmp_path):
|
|
|
),
|
|
|
{"jti": retry_ledger_jti},
|
|
|
)
|
|
|
+ for crash_jti in (
|
|
|
+ no_run_crash_jti,
|
|
|
+ evidence_crash_jti,
|
|
|
+ ):
|
|
|
+ if crash_jti is not None:
|
|
|
+ connection.execute(
|
|
|
+ text(
|
|
|
+ "DELETE FROM public.runner_task_executions "
|
|
|
+ "WHERE token_jti = CAST(:jti AS uuid)"
|
|
|
+ ),
|
|
|
+ {"jti": crash_jti},
|
|
|
+ )
|
|
|
connection.execute(
|
|
|
text(
|
|
|
"""
|
|
|
@@ -1545,7 +1858,9 @@ def test_real_postgres_mysql_minio_polars_cross_source_execution(tmp_path):
|
|
|
"CAST(:lease_correlation_id AS uuid), "
|
|
|
"CAST(:sample_crash_correlation_id AS uuid), "
|
|
|
"CAST(:receipt_correlation_id AS uuid), "
|
|
|
- "CAST(:failed_receipt_correlation_id AS uuid))"
|
|
|
+ "CAST(:failed_receipt_correlation_id AS uuid), "
|
|
|
+ "CAST(:no_run_crash_correlation_id AS uuid), "
|
|
|
+ "CAST(:evidence_crash_correlation_id AS uuid))"
|
|
|
),
|
|
|
{
|
|
|
"correlation_id": correlation_id,
|
|
|
@@ -1559,6 +1874,12 @@ def test_real_postgres_mysql_minio_polars_cross_source_execution(tmp_path):
|
|
|
"failed_receipt_correlation_id": (
|
|
|
failed_receipt_correlation_id
|
|
|
),
|
|
|
+ "no_run_crash_correlation_id": (
|
|
|
+ no_run_crash_correlation_id
|
|
|
+ ),
|
|
|
+ "evidence_crash_correlation_id": (
|
|
|
+ evidence_crash_correlation_id
|
|
|
+ ),
|
|
|
},
|
|
|
)
|
|
|
connection.execute(
|
|
|
@@ -1611,6 +1932,35 @@ def test_real_postgres_mysql_minio_polars_cross_source_execution(tmp_path):
|
|
|
),
|
|
|
{"id": deployment_id},
|
|
|
)
|
|
|
+ if gateway_candidate_id is not None:
|
|
|
+ connection.execute(
|
|
|
+ text(
|
|
|
+ "DELETE FROM public.workflow_canary_evidence "
|
|
|
+ "WHERE workflow_version_id = CAST(:id AS uuid)"
|
|
|
+ ),
|
|
|
+ {"id": gateway_candidate_id},
|
|
|
+ )
|
|
|
+ connection.execute(
|
|
|
+ text(
|
|
|
+ "DELETE FROM public.workflow_schedules "
|
|
|
+ "WHERE workflow_version_id = CAST(:id AS uuid)"
|
|
|
+ ),
|
|
|
+ {"id": gateway_candidate_id},
|
|
|
+ )
|
|
|
+ connection.execute(
|
|
|
+ text(
|
|
|
+ "DELETE FROM public.workflow_engine_bindings "
|
|
|
+ "WHERE workflow_version_id = CAST(:id AS uuid)"
|
|
|
+ ),
|
|
|
+ {"id": gateway_candidate_id},
|
|
|
+ )
|
|
|
+ connection.execute(
|
|
|
+ text(
|
|
|
+ "DELETE FROM public.dataflow_workflow_versions "
|
|
|
+ "WHERE id = CAST(:id AS uuid)"
|
|
|
+ ),
|
|
|
+ {"id": gateway_candidate_id},
|
|
|
+ )
|
|
|
connection.execute(
|
|
|
text(
|
|
|
"DELETE FROM public.dataflow_versions "
|