50000x · 09 — Lessons (new + refreshed)
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.
\dp <table>before authoring a TRIGGER-bearing migration.- If the application role lacks
t, the migration needs a one-timeGRANT TRIGGER ON <table> TO <role>issued by the owner first. - Prefer GRANT TRIGGER once over running every trigger migration as the owner.
- 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.
- Pre-compute uuids and suffixes deterministically in PL/pgSQL.
- RAISE EXCEPTION on any non-
okstatus — fail the whole TX rather than partial-commit. - In-TX verification SELECTs assert row deltas before COMMIT.
- 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.
- Before INSERT into event_outbox:
SELECT event_stream FROM event_type_registry WHERE event_domain=? AND event_type=?. - Don't synthesize the stream from the qualified name; many event_types share a stream (e.g. all
template.*events use streamupdate).
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.
- On
ERROR: permission denied, immediately verify TX rolled back. - Capture the exact privilege gap.
- Provide the one-line unblock recipe (GRANT, owner-side apply).
- 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.