11000x · 02 — Migration 029: Piece Event Runtime substrate (LIVE applied)
11000x · 02 — Migration 029 substrate (LIVE applied)
Files
sql/iu-core/029_piece_event_runtime_substrate.sql— applied LIVEsql/iu-core/rollback/029_piece_event_runtime_substrate.rollback.sql— READY
What it ships (all in public)
| Object | Class | Purpose |
|---|---|---|
v_piece_event_outbox |
VIEW | filter event_outbox to event_domain='piece' |
fn_iu_piece_emit_event(text,text,uuid,text,jsonb) |
FUNCTION | gated emitter — parallel to fn_iu_emit_event but hardwired to event_domain='piece' |
fn_iu_lifecycle_log_emit_piece_event_trg() |
FUNCTION | trigger fn — maps transition_type → piece.* event_type and PERFORMs fn_iu_piece_emit_event |
fn_iu_piece_event_runtime_healthcheck() |
FUNCTION | 8th healthcheck surface — returns jsonb verdict |
trg_iu_lifecycle_log_emit_piece_event |
TRIGGER | AFTER INSERT on iu_lifecycle_log FOR EACH ROW |
piece_event_runtime.emit_enabled |
CONFIG | default 'false' (runtime gate) |
piece_event_runtime.dry_run_only |
CONFIG | default 'true' (discriminator) |
Live apply transcript
BEGIN
INSERT 0 2
CREATE VIEW
COMMENT
CREATE FUNCTION
COMMENT
CREATE FUNCTION
COMMENT
DROP TRIGGER
NOTICE: trigger "trg_iu_lifecycle_log_emit_piece_event" for relation "public.iu_lifecycle_log" does not exist, skipping
CREATE TRIGGER
WARNING: [TRIGGER-GUARD] DDL detected: CREATE TRIGGER on trg_iu_lifecycle_log_emit_piece_event on public.iu_lifecycle_log
COMMENT
CREATE FUNCTION
COMMENT
DO
COMMIT
The TRIGGER-GUARD WARNING is an audit, not a failure — DDL on iu_lifecycle_log is intentionally announced. The COMMIT confirms the in-migration DO $$ ... fn_iu_piece_event_runtime_healthcheck() ... $$ post-apply assertion passed.
Healthcheck verdict immediately after apply
{
"surface": "piece_event_runtime",
"ok": true,
"view_exists": true,
"fn_emit_exists": true,
"fn_trg_exists": true,
"trigger_exists": true,
"gate_keys": 2,
"piece_event_types_active": 6,
"emit_enabled": "false",
"dry_run_only": "true"
}
Invariants enforced by the substrate
- Master gate first:
fn_iu_core_routes_enabled()is checked BEFORE the runtime gate, so the existing IU-Core kill switch still works. - Runtime gate before INSERT:
piece_event_runtime.emit_enabledis checked BEFORE the registry lookup, BEFORE theINSERT INTO event_outbox. Confirmed by the pinning testtest_gate_short_circuits_before_insert. - Registry-driven:
event_streamanddelivery_laneare sourced fromevent_type_registry(never hardcoded in the fn body), in line with thetrg_event_outbox_type_validateconstraint pattern. - Idempotency at outbox layer:
ON CONFLICT DO NOTHING. - emit_mode stamped in
safe_payload:'dry_run'whendry_run_only=true,'live'whenfalse. Downstream subscribers can route accordingly. - Transition mapping is exhaustive on the 3 piece-lifecycle transitions only:
supersede→piece.supersededsplit→piece.splitmerge→piece.mergedOthertransition_typevalues (e.g.enact— currently the only one in iu_lifecycle_log) returnNEWimmediately, so the existing 146 enact rows continue to bypass emission.
Why no new outbox table?
Per Decision Bank ("prefer reusing existing event_outbox pattern if compatible") and the 10000x author-mode design's 02-piece-event-outbox-substrate-design.md (which called for a separate iu_piece_event_outbox), we evaluated both:
- Separate table: cleaner isolation, requires schema duplication, double-write for subscribers.
- Shared event_outbox with event_domain='piece' filter: zero schema duplication, existing subscribers can pick up by widening their domain filter, no extra index maintenance.
Live event_outbox already had: event_domain column with 8-value CHECK; safe_payload guard; payload_classification; canonical_address — everything we needed except the 'piece' enum value. We extended the CHECK in migration 030 rather than duplicating the table.
The view v_piece_event_outbox provides the domain-projected read surface so downstream code never has to filter manually.