Context
0098 made actions the unit of behaviour, and 0118 set date versions. Neither said what a handler receives, where its transaction starts, how idempotency is stored or how two versions of one action run together. 0036 put idempotency in the Encore cache; the owner moved the store to the cache package (C-34) and fixed the contract on 2026-10-20 (mem L860–864).Decision
- Handler (AC1).
async handle(input, ctx).ctxcarries the actor andonBehalfOf, the tenant, scope, locale, time zone and currency, request ids, the idempotency key,ctx.now(), an abort signal,ctx.emit,ctx.transition,ctx.afterCommit,ctx.authorizeandctx.feedback. Dependencies come through the constructor. - Transaction (AC2). One database transaction per action. Queries have none. Events go through the outbox inside that
transaction. External calls happen only after commit. Multi-step work becomes a workflow behind the same action key. A
lint flags network calls inside
handle(). - Idempotency (AC3). An
Idempotency-Keyis required for client writes, and the SDKs generate it. The key, a hash of the payload and the result are stored for 24 hours in the cache package (ADR 0150, wave 1 part 3). A different payload with the same key returns409. Jobs, events and workflow steps derive their own keys. This closes gap B46. - Versions (AC4).
key@versionversions run side by side. Public APIs get deprecation with a sunset of at least 6 months. REST and MCP map date versions (0118) to action versions. Deprecated usage is metered and shown to app owners. - Audit. The pipeline’s audit step audits every action. This reconciles the SEC3 rule with 0046 (gap B19), together with 0134.
- The money keys in Postgres with the ledger entry (0036 decision 2) are restated in the commerce ADR 0220, not here.
Consequences
- Handlers are testable by passing a fake
ctx. Dependencies are constructor arguments. - Idempotency storage depends on the cache package, so a cache outage affects retried writes. The limit is for the cache ADR to name.
- Gen and the SDKs must generate
Idempotency-Keyfor every client write.
Enforcement
planned — a lint flags network calls inhandle(); gen rejects a client write action with no idempotency declaration; the
audit step is part of the action pipeline. The idempotency rule is also an item for .agents/rules/http-conventions.md (final pass).
Alternatives
- Idempotency in the Encore cache (0036): superseded (C-34).
- Hand-rolled key storage in handlers: rejected by the pipeline rule of 0098.
Supersedes and sources
Supersedes: 0036. Amends: 0098, 0118. Sources: owner action-runtime answers AC1 to AC4 and the audit reconciliation (stackra-decisions-2026-10-19.md L860–864, 2026-10-20); gap B46, B19; contradiction C-34.