@@ -349,6 +349,98 @@ def test_confirm_mode_without_confirm_fn_defaults_to_not_placed(repo):
349349 assert len (broker .place_calls ) == 0
350350
351351
352+ # -- rule_id metadata (Phase-2 debt: orders.rule_id was always written NULL) ----------------------
353+
354+
355+ def test_placed_order_carries_the_signals_rule_id (repo ):
356+ """The fix under test: `orders.rule_id` used to be hardcoded `None` in `_order_row`
357+ regardless of the signal. It must now carry `signal.rule_id` end to end through
358+ `_build_intent`'s `OrderIntent.rule_id`.
359+ """
360+ rule_id = repo .insert_rule ("pullback_continuation" , {}, status = "live" )
361+ broker = FakeBroker ()
362+ signal = _enter_signal (rule_id = rule_id )
363+
364+ result = execute (signal , broker , repo , _config (), mode = "autonomous" , now_ts = NOW_TS )
365+
366+ assert result .placed is True
367+ order = repo .get_order (result .order_id )
368+ assert order ["rule_id" ] == rule_id
369+
370+
371+ def test_a_signal_with_no_rule_id_still_writes_none (repo ):
372+ """Backward-compat: a signal from a hand-constructed rule (no `rule_id` supplied, the
373+ default) still writes `NULL`, exactly as before this fix."""
374+ broker = FakeBroker ()
375+ signal = _enter_signal () # no rule_id override -> defaults to None
376+
377+ result = execute (signal , broker , repo , _config (), mode = "autonomous" , now_ts = NOW_TS )
378+
379+ assert result .placed is True
380+ order = repo .get_order (result .order_id )
381+ assert order ["rule_id" ] is None
382+
383+
384+ def test_rule_id_is_purely_additive_metadata_placement_and_guards_are_unchanged (repo ):
385+ """The metadata-only guarantee: two otherwise-identical signals, differing only in
386+ `rule_id`, must produce byte-for-byte identical guard/placement outcomes -- same veto
387+ decisions, same `placed`, same broker calls/order-configuration, same sized qty/notional.
388+ The ONLY difference in the resulting order rows is the `rule_id` column.
389+ """
390+ rule_id = repo .insert_rule ("pullback_continuation" , {}, status = "live" )
391+ broker_a = FakeBroker ()
392+ broker_b = FakeBroker ()
393+ signal_no_id = _enter_signal (rule_id = None )
394+ signal_with_id = _enter_signal (rule_id = rule_id )
395+
396+ result_a = execute (signal_no_id , broker_a , repo , _config (), mode = "autonomous" , now_ts = NOW_TS )
397+ result_b = execute (
398+ signal_with_id , broker_b , repo , _config (), mode = "autonomous" , now_ts = NOW_TS + 1
399+ )
400+
401+ assert result_a .placed == result_b .placed is True
402+ assert result_a .vetoed_by == result_b .vetoed_by == []
403+ assert len (broker_a .preview_calls ) == len (broker_b .preview_calls )
404+ assert len (broker_a .place_calls ) == len (broker_b .place_calls )
405+ assert (
406+ broker_a .place_calls [0 ]["order_configuration" ]
407+ == broker_b .place_calls [0 ]["order_configuration" ]
408+ )
409+
410+ order_a = repo .get_order (result_a .order_id )
411+ order_b = repo .get_order (result_b .order_id )
412+ # Every field EXCEPT rule_id/created_at/updated_at/id must match -- proving rule_id is the
413+ # only thing that changed.
414+ for field in (
415+ "mode" ,
416+ "product_id" ,
417+ "side" ,
418+ "order_type" ,
419+ "qty" ,
420+ "status" ,
421+ "confirmation" ,
422+ ):
423+ assert order_a [field ] == order_b [field ], f"{ field } differs -- not metadata-only"
424+ assert order_a ["rule_id" ] is None
425+ assert order_b ["rule_id" ] == rule_id
426+
427+
428+ def test_rail_violating_signal_with_a_rule_id_is_still_vetoed_the_same_way (repo ):
429+ """`rule_id` must not influence guard decisions -- a vetoed intent stays vetoed."""
430+ rule_id = repo .insert_rule ("pullback_continuation" , {}, status = "live" )
431+ broker = NoNetworkBroker ()
432+ signal = _enter_signal (
433+ product_id = "DOGE-USD" , setup = _setup (product_id = "DOGE-USD" ), rule_id = rule_id
434+ )
435+
436+ result = execute (signal , broker , repo , _config (), mode = "autonomous" , now_ts = NOW_TS )
437+
438+ assert result .placed is False
439+ assert result .order_id is None
440+ assert any (v .startswith ("halal_allowlist" ) for v in result .vetoed_by )
441+ assert repo .get_orders () == []
442+
443+
352444# -- rail-violating signal -> vetoed, never previews/places --------------------------------------
353445
354446
0 commit comments