IU Test b–f Command Pack — 06 Test e Trigger-In/Out Plan
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=false→ routes 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 inevent_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_runconsumes, advancesiu_route_worker_cursor, writesiu_route_attempt. fn_iu_outbound_route_delivery_guardenforces 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_letterrow appears (first population of the 0-row table) with a classification (transient|permanent|poison). - Assert
fn_iu_route_dead_letter_replaycan replay the transient case idempotently (butqueue.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_subscriptiondispatch unique on(subscription_id, event_outbox_id); no duplicateiu_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_letterrows 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).