Skip to main content
Relates to 0078 (the messaging packages), 0102, 0142 (drivers), 0148, 0150 and 0151 (the drivers these capabilities select). It amends none of them.

Context

Cache, queue, outbox, workflows, realtime and the scheduler are each a capability that a product turns on, and each has a production driver that needs infrastructure. If each capability is built with its own wiring rules, every phase rewrites code written in an earlier phase, and a product cannot test a capability before its infrastructure exists. The owner settled on 2026-10-21 one rule for every capability, and an order in which they arrive.

Decision

  1. One option, one driver. Each capability is turned on by one option or flag on an existing declaration, and a driver is chosen in config. Nothing else changes in the author’s code.
  2. Memory driver and fake first. Each capability ships a memory driver and a fake in its ./testing entry before it ships its production driver. The gate for each capability is: the module’s tests pass with the memory driver and the fake, then the integration tier passes with the production driver (0182).
  3. No phase rewrites an earlier phase. A later phase adds a driver or a flag. It does not change the code written for an earlier phase.
  4. The order. The capabilities arrive in this order, and each one’s option and driver list is:
    1. Cache: cache: { ttl, invalidateOn: [Events.x] } on a @Query, or @Cacheable and @CacheEvict on a service method. Drivers: memory, then valkey.
    2. Queue: queue on @OnEvent, @AsJob on an action, and .queue() on a client call. Drivers: memory, then postgres, then encore-pubsub.
    3. Outbox and integration events: public: true on a defineEvents entry, and @OnEvent(Producer.x) on the consumer. Drivers: memory, then a postgres outbox, then encore-pubsub.
    4. Workflows: runs: XWorkflow on an action. Drivers: native, then temporal.
    5. Realtime: broadcast: { channel } on an event, and refreshOn: [Events.x] on a widget. Drivers: memory, then SSE or WebSocket.
    6. Scheduler: @Cron on a job action. It runs alongside the others. Drivers: the Encore CronJob in SaaS, with a croner timer and an advisory lock on-prem.
  5. Config picks the driver. The driver is named in the config layers of 0146, including runway.yaml. Code never names a production driver.

Consequences

  • A product can build and test a capability before its infrastructure exists, and an agent reads one option to know what is turned on.
  • The memory driver is a real implementation, so it must be kept in step with its production driver by the conformance tests.
  • A capability’s fake is part of its package, which adds a small amount of code per capability.
  • The order is a sequencing rule. It does not stop a team from building two capabilities in parallel, but no capability may ship its production driver before its memory driver and fake.

Enforcement

planned — the conformance test of 0144 checks that each capability’s driver list includes memory and a fake in ./testing, and stackra gen fails when a declared capability names a driver that is not in its list. Nothing checks this yet.

Supersedes and sources

Supersedes: none. Amends: none. Sources: owner settlement in chat, 2026-10-21 (programming model question Q12).