feat(workbench): add dynamic task approval lifecycle
This commit is contained in:
@@ -6,10 +6,14 @@ import argparse
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
from uuid import NAMESPACE_URL, uuid5
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from uuid import NAMESPACE_URL, UUID, uuid5
|
||||
|
||||
import httpx
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
|
||||
from samples.peripheral.seed_identity import SEED_ACTOR_ID
|
||||
|
||||
try:
|
||||
@@ -24,6 +28,16 @@ STATUS = {"draft": "scheduled", "open": "assigned", "in_progress": "in_progress"
|
||||
EVENT_WORK_ORDER_TYPES = frozenset({"water_quality_complaint", "burst_report"})
|
||||
|
||||
|
||||
def peripheral_order_id(external_id: str | None) -> str | None:
|
||||
"""Return only canonical IDs owned by this synchronizer's namespace."""
|
||||
if not external_id or not external_id.startswith("peripheral_sample:"):
|
||||
return None
|
||||
try:
|
||||
return str(UUID(external_id.removeprefix("peripheral_sample:")))
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def should_project_work_order(order: dict, linked_order_ids: set[str]) -> bool:
|
||||
"""Exclude retired seed checks while preserving user and workflow orders."""
|
||||
work_order_id = order["work_order_id"]
|
||||
@@ -87,6 +101,7 @@ async def sync_once(client: httpx.AsyncClient, settings: WorkerSettings, periphe
|
||||
linked_order_ids = set(linked)
|
||||
|
||||
cursor, synchronized = None, 0
|
||||
seen_order_ids: set[str] = set()
|
||||
while True:
|
||||
params = {"limit": 100}
|
||||
if cursor:
|
||||
@@ -98,6 +113,7 @@ async def sync_once(client: httpx.AsyncClient, settings: WorkerSettings, periphe
|
||||
for order in page["items"]:
|
||||
if order["project_id"] != settings.project_id or order["origin"] != "synthetic":
|
||||
raise ValueError("peripheral work order scope or origin mismatch")
|
||||
seen_order_ids.add(order["work_order_id"])
|
||||
if not should_project_work_order(order, linked_order_ids):
|
||||
continue
|
||||
external_id = f"peripheral_sample:{order['work_order_id']}"
|
||||
@@ -162,7 +178,37 @@ async def sync_once(client: httpx.AsyncClient, settings: WorkerSettings, periphe
|
||||
synchronized += 1
|
||||
cursor = page.get("next_cursor")
|
||||
if not cursor:
|
||||
return synchronized
|
||||
break
|
||||
|
||||
for external_id, task in existing.items():
|
||||
order_id = peripheral_order_id(external_id)
|
||||
if order_id is None:
|
||||
continue
|
||||
if order_id in seen_order_ids:
|
||||
continue
|
||||
# The list may change during pagination. Only a scoped 404 proves that
|
||||
# the source order is now absent; transient errors must stop this tick.
|
||||
response = await client.get(
|
||||
f"{peripheral_url.rstrip('/')}/api/sample/v1/work-orders/{order_id}",
|
||||
headers=headers,
|
||||
)
|
||||
if response.status_code == 200:
|
||||
continue
|
||||
if response.status_code != 404:
|
||||
response.raise_for_status()
|
||||
if response.json().get("code") != "WORK_ORDER_NOT_FOUND":
|
||||
raise ValueError("peripheral work order lookup did not confirm deletion")
|
||||
result = await client.post(
|
||||
f"{tasks_url}/{task['task_id']}/withdraw-external-source",
|
||||
headers=headers,
|
||||
json={
|
||||
"request_id": str(uuid5(NAMESPACE_URL, f"{settings.project_id}:{external_id}:withdraw")),
|
||||
"source_event_id": external_id,
|
||||
},
|
||||
)
|
||||
result.raise_for_status()
|
||||
synchronized += 1
|
||||
return synchronized
|
||||
|
||||
|
||||
async def run(once: bool):
|
||||
|
||||
Reference in New Issue
Block a user