Transactional Outbox
events.cloud_events posts immediately and synchronously — a crash between
“did the work” and “sent the event” loses it, and a network blip drops it (no
durability, no retry, no replay). The transactional-outbox pattern persists each
event first and drains it later with at-least-once delivery and a dead-letter
cap, so events survive sink outages.
Pure standard library (json); imports no PySide6. The delivery sink
is injected and the store is in-memory with JSON persistence, so draining is
fully deterministic in CI.
Headless API
from je_auto_control import Outbox
box = Outbox()
box.enqueue({"type": "order.created", "id": 7}) # buffered, pending
box.enqueue({"type": "order.paid", "id": 7})
result = box.drain(post_to_webhook, max_batch=100, max_attempts=5)
# {"sent": 2, "failed": 0, "remaining": 0}
box.pending() # entries still awaiting delivery
box.dead_letters() # entries that exhausted their attempts
enqueue appends an event as pending and returns its id. drain delivers
up to max_batch pending entries through the injected sink; a sink
exception leaves the entry pending for retry until max_attempts, after which
it is dead-lettered (recorded with its error). Delivery is at-least-once: a sink
that succeeds but is interrupted before the entry is marked sent will be retried.
save / load persist the whole buffer as JSON so events outlive the
process.
Executor commands
AC_outbox_enqueue returns {id, pending}; AC_outbox_pending returns
{pending}. Both use a named-instance registry and are exposed as MCP tools
(ac_outbox_enqueue / ac_outbox_pending) and as Script Builder commands
under Flow. Draining requires a callable sink, so it stays a headless / API
operation.