From db32450c68312d57726aabdddc38a72ebc210fc6 Mon Sep 17 00:00:00 2001 From: Carla Pinto Date: Wed, 30 Sep 2026 19:40:03 +0200 Subject: [PATCH] feature-rc/SIENTIAPDE-2164 -fix: apply Temporal replay-safe unpacking and guard against missing filter counters (review findings) --- laborious/activities/gates.py | 8 +++++++- .../workflows/sub_workflows/prediction_process.py | 13 +++++++++++-- tests/laborious/activities/test_gates.py | 9 ++++++++- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/laborious/activities/gates.py b/laborious/activities/gates.py index 28e63d5..7cbab54 100644 --- a/laborious/activities/gates.py +++ b/laborious/activities/gates.py @@ -362,7 +362,13 @@ class Gates(SientiaMonitoring): { 'name': fil, 'policy': path_flag, - 'counter': input_filter_counters[fil](data, filter_config), + # `.get(..., lambda *_: 0)`, not `[fil]`: a filter added to + # `input_filter_functions` without a matching counter here degrades to + # counter=0 instead of a raw KeyError crashing this prediction (review + # finding, 2026-09-30 — see test enforcing key parity between the two). + 'counter': input_filter_counters.get(fil, lambda *_: 0)( + data, filter_config + ), } for fil, filter_config in matched ], diff --git a/laborious/workflows/sub_workflows/prediction_process.py b/laborious/workflows/sub_workflows/prediction_process.py index 391be1c..67b7fd6 100644 --- a/laborious/workflows/sub_workflows/prediction_process.py +++ b/laborious/workflows/sub_workflows/prediction_process.py @@ -133,8 +133,17 @@ class PredictionProcess: } # `_metadata_id` (prediction_metadata document id, only non-None when path_flag ends up - # CONTINUE) isn't threaded through yet — SIENTIAPDE-2167 wires it to Postgres. - path_flag, confidence, comment, _metadata_id = await workflow.execute_local_activity_method( + # CONTINUE) isn't threaded through yet — SIENTIAPDE-2167 wires it to Postgres. Star-unpack + # rather than a fixed 4-tuple: `input_gate` is a local activity, so its result is recorded + # in workflow history; an execution already in flight when this field was added would + # replay a 3-element result here, and exact unpacking would raise ValueError on replay + # (review finding, 2026-09-30). + ( + path_flag, + confidence, + comment, + *_metadata_id, + ) = await workflow.execute_local_activity_method( Activities.input_gate, gate_input, retry_policy=retry_policy, diff --git a/tests/laborious/activities/test_gates.py b/tests/laborious/activities/test_gates.py index 5b581e4..54bf66b 100644 --- a/tests/laborious/activities/test_gates.py +++ b/tests/laborious/activities/test_gates.py @@ -6,7 +6,7 @@ from sientia_do.notifications.models import NotificationLevel from sientia_do.observability.metrics_controller import MetricsController from sientia_do.observability.sientia_monitoring import SientiaMonitoring -from laborious.activities.gates import Gates +from laborious.activities.gates import Gates, input_filter_counters, input_filter_functions @fixture(autouse=True) @@ -136,6 +136,13 @@ def test_write_prediction_metadata_insert_failure_swallowed(gates_activity, mong gates_activity.error.assert_called_once() +def test_input_filter_counters_covers_every_input_filter_function(): + # A filter added to input_filter_functions without a matching entry here would degrade to + # counter=0 (see the `.get(..., lambda *_: 0)` fallback in input_gate) instead of crashing — + # but this test should still catch the gap before it ships (review finding, 2026-09-30). + assert set(input_filter_counters) == set(input_filter_functions) + + def test_input_gate_invalid_filter(gates_activity, mongo: MagicMock): # Arrange input_data = {