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:
DCCONSTRUCTIONS
2026-09-02 00:58:37 +03:00
parent d655d6998d
commit a945d665dd
47 changed files with 6008 additions and 1034 deletions
+55
View File
@@ -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: