QTZPOC-20

QTZPOC-20: Implement .sientia model bundle import workflow

Introduces a new Temporal workflow and activities to import .sientia model bundles. This enables:
- Secure decryption and validation of bundles through a seven-gate process.
- Provisioning imported models in MLflow and updating the MongoDB model listing.
- Comprehensive status tracking in the public.experiment_run table with new IMPORT_SUCCESS/IMPORT_ERROR states.
- Dedicated metrics, robust error handling, and configurable settings.
- Includes updated e2e database fixtures and extensive unit/integration tests for bundle format, reader, and overall workflow.
This commit is contained in:
2026-08-25 10:40:46 -03:00
parent 291d16efa5
commit 0e445de386
27 changed files with 4471 additions and 11 deletions

View File

@@ -10,6 +10,8 @@ The worker supports multiple runtime-scoped task queues (via ``sientia_do.tempor
- minimal_retrain-{runtime}-queue: Model retraining workflows
- drift-{runtime}-queue: Drift detection workflows
- simple_metrics-{runtime}-queue: Simple metrics workflows
- import_model-{runtime}-queue: `.sientia` model import — registered **only** when
``IMPORT_MODEL_ENABLED`` is truthy, so the containment boundary stays a deployment decision
``RUNTIME`` must be set; it is passed to every ``prepare_worker`` call. Schedulers must use the
same queue names (breaking change vs legacy ``drift-queue`` / ``simple_metrics-queue``).
@@ -30,6 +32,9 @@ Environment Variables:
- HTTP_METRICS_PORT: Prometheus metrics server port (default: 9090)
- HTTP_SDK_METRICS_PORT: Temporal SDK metrics port (default: 9091)
- PROJECT_NAME: Project name for notifications (default: laborious)
- IMPORT_MODEL_ENABLED: register the fifth (import) worker; unset by default
- IMPORT_PASSWORD_KEY / IMPORT_BUNDLE_* / IMPORT_STATUS_DB_*: import configuration, see
``laborious.utils.connectors_config``
"""
from temporalio import client, workflow
@@ -53,11 +58,14 @@ with workflow.unsafe.imports_passed_through():
from laborious import metrics
from laborious.activities.activities import Activities
from laborious.utils.connectors_config import (
build_import_config,
build_import_status_config,
build_minio_config,
build_mlflow_config,
build_opc_config,
)
from laborious.workflows.drift import Drift
from laborious.workflows.import_model import ImportModel
from laborious.workflows.minimal_retrain import MinimalRetrain
from laborious.workflows.predictions_batch import PredictionsBatch
from laborious.workflows.simple_metrics import SimpleMetrics
@@ -137,6 +145,9 @@ async def main():
pi_web_api_config=build_api_config(),
logger=logger,
notification_handler=notification_handler,
mongo_config=mongo_config,
import_config=build_import_config(),
import_status_config=build_import_status_config(),
)
logger.custom_info('Initializing OPC...', metadata_runtime)
@@ -235,6 +246,42 @@ async def main():
),
]
# The fifth worker is opt-in: containment (QTZPOC-19) wants the importer in its own runtime with
# its own credentials, and a flag lets that be a deployment decision instead of forcing every
# Laborious runtime to poll the import queue today. Unset, the four workers above are unaffected
# and no import queue is polled at all.
if os.getenv('IMPORT_MODEL_ENABLED', '').strip().lower() in ('1', 'true', 'yes', 'on'):
logger.custom_info('Import model worker enabled', metadata_runtime)
workers.append(
prepare_worker(
temporal_client=temporal_client,
main_workflow=ImportModel,
other_workflows=[],
activities=[
# Import log row (the BFF database, update-only)
activities.claim_import_status,
activities.record_import_status,
activities.record_import_names,
activities.record_import_terminal_status,
activities.report_import_status_write_failure,
# Bundle
activities.download_import_bundle,
activities.open_import_bundle,
# MLflow provisioning
activities.create_import_experiment,
activities.upload_import_artifacts,
activities.register_import_model_version,
# The model listing
activities.write_import_model_document,
# Cleanup and retention
activities.cleanup_import_files,
activities.ensure_import_bundle_retention,
],
logger=logger,
runtime=runtime,
)
)
handlers = []
for w in workers:
handlers.append(w.run())