fix(release-health): per-cadence report sinks, accept default catch-up window
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Build and Publish Container Image / build-and-push (push) Successful in 1m11s

The probe expected the daily fep_feedback_intake sink on all three
frontend-patterns definitions and required an explicit
catchup_window_seconds that live rows do not carry. It now checks each
cadence's own sink, and accepts an absent window, which resolves to the
catchup_latest 24h default (the schedule's catchup_window is still
checked). Closes ACTIVITY-WP-ADHOC-2026-10-19-T03.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 244572@bnt-lap001
Assistant-Session: 3b235e1a-ae45-4d0f-ae70-17a9bd78ece8
This commit is contained in:
tegwick 2026-10-20 00:36:24 +02:00
parent bbe2081a2e
commit b186746f4a
4 changed files with 43 additions and 9 deletions

View file

@ -11,13 +11,13 @@ from temporalio.api.common.v1 import Payloads, WorkflowType
from temporalio.api.taskqueue.v1 import TaskQueue
from temporalio.api.workflow.v1 import NewWorkflowExecutionInfo
from activity_core.release_health import FRONTEND, SINK, definition_matches, probe, schedule_matches
from activity_core.release_health import FRONTEND, SINKS, definition_matches, probe, schedule_matches
def rows():
return [SimpleNamespace(id=UUID(identity),enabled=True,trigger_type='cron',
trigger_config={'cron_expression':cron,'timezone':'Europe/Berlin','misfire_policy':'catchup_latest','catchup_window_seconds':86400},
instructions_json=[{'id':f'frontend-patterns-{cadence}-report','model':'deterministic','report_sinks':[dict(SINK)]}])
instructions_json=[{'id':f'frontend-patterns-{cadence}-report','model':'deterministic','report_sinks':[dict(SINKS[cadence])]}])
for identity,(cadence,cron) in FRONTEND.items()]
@ -74,7 +74,7 @@ async def test_actual_describe_payload_arguments_are_decoded_with_bound_client_c
@pytest.mark.parametrize('field,value',[('enabled',False),('trigger_type','event'),
('instructions_json',[{'model':'llm','report_sinks':[SINK]}])])
('instructions_json',[{'model':'llm','report_sinks':[SINKS['daily']]}])])
async def test_definition_behavior_drift_refused(field,value):
data=rows();setattr(data[0],field,value)
assert (await probe(*sources(items=data)))['healthy'] is False
@ -87,6 +87,20 @@ def test_trigger_scope_drift_refused(field,value):
assert not definition_matches(row)
async def test_live_rows_without_explicit_catchup_window_use_policy_default():
data=rows()
for row in data:del row.trigger_config['catchup_window_seconds']
assert (await probe(*sources(items=data)))['healthy'] is True
def test_each_cadence_requires_its_own_report_sink():
for row in rows():
cadence=FRONTEND[str(row.id)][0]
other=next(c for c in SINKS if c!=cadence)
row.instructions_json[0]['report_sinks']=[dict(SINKS[other])]
assert not definition_matches(row)
@pytest.mark.parametrize('change',['pause','limit','extra_calendar','interval','skip','cron','queue','args','sink','policy','timezone'])
async def test_schedule_and_report_drift_fail_closed(change):
data=rows();descriptions={identity:description(identity,True) for identity in FRONTEND}