KB-2FC6

IU Test b–f Command Pack — 06 Test e Trigger-In/Out Plan

7 min read Revision 1
iutest-etrigger-in-outevent-typeroutedeliverycommand-pack2026-05-28

06 — Test e: Trigger-In / Trigger-Out Plan (MUTATING, DELIVERY-GATED)

Goal: For an IU, prove trigger-in (an external/DB event activates IU-side logic) and trigger-out (an IU lifecycle change emits an event that is routed and delivered), end-to-end with idempotency, DLQ handling, and bounded delivery. Must NOT run before iu.* event-type registration (U6) and the bounded delivery gate (08).

Owner law: Điều 45 (event/queue/executor/route/heartbeat). Điều 35 (DOT), Điều 32 (approval to open delivery). Mutation: YES (event rows, route attempts; bounded). Readiness: MEDIUM.

1. Live substrate (verified)

  • Trigger-in: iu_sql_event_route (1 row), fn_iu_sql_link_inbound_capture.
  • Trigger-out: iu_outbound_route (15 rows), fn_iu_route_deliver, fn_iu_outbound_route_delivery_guard, fn_iu_emit_event, fn_iu_piece_emit_event.
  • Worker: fn_iu_route_worker_run, fn_iu_route_worker_enabled, fn_iu_route_worker_health; iu_route_attempt (68), iu_route_dead_letter (0), iu_route_worker_cursor.
  • Bus: event_outbox (148,076), event_read (147,715), event_pending (0), event_subscription (3), event_type_registry (31).
  • Replay: fn_iu_route_dead_letter_replay.
  • DOT: dot_iu_auto_instantiate_from_event, dot_iu_auto_instantiate_rollback_by_actor, iu.post_cut.axis_materialize.

2. Gating reality

  • iu_core.routes_master_enabled=true, iu_core.route_worker_enabled=true → worker runs.
  • iu_core.delivery_enabled=falseroutes are evaluated but not delivered; this is the key flip for test e.
  • iu_core.auto_instantiate_enabled=false → trigger-in auto-instantiate is off.
  • queue.job_substrate.enabled=false, queue.dlq.replay_enabled=false.
  • queue.heartbeat.enabled=true, stale threshold 300s.

3. Event types needed (U6 — registration prerequisite)

The 31 registered types include split, merged, structure_*, piece_*, collection_*, staging.* — but no per-IU trigger contract iu.*. U6 must register (document-only here; registration is forbidden in this macro) at least:

  • iu.trigger_in.received (an inbound event consumed by an IU)
  • iu.trigger_out.emitted (an IU emitted an outbound event)
  • iu.route.delivered / iu.route.failed (delivery outcome) Each with a JSON schema in event_type_registry, compat_mode=forward, refs-only payload (deny-list: body/payload/secret/vector — MP-D8).

4. Trigger-in path

external/DB change → iu_sql_event_route match → fn_iu_sql_link_inbound_capture → IU-side effect (e.g. envelope refresh request). Test injects a controlled inbound signal (against a test IU's iu_sql_event_route) and asserts capture is recorded idempotently (on idempotency_key) with no duplicate.

5. Trigger-out path

IU lifecycle change (e.g. piece reorder on a test collection) → fn_iu_piece_emit_event → event_outbox row (iu.trigger_out.emitted) → iu_outbound_route match → fn_iu_route_deliver (under delivery gate) → iu_route_attempt row → delivery outcome.

6. Route path / worker path

  • Route resolution via iu_outbound_route (15 live routes) filtered to the test route.
  • Worker fn_iu_route_worker_run consumes, advances iu_route_worker_cursor, writes iu_route_attempt.
  • fn_iu_outbound_route_delivery_guard enforces the delivery gate.

7. Delivery gate

iu_core.delivery_enabled bounded-open (protocol 08) scoped to a single test route if the guard supports route-scoped enablement; otherwise global flip for a strictly bounded window with all non-test routes verified inert (no pending real deliveries) before opening. Close immediately after; verify iu_route_attempt only gained the test rows.

8. DLQ / failure assertion

  • Force a delivery failure (test route pointed at an intentionally failing sink) → assert a iu_route_dead_letter row appears (first population of the 0-row table) with a classification (transient|permanent|poison).
  • Assert fn_iu_route_dead_letter_replay can replay the transient case idempotently (but queue.dlq.replay_enabled=false → replay itself is gated; document as bounded sub-step or defer).

9. Idempotency expectation

  • Trigger-in: re-injecting the same inbound signal (same idempotency_key) → no duplicate capture.
  • Trigger-out: re-emitting the same lifecycle event → event_subscription dispatch unique on (subscription_id, event_outbox_id); no duplicate iu_route_attempt.

10. route_attempt evidence

iu_route_attempt rows for the test route with status, classification, timestamps; before/after counts (baseline 68). dot_iu_command_run audit rows for each DOT call.

11. Cleanup / close

  • Retire the test route and test IU; remove test iu_route_attempt/iu_route_dead_letter rows or mark them test-evidence per Council choice.
  • Close delivery_enabled (and any other opened gate); verify closed; confirm no real route delivered during the window.

12. Exact PASS criteria

PASS iff: trigger-in capture is idempotent; trigger-out emits a registered iu.* event that routes and delivers to the test sink under a bounded delivery window; a forced failure produces a classified iu_route_dead_letter row; idempotency holds on re-run; the delivery gate is verified closed with zero real-route impact. Without U6 (event types) the test is BLOCKED; without the bounded delivery gate it is BLOCKED.

13. Failure modes

Mode Detection Handling
delivery gate flips a real route pre-open route inventory + post-check abort; force-close; incident
duplicate delivery (subscription_id,event_outbox_id) unique + attempt count FAIL idempotency
no DLQ on forced failure dead_letter count unchanged FAIL; worker not classifying
event type missing emit refused (unregistered) blocked on U6 (expected)
gate left open post-check incident; force-close

14. Why this must not run before event-type registration and gate protocol

Emitting iu.* events before they are registered in event_type_registry either fails (register-before-emit) or pollutes the 148k-row bus with unschema'd events — a Điều 45 violation. Opening delivery_enabled without the bounded protocol risks delivering across the 15 live routes to real sinks (side effects beyond the test). Hence test e sits behind U6 + macro 3 (gate protocol) + macro 5 (event contract).

Back to Knowledge Hub knowledge/dev/reports/architecture/iu-test-b-to-f-readiness-command-pack-2026-05-28/06-test-e-trigger-in-out-plan.md