{"record":{"id":"bc7efad251acd323","repo":"different-ai/openwork","slug":"automation-engine-event-receipt-mismatch","errorCode":null,"errorMessage":"Automation engine event receipt mismatch","messagePattern":"Automation engine event receipt mismatch","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/automations/src/engine.ts","lineNumber":228,"sourceCode":"/** Validates events before Den persists them and advances its durable cursor. */\nexport function createAutomationEngineEventSequenceValidator(\n  rawReceipt: AutomationEngineAdmissionReceipt,\n  afterSequence = 0,\n): AutomationEngineEventSequenceValidator {\n  const receipt = automationEngineAdmissionReceiptSchema.parse(rawReceipt)\n  if (!Number.isSafeInteger(afterSequence) || afterSequence < 0) {\n    throw new Error(\"Automation engine event cursor must be a non-negative integer\")\n  }\n  let cursor = afterSequence\n  const eventKeys = new Set<string>()\n  return {\n    get cursor() {\n      return cursor\n    },\n    accept(rawEvent) {\n      const event = automationEngineEventSchema.parse(rawEvent)\n      if (event.executionId !== receipt.executionId || event.runId !== receipt.runId) {\n        throw new Error(\"Automation engine event receipt mismatch\")\n      }\n      if (event.sequence !== cursor + 1) {\n        throw new Error(\"Automation engine event sequence is not contiguous\")\n      }\n      if (eventKeys.has(event.idempotencyKey)) {\n        throw new Error(\"Automation engine event idempotency key was repeated\")\n      }\n      eventKeys.add(event.idempotencyKey)\n      cursor = event.sequence\n    },\n  }\n}\n","sourceCodeStart":210,"sourceCodeEnd":241,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/packages/automations/src/engine.ts#L210-L241","documentation":"The sequence validator is bound to a specific admission receipt (executionId + runId). Every event fed to accept() must belong to that same execution/run. If the event's executionId or runId differs from the receipt's, the event stream and the validated admission are out of sync, so the event is rejected.","triggerScenarios":"Calling validator.accept(event) with an event produced by a different execution or run than the receipt passed to createAutomationEngineEventSequenceValidator — e.g. mixing event streams, replaying events after a new run started, or a stale validator reused across runs.","commonSituations":"Caching a validator instance and feeding it a resumed run's events; two concurrent executions writing to a shared event log consumed by one validator; retrying with a new executionId but the old receipt.","solutions":["Create a fresh validator per execution/run, matching the receipt you obtained from admission","Filter the event stream by the receipt's executionId and runId before calling accept()","If the run was re-admitted, re-fetch the new receipt and rebuild the validator"],"exampleFix":"// before\nvalidator.accept(eventFromOtherExecution)\n// after\nif (event.executionId === receipt.executionId && event.runId === receipt.runId) validator.accept(event)","handlingStrategy":"type-guard","validationCode":"const belongsToReceipt = (event: AutomationEngineEvent, receipt: AutomationEngineAdmissionReceipt): boolean =>\n  event.executionId === receipt.executionId && event.runId === receipt.runId\n// filter stream before accepting: events.filter(e => belongsToReceipt(e, receipt)).forEach(e => validator.accept(e))","typeGuard":"const isEventForReceipt = (e: AutomationEngineEvent, r: AutomationEngineAdmissionReceipt): boolean =>\n  e.executionId === r.executionId && e.runId === r.runId","tryCatchPattern":"try {\n  validator.accept(event)\n} catch (e) {\n  if (e instanceof Error && e.message === \"Automation engine event receipt mismatch\") {\n    // rebuild validator from the receipt matching this event's execution/run\n    validator = createAutomationEngineEventSequenceValidator(receiptFor(event), event.sequence - 1)\n  } else throw e\n}","preventionTips":["Scope each validator to one execution/run and never reuse it across runs","Key event subscriptions by executionId+runId so streams can't interleave","On re-admission, always discard the old validator and receipt together"],"tags":["event-stream","mismatch","automations"],"backgroundTag":"event-receipt-mismatch","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}