KB-4CF6

50000x · 09 — Lessons (new + refreshed)

5 min read Revision 1
iu-core50000xlessonsfeedback

50000x · 09 — Lessons (new + refreshed)

NEW — Role-grant boundary on productized tables ([[feedback-trigger-grant-on-foreign-owned-tables]])

Rule. When wiring a new TRIGGER on a table owned by a different role (here: workflow_admin-owned productized lineage table, vs directus application role), directus having arwdx privileges is insufficient — TRIGGER privilege (t) is separate and is held only by the owner by default.

Why. Deliberate separation-of-duty boundary in PG. Owner holds DDL/TRIGGER; application role holds DML. CREATE TRIGGER is DDL.

How to apply.

  1. \dp <table> before authoring a TRIGGER-bearing migration.
  2. If the application role lacks t, the migration needs a one-time GRANT TRIGGER ON <table> TO <role> issued by the owner first.
  3. Prefer GRANT TRIGGER once over running every trigger migration as the owner.
  4. Alternatively, install the trigger as the owner that EXECUTES the application's fn.

Proven 50000x mig 036 attempt (rolled back atomically when CREATE TRIGGER failed; no partial state).

NEW — Bulk auto-compose is gate-toggle + linear loop ([[feedback-bulk-orchestrator-is-loop-plus-gate-toggle]])

Rule. For N=20+ durable auto-instantiate scaleouts, the pattern is one TX: open composer_enabled + auto_instantiate_enabled, loop fn_iu_auto_instantiate_from_event N times with unique event_id+suffix per iteration, close both gates, COMMIT. The orchestrator is the bulk mechanism; no fn_iu_auto_instantiate_bulk is needed.

Why. Each call is atomic and validates its own gates / templates / idempotency. Looping inside one TX makes the whole batch all-or-nothing without sacrificing per-call observability. Gate toggle before/after COMMIT leaves no enabled gate.

How to apply.

  1. Pre-compute uuids and suffixes deterministically in PL/pgSQL.
  2. RAISE EXCEPTION on any non-ok status — fail the whole TX rather than partial-commit.
  3. In-TX verification SELECTs assert row deltas before COMMIT.
  4. Post-COMMIT re-read to confirm durability and gate closure.

Proven 50000x Phase E (N=20, 4 templates × 5 each, 100% digest-matched).

NEW — event_outbox validator trigger enforces stream identity ([[feedback-event-outbox-stream-must-match-registry]])

Rule. event_outbox has a CHECK trigger that asserts the inserted event_stream matches the event_stream registered in event_type_registry for the (event_domain, event_type) pair. For iu.template.instance_auto_composed the registered stream is literally update.

Why. Stream is the partitioning / consumer-routing key.

How to apply.

  1. Before INSERT into event_outbox: SELECT event_stream FROM event_type_registry WHERE event_domain=? AND event_type=?.
  2. Don't synthesize the stream from the qualified name; many event_types share a stream (e.g. all template.* events use stream update).

Proven 50000x Phase F bounded probe (event_stream='update' required).

NEW — Honest carry-forward beats partial trigger install ([[feedback-honest-channel-block-beats-partial-trigger]])

Rule. When a migration fails mid-apply due to a permission boundary (not a logic bug), let the TX roll back atomically, keep the SQL file as authored-only, document the unblock recipe. DO NOT hack around the boundary by running half of the migration as the application role and half as a borrowed superuser.

Why. Half-applied DDL is a worse state than no application.

How to apply.

  1. On ERROR: permission denied, immediately verify TX rolled back.
  2. Capture the exact privilege gap.
  3. Provide the one-line unblock recipe (GRANT, owner-side apply).
  4. DO NOT pursue a "creative" route that bypasses the boundary.

Proven 50000x mig 036 attempt.

REFRESH — [[feedback-channel-memory-drifts-verify-live]]

Still authoritative. 50000x verified at start: container postgres, DB directus, user directus.

REFRESH — [[feedback-no-truncated-uuids-for-apply-sql]]

Still authoritative; 50000x used gen_random_uuid() at runtime.

REFRESH — [[feedback-registry-table-schemas-vary]]

Reinforced 50000x Phase F: event_outbox columns are event_severity (not severity), safe_payload (not payload); iu_route_attempt uses started_at; iu_route_dead_letter uses first_failed_at.

REFRESH — [[feedback-honest-partial-over-fake-pass]]

50000x repeated the 40000x pattern: honest PARTIAL_PASS with explicit carry-forward.

REFRESH — [[feedback-pinning-tests-bump-per-macro]]

50000x is the FIRST macro since 7000x that bumped ZERO pinning tests (Phase D would have bumped but the apply was blocked). The lesson reinforces in reverse: if you do NOT apply the surface, you must NOT bump SSOT or tests.

Back to Knowledge Hub knowledge/dev/laws/dieu44-trien-khai/v0.6-iu-core-50000x-autoscope-refresh-scaleout-event-ops-closeout-open-goal/09-lessons.md