@@ -40,6 +40,7 @@ def _trigger_request(*candidates: dict[str, object]) -> dict[str, object]:
4040 },
4141 "trigger_policy" : {
4242 "enabled_kinds" : [
43+ "bounded_segment_milestone" ,
4344 "cadence_due" ,
4445 "manual" ,
4546 "material_blocker" ,
@@ -242,6 +243,119 @@ def test_vision_requires_validated_closure_and_continuation() -> None:
242243 assert payload ["report_kind" ] == "milestone_update"
243244
244245
246+ @pytest .mark .parametrize ("transition" , ["segment_completed" , "replan_entered" ])
247+ def test_bounded_segment_with_remaining_todos_triggers_milestone_update (
248+ transition : str ,
249+ ) -> None :
250+ request = _trigger_request (
251+ _candidate (
252+ "cadence_due" ,
253+ source_ref = "scheduler:weekly-window/2026-w29" ,
254+ evidence_digest = "sha256:weekly-due" ,
255+ facts = {"due" : True },
256+ ),
257+ _candidate (
258+ "bounded_segment_milestone" ,
259+ source_ref = "segment:2026-w29" ,
260+ evidence_digest = f"sha256:{ transition } " ,
261+ facts = {
262+ "segment_ref" : "week-2026-w29" ,
263+ "transition" : transition ,
264+ "delivered_count" : 4 ,
265+ "remaining_todo_count" : 12 ,
266+ "durable_writeback" : True ,
267+ },
268+ ),
269+ )
270+
271+ payload = build_periodic_report_trigger_decision (request )
272+ run = build_periodic_report_run (_run_request (payload ))
273+
274+ assert payload ["eligible" ] is True
275+ assert payload ["selected_trigger_kind" ] == "bounded_segment_milestone"
276+ assert payload ["report_kind" ] == "milestone_update"
277+ assert len (payload ["coalesced_trigger_ids" ]) == 2
278+ assert run ["trigger_receipt" ]["report_kind" ] == "milestone_update"
279+
280+
281+ @pytest .mark .parametrize (
282+ ("transition" , "durable_writeback" , "reason" ),
283+ [
284+ ("vision_checkpoint" , True , "segment_checkpoint_only" ),
285+ ("replan_entered" , False , "segment_writeback_pending" ),
286+ ],
287+ )
288+ def test_bounded_segment_requires_material_transition_and_writeback (
289+ transition : str ,
290+ durable_writeback : bool ,
291+ reason : str ,
292+ ) -> None :
293+ payload = build_periodic_report_trigger_decision (
294+ _trigger_request (
295+ _candidate (
296+ "bounded_segment_milestone" ,
297+ source_ref = "segment:2026-w29" ,
298+ evidence_digest = f"sha256:{ transition } " ,
299+ facts = {
300+ "segment_ref" : "week-2026-w29" ,
301+ "transition" : transition ,
302+ "delivered_count" : 4 ,
303+ "remaining_todo_count" : 12 ,
304+ "durable_writeback" : durable_writeback ,
305+ },
306+ )
307+ )
308+ )
309+
310+ assert payload ["eligible" ] is False
311+ assert payload ["suppressed_triggers" ][0 ]["reason" ] == reason
312+
313+
314+ @pytest .mark .parametrize (
315+ "facts, error" ,
316+ [
317+ (
318+ {
319+ "transition" : "segment_completed" ,
320+ "durable_writeback" : True ,
321+ },
322+ "segment_ref is required" ,
323+ ),
324+ (
325+ {
326+ "segment_ref" : "week-2026-w29" ,
327+ "transition" : "unknown_transition" ,
328+ "durable_writeback" : True ,
329+ },
330+ "transition is invalid" ,
331+ ),
332+ (
333+ {
334+ "segment_ref" : "week-2026-w29" ,
335+ "transition" : "segment_completed" ,
336+ "remaining_todo_count" : - 1 ,
337+ "durable_writeback" : True ,
338+ },
339+ "remaining_todo_count must be between" ,
340+ ),
341+ ],
342+ )
343+ def test_bounded_segment_facts_are_strictly_validated (
344+ facts : dict [str , object ], error : str
345+ ) -> None :
346+ with pytest .raises (ValueError , match = error ):
347+ build_periodic_report_trigger_decision (
348+ _trigger_request (
349+ _candidate (
350+ "bounded_segment_milestone" ,
351+ source_ref = "segment:2026-w29" ,
352+ evidence_digest = "sha256:invalid-segment" ,
353+ facts = facts ,
354+ )
355+ )
356+ )
357+
358+
245359def test_urgent_blocker_bypasses_cooldown_and_coalesces_material_decision () -> None :
246360 request = _trigger_request (
247361 _candidate (
0 commit comments