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

@ -286,7 +286,7 @@
| task | ACTIVITY-WP-ADHOC-2026-09-14-T02 | done | — | workplans/ADHOC-2026-09-14.md |
| task | ACTIVITY-WP-ADHOC-2026-10-19-T01 | done | — | workplans/ADHOC-2026-10-19.md |
| task | ACTIVITY-WP-ADHOC-2026-10-19-T02 | done | — | workplans/ADHOC-2026-10-19.md |
| task | ACTIVITY-WP-ADHOC-2026-10-19-T03 | todo | — | workplans/ADHOC-2026-10-19.md |
| task | ACTIVITY-WP-ADHOC-2026-10-19-T03 | done | — | workplans/ADHOC-2026-10-19.md |
| task | T01 | done | — | workplans/custodian-WP-0001-temporal-backbone.md |
| task | T02 | done | — | workplans/custodian-WP-0001-temporal-backbone.md |
| task | T03 | done | — | workplans/custodian-WP-0001-temporal-backbone.md |

View file

@ -15,8 +15,12 @@ FRONTEND = {
str(uuid5(NAMESPACE, f'frontend-patterns-{cadence}')): (cadence, cron)
for cadence, cron in [('daily', '0 2 * * *'), ('weekly', '0 3 * * 2'), ('monthly', '0 9 1 * *')]
}
SINK = {'type': 'state-hub-progress', 'event_type': 'fep_feedback_intake',
'author': 'activity-core', 'topic_id': 'fe2aaa78-9c20-4feb-b3d2-4fe0529572a3'}
SINKS = {
cadence: {'type': 'state-hub-progress', 'event_type': event_type,
'author': 'activity-core', 'topic_id': 'fe2aaa78-9c20-4feb-b3d2-4fe0529572a3'}
for cadence, event_type in [('daily', 'fep_feedback_intake'), ('weekly', 'fep_improvement_review'),
('monthly', 'fep_monthly_review')]
}
def definition_matches(row):
@ -29,12 +33,14 @@ def definition_matches(row):
and row.trigger_config.get('cron_expression') == cron
and row.trigger_config.get('timezone') == 'Europe/Berlin'
and row.trigger_config.get('misfire_policy') == 'catchup_latest'
and row.trigger_config.get('catchup_window_seconds') == 86400
# Absent means the catchup_latest default (24h, schedule_manager); the
# schedule's actual catchup_window is checked separately.
and row.trigger_config.get('catchup_window_seconds') in (None, 86400)
and row.trigger_config.get('jitter_seconds', 0) == 0
and isinstance(instructions, list) and len(instructions) == 1
and instructions[0].get('id') == f'frontend-patterns-{cadence}-report'
and instructions[0].get('model') == 'deterministic'
and instructions[0].get('report_sinks') == [SINK])
and instructions[0].get('report_sinks') == [SINKS[cadence]])
def expanded(ranges, minimum, maximum):

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}

View file

@ -57,7 +57,7 @@ f9f2640 / `sha256:fdc631d7`. Evidence:
```task
id: ACTIVITY-WP-ADHOC-2026-10-19-T03
status: todo
status: done
priority: medium
state_hub_task_id: "0bfc9af8-1b75-59ab-91e6-cef5b09bc08d"
```
@ -71,3 +71,17 @@ three frontend-patterns definitions, while weekly and monthly use
`fep_improvement_review` and `fep_monthly_review`. Fix the probe to check the
per-cadence sinks, and decide whether to run an admin definition sync or
accept a missing `catchup_window_seconds` when the schedule enforces it.
2026-10-20: fixed in `release_health.py`.
- Each cadence now expects its own sink: daily `fep_feedback_intake`, weekly
`fep_improvement_review`, monthly `fep_monthly_review`. All sit on topic
`fe2aaa78` with author activity-core, as in the 2026-10-03
natural-cadence evidence.
- A row with no `catchup_window_seconds` is accepted, because the
`catchup_latest` policy's default in `schedule_manager` is 24h. An explicit
value other than 86400 is still refused, and the probe still checks the live
Temporal `catchup_window` separately. No admin definition sync is needed.
- Two new tests cover these cases. The full suite passes (877 passed,
1 skipped).
- Not deployed: the fix ships with the next image release, and live
`/release/health` stays false until then.