feat(observatory): add installed package dispatch and durable publication
Checkpoint existing backend lifecycle changes. Focused verification found nine legacy fixture failures in portable LAB V1 executor/runtime tests; repair follows separately without rewriting this snapshot. ADR date retains its intentional Markdown hard break.
This commit is contained in:
@@ -47,6 +47,9 @@ from k1link.observatory.m49_queue_binding import (
|
||||
M49QueueBindingError,
|
||||
M49RecordedQueueBindingService,
|
||||
)
|
||||
from k1link.observatory.portable_publication_reconciler import (
|
||||
PortablePublicationReconciler,
|
||||
)
|
||||
from k1link.observatory.portable_queue_binding import (
|
||||
PortableQueueBindingError,
|
||||
PortableRecordedQueueBindingService,
|
||||
@@ -58,6 +61,7 @@ from k1link.observatory.portable_result_contract import (
|
||||
from k1link.observatory.portable_result_publisher import (
|
||||
resolve_published_portable_calculation_profile,
|
||||
)
|
||||
from k1link.observatory.portable_result_view import PortableResultViewService
|
||||
from k1link.observatory.portable_run_definitions import (
|
||||
PortableRunDefinitionRegistry,
|
||||
PortableRunDefinitionRegistryError,
|
||||
@@ -478,6 +482,18 @@ except (PortableWorkerIntegrationError, OSError, ValueError) as exc:
|
||||
# Failure remains isolated from K1, Simulation and legacy LAB.
|
||||
OBSERVATORY_PORTABLE_WORKER_INTEGRATION = None
|
||||
OBSERVATORY_PORTABLE_WORKER_INTEGRATION_ERROR = str(exc)
|
||||
OBSERVATORY_PUBLICATION_RECONCILER = (
|
||||
None
|
||||
if (
|
||||
OBSERVATORY_RECORDED_JOB_QUEUE is None
|
||||
or OBSERVATORY_PORTABLE_WORKER_INTEGRATION is None
|
||||
)
|
||||
else PortablePublicationReconciler(
|
||||
queue=OBSERVATORY_RECORDED_JOB_QUEUE,
|
||||
artifact_transport=OBSERVATORY_PORTABLE_WORKER_INTEGRATION.artifact_transport,
|
||||
result_publisher=OBSERVATORY_PORTABLE_WORKER_INTEGRATION.result_publisher,
|
||||
)
|
||||
)
|
||||
OBSERVATORY_WORKER_API_GATE_ENABLED = OBSERVATORY_WORKER_LOCAL_ENABLED
|
||||
OBSERVATORY_WORKER_CLAIM_LEASE_READY = (
|
||||
OBSERVATORY_WORKER_API_GATE_ENABLED
|
||||
@@ -539,6 +555,7 @@ try:
|
||||
registry=OBSERVATORY_PORTABLE_DEFINITION_REGISTRY,
|
||||
capability_probe=OBSERVATORY_PORTABLE_BINDING_SERVICE,
|
||||
dispatch_available=OBSERVATORY_WORKER_DISPATCH_READY,
|
||||
equipment_capture_registry=session_store.equipment_capture_registry,
|
||||
)
|
||||
OBSERVATORY_PORTABLE_SETUP_PROJECTOR_ERROR = None
|
||||
except (
|
||||
@@ -825,9 +842,22 @@ async def _recording_preparation_reconciler() -> None:
|
||||
await asyncio.sleep(2.0)
|
||||
|
||||
|
||||
async def _portable_result_publication_reconciler() -> None:
|
||||
service = OBSERVATORY_PUBLICATION_RECONCILER
|
||||
if service is None:
|
||||
return
|
||||
while True:
|
||||
# Durable state remains pending/failed and is retried on the next
|
||||
# bounded pass or through the explicit operator action.
|
||||
with suppress(OSError, ValueError):
|
||||
await asyncio.to_thread(service.run_once)
|
||||
await asyncio.sleep(15.0)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def app_lifespan(_: FastAPI) -> AsyncIterator[None]:
|
||||
reconciler: asyncio.Task[None] | None = None
|
||||
publication_reconciler: asyncio.Task[None] | None = None
|
||||
try:
|
||||
configure_scanner_diagnostics(session_store.data_dir / "logs")
|
||||
session_recording_preparation_manager.start()
|
||||
@@ -841,6 +871,9 @@ async def app_lifespan(_: FastAPI) -> AsyncIterator[None]:
|
||||
# expensive on field captures. Start it immediately in the background
|
||||
# instead of holding the ASGI startup gate.
|
||||
reconciler = asyncio.create_task(_recording_preparation_reconciler())
|
||||
publication_reconciler = asyncio.create_task(
|
||||
_portable_result_publication_reconciler()
|
||||
)
|
||||
yield
|
||||
finally:
|
||||
await map_gateway_proxy.close()
|
||||
@@ -848,6 +881,10 @@ async def app_lifespan(_: FastAPI) -> AsyncIterator[None]:
|
||||
reconciler.cancel()
|
||||
with suppress(asyncio.CancelledError):
|
||||
await reconciler
|
||||
if publication_reconciler is not None:
|
||||
publication_reconciler.cancel()
|
||||
with suppress(asyncio.CancelledError):
|
||||
await publication_reconciler
|
||||
await asyncio.to_thread(session_recording_preparation_manager.close)
|
||||
await asyncio.to_thread(lidar_local_surface_read_service.close)
|
||||
plugin_environment.close()
|
||||
@@ -1036,6 +1073,24 @@ app.include_router(
|
||||
portable_setup_projector=OBSERVATORY_PORTABLE_SETUP_PROJECTOR,
|
||||
portable_setup_projector_error=OBSERVATORY_PORTABLE_SETUP_PROJECTOR_ERROR,
|
||||
portable_binding_service=OBSERVATORY_PORTABLE_BINDING_SERVICE,
|
||||
portable_result_view=(
|
||||
None
|
||||
if session_artifact_gateway is None
|
||||
else PortableResultViewService(
|
||||
sessions=session_store,
|
||||
artifacts=session_artifact_gateway.store,
|
||||
)
|
||||
),
|
||||
portable_artifact_transport=(
|
||||
None
|
||||
if OBSERVATORY_PORTABLE_WORKER_INTEGRATION is None
|
||||
else OBSERVATORY_PORTABLE_WORKER_INTEGRATION.artifact_transport
|
||||
),
|
||||
portable_result_publisher=(
|
||||
None
|
||||
if OBSERVATORY_PORTABLE_WORKER_INTEGRATION is None
|
||||
else OBSERVATORY_PORTABLE_WORKER_INTEGRATION.result_publisher
|
||||
),
|
||||
)
|
||||
)
|
||||
if OBSERVATORY_WORKER_DISPATCH_READY:
|
||||
|
||||
Reference in New Issue
Block a user