@pattern-stack/codegen 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. package/dist/{job-orchestrator.protocol-CHOEqBDk.d.ts → job-orchestrator.protocol-CARhMLCO.d.ts} +1 -1
  2. package/dist/runtime/subsystems/analytics/analytics.module.js +6 -2
  3. package/dist/runtime/subsystems/analytics/analytics.module.js.map +1 -1
  4. package/dist/runtime/subsystems/analytics/analytics.tokens.d.ts +0 -11
  5. package/dist/runtime/subsystems/analytics/analytics.tokens.js +6 -2
  6. package/dist/runtime/subsystems/analytics/analytics.tokens.js.map +1 -1
  7. package/dist/runtime/subsystems/analytics/cube-backend.js +6 -2
  8. package/dist/runtime/subsystems/analytics/cube-backend.js.map +1 -1
  9. package/dist/runtime/subsystems/analytics/index.js +6 -2
  10. package/dist/runtime/subsystems/analytics/index.js.map +1 -1
  11. package/dist/runtime/subsystems/auth/auth.module.js +12 -6
  12. package/dist/runtime/subsystems/auth/auth.module.js.map +1 -1
  13. package/dist/runtime/subsystems/auth/auth.tokens.d.ts +0 -28
  14. package/dist/runtime/subsystems/auth/auth.tokens.js +12 -8
  15. package/dist/runtime/subsystems/auth/auth.tokens.js.map +1 -1
  16. package/dist/runtime/subsystems/auth/controllers/auth.controller.js +12 -5
  17. package/dist/runtime/subsystems/auth/controllers/auth.controller.js.map +1 -1
  18. package/dist/runtime/subsystems/auth/index.js +12 -8
  19. package/dist/runtime/subsystems/auth/index.js.map +1 -1
  20. package/dist/runtime/subsystems/auth/middleware/requester-context.js +12 -1
  21. package/dist/runtime/subsystems/auth/middleware/requester-context.js.map +1 -1
  22. package/dist/runtime/subsystems/bridge/bridge-delivery-handler.d.ts +1 -1
  23. package/dist/runtime/subsystems/bridge/bridge-delivery-handler.js +10 -2
  24. package/dist/runtime/subsystems/bridge/bridge-delivery-handler.js.map +1 -1
  25. package/dist/runtime/subsystems/bridge/bridge-outbox-drain-hook.js +10 -2
  26. package/dist/runtime/subsystems/bridge/bridge-outbox-drain-hook.js.map +1 -1
  27. package/dist/runtime/subsystems/bridge/bridge.module.d.ts +1 -1
  28. package/dist/runtime/subsystems/bridge/bridge.module.js +14 -9
  29. package/dist/runtime/subsystems/bridge/bridge.module.js.map +1 -1
  30. package/dist/runtime/subsystems/bridge/bridge.protocol.d.ts +1 -1
  31. package/dist/runtime/subsystems/bridge/event-flow.service.d.ts +1 -1
  32. package/dist/runtime/subsystems/bridge/event-flow.service.js +9 -1
  33. package/dist/runtime/subsystems/bridge/event-flow.service.js.map +1 -1
  34. package/dist/runtime/subsystems/bridge/index.d.ts +1 -1
  35. package/dist/runtime/subsystems/bridge/index.js +14 -9
  36. package/dist/runtime/subsystems/bridge/index.js.map +1 -1
  37. package/dist/runtime/subsystems/cache/cache.drizzle-backend.js +6 -1
  38. package/dist/runtime/subsystems/cache/cache.drizzle-backend.js.map +1 -1
  39. package/dist/runtime/subsystems/cache/cache.memory-backend.js +6 -1
  40. package/dist/runtime/subsystems/cache/cache.memory-backend.js.map +1 -1
  41. package/dist/runtime/subsystems/cache/cache.module.js +6 -2
  42. package/dist/runtime/subsystems/cache/cache.module.js.map +1 -1
  43. package/dist/runtime/subsystems/cache/cache.tokens.d.ts +0 -10
  44. package/dist/runtime/subsystems/cache/cache.tokens.js +6 -2
  45. package/dist/runtime/subsystems/cache/cache.tokens.js.map +1 -1
  46. package/dist/runtime/subsystems/cache/index.js +6 -2
  47. package/dist/runtime/subsystems/cache/index.js.map +1 -1
  48. package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js +5 -0
  49. package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js.map +1 -1
  50. package/dist/runtime/subsystems/events/event-bus.memory-backend.js +5 -0
  51. package/dist/runtime/subsystems/events/event-bus.memory-backend.js.map +1 -1
  52. package/dist/runtime/subsystems/events/event-bus.redis-backend.js +5 -1
  53. package/dist/runtime/subsystems/events/event-bus.redis-backend.js.map +1 -1
  54. package/dist/runtime/subsystems/events/events.module.js +5 -1
  55. package/dist/runtime/subsystems/events/events.module.js.map +1 -1
  56. package/dist/runtime/subsystems/events/events.tokens.d.ts +5 -11
  57. package/dist/runtime/subsystems/events/events.tokens.js +5 -1
  58. package/dist/runtime/subsystems/events/events.tokens.js.map +1 -1
  59. package/dist/runtime/subsystems/events/generated/bus.js +5 -0
  60. package/dist/runtime/subsystems/events/generated/bus.js.map +1 -1
  61. package/dist/runtime/subsystems/events/generated/index.js +5 -0
  62. package/dist/runtime/subsystems/events/generated/index.js.map +1 -1
  63. package/dist/runtime/subsystems/events/index.js +5 -1
  64. package/dist/runtime/subsystems/events/index.js.map +1 -1
  65. package/dist/runtime/subsystems/index.d.ts +3 -3
  66. package/dist/runtime/subsystems/index.js +34 -26
  67. package/dist/runtime/subsystems/index.js.map +1 -1
  68. package/dist/runtime/subsystems/integration/incremental-read.d.ts +35 -8
  69. package/dist/runtime/subsystems/integration/incremental-read.js +9 -6
  70. package/dist/runtime/subsystems/integration/incremental-read.js.map +1 -1
  71. package/dist/runtime/subsystems/integration/index.d.ts +1 -1
  72. package/dist/runtime/subsystems/integration/index.js +9 -6
  73. package/dist/runtime/subsystems/integration/index.js.map +1 -1
  74. package/dist/runtime/subsystems/jobs/bullmq.config.d.ts +0 -9
  75. package/dist/runtime/subsystems/jobs/bullmq.config.js +6 -2
  76. package/dist/runtime/subsystems/jobs/bullmq.config.js.map +1 -1
  77. package/dist/runtime/subsystems/jobs/index.d.ts +1 -1
  78. package/dist/runtime/subsystems/jobs/index.js +13 -9
  79. package/dist/runtime/subsystems/jobs/index.js.map +1 -1
  80. package/dist/runtime/subsystems/jobs/job-handler.base.d.ts +1 -1
  81. package/dist/runtime/subsystems/jobs/job-handler.base.js +5 -1
  82. package/dist/runtime/subsystems/jobs/job-handler.base.js.map +1 -1
  83. package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.d.ts +1 -1
  84. package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.js +10 -3
  85. package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.js.map +1 -1
  86. package/dist/runtime/subsystems/jobs/job-orchestrator.drizzle-backend.d.ts +1 -1
  87. package/dist/runtime/subsystems/jobs/job-orchestrator.drizzle-backend.js +8 -1
  88. package/dist/runtime/subsystems/jobs/job-orchestrator.drizzle-backend.js.map +1 -1
  89. package/dist/runtime/subsystems/jobs/job-orchestrator.memory-backend.d.ts +1 -1
  90. package/dist/runtime/subsystems/jobs/job-orchestrator.memory-backend.js +9 -1
  91. package/dist/runtime/subsystems/jobs/job-orchestrator.memory-backend.js.map +1 -1
  92. package/dist/runtime/subsystems/jobs/job-orchestrator.protocol.d.ts +1 -1
  93. package/dist/runtime/subsystems/jobs/job-run-keyset-cursor.d.ts +1 -1
  94. package/dist/runtime/subsystems/jobs/job-run-service.drizzle-backend.d.ts +1 -1
  95. package/dist/runtime/subsystems/jobs/job-run-service.drizzle-backend.js +8 -2
  96. package/dist/runtime/subsystems/jobs/job-run-service.drizzle-backend.js.map +1 -1
  97. package/dist/runtime/subsystems/jobs/job-run-service.memory-backend.d.ts +1 -1
  98. package/dist/runtime/subsystems/jobs/job-run-service.memory-backend.js +8 -2
  99. package/dist/runtime/subsystems/jobs/job-run-service.memory-backend.js.map +1 -1
  100. package/dist/runtime/subsystems/jobs/job-run-service.protocol.d.ts +1 -1
  101. package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.d.ts +1 -1
  102. package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.js +5 -0
  103. package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.js.map +1 -1
  104. package/dist/runtime/subsystems/jobs/job-worker.d.ts +1 -1
  105. package/dist/runtime/subsystems/jobs/job-worker.js +10 -4
  106. package/dist/runtime/subsystems/jobs/job-worker.js.map +1 -1
  107. package/dist/runtime/subsystems/jobs/job-worker.module.d.ts +5 -2
  108. package/dist/runtime/subsystems/jobs/job-worker.module.js +13 -8
  109. package/dist/runtime/subsystems/jobs/job-worker.module.js.map +1 -1
  110. package/dist/runtime/subsystems/jobs/jobs-domain.module.js +11 -6
  111. package/dist/runtime/subsystems/jobs/jobs-domain.module.js.map +1 -1
  112. package/dist/runtime/subsystems/jobs/jobs-domain.tokens.d.ts +0 -11
  113. package/dist/runtime/subsystems/jobs/jobs-domain.tokens.js +8 -4
  114. package/dist/runtime/subsystems/jobs/jobs-domain.tokens.js.map +1 -1
  115. package/dist/runtime/subsystems/jobs/jobs-errors.d.ts +1 -1
  116. package/dist/runtime/subsystems/observability/index.d.ts +1 -1
  117. package/dist/runtime/subsystems/observability/index.js +9 -1
  118. package/dist/runtime/subsystems/observability/index.js.map +1 -1
  119. package/dist/runtime/subsystems/observability/observability.module.js +9 -1
  120. package/dist/runtime/subsystems/observability/observability.module.js.map +1 -1
  121. package/dist/runtime/subsystems/observability/observability.protocol.d.ts +1 -1
  122. package/dist/runtime/subsystems/observability/observability.service.d.ts +1 -1
  123. package/dist/runtime/subsystems/observability/observability.service.js +9 -1
  124. package/dist/runtime/subsystems/observability/observability.service.js.map +1 -1
  125. package/dist/runtime/subsystems/observability/reporters/bridge-metrics.reporter.d.ts +1 -1
  126. package/dist/runtime/subsystems/observability/reporters/index.d.ts +1 -1
  127. package/dist/runtime/subsystems/storage/index.js +5 -1
  128. package/dist/runtime/subsystems/storage/index.js.map +1 -1
  129. package/dist/runtime/subsystems/storage/storage.module.js +5 -1
  130. package/dist/runtime/subsystems/storage/storage.module.js.map +1 -1
  131. package/dist/runtime/subsystems/storage/storage.tokens.d.ts +0 -8
  132. package/dist/runtime/subsystems/storage/storage.tokens.js +5 -1
  133. package/dist/runtime/subsystems/storage/storage.tokens.js.map +1 -1
  134. package/dist/runtime/subsystems/token-key.d.ts +7 -0
  135. package/dist/runtime/subsystems/token-key.js +8 -0
  136. package/dist/runtime/subsystems/token-key.js.map +1 -0
  137. package/dist/src/cli/index.js +362 -233
  138. package/dist/src/cli/index.js.map +1 -1
  139. package/package.json +5 -1
  140. package/runtime/subsystems/analytics/analytics.tokens.ts +6 -2
  141. package/runtime/subsystems/auth/auth.tokens.ts +15 -8
  142. package/runtime/subsystems/cache/cache.tokens.ts +7 -2
  143. package/runtime/subsystems/events/events.tokens.ts +8 -1
  144. package/runtime/subsystems/index.ts +6 -1
  145. package/runtime/subsystems/integration/incremental-read.ts +43 -9
  146. package/runtime/subsystems/integration/index.ts +1 -0
  147. package/runtime/subsystems/jobs/bullmq.config.ts +5 -2
  148. package/runtime/subsystems/jobs/job-handler.base.ts +6 -1
  149. package/runtime/subsystems/jobs/job-worker.module.ts +5 -1
  150. package/runtime/subsystems/jobs/job-worker.ts +4 -1
  151. package/runtime/subsystems/jobs/jobs-domain.tokens.ts +10 -7
  152. package/runtime/subsystems/storage/storage.tokens.ts +6 -1
  153. package/runtime/subsystems/token-key.ts +7 -0
  154. package/src/config/runtime-mode.mjs +82 -0
  155. package/templates/entity/new/backend/modules/core/integration-source.ejs.t +3 -2
  156. package/templates/entity/new/clean-lite-ps/controller.ejs.t +1 -1
  157. package/templates/entity/new/clean-lite-ps/module.ejs.t +1 -1
  158. package/templates/entity/new/clean-lite-ps/prompt-extension.js +8 -2
  159. package/templates/entity/new/clean-lite-ps/repository.ejs.t +4 -4
  160. package/templates/entity/new/clean-lite-ps/service.ejs.t +4 -4
  161. package/templates/entity/new/prompt.js +49 -10
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../runtime/subsystems/jobs/jobs-domain.module.ts","../../../../runtime/constants/tokens.ts","../../../../runtime/subsystems/jobs/jobs-domain.tokens.ts","../../../../runtime/subsystems/jobs/job-orchestrator.drizzle-backend.ts","../../../../runtime/subsystems/jobs/job-orchestration.schema.ts","../../../../runtime/subsystems/jobs/jobs-errors.ts","../../../../runtime/subsystems/jobs/job-run-service.drizzle-backend.ts","../../../../runtime/subsystems/jobs/job-run-keyset-cursor.ts","../../../../runtime/subsystems/jobs/job-step-service.drizzle-backend.ts","../../../../runtime/subsystems/jobs/job-orchestrator.memory-backend.ts","../../../../runtime/subsystems/jobs/job-handler.base.ts","../../../../runtime/subsystems/jobs/job-run-service.memory-backend.ts","../../../../runtime/subsystems/jobs/job-step-service.memory-backend.ts","../../../../runtime/subsystems/jobs/memory-job-store.ts","../../../../runtime/subsystems/jobs/pool-config.loader.ts","../../../../runtime/subsystems/jobs/bullmq.config.ts"],"sourcesContent":["/**\n * JobsDomainModule — `DynamicModule.forRoot({ backend })` factory wiring\n * the three jobs-domain protocol tokens to a backend implementation\n * (ADR-022, JOB-5).\n *\n * Mirrors `EventsModule.forRoot()` exactly:\n * - `global: true` so consumer entity modules don't have to import this\n * individually — `JOB_ORCHESTRATOR` / `JOB_RUN_SERVICE` /\n * `JOB_STEP_SERVICE` are available project-wide.\n * - One backend at a time (Drizzle for production, Memory for tests).\n *\n * Backend swappability follows the core/extension protocol from CLAUDE.md:\n * the three tokens are the **core contract**; backend-specific tunables\n * live under `extensions.<backend>` so opting into a feature is explicit\n * and the type system reserves the slot.\n */\nimport { Module, type DynamicModule, type Provider } from '@nestjs/common';\nimport { DRIZZLE } from '../../constants/tokens';\nimport {\n JOB_ORCHESTRATOR,\n JOB_RUN_SERVICE,\n JOB_STEP_SERVICE,\n JOBS_MULTI_TENANT,\n} from './jobs-domain.tokens';\nimport { DrizzleJobOrchestrator } from './job-orchestrator.drizzle-backend';\nimport { DrizzleJobRunService } from './job-run-service.drizzle-backend';\nimport { DrizzleJobStepService } from './job-step-service.drizzle-backend';\n// #6 — `BullMQJobOrchestrator` is lazy-loaded only when `backend: 'bullmq'`\n// is selected. The backend file is filtered out of drizzle/memory installs\n// (see `backendFileFilter`); a non-literal dynamic import below sidesteps\n// consumer-side tsc resolution of an absent file.\nimport { MemoryJobOrchestrator } from './job-orchestrator.memory-backend';\nimport { MemoryJobRunService } from './job-run-service.memory-backend';\nimport { MemoryJobStepService } from './job-step-service.memory-backend';\nimport { MemoryJobStore } from './memory-job-store';\nimport {\n BULLMQ_CONNECTION,\n BULLMQ_RESOLVED_CONFIG,\n resolveBullMqConfig,\n type BullMqExtensionsConfig,\n} from './bullmq.config';\n\n/**\n * Drizzle backend extensions surface. None are wired into the Drizzle\n * orchestrator yet — this is the **typed reservation** for the LISTEN/NOTIFY\n * + tunable poll-interval extensions called out in ADR-022. App code\n * passing these today is parsed but not yet dispatched; when the\n * Drizzle orchestrator grows the consumer hooks, opt-in code paths will\n * read directly from these fields.\n */\nexport interface DrizzleBackendExtensions {\n /** Use Postgres LISTEN/NOTIFY to wake the polling loop. Default false. */\n listenNotify?: boolean;\n /** Polling interval when LISTEN/NOTIFY is off (ms). Default 1000. */\n pollIntervalMs?: number;\n}\n\nexport interface JobsDomainModuleOptions {\n backend: 'drizzle' | 'memory' | 'bullmq';\n /**\n * Backend-specific extensions. Only the matching backend's extensions\n * are read at boot; non-matching keys are ignored. This is the\n * core/extension protocol surface — see CLAUDE.md.\n */\n extensions?: {\n drizzle?: DrizzleBackendExtensions;\n /**\n * BullMQ backend extensions (BULLMQ-1). Snake_case mirrors the YAML\n * under `jobs.extensions.bullmq`. `redis_url` falls back to\n * `process.env.REDIS_URL` then `redis://localhost:6379`.\n */\n bullmq?: BullMqExtensionsConfig;\n };\n /** Multi-tenancy opt-in. Wired by JOB-8; module signature stays stable. */\n multiTenant?: boolean;\n}\n\n@Module({})\nexport class JobsDomainModule {\n static forRoot(opts: JobsDomainModuleOptions): DynamicModule {\n const multiTenant = opts.multiTenant ?? false;\n\n const providers: Provider[] = [\n // JOB-8 — boolean provider consumed by the four service-layer backends.\n // Always provided (even when `multiTenant === false`) so `@Inject`\n // always resolves; backends short-circuit the enforcement path when\n // the value is `false`. See `jobs-domain.tokens.ts` for the claim-loop\n // cross-tenant-by-design decision.\n { provide: JOBS_MULTI_TENANT, useValue: multiTenant },\n ];\n\n if (opts.backend === 'memory') {\n // The store is a plain class — wired as a singleton `useValue` so\n // unit tests can pull it out via `.get(MemoryJobStore)` for direct\n // assertions (matches the access pattern in JOB-4 specs).\n const store = new MemoryJobStore();\n providers.push({ provide: MemoryJobStore, useValue: store });\n providers.push(MemoryJobStepService);\n providers.push({ provide: JOB_STEP_SERVICE, useExisting: MemoryJobStepService });\n providers.push(MemoryJobOrchestrator);\n providers.push({ provide: JOB_ORCHESTRATOR, useExisting: MemoryJobOrchestrator });\n providers.push(MemoryJobRunService);\n providers.push({ provide: JOB_RUN_SERVICE, useExisting: MemoryJobRunService });\n } else if (opts.backend === 'bullmq') {\n // BULLMQ-1 — BullMQ orchestrator over a Postgres source of truth. The\n // run/step services stay Drizzle (domain reads + `listForScope` are\n // Postgres queries, unchanged per spec). Only the orchestrator's\n // claim/dispatch half swaps to BullMQ.\n //\n // #6 — the bullmq backend module is filtered out of drizzle/memory\n // installs (no `bullmq` peer dep, no consumer-side tsc compile of an\n // unused file). The factory below dynamic-imports it via a non-literal\n // specifier so TS treats the module type as `any` and never tries to\n // resolve the absent file on a drizzle/memory consumer.\n const resolved = resolveBullMqConfig(opts.extensions?.bullmq);\n providers.push({ provide: BULLMQ_CONNECTION, useValue: resolved.connection });\n providers.push({ provide: BULLMQ_RESOLVED_CONFIG, useValue: resolved });\n providers.push({\n provide: JOB_ORCHESTRATOR,\n useFactory: async (...args: unknown[]): Promise<object> => {\n const specifier = './job-orchestrator.bullmq-backend';\n const mod = (await import(specifier)) as {\n BullMQJobOrchestrator: new (...args: unknown[]) => object;\n };\n return new mod.BullMQJobOrchestrator(...args);\n },\n // The bullmq orchestrator constructor mirrors DrizzleJobOrchestrator's\n // injection list: DRIZZLE + JOBS_MULTI_TENANT + the resolved BullMQ\n // tokens. Importing token references would force a static dep on the\n // tokens file in this module's import graph; using the existing\n // symbols already in scope is sufficient.\n inject: [DRIZZLE, JOBS_MULTI_TENANT, BULLMQ_CONNECTION, BULLMQ_RESOLVED_CONFIG],\n });\n providers.push({ provide: JOB_RUN_SERVICE, useClass: DrizzleJobRunService });\n providers.push({ provide: JOB_STEP_SERVICE, useClass: DrizzleJobStepService });\n } else {\n providers.push({ provide: JOB_ORCHESTRATOR, useClass: DrizzleJobOrchestrator });\n providers.push({ provide: JOB_RUN_SERVICE, useClass: DrizzleJobRunService });\n providers.push({ provide: JOB_STEP_SERVICE, useClass: DrizzleJobStepService });\n }\n\n const exports = [\n JOB_ORCHESTRATOR,\n JOB_RUN_SERVICE,\n JOB_STEP_SERVICE,\n JOBS_MULTI_TENANT,\n ];\n // BULLMQ-1 — only export the BullMQ tokens when they were actually\n // provided. Nest throws \"exported but not provided\" otherwise. Exported so\n // JobWorkerModule (which imports this module) can read the resolved\n // connection/config to spawn BullMQ workers.\n if (opts.backend === 'bullmq') {\n exports.push(BULLMQ_CONNECTION, BULLMQ_RESOLVED_CONFIG);\n }\n\n return {\n module: JobsDomainModule,\n global: true,\n providers,\n exports,\n };\n }\n}\n","/**\n * NestJS injection tokens\n *\n * Used with @Inject() decorator in concrete repository constructors.\n */\n\n/**\n * Injection token for the Drizzle ORM database client.\n *\n * Usage in concrete repositories:\n * ```typescript\n * constructor(@Inject(DRIZZLE) db: DrizzleClient) { super(db); }\n * ```\n */\nexport const DRIZZLE = 'DRIZZLE' as const;\n\n/**\n * Injection token for the event bus (IEventBus).\n *\n * Optional — only resolved when EventsModule.forRoot() is registered.\n * BaseService uses this with @Optional() to emit lifecycle events\n * without requiring the events subsystem to be installed.\n *\n * Usage in services/use cases:\n * ```typescript\n * @Optional() @Inject(EVENT_BUS) eventBus?: IEventBus\n * ```\n */\nexport const EVENT_BUS = 'EVENT_BUS' as const;\n","/**\n * Injection tokens for the job orchestration domain layer (ADR-022, JOB-2).\n *\n * Consumer code injects these symbols via `@Inject(JOB_ORCHESTRATOR)` etc.;\n * concrete backends (JOB-3 Drizzle, JOB-4 Memory) provide the implementations\n * through `JobsDomainModule.forRoot({ backend })` in JOB-5.\n *\n * Each token is a unique `Symbol` — guaranteed distinct from every other\n * Symbol at runtime, which is exactly the uniqueness guarantee Nest's DI\n * container relies on for token-based lookup.\n */\nexport const JOB_ORCHESTRATOR = Symbol('JOB_ORCHESTRATOR');\nexport const JOB_RUN_SERVICE = Symbol('JOB_RUN_SERVICE');\nexport const JOB_STEP_SERVICE = Symbol('JOB_STEP_SERVICE');\n\n/**\n * Multi-tenancy opt-in flag (JOB-8). Bound to the boolean passed in via\n * `JobsDomainModule.forRoot({ multiTenant })`, defaulting to `false`.\n *\n * When `true`, the four service-layer backends (Drizzle + Memory orchestrator\n * and run-service) enforce `tenantId` on every mutating / targeted-read call:\n * `start`, `cancel`, `listForScope`, `cancelForScope`, `rescheduleForScope`.\n * Missing (`undefined`) `tenantId` throws `MissingTenantIdError`; explicit\n * `null` opts into cross-tenant background work and passes through.\n *\n * The JobWorker claim loop is **cross-tenant by design** — the worker has no\n * tenant context; `tenantId` is populated at write time and enforced on\n * targeted reads. See docs/specs/JOB-8.md.\n */\nexport const JOBS_MULTI_TENANT = Symbol('JOBS_MULTI_TENANT');\n","/**\n * DrizzleJobOrchestrator — Postgres-backed implementation of\n * `IJobOrchestrator` (ADR-022, JOB-3).\n *\n * Single-layer architecture: `start` writes a single `job_run` row; the\n * `JobWorker` polling loop claims it directly via `FOR UPDATE SKIP LOCKED`.\n * No `job_queue` table, no executor port. See `docs/specs/JOB-3.md`.\n */\nimport { randomUUID } from 'node:crypto';\nimport { Inject, Injectable, Logger } from '@nestjs/common';\nimport { and, desc, eq, gt, inArray, isNotNull, ne, notInArray, sql } from 'drizzle-orm';\nimport type { DrizzleClient } from '../../types/drizzle';\nimport type { DrizzleTransaction } from '../events/event-bus.protocol';\nimport { DRIZZLE } from '../../constants/tokens';\nimport {\n jobRuns,\n jobs,\n type JobDefinitionRow,\n type JobRunRow,\n} from './job-orchestration.schema';\nimport type {\n CancelOptions,\n IJobOrchestrator,\n JobPoolDef,\n JobRun,\n JobUpsertEntry,\n StartOptions,\n} from './job-orchestrator.protocol';\nimport {\n JobCollisionError,\n JobNotReplayableError,\n JobTemplateFieldMissingError,\n JobTypeNotFoundError,\n MissingTenantIdError,\n} from './jobs-errors';\nimport { jobSteps } from './job-orchestration.schema';\nimport { JOBS_MULTI_TENANT } from './jobs-domain.tokens';\n\n/**\n * Terminal statuses — transitions into these are final. Used by `cancel`\n * (to short-circuit idempotently) and by `replay` (as the guard gate).\n */\nexport const TERMINAL_STATUSES = [\n 'completed',\n 'failed',\n 'timed_out',\n 'canceled',\n] as const;\ntype TerminalStatus = (typeof TERMINAL_STATUSES)[number];\ntype JobRunStatus = JobRunRow['status'];\n\n/** Statuses excluded from dedupe window matches per ADR-022. */\nconst DEDUPE_EXCLUDED_STATUSES: JobRunStatus[] = ['canceled', 'failed'];\n/** Statuses that count as in-flight for concurrency collision checks. */\nconst IN_FLIGHT_STATUSES: JobRunStatus[] = ['pending', 'running'];\n\n/**\n * Substitute `{{field}}` placeholders against the input payload.\n *\n * Implementation decision (JOB-3, 2026-04-19): simple `{{field}}` single-key\n * substitution, no dotted paths, no Mustache/Handlebars dependency. A missing\n * field throws `JobTemplateFieldMissingError` synchronously — cheaper than\n * discovering the misconfiguration at claim time.\n */\nexport function evaluateKeyTemplate(\n template: string,\n input: Record<string, unknown>,\n): string {\n return template.replace(/\\{\\{\\s*([a-zA-Z0-9_]+)\\s*\\}\\}/g, (_match, field: string) => {\n const value = input[field];\n if (value === undefined || value === null) {\n throw new JobTemplateFieldMissingError(template, field);\n }\n return String(value);\n });\n}\n\n@Injectable()\nexport class DrizzleJobOrchestrator implements IJobOrchestrator {\n // TODO(logging-subsystem): swap to ILogger once ADR-028 lands\n private readonly logger = new Logger(DrizzleJobOrchestrator.name);\n\n constructor(\n @Inject(DRIZZLE) private readonly db: DrizzleClient,\n @Inject(JOBS_MULTI_TENANT) private readonly multiTenant: boolean,\n ) {}\n\n /**\n * JOB-8 — resolve `tenantId` for a mutating / targeted-read call.\n * Returns the tenant value that should be written to the row (or compared\n * against in a WHERE clause). When `multiTenant` is off, the column is\n * forced to `null` regardless of what callers pass. When on, `undefined`\n * throws; `null` and strings pass through untouched.\n */\n private resolveTenantId(\n method: string,\n tenantId: string | null | undefined,\n ): string | null {\n if (!this.multiTenant) return null;\n if (tenantId === undefined) throw new MissingTenantIdError(method);\n return tenantId;\n }\n\n // ==========================================================================\n // start\n // ==========================================================================\n\n async start(\n type: string,\n input: unknown,\n opts: StartOptions = {},\n tx?: DrizzleTransaction,\n ): Promise<JobRun> {\n const payload = (input ?? {}) as Record<string, unknown>;\n\n // JOB-8 — resolve tenant gate up front so `multi_tenant=true` +\n // undefined surfaces before any row is touched.\n const tenantId = this.resolveTenantId('start', opts.tenantId);\n\n // BRIDGE-7: thread the optional caller tx through every read/write\n // in this method so EventFlowService.publishAndStart can bundle the\n // outbox insert, the eager job_run insert, and (for Case B) the\n // bridge_delivery pre-write into a single transaction.\n const client = (tx ?? this.db) as DrizzleClient;\n\n // 1a. Load job definition.\n const [def] = await client\n .select()\n .from(jobs)\n .where(eq(jobs.type, type))\n .limit(1);\n if (!def) throw new JobTypeNotFoundError(type);\n const definition = def as JobDefinitionRow;\n\n // 1b. Dedupe check.\n if (definition.dedupeKeyTemplate && definition.dedupeWindowMs) {\n const dedupeKey = evaluateKeyTemplate(definition.dedupeKeyTemplate, payload);\n const windowStart = new Date(Date.now() - definition.dedupeWindowMs);\n const existing = await client\n .select()\n .from(jobRuns)\n .where(\n and(\n eq(jobRuns.jobType, type),\n eq(jobRuns.dedupeKey, dedupeKey),\n gt(jobRuns.createdAt, windowStart),\n // status NOT IN ('canceled', 'failed')\n notInStatus(DEDUPE_EXCLUDED_STATUSES),\n ),\n )\n .orderBy(desc(jobRuns.createdAt))\n .limit(1);\n if (existing.length > 0) {\n return existing[0] as JobRun;\n }\n }\n\n // 1c. Concurrency collision check.\n let concurrencyKey: string | null = null;\n if (definition.concurrencyKeyTemplate) {\n concurrencyKey = evaluateKeyTemplate(\n definition.concurrencyKeyTemplate,\n payload,\n );\n const inFlight = await client\n .select()\n .from(jobRuns)\n .where(\n and(\n eq(jobRuns.concurrencyKey, concurrencyKey),\n inArray(jobRuns.status, IN_FLIGHT_STATUSES),\n ),\n )\n .limit(1);\n if (inFlight.length > 0) {\n const incumbent = inFlight[0] as JobRun;\n switch (definition.collisionMode) {\n case 'reject':\n throw new JobCollisionError(type, concurrencyKey, incumbent);\n case 'replace':\n // JOB-8 — thread the incumbent's own tenantId through the\n // internal cascade. Without this, every `replace`-collision\n // start() under multiTenant=true throws MissingTenantIdError\n // from the inner cancel() call instead of cancelling the\n // incumbent. Mirrors the memory backend's `cancelLocked(\n // incumbent.id, ..., incumbent.tenantId)` pattern.\n await this.cancel(incumbent.id, {\n cascade: true,\n reason: 'replaced',\n tenantId: incumbent.tenantId,\n });\n break;\n case 'queue':\n // Fall through — row is inserted; claim query gates it until\n // the incumbent transitions (see JobWorker.processRun queue gate).\n break;\n }\n }\n }\n\n // 1d. Resolve id + rootRunId, INSERT.\n const newId = randomUUID();\n let rootRunId: string = newId;\n if (opts.parentRunId) {\n const [parent] = await client\n .select({ rootRunId: jobRuns.rootRunId })\n .from(jobRuns)\n .where(eq(jobRuns.id, opts.parentRunId))\n .limit(1);\n if (!parent) {\n throw new Error(\n `parentRunId ${opts.parentRunId} does not reference an existing job_run`,\n );\n }\n rootRunId = parent.rootRunId;\n }\n\n const dedupeKey =\n definition.dedupeKeyTemplate\n ? evaluateKeyTemplate(definition.dedupeKeyTemplate, payload)\n : null;\n\n const [inserted] = await client\n .insert(jobRuns)\n .values({\n id: newId,\n jobType: type,\n jobVersion: definition.version,\n parentRunId: opts.parentRunId ?? null,\n rootRunId,\n parentClosePolicy: opts.parentClosePolicy ?? 'terminate',\n scopeEntityType: opts.scope?.entityType ?? null,\n scopeEntityId: opts.scope?.entityId ?? null,\n tenantId,\n tags: opts.tags ?? {},\n pool: opts.pool ?? definition.pool,\n priority: opts.priority ?? definition.priorityDefault,\n concurrencyKey,\n dedupeKey,\n status: 'pending',\n input: payload,\n output: null,\n error: null,\n triggerSource: opts.triggerSource ?? 'manual',\n triggerRef: opts.triggerRef ?? null,\n runAt: opts.runAt ?? new Date(),\n startedAt: null,\n finishedAt: null,\n claimedAt: null,\n attempts: 0,\n })\n .returning();\n\n return inserted as JobRun;\n }\n\n // ==========================================================================\n // cancel\n // ==========================================================================\n\n async cancel(runId: string, opts: CancelOptions = {}): Promise<void> {\n // JOB-8 — resolve tenant gate up front (strict undefined-throws).\n const tenantId = this.resolveTenantId('cancel', opts.tenantId);\n\n // Load target.\n const [target] = await this.db\n .select()\n .from(jobRuns)\n .where(eq(jobRuns.id, runId))\n .limit(1);\n if (!target) return;\n // JOB-8 — cross-tenant cancel is a silent no-op (no existence leak).\n if (this.multiTenant && target.tenantId !== tenantId) return;\n if (TERMINAL_STATUSES.includes(target.status as TerminalStatus)) {\n return; // idempotent\n }\n\n // Atomic transition, guarded against concurrent terminal moves.\n const [cancelled] = await this.db\n .update(jobRuns)\n .set({\n status: 'canceled',\n finishedAt: new Date(),\n updatedAt: new Date(),\n })\n .where(\n and(eq(jobRuns.id, runId), notInStatus([...TERMINAL_STATUSES])),\n )\n .returning();\n\n if (!cancelled) return; // lost the race; already terminal\n\n if (opts.cascade === false) return;\n\n // Fetch descendants and branch on parent_close_policy.\n const descendants = await this.db\n .select()\n .from(jobRuns)\n .where(\n and(\n eq(jobRuns.rootRunId, target.rootRunId),\n ne(jobRuns.id, runId),\n notInStatus([...TERMINAL_STATUSES]),\n ),\n );\n\n for (const child of descendants) {\n const policy = (child as JobRunRow).parentClosePolicy;\n if (policy === 'abandon') continue;\n // 'terminate' | 'cancel' — both transition the child to canceled.\n await this.db\n .update(jobRuns)\n .set({\n status: 'canceled',\n finishedAt: new Date(),\n updatedAt: new Date(),\n })\n .where(\n and(\n eq(jobRuns.id, (child as JobRunRow).id),\n notInStatus([...TERMINAL_STATUSES]),\n ),\n );\n }\n\n void opts.reason; // reserved for future audit logging\n }\n\n // ==========================================================================\n // replay\n // ==========================================================================\n\n async replay(runId: string): Promise<JobRun> {\n // Load target + its job definition (we need replay_from).\n const [target] = await this.db\n .select()\n .from(jobRuns)\n .where(eq(jobRuns.id, runId))\n .limit(1);\n if (!target) {\n throw new Error(`replay: run ${runId} not found`);\n }\n const run = target as JobRunRow;\n if (!TERMINAL_STATUSES.includes(run.status as TerminalStatus)) {\n throw new JobNotReplayableError(runId, run.status);\n }\n\n const [def] = await this.db\n .select()\n .from(jobs)\n .where(eq(jobs.type, run.jobType))\n .limit(1);\n if (!def) throw new JobTypeNotFoundError(run.jobType);\n const mode = (def as JobDefinitionRow).replayFrom;\n\n // Atomic: step reset + run reset must commit together.\n const result = await this.db.transaction(async (tx) => {\n if (mode === 'scratch') {\n await tx.delete(jobSteps).where(eq(jobSteps.jobRunId, runId));\n } else if (mode === 'last_step') {\n // Delete only non-completed step rows — completed steps stay memoised.\n await tx\n .delete(jobSteps)\n .where(\n and(eq(jobSteps.jobRunId, runId), ne(jobSteps.status, 'completed')),\n );\n } else {\n // 'last_checkpoint' — Phase 1 has no explicit checkpoint markers, so\n // behaviour collapses to `last_step`. See docs/specs/JOB-3.md\n // \"Implementation Decisions\" — planned divergence in a later phase.\n await tx\n .delete(jobSteps)\n .where(\n and(eq(jobSteps.jobRunId, runId), ne(jobSteps.status, 'completed')),\n );\n }\n\n const [updated] = await tx\n .update(jobRuns)\n .set({\n status: 'pending',\n attempts: 0,\n runAt: new Date(),\n startedAt: null,\n finishedAt: null,\n claimedAt: null,\n error: null,\n output: null,\n updatedAt: new Date(),\n })\n .where(eq(jobRuns.id, runId))\n .returning();\n return updated as JobRunRow;\n });\n\n return result as JobRun;\n }\n\n // ==========================================================================\n // upsertJobRows — boot-time materialisation of `job` definitions\n // ==========================================================================\n\n /**\n * Hash-gated `INSERT … ON CONFLICT (type) DO UPDATE … WHERE` per Q3\n * resolution (2026-04-19): the `UPDATE` branch executes only when one\n * of the persisted metadata fields differs from the incoming payload;\n * `version` bumps only on real change; concurrent boots with identical\n * content are idempotent no-ops.\n *\n * Why this shape (not `DO NOTHING`, not advisory locks):\n * - `DO NOTHING` would let an old-version instance leave a stale row\n * that a new-version instance can't overwrite during a rolling deploy.\n * - Advisory locks add latency and leak risk under crashes.\n * - The `WHERE … IS DISTINCT FROM …` clause makes the conditional\n * atomic — no read-modify-write race on `version` between concurrent\n * boots.\n *\n * Orphan detection: a single `SELECT type FROM job WHERE type NOT IN (...)`\n * returns the types present in DB but absent from `entries`. Caller (boot\n * validator) decides whether to throw `BootValidationError`.\n */\n async upsertJobRows(\n entries: JobUpsertEntry[],\n poolConfig: ReadonlyMap<string, JobPoolDef>,\n ): Promise<{ orphaned: string[] }> {\n void poolConfig; // pool validation is the module's responsibility; orchestrator just persists\n\n for (const entry of entries) {\n const meta = entry.meta;\n const pool = meta.pool ?? 'batch';\n const retryPolicy = meta.retry ?? {\n attempts: 1,\n backoff: 'fixed' as const,\n baseMs: 0,\n };\n const concurrencyKeyTemplate =\n (meta.concurrency as { key?: unknown } | undefined)?.key;\n const concurrencyKeyTemplateStr =\n typeof concurrencyKeyTemplate === 'string' ? concurrencyKeyTemplate : null;\n const collisionMode =\n (meta.concurrency?.collisionMode as JobDefinitionRow['collisionMode']) ??\n 'queue';\n const dedupeKeyTemplate =\n (meta.dedupe as { key?: unknown } | undefined)?.key;\n const dedupeKeyTemplateStr =\n typeof dedupeKeyTemplate === 'string' ? dedupeKeyTemplate : null;\n const dedupeWindowMs = meta.dedupe?.windowMs ?? null;\n const timeoutMs = meta.timeoutMs ?? null;\n const replayFrom = meta.replayFrom ?? 'last_checkpoint';\n const scopeEntityType = meta.scope?.entity ?? null;\n // Q3 resolution: priority_default and replay_from are part of the\n // hashed metadata even though they aren't currently set via decorator\n // metadata above (priority_default has no `@JobHandler` field yet).\n // Default to 0 to keep UPDATE branch quiet across deploys.\n const priorityDefault = 0;\n\n // Hash-gated upsert: every metadata column appears in the WHERE clause\n // so the UPDATE branch only fires on a real change. `version` bumps\n // exactly when the WHERE matches.\n await this.db\n .insert(jobs)\n .values({\n type: entry.type,\n version: 1,\n pool,\n scopeEntityType,\n retryPolicy,\n timeoutMs,\n concurrencyKeyTemplate: concurrencyKeyTemplateStr,\n collisionMode,\n dedupeKeyTemplate: dedupeKeyTemplateStr,\n dedupeWindowMs,\n priorityDefault,\n replayFrom,\n })\n .onConflictDoUpdate({\n target: jobs.type,\n set: {\n pool: sql`EXCLUDED.pool`,\n scopeEntityType: sql`EXCLUDED.scope_entity_type`,\n retryPolicy: sql`EXCLUDED.retry_policy`,\n timeoutMs: sql`EXCLUDED.timeout_ms`,\n concurrencyKeyTemplate: sql`EXCLUDED.concurrency_key_template`,\n collisionMode: sql`EXCLUDED.collision_mode`,\n dedupeKeyTemplate: sql`EXCLUDED.dedupe_key_template`,\n dedupeWindowMs: sql`EXCLUDED.dedupe_window_ms`,\n priorityDefault: sql`EXCLUDED.priority_default`,\n replayFrom: sql`EXCLUDED.replay_from`,\n version: sql`${jobs.version} + 1`,\n updatedAt: sql`now()`,\n },\n // The hash gate: every field listed in the Q3 resolution appears\n // here. `IS DISTINCT FROM` is the null-safe inequality operator;\n // jsonb cast to text gives stable comparison without invoking a\n // dedicated hash column (avoids a JOB-1 schema migration).\n setWhere: sql`\n ${jobs.pool} IS DISTINCT FROM EXCLUDED.pool OR\n ${jobs.retryPolicy}::text IS DISTINCT FROM EXCLUDED.retry_policy::text OR\n ${jobs.timeoutMs} IS DISTINCT FROM EXCLUDED.timeout_ms OR\n ${jobs.concurrencyKeyTemplate} IS DISTINCT FROM EXCLUDED.concurrency_key_template OR\n ${jobs.collisionMode} IS DISTINCT FROM EXCLUDED.collision_mode OR\n ${jobs.dedupeKeyTemplate} IS DISTINCT FROM EXCLUDED.dedupe_key_template OR\n ${jobs.dedupeWindowMs} IS DISTINCT FROM EXCLUDED.dedupe_window_ms OR\n ${jobs.priorityDefault} IS DISTINCT FROM EXCLUDED.priority_default OR\n ${jobs.replayFrom} IS DISTINCT FROM EXCLUDED.replay_from OR\n ${jobs.scopeEntityType} IS DISTINCT FROM EXCLUDED.scope_entity_type\n `,\n });\n }\n\n // Orphan detection: any `job` row whose type is not in the registry.\n const types = entries.map((e) => e.type);\n const orphans =\n types.length === 0\n ? await this.db.select({ type: jobs.type }).from(jobs)\n : await this.db\n .select({ type: jobs.type })\n .from(jobs)\n .where(notInArray(jobs.type, types));\n\n return { orphaned: orphans.map((o) => o.type) };\n }\n}\n\n// ─── Helpers ────────────────────────────────────────────────────────────────\n\nfunction notInStatus(statuses: JobRunStatus[]) {\n // Drizzle's inArray composes with `not` via negation helper; use raw sql\n // to stay readable. `inArray` + `.not()` isn't idiomatic in 0.45.\n const negated = statuses.map((s) => ne(jobRuns.status, s));\n return and(...negated);\n}\n\n// `isNotNull` + `gt` imports are retained for potential future use; silence\n// unused-import lint by re-exporting via `void`.\nvoid isNotNull;\n","/**\n * Drizzle schema for the job orchestration domain (ADR-022).\n *\n * Three tables model the lifecycle of a durable job:\n * - `job` — definitions keyed by handler type (e.g. 'onboarding').\n * - `job_run` — one row per attempt to execute a job; worker claims\n * rows directly via SELECT ... FOR UPDATE SKIP LOCKED.\n * - `job_step` — individual steps within a run; memoises output for replay.\n *\n * Phase 1 ships only this layer. There is no `job_queue` table, no executor\n * port — see ADR-022 and `.claude/skills/jobs/SKILL.md` for the rationale.\n */\nimport {\n pgEnum,\n pgTable,\n uuid,\n text,\n jsonb,\n integer,\n timestamp,\n index,\n uniqueIndex,\n} from 'drizzle-orm/pg-core';\nimport { sql } from 'drizzle-orm';\nimport type { InferSelectModel } from 'drizzle-orm';\n\n// ─── Internal $type<> helpers ───────────────────────────────────────────────\n// Annotation types for jsonb columns only. JOB-2 defines the public protocol\n// types; these remain private to this file.\n\ntype RetryPolicy = {\n attempts: number;\n backoff: 'fixed' | 'exponential';\n baseMs: number;\n nonRetryableErrors?: string[];\n};\n\ntype JobRunError = {\n message: string;\n stack?: string;\n retryable: boolean;\n attempt: number;\n};\n\n// ─── Enums ──────────────────────────────────────────────────────────────────\n\nexport const jobRunStatusEnum = pgEnum('job_run_status', [\n 'pending',\n 'running',\n 'waiting',\n 'completed',\n 'failed',\n 'timed_out',\n 'canceled',\n]);\n\n// extended in ADR-027: tool_call | llm_call | wait | checkpoint | message\nexport const jobStepKindEnum = pgEnum('job_step_kind', ['task']);\n\nexport const jobStepStatusEnum = pgEnum('job_step_status', [\n 'pending',\n 'running',\n 'completed',\n 'failed',\n 'skipped',\n]);\n\nexport const collisionModeEnum = pgEnum('job_collision_mode', [\n 'queue',\n 'reject',\n 'replace',\n]);\n\nexport const replayFromEnum = pgEnum('job_replay_from', [\n 'scratch',\n 'last_step',\n 'last_checkpoint',\n]);\n\nexport const parentClosePolicyEnum = pgEnum('job_parent_close_policy', [\n 'terminate',\n 'cancel',\n 'abandon',\n]);\n\n// Phase 3 placeholder — see ADR-025\nexport const waitKindEnum = pgEnum('job_wait_kind', ['signal']);\n\n// Phase 2 may add more sources; requires Atlas migration\nexport const triggerSourceEnum = pgEnum('job_trigger_source', [\n 'manual',\n 'schedule',\n 'event',\n 'parent',\n]);\n\n// ─── job ────────────────────────────────────────────────────────────────────\n\nexport const jobs = pgTable('job', {\n type: text('type').primaryKey(),\n version: integer('version').notNull().default(1),\n pool: text('pool').notNull(),\n scopeEntityType: text('scope_entity_type'),\n retryPolicy: jsonb('retry_policy').notNull().$type<RetryPolicy>(),\n timeoutMs: integer('timeout_ms'),\n concurrencyKeyTemplate: text('concurrency_key_template'),\n collisionMode: collisionModeEnum('collision_mode').notNull().default('queue'),\n dedupeKeyTemplate: text('dedupe_key_template'),\n dedupeWindowMs: integer('dedupe_window_ms'),\n priorityDefault: integer('priority_default').notNull().default(0),\n replayFrom: replayFromEnum('replay_from').notNull().default('last_checkpoint'),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),\n});\n\nexport type JobDefinitionRow = InferSelectModel<typeof jobs>;\n\n// ─── job_run ────────────────────────────────────────────────────────────────\n\nexport const jobRuns = pgTable(\n 'job_run',\n {\n id: uuid('id').primaryKey().defaultRandom(),\n jobType: text('job_type').notNull().references(() => jobs.type),\n jobVersion: integer('job_version').notNull(),\n parentRunId: uuid('parent_run_id').references((): any => jobRuns.id),\n /**\n * Service generates `id` client-side via randomUUID() and sets\n * root_run_id = id for root runs (single INSERT, no self-FK race).\n */\n rootRunId: uuid('root_run_id').notNull(),\n parentClosePolicy: parentClosePolicyEnum('parent_close_policy')\n .notNull()\n .default('terminate'),\n scopeEntityType: text('scope_entity_type'),\n scopeEntityId: text('scope_entity_id'),\n tenantId: text('tenant_id'),\n tags: jsonb('tags').notNull().default({}).$type<Record<string, string>>(),\n pool: text('pool').notNull(),\n priority: integer('priority').notNull().default(0),\n concurrencyKey: text('concurrency_key'),\n dedupeKey: text('dedupe_key'),\n status: jobRunStatusEnum('status').notNull().default('pending'),\n input: jsonb('input').notNull().$type<Record<string, unknown>>(),\n output: jsonb('output').$type<Record<string, unknown>>(),\n error: jsonb('error').$type<JobRunError>(),\n triggerSource: triggerSourceEnum('trigger_source').notNull(),\n triggerRef: text('trigger_ref'),\n runAt: timestamp('run_at', { withTimezone: true }).notNull().defaultNow(),\n startedAt: timestamp('started_at', { withTimezone: true }),\n finishedAt: timestamp('finished_at', { withTimezone: true }),\n claimedAt: timestamp('claimed_at', { withTimezone: true }),\n attempts: integer('attempts').notNull().default(0),\n // Phase 3 placeholder — see ADR-025\n waitKind: waitKindEnum('wait_kind'),\n // Phase 3 placeholder — see ADR-025\n resumeToken: text('resume_token'),\n // Phase 3 placeholder — see ADR-025\n waitDeadline: timestamp('wait_deadline', { withTimezone: true }),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),\n },\n (t) => ({\n /** Claim query: ORDER BY priority DESC, run_at ASC. */\n idxJobRunClaim: index('idx_job_run_claim').on(t.status, t.pool, t.runAt),\n /** Tree traversal / cascade cancel. */\n idxJobRunRoot: index('idx_job_run_root').on(t.rootRunId),\n /** listForScope query. */\n idxJobRunScope: index('idx_job_run_scope').on(t.scopeEntityType, t.scopeEntityId),\n /** Idempotency collapse — partial index. */\n idxJobRunDedupe: index('idx_job_run_dedupe')\n .on(t.jobType, t.dedupeKey)\n .where(sql`${t.dedupeKey} IS NOT NULL`),\n /** Collision check — partial index. */\n idxJobRunConcurrency: index('idx_job_run_concurrency')\n .on(t.concurrencyKey)\n .where(\n sql`${t.concurrencyKey} IS NOT NULL AND ${t.status} IN ('pending','running')`,\n ),\n }),\n);\n\nexport type JobRunRow = InferSelectModel<typeof jobRuns>;\n\n// ─── job_step ───────────────────────────────────────────────────────────────\n\nexport const jobSteps = pgTable(\n 'job_step',\n {\n id: uuid('id').primaryKey().defaultRandom(),\n jobRunId: uuid('job_run_id').notNull().references(() => jobRuns.id),\n stepId: text('step_id').notNull(),\n kind: jobStepKindEnum('kind').notNull().default('task'),\n /**\n * Monotonic within run. integer (max ~2B per run) is sufficient —\n * downgraded from ADR-022's bigint; revisit only if a single run\n * ever exceeds 2 billion steps.\n */\n seq: integer('seq').notNull(),\n status: jobStepStatusEnum('status').notNull().default('pending'),\n input: jsonb('input').$type<Record<string, unknown>>(),\n /** Memoised on success for replay. */\n output: jsonb('output').$type<Record<string, unknown>>(),\n error: jsonb('error').$type<JobRunError>(),\n attempts: integer('attempts').notNull().default(0),\n startedAt: timestamp('started_at', { withTimezone: true }),\n finishedAt: timestamp('finished_at', { withTimezone: true }),\n },\n (t) => ({\n /** No duplicate step IDs per run. */\n idxJobStepRunStep: uniqueIndex('idx_job_step_run_step').on(t.jobRunId, t.stepId),\n /** Ordered timeline reads. */\n idxJobStepTimeline: index('idx_job_step_timeline').on(t.jobRunId, t.seq),\n }),\n);\n\nexport type JobStepRow = InferSelectModel<typeof jobSteps>;\n","/**\n * Typed errors for the job orchestration domain (ADR-022, JOB-3).\n *\n * All thrown by the Drizzle orchestrator (and mirrored by the Memory\n * backend in JOB-4). They exist as classes so consumers can `instanceof`\n * them in catch blocks and exception filters can map them to HTTP codes.\n */\nimport type { JobRun } from './job-orchestrator.protocol';\n\n/**\n * `start(type, …)` was called for a job type that has no row in the `job`\n * table. At runtime this usually means the handler was not decorated or the\n * boot validator (JOB-5) has not registered it yet.\n */\nexport class JobTypeNotFoundError extends Error {\n override readonly name = 'JobTypeNotFoundError';\n constructor(public readonly jobType: string) {\n super(`No job definition registered for type '${jobType}'.`);\n }\n}\n\n/**\n * Thrown by `start` when `collision_mode === 'reject'` and a non-terminal\n * run with the same `concurrency_key` already exists. Carries the incumbent\n * so callers can surface its id or subscribe to its completion event.\n */\nexport class JobCollisionError extends Error {\n override readonly name = 'JobCollisionError';\n constructor(\n public readonly jobType: string,\n public readonly concurrencyKey: string,\n public readonly incumbent: JobRun,\n ) {\n super(\n `Job type '${jobType}' has an in-flight run with concurrency_key ` +\n `'${concurrencyKey}' (incumbent ${incumbent.id}); collision_mode=reject.`,\n );\n }\n}\n\n/**\n * `replay` was called on a run that is not in a replayable terminal state\n * (i.e. still `pending` / `running` / `waiting`). Replay always spawns\n * fresh execution and therefore requires the source run to be settled.\n */\nexport class JobNotReplayableError extends Error {\n override readonly name = 'JobNotReplayableError';\n constructor(\n public readonly runId: string,\n public readonly currentStatus: string,\n ) {\n super(\n `Run ${runId} is not replayable from status '${currentStatus}'. ` +\n `Only 'completed', 'failed', 'timed_out', and 'canceled' are eligible.`,\n );\n }\n}\n\n/**\n * A `concurrency_key_template` or `dedupe_key_template` referenced a field\n * that is not present on the input payload. Caught at `start` time so the\n * caller sees the misconfiguration synchronously rather than at claim time.\n */\nexport class JobTemplateFieldMissingError extends Error {\n override readonly name = 'JobTemplateFieldMissingError';\n constructor(\n public readonly template: string,\n public readonly field: string,\n ) {\n super(\n `Template '${template}' references input field '${field}' which is ` +\n `missing or undefined on the payload.`,\n );\n }\n}\n\n/**\n * Thrown by the four multi-tenant-aware service-layer backends (JOB-8)\n * when `JobsDomainModule` was configured with `multiTenant: true` but the\n * caller did not pass a `tenantId` in the relevant options object.\n *\n * **Strict enforcement rationale (resolved 2026-04-18).** Cross-tenant data\n * leakage is the worst class of bug a multi-tenant system can ship; surfacing\n * the misuse loudly at the call site (rather than silently defaulting to\n * `null` or to the \"last tenant seen\") prevents both accidental global\n * writes and sneaky reads that return a union of tenants.\n *\n * - `undefined` `tenantId` → throw this error.\n * - Explicit `null` `tenantId` → passes; opts the call into cross-tenant\n * background work (e.g. a nightly housekeeping job that must scan all\n * tenants). The row is persisted with `tenant_id = NULL`.\n */\nexport class MissingTenantIdError extends Error {\n override readonly name = 'MissingTenantIdError';\n constructor(public readonly method: string) {\n super(\n `MissingTenantIdError: JobsDomainModule was configured with ` +\n `multiTenant=true but ${method} was called without tenantId ` +\n `(undefined). Pass an explicit tenantId, or pass null for ` +\n `cross-tenant work.`,\n );\n }\n}\n\n/**\n * Thrown by `JobWorkerModule.onModuleInit` (Drizzle backend only) when the\n * `job` table contains type rows for which no `@JobHandler` is registered\n * in the running process. Surfaces every orphaned type at once so a single\n * boot tells the operator everything to clean up.\n *\n * Skipped entirely in memory mode (Q4 resolution 2026-04-19) — the memory\n * backend has no DB rows to validate; `MemoryJobOrchestrator.start()`\n * throws `JobTypeNotFoundError` synchronously for unknown types instead.\n */\nexport class BootValidationError extends Error {\n override readonly name = 'BootValidationError';\n constructor(public readonly missingHandlers: string[]) {\n super(\n `BootValidationError: ${missingHandlers.length} orphaned job type(s) ` +\n `in 'job' table with no matching @JobHandler in the running process: ` +\n `[${missingHandlers.join(', ')}]. Either register the handler(s) or ` +\n `remove the rows.`,\n );\n }\n}\n\n/**\n * Thrown by `JobWorkerModule.onModuleInit` when one or more `@JobHandler`\n * classes target a `reserved: true` pool from the resolved pool config\n * (the three `events_*` pools are reserved for the events subsystem\n * outbox drain). Listing every offender on a single boot avoids the\n * fix-one-restart-fix-next loop.\n */\nexport class ReservedPoolViolationError extends Error {\n override readonly name = 'ReservedPoolViolationError';\n constructor(\n public readonly offenders: ReadonlyArray<{\n handlerClass: string;\n pool: string;\n }>,\n ) {\n super(\n `ReservedPoolViolationError: ${offenders.length} @JobHandler(s) target ` +\n `reserved pools — reserved pools are framework-only:\\n` +\n offenders\n .map((o) => ` - ${o.handlerClass} → pool='${o.pool}'`)\n .join('\\n'),\n );\n }\n}\n","/**\n * DrizzleJobRunService — scope-oriented reads and bulk operations against\n * `job_run` (ADR-022, JOB-3).\n *\n * Separate from the orchestrator because the access pattern differs: this\n * service scans by `(scope_entity_type, scope_entity_id)` via\n * `idx_job_run_scope`, whereas orchestrator mutates individual runs by id.\n */\nimport { Inject, Injectable } from '@nestjs/common';\nimport { and, asc, desc, eq, gte, inArray, isNull, lt, or, sql } from 'drizzle-orm';\nimport type { DrizzleClient } from '../../types/drizzle';\nimport { DRIZZLE } from '../../constants/tokens';\nimport { jobRuns, type JobRunRow } from './job-orchestration.schema';\nimport type { JobRun } from './job-orchestrator.protocol';\nimport type {\n IJobRunService,\n ListForScopeOptions,\n CancelForScopeOptions,\n RescheduleForScopeOptions,\n PoolStatusCount,\n JobRunFailure,\n ListJobRunsQuery,\n JobRunPage,\n JobRunSummary,\n} from './job-run-service.protocol';\nimport {\n clampLimit,\n decodeKeysetCursor,\n encodeKeysetCursor,\n toJobRunSummary,\n} from './job-run-keyset-cursor';\nimport type { IJobOrchestrator } from './job-orchestrator.protocol';\nimport { JOB_ORCHESTRATOR, JOBS_MULTI_TENANT } from './jobs-domain.tokens';\nimport { MissingTenantIdError } from './jobs-errors';\n\nconst NON_TERMINAL_STATUSES: JobRunRow['status'][] = [\n 'pending',\n 'running',\n 'waiting',\n];\n\n@Injectable()\nexport class DrizzleJobRunService implements IJobRunService {\n constructor(\n @Inject(DRIZZLE) private readonly db: DrizzleClient,\n @Inject(JOB_ORCHESTRATOR) private readonly orchestrator: IJobOrchestrator,\n @Inject(JOBS_MULTI_TENANT) private readonly multiTenant: boolean,\n ) {}\n\n /**\n * JOB-8 — produce the tenant WHERE fragment (or `null` to opt out).\n * Returns `null` when multi-tenancy is off (caller skips the predicate).\n * Throws `MissingTenantIdError` when on + `undefined`.\n * When on + explicit `null`, filters `tenant_id IS NULL`.\n */\n private tenantCondition(\n method: string,\n tenantId: string | null | undefined,\n ) {\n if (!this.multiTenant) return null;\n if (tenantId === undefined) throw new MissingTenantIdError(method);\n return tenantId === null\n ? isNull(jobRuns.tenantId)\n : eq(jobRuns.tenantId, tenantId);\n }\n\n async listForScope(\n entityType: string,\n entityId: string,\n opts: ListForScopeOptions = {},\n ): Promise<JobRun[]> {\n const conditions = [\n eq(jobRuns.scopeEntityType, entityType),\n eq(jobRuns.scopeEntityId, entityId),\n ];\n const tenantCond = this.tenantCondition('listForScope', opts.tenantId);\n if (tenantCond) conditions.push(tenantCond);\n if (opts.status) {\n if (Array.isArray(opts.status)) {\n conditions.push(inArray(jobRuns.status, opts.status));\n } else {\n conditions.push(eq(jobRuns.status, opts.status));\n }\n }\n if (opts.jobType) {\n conditions.push(eq(jobRuns.jobType, opts.jobType));\n }\n\n const orderCol = (() => {\n switch (opts.orderBy) {\n case 'created_at asc':\n return asc(jobRuns.createdAt);\n case 'run_at desc':\n return desc(jobRuns.runAt);\n case 'run_at asc':\n return asc(jobRuns.runAt);\n case 'created_at desc':\n default:\n return desc(jobRuns.createdAt);\n }\n })();\n\n let q = this.db\n .select()\n .from(jobRuns)\n .where(and(...conditions))\n .orderBy(orderCol)\n .$dynamic();\n\n if (typeof opts.limit === 'number') {\n q = q.limit(opts.limit);\n }\n if (typeof opts.offset === 'number') {\n q = q.offset(opts.offset);\n }\n\n const rows = await q;\n return rows as JobRun[];\n }\n\n async cancelForScope(\n entityType: string,\n entityId: string,\n opts: CancelForScopeOptions = {},\n ): Promise<void> {\n const tenantCond = this.tenantCondition('cancelForScope', opts.tenantId);\n const conditions = [\n eq(jobRuns.scopeEntityType, entityType),\n eq(jobRuns.scopeEntityId, entityId),\n inArray(jobRuns.status, NON_TERMINAL_STATUSES),\n ];\n if (tenantCond) conditions.push(tenantCond);\n\n const rows = await this.db\n .select({ id: jobRuns.id })\n .from(jobRuns)\n .where(and(...conditions));\n\n for (const { id } of rows) {\n // Propagate the tenant gate into cascade-cancel. The scope query has\n // already narrowed to this tenant; passing `tenantId` through keeps\n // the orchestrator's per-row guard consistent under multi-tenant mode.\n await this.orchestrator.cancel(id, {\n cascade: true,\n tenantId: opts.tenantId,\n });\n }\n }\n\n async rescheduleForScope(\n entityType: string,\n entityId: string,\n newRunAt: Date,\n opts: RescheduleForScopeOptions = {},\n ): Promise<void> {\n const tenantCond = this.tenantCondition('rescheduleForScope', opts.tenantId);\n const conditions = [\n eq(jobRuns.scopeEntityType, entityType),\n eq(jobRuns.scopeEntityId, entityId),\n eq(jobRuns.status, 'pending'),\n ];\n if (tenantCond) conditions.push(tenantCond);\n\n await this.db\n .update(jobRuns)\n .set({ runAt: newRunAt, updatedAt: new Date() })\n .where(and(...conditions));\n }\n\n async countByPoolAndStatus(\n tenantId?: string | null,\n ): Promise<PoolStatusCount[]> {\n const tenantCond = this.tenantCondition('countByPoolAndStatus', tenantId);\n const rows = await this.db\n .select({\n pool: jobRuns.pool,\n status: jobRuns.status,\n count: sql<number>`count(*)::int`.as('count'),\n })\n .from(jobRuns)\n .where(tenantCond ?? undefined)\n .groupBy(jobRuns.pool, jobRuns.status);\n\n return rows.map((r) => ({\n pool: r.pool,\n status: r.status,\n count: Number(r.count),\n }));\n }\n\n async listRecentFailed(\n limit: number,\n tenantId?: string | null,\n ): Promise<JobRunFailure[]> {\n const conditions = [eq(jobRuns.status, 'failed' as const)];\n const tenantCond = this.tenantCondition('listRecentFailed', tenantId);\n if (tenantCond) conditions.push(tenantCond);\n\n const rows = await this.db\n .select()\n .from(jobRuns)\n .where(and(...conditions))\n .orderBy(desc(jobRuns.finishedAt), desc(jobRuns.updatedAt))\n .limit(limit);\n\n return rows.map((r) => ({\n runId: r.id,\n jobType: r.jobType,\n pool: r.pool,\n scopeEntityType: r.scopeEntityType,\n scopeEntityId: r.scopeEntityId,\n tenantId: r.tenantId,\n attempts: r.attempts,\n errorMessage: r.error?.message ?? null,\n failedAt: r.finishedAt ?? r.updatedAt,\n createdAt: r.createdAt,\n }));\n }\n\n async listJobRuns(query: ListJobRunsQuery = {}): Promise<JobRunPage> {\n const limit = clampLimit(query.limit);\n const conditions = [];\n\n const tenantCond = this.tenantCondition('listJobRuns', query.tenantId);\n if (tenantCond) conditions.push(tenantCond);\n if (query.poolId) conditions.push(eq(jobRuns.pool, query.poolId));\n if (query.rootRunId) conditions.push(eq(jobRuns.rootRunId, query.rootRunId));\n if (query.status) conditions.push(eq(jobRuns.status, query.status));\n if (query.since) conditions.push(gte(jobRuns.createdAt, query.since));\n\n // Keyset seek: WHERE (created_at, id) < (cursorCreatedAt, cursorId),\n // expanded into a SARGable OR so the same `created_at` index is used.\n if (query.cursor) {\n const keyset = decodeKeysetCursor(query.cursor);\n if (keyset) {\n conditions.push(\n or(\n lt(jobRuns.createdAt, keyset.createdAt),\n and(\n eq(jobRuns.createdAt, keyset.createdAt),\n lt(jobRuns.id, keyset.id),\n ),\n )!,\n );\n }\n }\n\n // Fetch one extra row to determine whether a next page exists without a\n // separate COUNT.\n const rows = await this.db\n .select()\n .from(jobRuns)\n .where(conditions.length > 0 ? and(...conditions) : undefined)\n .orderBy(desc(jobRuns.createdAt), desc(jobRuns.id))\n .limit(limit + 1);\n\n const hasMore = rows.length > limit;\n const page = hasMore ? rows.slice(0, limit) : rows;\n const items = page.map(toJobRunSummary);\n const last = page[page.length - 1];\n const nextCursor =\n hasMore && last\n ? encodeKeysetCursor({ createdAt: last.createdAt, id: last.id })\n : null;\n\n return { items, nextCursor };\n }\n\n /**\n * Internal helper used by cascade paths (not on the public protocol).\n * Exposed as a public method on the concrete class so infrastructure\n * code (cascade tests, debug tools) can call it without a cast.\n */\n async findByRootRunId(rootRunId: string): Promise<JobRun[]> {\n const rows = await this.db\n .select()\n .from(jobRuns)\n .where(eq(jobRuns.rootRunId, rootRunId));\n return rows as JobRun[];\n }\n}\n","/**\n * Keyset (seek) cursor codec for `IJobRunService.listJobRuns` (OBS-LIST-1).\n *\n * The list is ordered `created_at DESC, id DESC`. The cursor encodes the\n * `(createdAt, id)` of the last row on the previous page so the next page\n * can seek with `WHERE (created_at, id) < (cursorCreatedAt, cursorId)`\n * rather than an `OFFSET`. Keyset pagination stays O(log n) on deep pages\n * and is stable as new rows arrive at the head.\n *\n * The cursor is opaque to consumers: a base64url-encoded JSON tuple. Shape\n * is an implementation detail — never parse it outside this module.\n *\n * Also hosts `toJobRunSummary`, the single `JobRunRow → JobRunSummary`\n * projection shared by both backends so the narrow shape stays in sync.\n */\nimport type { JobRunRow } from './job-orchestration.schema';\nimport type { JobRunSummary } from './job-run-service.protocol';\n\nexport interface JobRunKeyset {\n /** `created_at` of the last row on the previous page. */\n createdAt: Date;\n /** `id` (UUID) tie-break of the last row on the previous page. */\n id: string;\n}\n\n/** Default page size when `limit` is omitted. */\nexport const DEFAULT_LIST_LIMIT = 50;\n/** Hard upper bound on page size to keep a single read bounded. */\nexport const MAX_LIST_LIMIT = 200;\n\n/** Clamp a caller-supplied `limit` into `[1, MAX_LIST_LIMIT]`. */\nexport function clampLimit(limit: number | undefined): number {\n if (typeof limit !== 'number' || !Number.isFinite(limit)) {\n return DEFAULT_LIST_LIMIT;\n }\n const floored = Math.floor(limit);\n if (floored < 1) return 1;\n if (floored > MAX_LIST_LIMIT) return MAX_LIST_LIMIT;\n return floored;\n}\n\nexport function encodeKeysetCursor(keyset: JobRunKeyset): string {\n const tuple = [keyset.createdAt.toISOString(), keyset.id];\n return Buffer.from(JSON.stringify(tuple), 'utf8').toString('base64url');\n}\n\n/**\n * Decode an opaque cursor back into its `(createdAt, id)` keyset. Returns\n * `null` for a malformed cursor so callers can treat garbage input as\n * \"start from the beginning\" rather than throwing on user-supplied data.\n */\nexport function decodeKeysetCursor(cursor: string): JobRunKeyset | null {\n try {\n const json = Buffer.from(cursor, 'base64url').toString('utf8');\n const parsed = JSON.parse(json) as unknown;\n if (!Array.isArray(parsed) || parsed.length !== 2) return null;\n const [iso, id] = parsed;\n if (typeof iso !== 'string' || typeof id !== 'string') return null;\n const createdAt = new Date(iso);\n if (Number.isNaN(createdAt.getTime())) return null;\n return { createdAt, id };\n } catch {\n return null;\n }\n}\n\n/**\n * Project a raw `job_run` row into the narrow `JobRunSummary` shape exposed\n * by `listJobRuns`. `errorMessage` is pulled from the jsonb `error.message`.\n */\nexport function toJobRunSummary(r: JobRunRow): JobRunSummary {\n return {\n runId: r.id,\n rootRunId: r.rootRunId,\n parentRunId: r.parentRunId,\n triggerSource: r.triggerSource,\n triggerRef: r.triggerRef,\n jobType: r.jobType,\n pool: r.pool,\n status: r.status,\n scopeEntityType: r.scopeEntityType,\n scopeEntityId: r.scopeEntityId,\n tenantId: r.tenantId,\n attempts: r.attempts,\n errorMessage: r.error?.message ?? null,\n runAt: r.runAt,\n startedAt: r.startedAt,\n finishedAt: r.finishedAt,\n createdAt: r.createdAt,\n };\n}\n","/**\n * DrizzleJobStepService — upsert + lookup on `job_step` for replay-safe\n * memoization (ADR-022, JOB-3).\n *\n * `recordStep` upserts on the `(job_run_id, step_id)` unique index — each\n * step row is written as `running` first, then transitioned to a terminal\n * state (`completed` / `failed` / `skipped`). `findStep` is the hot path\n * that `ctx.step()` consults on every invocation; null on miss.\n */\nimport { Inject, Injectable } from '@nestjs/common';\nimport { and, eq } from 'drizzle-orm';\nimport type { DrizzleClient } from '../../types/drizzle';\nimport { DRIZZLE } from '../../constants/tokens';\nimport { jobSteps, type JobStepRow } from './job-orchestration.schema';\nimport type {\n IJobStepService,\n JobStep,\n RecordStepInput,\n} from './job-step-service.protocol';\n\n@Injectable()\nexport class DrizzleJobStepService implements IJobStepService {\n constructor(@Inject(DRIZZLE) private readonly db: DrizzleClient) {}\n\n async recordStep(input: RecordStepInput): Promise<JobStep> {\n const values = {\n jobRunId: input.jobRunId,\n stepId: input.stepId,\n kind: input.kind,\n seq: input.seq,\n status: input.status,\n input: (input.input ?? null) as Record<string, unknown> | null,\n output: (input.output ?? null) as Record<string, unknown> | null,\n error: input.error ?? null,\n attempts: input.attempts ?? 0,\n startedAt: input.startedAt ?? null,\n finishedAt: input.finishedAt ?? null,\n };\n\n const [row] = await this.db\n .insert(jobSteps)\n .values(values)\n .onConflictDoUpdate({\n target: [jobSteps.jobRunId, jobSteps.stepId],\n set: {\n status: values.status,\n output: values.output,\n error: values.error,\n finishedAt: values.finishedAt,\n attempts: values.attempts,\n },\n })\n .returning();\n\n return row as JobStep;\n }\n\n async findStep(runId: string, stepId: string): Promise<JobStep | null> {\n const [row] = await this.db\n .select()\n .from(jobSteps)\n .where(and(eq(jobSteps.jobRunId, runId), eq(jobSteps.stepId, stepId)))\n .limit(1);\n return ((row as JobStepRow | undefined) ?? null) as JobStep | null;\n }\n}\n","/**\n * MemoryJobOrchestrator — in-process implementation of `IJobOrchestrator`\n * (ADR-022, JOB-4).\n *\n * Exists solely for the unit test suite: reproduces the Drizzle backend's\n * observable behaviour (claim ordering, collision modes, dedupe collapse,\n * memoization cache, replay row-clearing, cascade cancel) without a\n * database. Not production — the single-process mutex is a substitute for\n * Postgres' `FOR UPDATE SKIP LOCKED`; acceptable non-parity is listed in\n * `docs/specs/JOB-4.md` (fsync, query perf, multi-process claim).\n *\n * The `MemoryJobStore` is shared with `MemoryJobRunService` /\n * `MemoryJobStepService` — all three services mutate the same Maps under\n * the orchestrator's mutex.\n */\nimport { randomUUID } from 'node:crypto';\nimport { Inject, Injectable, Logger, Optional } from '@nestjs/common';\nimport { ModuleRef } from '@nestjs/core';\nimport type {\n JobDefinitionRow,\n JobRunRow,\n} from './job-orchestration.schema';\nimport type {\n CancelOptions,\n IJobOrchestrator,\n JobPoolDef,\n JobRun,\n JobUpsertEntry,\n StartOptions,\n} from './job-orchestrator.protocol';\nimport type {\n JobContext,\n JobHandlerBase,\n JobHandlerMeta,\n RetryPolicy,\n SpawnChildOptions,\n StepOptions,\n} from './job-handler.base';\nimport { ParentClosePolicy } from './job-handler.base';\nimport {\n JobCollisionError,\n JobNotReplayableError,\n JobTemplateFieldMissingError,\n JobTypeNotFoundError,\n MissingTenantIdError,\n} from './jobs-errors';\nimport { MemoryJobStore } from './memory-job-store';\nimport { MemoryJobStepService } from './job-step-service.memory-backend';\nimport { JOBS_MULTI_TENANT } from './jobs-domain.tokens';\n\n/**\n * Sentinel `run_at` for runs that lost the `queue` collision — they stay\n * unclaimable until the incumbent transitions terminal and the orchestrator\n * advances their `run_at` back to `now()`. Mirrors the Drizzle backend's\n * `claim-time gate` behaviour without requiring a separate claim query.\n */\nconst QUEUED_RUN_AT = new Date(8_640_000_000_000_000); // \"distant future\"\nconst TERMINAL_STATUSES: JobRunRow['status'][] = [\n 'completed',\n 'failed',\n 'timed_out',\n 'canceled',\n];\nconst DEDUPE_EXCLUDED_STATUSES: JobRunRow['status'][] = ['canceled', 'failed'];\nconst IN_FLIGHT_STATUSES: JobRunRow['status'][] = ['pending', 'running'];\n\nfunction isTerminal(status: JobRunRow['status']): boolean {\n return TERMINAL_STATUSES.includes(status);\n}\n\n/**\n * Mirror of `evaluateKeyTemplate` in the Drizzle backend. Kept private here\n * rather than exported so the memory backend has no dependency on the\n * Drizzle module.\n */\nfunction evaluateKeyTemplate(\n template: string,\n input: Record<string, unknown>,\n): string {\n return template.replace(\n /\\{\\{\\s*([a-zA-Z0-9_]+)\\s*\\}\\}/g,\n (_m, field: string) => {\n const value = input[field];\n if (value === undefined || value === null) {\n throw new JobTemplateFieldMissingError(template, field);\n }\n return String(value);\n },\n );\n}\n\n/**\n * Single-promise-chain mutex. Every mutating op on the store goes through\n * `run(...)` so two concurrent `start` calls observe the same sequential\n * consistency Postgres gives us via `FOR UPDATE SKIP LOCKED`. Error\n * swallowing on the chain pointer prevents one failed call from poisoning\n * the queue for subsequent callers.\n *\n * Kept private to this file on purpose — the spec explicitly forbids\n * exporting this; it exists only for the memory backend's internal\n * serialisation.\n */\nclass PromiseMutex {\n private queue: Promise<void> = Promise.resolve();\n\n async run<T>(fn: () => Promise<T>): Promise<T> {\n const next = this.queue.then(() => fn());\n // Swallow errors on the chain pointer so a throwing `fn` doesn't\n // permanently reject every future caller.\n this.queue = next.then(\n () => undefined,\n () => undefined,\n );\n return next;\n }\n}\n\n/** Handler registry entry — class + frozen metadata. */\ninterface HandlerRegistration {\n type: string;\n meta: JobHandlerMeta<unknown>;\n handlerClass: new (...args: unknown[]) => JobHandlerBase<unknown>;\n}\n\n@Injectable()\nexport class MemoryJobOrchestrator implements IJobOrchestrator {\n private readonly logger = new Logger(MemoryJobOrchestrator.name);\n private readonly mutex = new PromiseMutex();\n private readonly handlerRegistry = new Map<string, HandlerRegistration>();\n\n /**\n * `runId → dependent runId[]` — when a run with `concurrencyKey = K`\n * blocks on an incumbent, its id is added here under the incumbent's id.\n * On incumbent terminal transition we advance every dependent's `runAt`\n * back to `now()` so it becomes claimable.\n */\n private readonly queueBlockers = new Map<string, string[]>();\n\n constructor(\n private readonly store: MemoryJobStore,\n private readonly stepService: MemoryJobStepService,\n @Inject(JOBS_MULTI_TENANT) private readonly multiTenant: boolean,\n @Optional() private readonly moduleRef?: ModuleRef,\n ) {}\n\n /**\n * JOB-8 — mirror of the Drizzle backend's `resolveTenantId`. Returns the\n * value to stamp on `tenant_id` / compare against in memory predicates.\n * Off → always `null`. On + `undefined` → throw. On + `null`/string → pass.\n */\n private resolveTenantId(\n method: string,\n tenantId: string | null | undefined,\n ): string | null {\n if (!this.multiTenant) return null;\n if (tenantId === undefined) throw new MissingTenantIdError(method);\n return tenantId;\n }\n\n // ==========================================================================\n // registerHandler — replaces Drizzle's `job` table upsert\n // ==========================================================================\n\n /**\n * Populate the in-memory job definition row plus handler class lookup.\n * Called by `JobWorkerModule.onModuleInit` in memory mode, or directly by\n * unit tests that want to seed the registry without NestJS.\n */\n registerHandler<TInput>(\n type: string,\n meta: JobHandlerMeta<TInput>,\n handlerClass: new (...args: unknown[]) => JobHandlerBase<TInput>,\n ): void {\n const concurrencyKeyTemplate =\n (meta.concurrency as { key?: string } | undefined)?.key ?? null;\n const dedupeKeyTemplate =\n (meta.dedupe as { key?: string } | undefined)?.key ?? null;\n const dedupeWindowMs = meta.dedupe?.windowMs ?? null;\n const now = new Date();\n\n const def: JobDefinitionRow = {\n type,\n version: 1,\n pool: meta.pool ?? 'batch',\n scopeEntityType: meta.scope?.entity ?? null,\n retryPolicy: meta.retry ?? {\n attempts: 1,\n backoff: 'fixed',\n baseMs: 0,\n },\n timeoutMs: meta.timeoutMs ?? null,\n concurrencyKeyTemplate:\n typeof concurrencyKeyTemplate === 'string' ? concurrencyKeyTemplate : null,\n collisionMode:\n (meta.concurrency?.collisionMode as JobDefinitionRow['collisionMode']) ??\n 'queue',\n dedupeKeyTemplate:\n typeof dedupeKeyTemplate === 'string' ? dedupeKeyTemplate : null,\n dedupeWindowMs,\n priorityDefault: 0,\n replayFrom: meta.replayFrom ?? 'last_checkpoint',\n createdAt: now,\n updatedAt: now,\n };\n\n this.store.jobs.set(type, def);\n this.handlerRegistry.set(type, {\n type,\n meta: meta as JobHandlerMeta<unknown>,\n handlerClass: handlerClass as unknown as new (\n ...args: unknown[]\n ) => JobHandlerBase<unknown>,\n });\n }\n\n /** Test helper — look up a registered handler without exposing the map. */\n getHandlerRegistration(type: string): HandlerRegistration | undefined {\n return this.handlerRegistry.get(type);\n }\n\n /**\n * Boot-time upsert per `IJobOrchestrator.upsertJobRows`. Memory backend\n * just funnels each entry through `registerHandler`. The validator is\n * skipped entirely in memory mode (Q4 resolution 2026-04-19), so the\n * orphaned list is always empty — there are no DB rows to compare against.\n */\n async upsertJobRows(\n entries: JobUpsertEntry[],\n poolConfig: ReadonlyMap<string, JobPoolDef>,\n ): Promise<{ orphaned: string[] }> {\n void poolConfig; // pool validation is the module's responsibility\n for (const entry of entries) {\n this.registerHandler(\n entry.type,\n entry.meta as JobHandlerMeta<unknown>,\n entry.handlerClass as new (...args: unknown[]) => JobHandlerBase<unknown>,\n );\n }\n return { orphaned: [] };\n }\n\n // ==========================================================================\n // start\n // ==========================================================================\n\n async start(\n type: string,\n input: unknown,\n opts: StartOptions = {},\n // BRIDGE-7: signature parity with Drizzle backend. The memory backend\n // has no real transactions (its \"atomic\" boundary is a process-wide\n // mutex acquired by the body below), so the parameter is intentionally\n // ignored. Accepting it lets EventFlowService unit tests exercise the\n // same code path without two stub orchestrators.\n _tx?: unknown,\n ): Promise<JobRun> {\n // JOB-8 — resolve tenant gate outside the mutex so the error throws\n // synchronously-ish from the caller's stack rather than via the mutex's\n // deferred chain (matches Drizzle backend's pre-transaction guard).\n const tenantId = this.resolveTenantId('start', opts.tenantId);\n\n return this.mutex.run(async () => {\n const payload = (input ?? {}) as Record<string, unknown>;\n const definition = this.store.jobs.get(type);\n if (!definition) throw new JobTypeNotFoundError(type);\n\n // 1. Dedupe — return existing non-excluded run within the window.\n if (definition.dedupeKeyTemplate && definition.dedupeWindowMs) {\n const dedupeKey = evaluateKeyTemplate(\n definition.dedupeKeyTemplate,\n payload,\n );\n const windowStart = Date.now() - definition.dedupeWindowMs;\n const existing = this.findDedupeCandidate(type, dedupeKey, windowStart);\n if (existing) return existing;\n }\n\n // 2. Concurrency collision check.\n let concurrencyKey: string | null = null;\n let queueBlockedBy: string | null = null;\n if (definition.concurrencyKeyTemplate) {\n concurrencyKey = evaluateKeyTemplate(\n definition.concurrencyKeyTemplate,\n payload,\n );\n const incumbent = this.findInFlightByConcurrencyKey(concurrencyKey);\n if (incumbent) {\n switch (definition.collisionMode) {\n case 'reject':\n throw new JobCollisionError(type, concurrencyKey, incumbent);\n case 'replace':\n // Cancel incumbent (cascading children). Must happen inside\n // the mutex — call the internal helper, not public `cancel()`\n // (public `cancel` would re-enter the mutex and deadlock).\n // Internal replace path sidesteps the tenant gate — it uses\n // the incumbent's own tenant (same concurrency key implies\n // same tenant in practice, but the gate is bypassed via\n // `incumbent.tenantId` to avoid accidental cross-tenant\n // MissingTenantIdError bubbling from the user's `start` call).\n this.cancelLocked(\n incumbent.id,\n { cascade: true, reason: 'replaced' },\n incumbent.tenantId,\n );\n break;\n case 'queue':\n queueBlockedBy = incumbent.id;\n break;\n }\n }\n }\n\n // 3. Resolve lineage.\n const newId = randomUUID();\n let rootRunId: string = newId;\n if (opts.parentRunId) {\n const parent = this.store.runs.get(opts.parentRunId);\n if (!parent) {\n throw new Error(\n `parentRunId ${opts.parentRunId} does not reference an existing job_run`,\n );\n }\n rootRunId = parent.rootRunId;\n }\n\n // 4. Compute dedupe key for the persisted row (separate from dedupe\n // short-circuit above — we store it even when no prior run matched\n // so future dedupe checks see it).\n const dedupeKey = definition.dedupeKeyTemplate\n ? evaluateKeyTemplate(definition.dedupeKeyTemplate, payload)\n : null;\n\n const now = new Date();\n const runAt = queueBlockedBy\n ? QUEUED_RUN_AT\n : (opts.runAt ?? now);\n\n const row: JobRunRow = {\n id: newId,\n jobType: type,\n jobVersion: definition.version,\n parentRunId: opts.parentRunId ?? null,\n rootRunId,\n parentClosePolicy: opts.parentClosePolicy ?? 'terminate',\n scopeEntityType: opts.scope?.entityType ?? null,\n scopeEntityId: opts.scope?.entityId ?? null,\n tenantId,\n tags: opts.tags ?? {},\n pool: opts.pool ?? definition.pool,\n priority: opts.priority ?? definition.priorityDefault,\n concurrencyKey,\n dedupeKey,\n status: 'pending',\n input: payload,\n output: null,\n error: null,\n triggerSource: opts.triggerSource ?? 'manual',\n triggerRef: opts.triggerRef ?? null,\n runAt,\n startedAt: null,\n finishedAt: null,\n claimedAt: null,\n attempts: 0,\n waitKind: null,\n resumeToken: null,\n waitDeadline: null,\n createdAt: now,\n updatedAt: now,\n };\n\n this.store.runs.set(newId, row);\n if (queueBlockedBy) {\n const list = this.queueBlockers.get(queueBlockedBy) ?? [];\n list.push(newId);\n this.queueBlockers.set(queueBlockedBy, list);\n }\n return row;\n });\n }\n\n // ==========================================================================\n // cancel\n // ==========================================================================\n\n async cancel(runId: string, opts: CancelOptions = {}): Promise<void> {\n // JOB-8 — strict tenant gate outside the mutex (matches Drizzle path).\n const tenantId = this.resolveTenantId('cancel', opts.tenantId);\n await this.mutex.run(async () => {\n this.cancelLocked(runId, opts, tenantId);\n });\n }\n\n /**\n * Internal cancel that assumes the caller already holds the mutex.\n * Synchronous because all store ops are in-memory. Idempotent.\n *\n * `tenantForGate` is the already-validated tenant id (or `null`). When\n * non-null it gates the initial cancellation to that tenant's run; the\n * cascade step then sweeps descendants on the same `rootRunId` without\n * re-checking — children of a tenant-gated parent always share the\n * tenant (enforced at `start` time).\n */\n private cancelLocked(\n runId: string,\n opts: CancelOptions,\n tenantForGate: string | null,\n ): void {\n const run = this.store.runs.get(runId);\n if (!run) return;\n // JOB-8 — cross-tenant cancel is silent no-op.\n if (this.multiTenant && run.tenantId !== tenantForGate) return;\n if (isTerminal(run.status)) return;\n\n const now = new Date();\n\n // Collect descendants up front so Cancel-policy parents can wait on\n // children (their `finished_at` is set after children transition).\n const descendants =\n opts.cascade === false\n ? []\n : Array.from(this.store.runs.values()).filter(\n (r) =>\n r.rootRunId === run.rootRunId &&\n r.id !== runId &&\n !isTerminal(r.status),\n );\n\n // Group by policy stored on the child.\n const terminateChildren = descendants.filter(\n (d) => d.parentClosePolicy === ParentClosePolicy.Terminate,\n );\n const cancelChildren = descendants.filter(\n (d) => d.parentClosePolicy === ParentClosePolicy.Cancel,\n );\n // 'abandon' → do nothing.\n\n // Terminate policy: cancel children, then parent.\n for (const child of terminateChildren) {\n this.transitionToCanceled(child.id, now);\n }\n\n // Cancel policy: cancel children first, then parent (so parent's\n // finished_at is set only after children transitioned).\n for (const child of cancelChildren) {\n this.transitionToCanceled(child.id, now);\n }\n\n this.transitionToCanceled(runId, now);\n\n void opts.reason; // reserved for future audit logging\n }\n\n private transitionToCanceled(runId: string, at: Date): void {\n const run = this.store.runs.get(runId);\n if (!run) return;\n if (isTerminal(run.status)) return;\n const next: JobRunRow = {\n ...run,\n status: 'canceled',\n finishedAt: at,\n updatedAt: at,\n };\n this.store.runs.set(runId, next);\n this.unblockQueuedDependents(runId);\n }\n\n /**\n * When `runId` transitions to a terminal state, advance every dependent\n * `queue`-blocked run's `run_at` back to `now()` so `claimNext` picks\n * them up.\n */\n private unblockQueuedDependents(runId: string): void {\n const dependents = this.queueBlockers.get(runId);\n if (!dependents || dependents.length === 0) return;\n const now = new Date();\n for (const dep of dependents) {\n const depRun = this.store.runs.get(dep);\n if (!depRun) continue;\n if (depRun.status !== 'pending') continue;\n this.store.runs.set(dep, { ...depRun, runAt: now, updatedAt: now });\n }\n this.queueBlockers.delete(runId);\n }\n\n // ==========================================================================\n // claimNext — consumed by JobWorker in memory mode (tests exercise directly)\n // ==========================================================================\n\n async claimNext(pool: string): Promise<JobRunRow | null> {\n return this.mutex.run(async () => {\n const now = Date.now();\n const candidates = Array.from(this.store.runs.values()).filter(\n (r) =>\n r.status === 'pending' &&\n r.pool === pool &&\n r.runAt.getTime() <= now,\n );\n if (candidates.length === 0) return null;\n\n // ORDER BY priority DESC, run_at ASC (Drizzle parity).\n candidates.sort((a, b) => {\n if (a.priority !== b.priority) return b.priority - a.priority;\n return a.runAt.getTime() - b.runAt.getTime();\n });\n\n const winner = candidates[0]!;\n const claimedAt = new Date();\n const next: JobRunRow = {\n ...winner,\n status: 'running',\n claimedAt,\n startedAt: claimedAt,\n updatedAt: claimedAt,\n };\n this.store.runs.set(winner.id, next);\n return next;\n });\n }\n\n // ==========================================================================\n // replay\n // ==========================================================================\n\n async replay(runId: string): Promise<JobRun> {\n return this.mutex.run(async () => {\n const run = this.store.runs.get(runId);\n if (!run) throw new Error(`replay: run ${runId} not found`);\n if (!isTerminal(run.status)) {\n throw new JobNotReplayableError(runId, run.status);\n }\n const def = this.store.jobs.get(run.jobType);\n if (!def) throw new JobTypeNotFoundError(run.jobType);\n\n const mode = def.replayFrom;\n if (mode === 'scratch') {\n this.stepService.clearStepsForRun(runId);\n } else {\n // `last_step` and `last_checkpoint` collapse to the same semantic\n // in Phase 1 — delete non-completed rows, preserve memoized ones.\n // Matches the Drizzle backend exactly (see JOB-3 notes).\n this.stepService.clearIncompleteSteps(runId);\n }\n\n const now = new Date();\n const next: JobRunRow = {\n ...run,\n status: 'pending',\n attempts: 0,\n runAt: now,\n startedAt: null,\n finishedAt: null,\n claimedAt: null,\n error: null,\n output: null,\n updatedAt: now,\n };\n this.store.runs.set(runId, next);\n return next;\n });\n }\n\n // ==========================================================================\n // tick — used by unit tests + memory-mode JobWorker\n // ==========================================================================\n\n /**\n * Execute a single claimed run to completion, retry, or failure. Not on\n * `IJobOrchestrator` — it's the memory equivalent of the Drizzle\n * `JobWorker.processRun` code path. The unit tests drive it directly so\n * they can assert memoization across ticks without spinning up a worker.\n */\n async tick(runId: string): Promise<void> {\n // We load state outside the mutex because handler execution cannot\n // hold the serialisation lock — `fn()` inside `ctx.step` can call back\n // into `start` / `spawnChild` which would deadlock. Mutation points\n // (recordStep, status transition) go through the services or the\n // orchestrator entry points and re-enter the mutex there.\n const run = this.store.runs.get(runId);\n if (!run) throw new Error(`tick: run ${runId} not found`);\n if (run.status !== 'running') {\n throw new Error(\n `tick: run ${runId} must be 'running' (got '${run.status}')`,\n );\n }\n\n const registration = this.handlerRegistry.get(run.jobType);\n if (!registration) {\n await this.markFailed(run, new Error(\n `No handler registered for jobType='${run.jobType}'`,\n ), (run.attempts ?? 0) + 1);\n return;\n }\n const meta = registration.meta;\n const HandlerClass = registration.handlerClass;\n // Match the Drizzle backend: resolve the handler through Nest's\n // ModuleRef so `@Inject` constructor params work. ModuleRef is\n // @Optional() — zero-dep test stubs that construct this orchestrator\n // manually still hit the legacy `new HandlerClass()` path.\n // `get({ strict: false })` (not `create()`) — the handler must be a\n // provider in its owning module so cross-module @Inject dependencies\n // resolve. See job-worker.ts for the full rationale.\n const handler = this.moduleRef\n ? (this.moduleRef.get(\n HandlerClass as unknown as new (...args: unknown[]) => unknown,\n { strict: false },\n ) as JobHandlerBase<unknown>)\n : new HandlerClass();\n\n const ctx: JobContext<unknown> = {\n input: run.input,\n run: run as JobRun,\n step: this.makeStepFn(run),\n spawnChild: this.makeSpawnFn(run),\n logger: new Logger(`JobRun:${run.id}`),\n };\n\n const attemptsBefore = run.attempts ?? 0;\n try {\n const output = (await handler.run(ctx)) as Record<string, unknown> | undefined;\n await this.markCompleted(run, output ?? {}, attemptsBefore + 1);\n } catch (err) {\n const policy = meta.retry;\n const decision = classifyError(err, policy, attemptsBefore);\n const nextAttempts = attemptsBefore + 1;\n if (decision === 'retry' && policy) {\n const delay = computeBackoff(policy, nextAttempts);\n await this.rescheduleForRetry(run, err, nextAttempts, delay);\n } else {\n await this.markFailed(run, err, nextAttempts);\n }\n }\n }\n\n private makeStepFn(run: JobRunRow) {\n return async <TOutput>(\n stepId: string,\n fn: () => Promise<TOutput>,\n _opts?: StepOptions,\n ): Promise<TOutput> => {\n void _opts;\n const existing = await this.stepService.findStep(run.id, stepId);\n if (existing?.status === 'completed') {\n return existing.output as TOutput;\n }\n const seq = this.nextStepSeq(run.id);\n const startedAt = new Date();\n const nextAttempts = (existing?.attempts ?? 0) + 1;\n await this.stepService.recordStep({\n jobRunId: run.id,\n stepId,\n kind: 'task',\n seq,\n status: 'running',\n startedAt,\n attempts: nextAttempts,\n });\n try {\n const output = await fn();\n await this.stepService.recordStep({\n jobRunId: run.id,\n stepId,\n kind: 'task',\n seq,\n status: 'completed',\n output: output as Record<string, unknown> | undefined,\n finishedAt: new Date(),\n attempts: nextAttempts,\n });\n return output;\n } catch (err) {\n await this.stepService.recordStep({\n jobRunId: run.id,\n stepId,\n kind: 'task',\n seq,\n status: 'failed',\n error: serialiseError(err, nextAttempts, false),\n finishedAt: new Date(),\n attempts: nextAttempts,\n });\n throw err;\n }\n };\n }\n\n private makeSpawnFn(run: JobRunRow) {\n return async (\n type: string,\n input: unknown,\n opts?: SpawnChildOptions,\n ): Promise<JobRun> => {\n return this.start(type, input, {\n parentRunId: run.id,\n parentClosePolicy: opts?.closePolicy,\n runAt: opts?.runAt,\n priority: opts?.priority,\n tags: opts?.tags,\n triggerSource: 'parent',\n triggerRef: run.id,\n });\n };\n }\n\n private nextStepSeq(runId: string): number {\n const rows = this.store.steps.get(runId);\n if (!rows || rows.length === 0) return 1;\n let max = 0;\n for (const r of rows) if (r.seq > max) max = r.seq;\n return max + 1;\n }\n\n private async markCompleted(\n run: JobRunRow,\n output: Record<string, unknown>,\n attempts: number,\n ): Promise<void> {\n await this.mutex.run(async () => {\n const current = this.store.runs.get(run.id);\n if (!current || isTerminal(current.status)) return;\n const now = new Date();\n this.store.runs.set(run.id, {\n ...current,\n status: 'completed',\n output,\n finishedAt: now,\n updatedAt: now,\n attempts,\n });\n this.unblockQueuedDependents(run.id);\n });\n }\n\n private async markFailed(\n run: JobRunRow,\n err: unknown,\n attempts: number,\n ): Promise<void> {\n await this.mutex.run(async () => {\n const current = this.store.runs.get(run.id);\n if (!current || isTerminal(current.status)) return;\n const now = new Date();\n this.store.runs.set(run.id, {\n ...current,\n status: 'failed',\n finishedAt: now,\n updatedAt: now,\n attempts,\n error: serialiseError(err, attempts, false),\n });\n this.unblockQueuedDependents(run.id);\n });\n\n // parent_close_policy = 'terminate' cascade mirrors the Drizzle worker\n // (cancel runs outside its own terminal transition). We pass the run's\n // own `tenantId` so the cancel passes the multi-tenant gate — this is\n // system-internal cascade, not a user-initiated call.\n if (run.parentClosePolicy === 'terminate') {\n try {\n await this.cancel(run.id, {\n cascade: true,\n reason: 'parent-failed',\n tenantId: run.tenantId,\n });\n } catch (cascadeErr) {\n this.logger.warn(\n `cascade on failed run ${run.id}: ${(cascadeErr as Error).message}`,\n );\n }\n }\n }\n\n private async rescheduleForRetry(\n run: JobRunRow,\n err: unknown,\n attempts: number,\n delayMs: number,\n ): Promise<void> {\n await this.mutex.run(async () => {\n const current = this.store.runs.get(run.id);\n if (!current || isTerminal(current.status)) return;\n const now = new Date();\n this.store.runs.set(run.id, {\n ...current,\n status: 'pending',\n attempts,\n runAt: new Date(Date.now() + delayMs),\n startedAt: null,\n claimedAt: null,\n updatedAt: now,\n error: serialiseError(err, attempts, true),\n });\n });\n }\n\n // ==========================================================================\n // Internal queries — used by start / cancel\n // ==========================================================================\n\n private findDedupeCandidate(\n jobType: string,\n dedupeKey: string,\n windowStartMs: number,\n ): JobRunRow | null {\n let best: JobRunRow | null = null;\n for (const r of this.store.runs.values()) {\n if (r.jobType !== jobType) continue;\n if (r.dedupeKey !== dedupeKey) continue;\n if (DEDUPE_EXCLUDED_STATUSES.includes(r.status)) continue;\n if (r.createdAt.getTime() <= windowStartMs) continue;\n if (!best || r.createdAt.getTime() > best.createdAt.getTime()) {\n best = r;\n }\n }\n return best;\n }\n\n private findInFlightByConcurrencyKey(key: string): JobRunRow | null {\n for (const r of this.store.runs.values()) {\n if (r.concurrencyKey !== key) continue;\n if (!IN_FLIGHT_STATUSES.includes(r.status)) continue;\n return r;\n }\n return null;\n }\n}\n\n// ─── Pure helpers (mirrored from JobWorker so memory mode is standalone) ────\n\nfunction classifyError(\n err: unknown,\n policy: RetryPolicy | undefined,\n currentAttempts: number,\n): 'retry' | 'fail' {\n if (!policy) return 'fail';\n const errObj = err as { name?: string; code?: string } | undefined;\n const name = errObj?.name;\n const code = errObj?.code;\n const nonRetryable = policy.nonRetryableErrors ?? [];\n if (nonRetryable.some((n) => n === name || n === code)) return 'fail';\n if (currentAttempts + 1 >= policy.attempts) return 'fail';\n return 'retry';\n}\n\nfunction computeBackoff(policy: RetryPolicy, attempts: number): number {\n const base = Math.max(policy.baseMs, 0);\n if (policy.backoff === 'fixed') return base;\n const exponent = Math.max(attempts - 1, 0);\n if (exponent >= 53) return Number.MAX_SAFE_INTEGER;\n const raw = base * Math.pow(2, exponent);\n if (!Number.isFinite(raw) || raw >= Number.MAX_SAFE_INTEGER) {\n return Number.MAX_SAFE_INTEGER;\n }\n return raw;\n}\n\nfunction serialiseError(err: unknown, attempt: number, retryable: boolean) {\n const e = err as { message?: string; stack?: string } | undefined;\n return {\n message: (e?.message ?? String(err)) as string,\n stack: e?.stack,\n retryable,\n attempt,\n };\n}\n","/**\n * Handler base class, JobContext, @JobHandler decorator, and policy types\n * for the job orchestration domain (ADR-022, JOB-2).\n *\n * User-authored jobs subclass `JobHandlerBase<TInput, TOutput>` and decorate\n * the class with `@JobHandler<TInput>('job_type', meta)`. The decorator\n * 1. stores metadata via `Reflect.defineMetadata` so Nest's reflector can\n * pick it up at module boot, and\n * 2. populates `JOB_HANDLER_REGISTRY` — a module-singleton map consumed by\n * `JobWorkerModule` (JOB-5) to materialise `job` rows and resolve\n * handler classes during claim/execute.\n *\n * No runtime orchestration lives here; this file is a pure type + decorator\n * surface so downstream PRs (JOB-3..JOB-5) can implement against a stable\n * shape.\n */\n// TODO(logging-subsystem): swap to ILogger once ADR-028 lands\nimport type { Logger } from '@nestjs/common';\nimport type { EventOfType, EventTypeName } from '../events/generated/types';\nimport type { JobRun } from './job-orchestrator.protocol';\n\n// ─── ParentClosePolicy ──────────────────────────────────────────────────────\n\n/**\n * What happens to running child runs when a parent enters a terminal state.\n * Stored on the child at spawn; changes to the parent after spawn do NOT\n * retroactively rewrite children.\n */\nexport enum ParentClosePolicy {\n Terminate = 'terminate',\n Cancel = 'cancel',\n Abandon = 'abandon',\n}\n\n// ─── Policy types ───────────────────────────────────────────────────────────\n\nexport interface RetryPolicy {\n attempts: number;\n backoff: 'fixed' | 'exponential';\n baseMs: number;\n nonRetryableErrors?: string[];\n}\n\nexport interface ConcurrencyPolicy<TInput> {\n key: (input: TInput) => string;\n collisionMode: 'queue' | 'reject' | 'replace';\n}\n\nexport interface DedupePolicy<TInput> {\n key: (input: TInput) => string;\n windowMs: number;\n}\n\n/**\n * Declarative scope reference. `TScope` is parameterised so JOB-7 can narrow\n * `entity` to the generated `ScopeEntityType` union at the call site without\n * modifying this file (OQ-1 resolution, 2026-04-20).\n */\nexport interface ScopeRef<TInput, TScope extends string = string> {\n entity: TScope;\n from: (input: TInput) => string;\n}\n\n/**\n * Bridge trigger authoring shape (BRIDGE-6 follow-up — BRIDGE-6 shipped the\n * generator + runtime for `@JobHandler({ triggers })` but never added the\n * authoring field to this type; the generator's tests scan source as strings,\n * so a real decorator was never compiled and the gap went uncaught).\n *\n * Declared on `@JobHandler({ triggers })`; the codegen bridge-registry\n * generator (`src/cli/shared/bridge-registry-generator.ts`) scans these from\n * source and emits `bridge/generated/registry.ts`, validating each `event`\n * against the generated `eventRegistry` at `gen-all`. The distributed union\n * narrows `map`/`when` per `event`, so callbacks are typed against the event\n * payload (ADR-023, \"typed against PayloadOfType<T>\").\n *\n * Typed against events' generated types — the same `import type` coupling the\n * bridge already has (erased at runtime). `jobs` must NOT import `bridge`, so\n * the post-gen `BridgeTriggerEntry` is deliberately not referenced here;\n * `triggerId`/`jobType` are computed by the generator, not authored.\n */\nexport type JobTrigger<TInput> = {\n [T in EventTypeName]: {\n /** Event type that fires this trigger. Validated against `eventRegistry`. */\n event: T;\n /** Maps the event to the job input. Inlined verbatim into the registry. */\n map: (event: EventOfType<T>) => TInput;\n /** Optional guard; `false` → wrapper records `status='skipped'`. */\n when?: (event: EventOfType<T>) => boolean;\n };\n}[EventTypeName];\n\nexport interface JobHandlerMeta<TInput> {\n pool?: string;\n scope?: ScopeRef<TInput>;\n retry?: RetryPolicy;\n concurrency?: ConcurrencyPolicy<TInput>;\n dedupe?: DedupePolicy<TInput>;\n timeoutMs?: number;\n replayFrom?: 'scratch' | 'last_step' | 'last_checkpoint';\n /**\n * Bridge triggers (ADR-023 Tier 3). Codegen scans these into `bridgeRegistry`;\n * the framework `BridgeDeliveryHandler` starts this job per matched event.\n * Absent for jobs started directly or via `IEventFlow.publishAndStart`.\n */\n triggers?: readonly JobTrigger<TInput>[];\n}\n\n// ─── Runtime option shapes ──────────────────────────────────────────────────\n\nexport interface StepOptions {\n retry?: RetryPolicy;\n timeoutMs?: number;\n}\n\nexport interface SpawnChildOptions {\n closePolicy?: ParentClosePolicy;\n runAt?: Date;\n priority?: number;\n tags?: Record<string, string>;\n}\n\n// ─── JobContext ─────────────────────────────────────────────────────────────\n\nexport interface JobContext<TInput> {\n readonly input: TInput;\n readonly run: JobRun;\n step<TOutput>(\n stepId: string,\n fn: () => Promise<TOutput>,\n opts?: StepOptions,\n ): Promise<TOutput>;\n spawnChild(type: string, input: unknown, opts?: SpawnChildOptions): Promise<JobRun>;\n readonly logger: Logger;\n // NOT in Phase 1 — deferred to ADR-025:\n // waitFor(kind, token, opts)\n // signal(token, payload)\n // sleep(ms)\n}\n\n// ─── JobHandlerBase ─────────────────────────────────────────────────────────\n\nexport abstract class JobHandlerBase<TInput, TOutput = unknown> {\n abstract run(ctx: JobContext<TInput>): Promise<TOutput>;\n}\n\n// ─── Registry + decorator ───────────────────────────────────────────────────\n\n/**\n * Module-singleton map keyed by job type. Populated by the `@JobHandler`\n * decorator at class definition time; consumed by `JobWorkerModule` (JOB-5)\n * to upsert `job` rows and resolve handler classes during claim/execute.\n */\nexport const JOB_HANDLER_REGISTRY = new Map<\n string,\n {\n type: string;\n meta: JobHandlerMeta<unknown>;\n handlerClass: new (...args: unknown[]) => JobHandlerBase<unknown>;\n }\n>();\n\nexport const JOB_HANDLER_METADATA_KEY = Symbol('JobHandlerMeta');\n\n/**\n * Class decorator that registers a handler with the job type, the full\n * metadata shape, and the target class constructor.\n *\n * Duplicate-type behaviour (OQ-3, resolved 2026-04-18):\n * - `NODE_ENV === 'production'` → throw; silent overwrite in prod is a\n * correctness bug.\n * - `NODE_ENV === 'test'` → silent overwrite (tests intentionally\n * re-register handlers).\n * - otherwise (dev) → `console.warn` + overwrite. `console`\n * is used intentionally instead of the Nest `Logger` — decorators run\n * at module-load time before any Nest container exists.\n */\nexport function JobHandler<TInput>(\n type: string,\n meta: JobHandlerMeta<TInput>,\n): ClassDecorator {\n return (target) => {\n if (JOB_HANDLER_REGISTRY.has(type)) {\n const env = process.env.NODE_ENV;\n if (env === 'production') {\n throw new Error(\n `[JobHandler] Duplicate registration for job type '${type}'. ` +\n `Each @JobHandler must declare a unique type.`,\n );\n }\n if (env !== 'test') {\n // eslint-disable-next-line no-console\n console.warn(\n `[JobHandler] Duplicate registration for job type '${type}'. ` +\n `Overwriting previous handler — this is almost certainly a bug.`,\n );\n }\n }\n\n Reflect.defineMetadata(JOB_HANDLER_METADATA_KEY, { type, meta }, target);\n JOB_HANDLER_REGISTRY.set(type, {\n type,\n meta: meta as JobHandlerMeta<unknown>,\n handlerClass: target as unknown as new (\n ...args: unknown[]\n ) => JobHandlerBase<unknown>,\n });\n };\n}\n\n// ─── HandlerRegistry — read helpers consumed by JobWorkerModule (JOB-5) ─────\n\n/**\n * Single entry shape returned by `HandlerRegistry.getAll()` / `.get()` and\n * exposed to `JobWorkerModule.onModuleInit` for boot-time upserts.\n *\n * Structurally compatible with `IJobOrchestrator.upsertJobRows`'s\n * `JobUpsertEntry` so the worker module can pass entries through verbatim\n * without re-mapping.\n */\nexport interface HandlerRegistryEntry {\n type: string;\n meta: JobHandlerMeta<unknown>;\n handlerClass: new (...args: unknown[]) => JobHandlerBase<unknown>;\n}\n\n/**\n * Read facade over `JOB_HANDLER_REGISTRY`. The decorator's write path is\n * unchanged; this namespace exists so consumers (the worker module, tests)\n * don't import the raw `Map` and accidentally mutate it.\n */\nexport namespace HandlerRegistry {\n /** All registered entries in insertion order. */\n export function getAll(): HandlerRegistryEntry[] {\n return Array.from(JOB_HANDLER_REGISTRY.values());\n }\n\n /** Lookup by job type, or `undefined` if no `@JobHandler` is registered. */\n export function get(type: string): HandlerRegistryEntry | undefined {\n return JOB_HANDLER_REGISTRY.get(type);\n }\n}\n","/**\n * MemoryJobRunService — scope-oriented queries and bulk ops over the\n * in-memory run store (ADR-022, JOB-4).\n *\n * Mirrors `DrizzleJobRunService` but scans `MemoryJobStore.runs.values()`.\n * Cancel delegates back to the orchestrator so cascade semantics stay in\n * one place.\n */\nimport { Inject, Injectable } from '@nestjs/common';\nimport type { JobRunRow } from './job-orchestration.schema';\nimport type { JobRun } from './job-orchestrator.protocol';\nimport type {\n IJobRunService,\n ListForScopeOptions,\n CancelForScopeOptions,\n RescheduleForScopeOptions,\n PoolStatusCount,\n JobRunFailure,\n ListJobRunsQuery,\n JobRunPage,\n} from './job-run-service.protocol';\nimport {\n clampLimit,\n decodeKeysetCursor,\n encodeKeysetCursor,\n toJobRunSummary,\n} from './job-run-keyset-cursor';\nimport type { IJobOrchestrator } from './job-orchestrator.protocol';\nimport { JOB_ORCHESTRATOR, JOBS_MULTI_TENANT } from './jobs-domain.tokens';\nimport { MissingTenantIdError } from './jobs-errors';\nimport { MemoryJobStore } from './memory-job-store';\n\nconst NON_TERMINAL_STATUSES: JobRunRow['status'][] = [\n 'pending',\n 'running',\n 'waiting',\n];\n\n@Injectable()\nexport class MemoryJobRunService implements IJobRunService {\n constructor(\n private readonly store: MemoryJobStore,\n @Inject(JOB_ORCHESTRATOR) private readonly orchestrator: IJobOrchestrator,\n @Inject(JOBS_MULTI_TENANT) private readonly multiTenant: boolean,\n ) {}\n\n /**\n * JOB-8 — produce a per-row predicate for the tenant gate.\n * Returns `null` when multi-tenancy is off (caller doesn't check).\n * Throws when on + `undefined`; matches `tenant_id IS NULL` on explicit\n * `null` to support cross-tenant background work.\n */\n private tenantPredicate(\n method: string,\n tenantId: string | null | undefined,\n ): ((r: JobRunRow) => boolean) | null {\n if (!this.multiTenant) return null;\n if (tenantId === undefined) throw new MissingTenantIdError(method);\n return (r) => r.tenantId === tenantId;\n }\n\n async listForScope(\n entityType: string,\n entityId: string,\n opts: ListForScopeOptions = {},\n ): Promise<JobRun[]> {\n const statusFilter = opts.status\n ? Array.isArray(opts.status)\n ? new Set(opts.status)\n : new Set([opts.status])\n : null;\n const tenantCheck = this.tenantPredicate('listForScope', opts.tenantId);\n\n const rows: JobRunRow[] = [];\n for (const r of this.store.runs.values()) {\n if (r.scopeEntityType !== entityType) continue;\n if (r.scopeEntityId !== entityId) continue;\n if (statusFilter && !statusFilter.has(r.status)) continue;\n if (opts.jobType && r.jobType !== opts.jobType) continue;\n if (tenantCheck && !tenantCheck(r)) continue;\n rows.push(r);\n }\n\n const orderBy = opts.orderBy ?? 'created_at desc';\n rows.sort((a, b) => compareBy(a, b, orderBy));\n\n const offset = opts.offset ?? 0;\n const limit = opts.limit;\n const sliced =\n typeof limit === 'number' ? rows.slice(offset, offset + limit) : rows.slice(offset);\n return sliced as JobRun[];\n }\n\n async cancelForScope(\n entityType: string,\n entityId: string,\n opts: CancelForScopeOptions = {},\n ): Promise<void> {\n const tenantCheck = this.tenantPredicate('cancelForScope', opts.tenantId);\n\n const ids: string[] = [];\n for (const r of this.store.runs.values()) {\n if (r.scopeEntityType !== entityType) continue;\n if (r.scopeEntityId !== entityId) continue;\n if (!NON_TERMINAL_STATUSES.includes(r.status)) continue;\n if (tenantCheck && !tenantCheck(r)) continue;\n ids.push(r.id);\n }\n for (const id of ids) {\n // Propagate the tenant gate through the orchestrator's cancel so the\n // internal per-row guard passes (no surprise MissingTenantIdError\n // once the scope query has already narrowed to this tenant).\n await this.orchestrator.cancel(id, {\n cascade: true,\n tenantId: opts.tenantId,\n });\n }\n }\n\n async rescheduleForScope(\n entityType: string,\n entityId: string,\n newRunAt: Date,\n opts: RescheduleForScopeOptions = {},\n ): Promise<void> {\n const tenantCheck = this.tenantPredicate('rescheduleForScope', opts.tenantId);\n for (const r of this.store.runs.values()) {\n if (r.scopeEntityType !== entityType) continue;\n if (r.scopeEntityId !== entityId) continue;\n if (r.status !== 'pending') continue;\n if (tenantCheck && !tenantCheck(r)) continue;\n this.store.runs.set(r.id, {\n ...r,\n runAt: newRunAt,\n updatedAt: new Date(),\n });\n }\n }\n\n async countByPoolAndStatus(\n tenantId?: string | null,\n ): Promise<PoolStatusCount[]> {\n const tenantCheck = this.tenantPredicate('countByPoolAndStatus', tenantId);\n const map = new Map<string, PoolStatusCount>();\n for (const r of this.store.runs.values()) {\n if (tenantCheck && !tenantCheck(r)) continue;\n const key = `${r.pool}\\0${r.status}`;\n const cur = map.get(key);\n if (cur) {\n cur.count += 1;\n } else {\n map.set(key, { pool: r.pool, status: r.status, count: 1 });\n }\n }\n return Array.from(map.values());\n }\n\n async listRecentFailed(\n limit: number,\n tenantId?: string | null,\n ): Promise<JobRunFailure[]> {\n const tenantCheck = this.tenantPredicate('listRecentFailed', tenantId);\n const failed: JobRunRow[] = [];\n for (const r of this.store.runs.values()) {\n if (r.status !== 'failed') continue;\n if (tenantCheck && !tenantCheck(r)) continue;\n failed.push(r);\n }\n failed.sort((a, b) => {\n const at = (a.finishedAt ?? a.updatedAt).getTime();\n const bt = (b.finishedAt ?? b.updatedAt).getTime();\n return bt - at;\n });\n return failed.slice(0, limit).map((r) => ({\n runId: r.id,\n jobType: r.jobType,\n pool: r.pool,\n scopeEntityType: r.scopeEntityType,\n scopeEntityId: r.scopeEntityId,\n tenantId: r.tenantId,\n attempts: r.attempts,\n errorMessage: r.error?.message ?? null,\n failedAt: r.finishedAt ?? r.updatedAt,\n createdAt: r.createdAt,\n }));\n }\n\n async listJobRuns(query: ListJobRunsQuery = {}): Promise<JobRunPage> {\n const limit = clampLimit(query.limit);\n const tenantCheck = this.tenantPredicate('listJobRuns', query.tenantId);\n const keyset = query.cursor ? decodeKeysetCursor(query.cursor) : null;\n\n const matched: JobRunRow[] = [];\n for (const r of this.store.runs.values()) {\n if (tenantCheck && !tenantCheck(r)) continue;\n if (query.poolId && r.pool !== query.poolId) continue;\n if (query.rootRunId && r.rootRunId !== query.rootRunId) continue;\n if (query.status && r.status !== query.status) continue;\n if (query.since && r.createdAt.getTime() < query.since.getTime()) continue;\n matched.push(r);\n }\n\n // Order created_at DESC, id DESC to match the Drizzle backend's keyset.\n matched.sort((a, b) => {\n const dt = b.createdAt.getTime() - a.createdAt.getTime();\n if (dt !== 0) return dt;\n return a.id < b.id ? 1 : a.id > b.id ? -1 : 0;\n });\n\n // Keyset seek: drop everything at/after the cursor's (created_at, id).\n const seeked = keyset\n ? matched.filter((r) => {\n const ct = r.createdAt.getTime();\n const kt = keyset.createdAt.getTime();\n if (ct < kt) return true;\n if (ct > kt) return false;\n return r.id < keyset.id;\n })\n : matched;\n\n const hasMore = seeked.length > limit;\n const page = hasMore ? seeked.slice(0, limit) : seeked;\n const items = page.map(toJobRunSummary);\n const last = page[page.length - 1];\n const nextCursor =\n hasMore && last\n ? encodeKeysetCursor({ createdAt: last.createdAt, id: last.id })\n : null;\n\n return { items, nextCursor };\n }\n\n /**\n * Direct lookup. Not on the protocol — concrete-class convenience for\n * tests. Matches `DrizzleJobRunService.findByRootRunId` in spirit; both\n * are debug / test helpers that sidestep the orchestrator.\n */\n findById(runId: string): JobRun | null {\n return (this.store.runs.get(runId) ?? null) as JobRun | null;\n }\n\n /** Public counterpart to the Drizzle backend's `findByRootRunId` helper. */\n findByRootRunId(rootRunId: string): JobRun[] {\n const out: JobRunRow[] = [];\n for (const r of this.store.runs.values()) {\n if (r.rootRunId === rootRunId) out.push(r);\n }\n return out as JobRun[];\n }\n}\n\nfunction compareBy(\n a: JobRunRow,\n b: JobRunRow,\n order: Exclude<ListForScopeOptions['orderBy'], undefined>,\n): number {\n switch (order) {\n case 'created_at asc':\n return a.createdAt.getTime() - b.createdAt.getTime();\n case 'run_at desc':\n return b.runAt.getTime() - a.runAt.getTime();\n case 'run_at asc':\n return a.runAt.getTime() - b.runAt.getTime();\n case 'created_at desc':\n default:\n return b.createdAt.getTime() - a.createdAt.getTime();\n }\n}\n","/**\n * MemoryJobStepService — in-memory implementation of `IJobStepService`\n * (ADR-022, JOB-4).\n *\n * Mirrors `DrizzleJobStepService` but against plain Maps. `findStep` only\n * returns `completed` rows (memoization cache hit); anything non-completed\n * is invisible so the ctx.step fn re-runs on replay / retry exactly like\n * the Drizzle backend (which deletes non-completed rows on replay).\n */\nimport { randomUUID } from 'node:crypto';\nimport { Injectable } from '@nestjs/common';\nimport type { JobStepRow } from './job-orchestration.schema';\nimport type {\n IJobStepService,\n JobStep,\n RecordStepInput,\n} from './job-step-service.protocol';\nimport { MemoryJobStore } from './memory-job-store';\n\n@Injectable()\nexport class MemoryJobStepService implements IJobStepService {\n constructor(private readonly store: MemoryJobStore) {}\n\n async findStep(runId: string, stepId: string): Promise<JobStep | null> {\n const rows = this.store.steps.get(runId);\n if (!rows) return null;\n const match = rows.find(\n (r) => r.stepId === stepId && r.status === 'completed',\n );\n return (match ?? null) as JobStep | null;\n }\n\n async recordStep(input: RecordStepInput): Promise<JobStep> {\n const rows = this.getOrCreateRows(input.jobRunId);\n const existingIdx = rows.findIndex((r) => r.stepId === input.stepId);\n\n const normalisedInput =\n (input.input ?? null) as Record<string, unknown> | null;\n const normalisedOutput =\n (input.output ?? null) as Record<string, unknown> | null;\n\n if (existingIdx >= 0) {\n const prev = rows[existingIdx]!;\n const next: JobStepRow = {\n ...prev,\n status: input.status,\n input: normalisedInput ?? prev.input,\n output: normalisedOutput ?? prev.output,\n error: input.error ?? prev.error,\n attempts: input.attempts ?? prev.attempts,\n startedAt: input.startedAt ?? prev.startedAt,\n finishedAt: input.finishedAt ?? prev.finishedAt,\n };\n rows[existingIdx] = next;\n return next as JobStep;\n }\n\n const seq = input.seq ?? this.nextSeq(rows);\n const row: JobStepRow = {\n id: randomUUID(),\n jobRunId: input.jobRunId,\n stepId: input.stepId,\n kind: input.kind,\n seq,\n status: input.status,\n input: normalisedInput,\n output: normalisedOutput,\n error: input.error ?? null,\n attempts: input.attempts ?? 0,\n startedAt: input.startedAt ?? null,\n finishedAt: input.finishedAt ?? null,\n };\n rows.push(row);\n return row as JobStep;\n }\n\n /**\n * Replay helper — wipe every step row for a run. Mirrors the `scratch`\n * replay mode of the Drizzle backend (`DELETE FROM job_step WHERE job_run_id = …`).\n */\n clearStepsForRun(runId: string): void {\n this.store.steps.delete(runId);\n }\n\n /**\n * Remove every non-`completed` row for the run. Memoized (`completed`)\n * rows are preserved — this is the `last_checkpoint` / `last_step`\n * semantics the Drizzle backend implements via\n * `DELETE … WHERE status != 'completed'`. Both replay modes route here\n * (Phase 1 collapses `last_step` onto this behaviour; see JOB-3 notes).\n */\n clearIncompleteSteps(runId: string): void {\n const rows = this.store.steps.get(runId);\n if (!rows) return;\n const kept = rows.filter((r) => r.status === 'completed');\n if (kept.length === 0) {\n this.store.steps.delete(runId);\n } else {\n this.store.steps.set(runId, kept);\n }\n }\n\n private getOrCreateRows(runId: string): JobStepRow[] {\n let rows = this.store.steps.get(runId);\n if (!rows) {\n rows = [];\n this.store.steps.set(runId, rows);\n }\n return rows;\n }\n\n private nextSeq(rows: JobStepRow[]): number {\n let max = 0;\n for (const r of rows) {\n if (r.seq > max) max = r.seq;\n }\n return max + 1;\n }\n}\n","/**\n * MemoryJobStore — the shared in-memory backing for the three memory-backend\n * services (ADR-022, JOB-4).\n *\n * Plain class, not `@Injectable()`. Wired as a `useValue` provider in\n * JOB-5's `JobsDomainModule.forRoot({ backend: 'memory' })` so unit tests\n * can keep a direct reference for `beforeEach` resets.\n *\n * All three memory services receive the same `MemoryJobStore` instance via\n * constructor injection; the store owns mutable state, the services are\n * stateless mutators.\n */\nimport type {\n JobDefinitionRow,\n JobRunRow,\n JobStepRow,\n} from './job-orchestration.schema';\n\nexport class MemoryJobStore {\n /** Runs keyed by `id` (single source of truth for status/scope/lineage). */\n readonly runs: Map<string, JobRunRow> = new Map();\n\n /** Steps keyed by `job_run_id`; array order matches insertion order. */\n readonly steps: Map<string, JobStepRow[]> = new Map();\n\n /** Job definitions keyed by `type` — memory mirror of the `job` table. */\n readonly jobs: Map<string, JobDefinitionRow> = new Map();\n\n /** Reset everything. Tests call this in `beforeEach`. */\n clear(): void {\n this.runs.clear();\n this.steps.clear();\n this.jobs.clear();\n }\n}\n","/**\n * Pool config loader for the job orchestration domain (ADR-022, JOB-5).\n *\n * Reads `codegen.config.yaml: jobs.pools` from `process.cwd()` (or an\n * explicit `configPath` for tests), merges user-defined pools onto the five\n * framework defaults, and returns the resolved `Map<string, PoolDefinition>`\n * consumed by `JobWorkerModule.onModuleInit` and `JobsDomainModule`'s\n * config-validator surface.\n *\n * Invariants:\n * - User cannot flip `reserved: true` on a framework pool — silently\n * preserved. The three `events_*` pools are reserved infrastructure\n * for the events outbox drain.\n * - User-defined pools cannot set `reserved: true` — `reserved` is\n * framework-only metadata.\n * - Missing `codegen.config.yaml` is not an error; loader returns the\n * framework defaults verbatim.\n *\n * Result is cached at module scope after first call so repeated reads (e.g.\n * a worker module + a one-off scaffold validator in the same process) hit\n * the same parse. Tests that pass `configPath` skip the cache and isolate.\n */\nimport { existsSync, readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { parse as parseYaml } from 'yaml';\n\nexport interface PoolDefinition {\n /** Routing identifier — reused as the per-pool worker queue name. */\n queue: string;\n /** Max parallel in-flight `processRun` calls for this pool's worker. */\n concurrency: number;\n /** `true` ⇒ user `@JobHandler` may not target it. Framework-only. */\n reserved: boolean;\n /** Free-text annotation surfaced in admin UIs / logs. */\n description?: string;\n}\n\nexport type PoolConfig = Map<string, PoolDefinition>;\n\n/**\n * Five framework defaults. Three reserved `events_*` pools drain the\n * `IEventBus` outbox (one per `DomainEvent.direction`); `interactive` and\n * `batch` are user-default pools (`batch` is the `@JobHandler` default\n * when no `pool` is specified).\n */\nexport const FRAMEWORK_POOLS: Readonly<Record<string, PoolDefinition>> = Object.freeze({\n events_inbound: Object.freeze({\n queue: 'jobs-events-inbound',\n concurrency: 20,\n reserved: true,\n description: 'Inbound events drain (events subsystem outbox).',\n }),\n events_change: Object.freeze({\n queue: 'jobs-events-change',\n concurrency: 30,\n reserved: true,\n description: 'Change events drain (events subsystem outbox).',\n }),\n events_outbound: Object.freeze({\n queue: 'jobs-events-outbound',\n concurrency: 10,\n reserved: true,\n description: 'Outbound events drain (events subsystem outbox).',\n }),\n interactive: Object.freeze({\n queue: 'jobs-interactive',\n concurrency: 20,\n reserved: false,\n description: 'User-facing latency-sensitive jobs.',\n }),\n batch: Object.freeze({\n queue: 'jobs-batch',\n concurrency: 5,\n reserved: false,\n description: 'Default pool for background jobs.',\n }),\n});\n\n/** Names of the framework reserved pools. Cheap inline lookup for the worker. */\nexport const RESERVED_POOL_NAMES: ReadonlySet<string> = new Set(\n Object.entries(FRAMEWORK_POOLS)\n .filter(([, def]) => def.reserved)\n .map(([name]) => name),\n);\n\n/**\n * Cache by absolute config path. The `cwd` default is normalised before\n * lookup so two callers passing the same path share the cache; explicit\n * test-only paths cache separately.\n */\nconst cache = new Map<string, PoolConfig>();\n\n/**\n * Reset the loader cache. Test-only — not exported from the package\n * `index.ts`. Useful for tests that mutate `process.cwd()` between cases.\n */\nexport function _resetPoolConfigCacheForTests(): void {\n cache.clear();\n}\n\n/**\n * Resolve the merged pool config.\n *\n * @param configPath optional absolute or cwd-relative path; defaults to\n * `${process.cwd()}/codegen.config.yaml`.\n */\nexport function loadPoolConfig(configPath?: string): PoolConfig {\n const resolved = resolve(configPath ?? `${process.cwd()}/codegen.config.yaml`);\n const cached = cache.get(resolved);\n if (cached) return cached;\n\n const merged = new Map<string, PoolDefinition>();\n // Seed with framework defaults first — they always take precedence on\n // `reserved` and provide defaults for `queue` / `concurrency` if user\n // overrides only some fields.\n for (const [name, def] of Object.entries(FRAMEWORK_POOLS)) {\n merged.set(name, { ...def });\n }\n\n if (!existsSync(resolved)) {\n cache.set(resolved, merged);\n return merged;\n }\n\n let raw: unknown;\n try {\n raw = parseYaml(readFileSync(resolved, 'utf8'));\n } catch (err) {\n throw new Error(\n `pool-config.loader: failed to parse YAML at ${resolved}: ${(err as Error).message}`,\n );\n }\n\n const userPools = extractUserPools(raw);\n for (const [name, userDef] of Object.entries(userPools)) {\n const existing = merged.get(name);\n if (existing) {\n // Framework pool — user may tweak concurrency + description but\n // cannot flip `reserved`. `queue` is frozen too (reserved framework\n // pools' queue identifiers are part of the cross-subsystem contract\n // with the events outbox drain).\n const next: PoolDefinition = {\n queue: existing.queue,\n concurrency:\n typeof userDef.concurrency === 'number'\n ? userDef.concurrency\n : existing.concurrency,\n reserved: existing.reserved,\n description: userDef.description ?? existing.description,\n };\n merged.set(name, next);\n continue;\n }\n // User-defined pool. Validate required fields; reject reserved.\n if (typeof userDef.queue !== 'string' || userDef.queue.length === 0) {\n throw new Error(\n `pool-config.loader: pool '${name}' must declare a non-empty 'queue'.`,\n );\n }\n if (typeof userDef.concurrency !== 'number' || userDef.concurrency <= 0) {\n throw new Error(\n `pool-config.loader: pool '${name}' must declare a positive 'concurrency'.`,\n );\n }\n if (userDef.reserved === true) {\n throw new Error(\n `pool-config.loader: user-defined pool '${name}' cannot set ` +\n `'reserved: true' — reserved is framework-only.`,\n );\n }\n merged.set(name, {\n queue: userDef.queue,\n concurrency: userDef.concurrency,\n reserved: false,\n description: userDef.description,\n });\n }\n\n cache.set(resolved, merged);\n return merged;\n}\n\n/**\n * Names of every non-reserved pool in the resolved config. The default\n * worker activation set when `JobWorkerModuleOptions.pools` is omitted —\n * the worker process never claims the reserved `events_*` pools by\n * default; those are bound by the events subsystem's outbox bridge.\n */\nexport function allNonReservedPoolNames(config: PoolConfig): string[] {\n const out: string[] = [];\n for (const [name, def] of config) {\n if (!def.reserved) out.push(name);\n }\n return out;\n}\n\n/**\n * Names of **every** pool in the resolved config, reserved `events_*` lanes\n * included. The activation set for a standalone worker booted with\n * `JobWorkerModule.forRoot({ allPools: true })` (BULLMQ-1 Phase 1) — the\n * single worker process drains both user pools and the bridge's reserved\n * pools so wrapper `job_run` rows are never stranded.\n */\nexport function allPoolNames(config: PoolConfig): string[] {\n return [...config.keys()];\n}\n\n// ─── internals ──────────────────────────────────────────────────────────────\n\ninterface UserPoolShape {\n queue?: string;\n concurrency?: number;\n reserved?: boolean;\n description?: string;\n}\n\nfunction extractUserPools(raw: unknown): Record<string, UserPoolShape> {\n if (!raw || typeof raw !== 'object') return {};\n const jobs = (raw as { jobs?: unknown }).jobs;\n if (!jobs || typeof jobs !== 'object') return {};\n const pools = (jobs as { pools?: unknown }).pools;\n if (!pools || typeof pools !== 'object') return {};\n const out: Record<string, UserPoolShape> = {};\n for (const [name, def] of Object.entries(pools as Record<string, unknown>)) {\n if (!def || typeof def !== 'object') continue;\n out[name] = def as UserPoolShape;\n }\n return out;\n}\n","/**\n * BullMQ backend configuration surface (BULLMQ-1, ADR-022 extension slot).\n *\n * The core `IJobOrchestrator` contract is backend-agnostic; everything in\n * this file is BullMQ-specific and lives behind the\n * `jobs.extensions.bullmq.*` config namespace (CLAUDE.md core/extension\n * protocol). The Drizzle backend never reads any of it.\n */\nimport { loadPoolConfig, type PoolConfig } from './pool-config.loader';\n\n/**\n * #6 — Structural mirror of BullMQ's `ConnectionOptions`. Declared locally\n * so this config file (which ships into EVERY jobs install, drizzle or\n * bullmq) does NOT need the `bullmq` peer dep resolved by the consumer's\n * tsc. The bullmq backend internally casts to the real `ConnectionOptions`\n * — that file is only vendored when `--backend bullmq` is selected\n * (see `backendFileFilter`).\n *\n * Accepts the `{ url }` shape this resolver emits, plus the host/port/\n * password/db form BullMQ also accepts, with an open index for any extra\n * ioredis options consumers may flow through.\n */\nexport type BullMqConnectionOptions = {\n url?: string;\n host?: string;\n port?: number;\n password?: string;\n db?: number;\n [key: string]: unknown;\n};\n\n/**\n * Typed shape of `codegen.config.yaml: jobs.extensions.bullmq`. Snake_case\n * because it mirrors the YAML the consumer authors.\n *\n * ```yaml\n * jobs:\n * backend: bullmq\n * extensions:\n * bullmq:\n * redis_url: redis://localhost:6379 # or env REDIS_URL\n * queue_prefix: myapp # optional namespace (ADR-022 OQ)\n * bull_board:\n * enabled: true\n * mount_path: /api/admin/queues\n * ```\n */\nexport interface BullMqExtensionsConfig {\n /**\n * Redis/Valkey connection URL. When omitted, the runtime resolves\n * `process.env.REDIS_URL`, then falls back to `redis://localhost:6379`.\n */\n redis_url?: string;\n /**\n * Optional queue-name prefix to avoid collisions when several codegen apps\n * share one Redis (ADR-022 §\"BullMQ queue naming collisions\"). Applied to\n * every pool queue alias.\n */\n queue_prefix?: string;\n /**\n * Bull Board dashboard — opt-in extension (not core). Mounting is the\n * consumer's responsibility (it needs the consumer's Express/Nest adapter +\n * admin auth); we only carry the config. See README + spec §Extensions.\n */\n bull_board?: {\n enabled: boolean;\n mount_path?: string;\n };\n}\n\n/**\n * The runtime form after `redis_url`/env resolution. This is what the\n * orchestrator + worker actually consume.\n */\nexport interface BullMqResolvedConfig {\n connection: BullMqConnectionOptions;\n queuePrefix?: string;\n bullBoard?: { enabled: boolean; mountPath: string };\n}\n\n/** DI token for the resolved BullMQ `ConnectionOptions` (ioredis-compatible). */\nexport const BULLMQ_CONNECTION = Symbol('BULLMQ_CONNECTION');\n\n/** DI token for the full resolved BullMQ config (prefix + bull board). */\nexport const BULLMQ_RESOLVED_CONFIG = Symbol('BULLMQ_RESOLVED_CONFIG');\n\nconst DEFAULT_REDIS_URL = 'redis://localhost:6379';\nconst DEFAULT_BULL_BOARD_MOUNT = '/admin/queues';\n\n/**\n * Resolve the BullMQ runtime config from the extension block.\n *\n * Precedence for the connection URL:\n * 1. explicit `extensions.bullmq.redis_url`\n * 2. `process.env.REDIS_URL`\n * 3. `redis://localhost:6379`\n *\n * Returns a `{ url }` connection shape — BullMQ/ioredis accept a URL string\n * via the `{ url }` ConnectionOptions form.\n */\nexport function resolveBullMqConfig(\n ext: BullMqExtensionsConfig | undefined,\n): BullMqResolvedConfig {\n const url =\n ext?.redis_url ?? process.env.REDIS_URL ?? DEFAULT_REDIS_URL;\n\n const resolved: BullMqResolvedConfig = {\n connection: { url },\n queuePrefix: ext?.queue_prefix,\n };\n if (ext?.bull_board?.enabled) {\n resolved.bullBoard = {\n enabled: true,\n mountPath: ext.bull_board.mount_path ?? DEFAULT_BULL_BOARD_MOUNT,\n };\n }\n return resolved;\n}\n\n/**\n * Resolve the BullMQ queue name for a *logical pool name*. The orchestrator\n * and worker MUST agree on this mapping or jobs are enqueued onto a queue\n * nobody consumes. Both derive it identically:\n *\n * 1. Look up the pool's `queue` alias (e.g. `jobs-batch`) in the resolved\n * pool config — the same alias `JobWorkerModule.onModuleInit` logs and\n * that the BullMQ `Worker` binds to.\n * 2. Fall back to the logical pool name when the pool is unknown (defensive;\n * still a stable, colon-free identifier).\n * 3. Apply the optional `queue_prefix` namespace for multi-app Redis\n * sharing — `:` is fine in the *queue name* (it is only forbidden in the\n * `jobId`, hence the sha1 there).\n *\n * `poolConfig` defaults to the cached `loadPoolConfig()` so callers that only\n * hold the logical pool name (the orchestrator) don't need to thread the map.\n */\nexport function resolvePoolQueueName(\n pool: string,\n config: BullMqResolvedConfig | null | undefined,\n poolConfig: PoolConfig = loadPoolConfig(),\n): string {\n const alias = poolConfig.get(pool)?.queue ?? pool;\n const prefix = config?.queuePrefix;\n return prefix ? `${prefix}:${alias}` : alias;\n}\n"],"mappings":"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","names":["sql","dedupeKey","sql","Inject","Injectable","and","desc","eq","inArray","sql","eq","inArray","desc","and","sql","Injectable","Inject","Inject","Injectable","and","eq","and","eq","Injectable","Inject","randomUUID","Inject","Injectable","Logger","HandlerRegistry","TERMINAL_STATUSES","DEDUPE_EXCLUDED_STATUSES","IN_FLIGHT_STATUSES","evaluateKeyTemplate","Logger","dedupeKey","randomUUID","Injectable","Inject","Inject","Injectable","NON_TERMINAL_STATUSES","Injectable","Inject","randomUUID","Injectable","randomUUID","Injectable"]}
1
+ {"version":3,"sources":["../../../../runtime/subsystems/jobs/jobs-domain.module.ts","../../../../runtime/constants/tokens.ts","../../../../runtime/subsystems/token-key.ts","../../../../runtime/subsystems/jobs/jobs-domain.tokens.ts","../../../../runtime/subsystems/jobs/job-orchestrator.drizzle-backend.ts","../../../../runtime/subsystems/jobs/job-orchestration.schema.ts","../../../../runtime/subsystems/jobs/jobs-errors.ts","../../../../runtime/subsystems/jobs/job-run-service.drizzle-backend.ts","../../../../runtime/subsystems/jobs/job-run-keyset-cursor.ts","../../../../runtime/subsystems/jobs/job-step-service.drizzle-backend.ts","../../../../runtime/subsystems/jobs/job-orchestrator.memory-backend.ts","../../../../runtime/subsystems/jobs/job-handler.base.ts","../../../../runtime/subsystems/jobs/job-run-service.memory-backend.ts","../../../../runtime/subsystems/jobs/job-step-service.memory-backend.ts","../../../../runtime/subsystems/jobs/memory-job-store.ts","../../../../runtime/subsystems/jobs/pool-config.loader.ts","../../../../runtime/subsystems/jobs/bullmq.config.ts"],"sourcesContent":["/**\n * JobsDomainModule — `DynamicModule.forRoot({ backend })` factory wiring\n * the three jobs-domain protocol tokens to a backend implementation\n * (ADR-022, JOB-5).\n *\n * Mirrors `EventsModule.forRoot()` exactly:\n * - `global: true` so consumer entity modules don't have to import this\n * individually — `JOB_ORCHESTRATOR` / `JOB_RUN_SERVICE` /\n * `JOB_STEP_SERVICE` are available project-wide.\n * - One backend at a time (Drizzle for production, Memory for tests).\n *\n * Backend swappability follows the core/extension protocol from CLAUDE.md:\n * the three tokens are the **core contract**; backend-specific tunables\n * live under `extensions.<backend>` so opting into a feature is explicit\n * and the type system reserves the slot.\n */\nimport { Module, type DynamicModule, type Provider } from '@nestjs/common';\nimport { DRIZZLE } from '../../constants/tokens';\nimport {\n JOB_ORCHESTRATOR,\n JOB_RUN_SERVICE,\n JOB_STEP_SERVICE,\n JOBS_MULTI_TENANT,\n} from './jobs-domain.tokens';\nimport { DrizzleJobOrchestrator } from './job-orchestrator.drizzle-backend';\nimport { DrizzleJobRunService } from './job-run-service.drizzle-backend';\nimport { DrizzleJobStepService } from './job-step-service.drizzle-backend';\n// #6 — `BullMQJobOrchestrator` is lazy-loaded only when `backend: 'bullmq'`\n// is selected. The backend file is filtered out of drizzle/memory installs\n// (see `backendFileFilter`); a non-literal dynamic import below sidesteps\n// consumer-side tsc resolution of an absent file.\nimport { MemoryJobOrchestrator } from './job-orchestrator.memory-backend';\nimport { MemoryJobRunService } from './job-run-service.memory-backend';\nimport { MemoryJobStepService } from './job-step-service.memory-backend';\nimport { MemoryJobStore } from './memory-job-store';\nimport {\n BULLMQ_CONNECTION,\n BULLMQ_RESOLVED_CONFIG,\n resolveBullMqConfig,\n type BullMqExtensionsConfig,\n} from './bullmq.config';\n\n/**\n * Drizzle backend extensions surface. None are wired into the Drizzle\n * orchestrator yet — this is the **typed reservation** for the LISTEN/NOTIFY\n * + tunable poll-interval extensions called out in ADR-022. App code\n * passing these today is parsed but not yet dispatched; when the\n * Drizzle orchestrator grows the consumer hooks, opt-in code paths will\n * read directly from these fields.\n */\nexport interface DrizzleBackendExtensions {\n /** Use Postgres LISTEN/NOTIFY to wake the polling loop. Default false. */\n listenNotify?: boolean;\n /** Polling interval when LISTEN/NOTIFY is off (ms). Default 1000. */\n pollIntervalMs?: number;\n}\n\nexport interface JobsDomainModuleOptions {\n backend: 'drizzle' | 'memory' | 'bullmq';\n /**\n * Backend-specific extensions. Only the matching backend's extensions\n * are read at boot; non-matching keys are ignored. This is the\n * core/extension protocol surface — see CLAUDE.md.\n */\n extensions?: {\n drizzle?: DrizzleBackendExtensions;\n /**\n * BullMQ backend extensions (BULLMQ-1). Snake_case mirrors the YAML\n * under `jobs.extensions.bullmq`. `redis_url` falls back to\n * `process.env.REDIS_URL` then `redis://localhost:6379`.\n */\n bullmq?: BullMqExtensionsConfig;\n };\n /** Multi-tenancy opt-in. Wired by JOB-8; module signature stays stable. */\n multiTenant?: boolean;\n}\n\n@Module({})\nexport class JobsDomainModule {\n static forRoot(opts: JobsDomainModuleOptions): DynamicModule {\n const multiTenant = opts.multiTenant ?? false;\n\n const providers: Provider[] = [\n // JOB-8 — boolean provider consumed by the four service-layer backends.\n // Always provided (even when `multiTenant === false`) so `@Inject`\n // always resolves; backends short-circuit the enforcement path when\n // the value is `false`. See `jobs-domain.tokens.ts` for the claim-loop\n // cross-tenant-by-design decision.\n { provide: JOBS_MULTI_TENANT, useValue: multiTenant },\n ];\n\n if (opts.backend === 'memory') {\n // The store is a plain class — wired as a singleton `useValue` so\n // unit tests can pull it out via `.get(MemoryJobStore)` for direct\n // assertions (matches the access pattern in JOB-4 specs).\n const store = new MemoryJobStore();\n providers.push({ provide: MemoryJobStore, useValue: store });\n providers.push(MemoryJobStepService);\n providers.push({ provide: JOB_STEP_SERVICE, useExisting: MemoryJobStepService });\n providers.push(MemoryJobOrchestrator);\n providers.push({ provide: JOB_ORCHESTRATOR, useExisting: MemoryJobOrchestrator });\n providers.push(MemoryJobRunService);\n providers.push({ provide: JOB_RUN_SERVICE, useExisting: MemoryJobRunService });\n } else if (opts.backend === 'bullmq') {\n // BULLMQ-1 — BullMQ orchestrator over a Postgres source of truth. The\n // run/step services stay Drizzle (domain reads + `listForScope` are\n // Postgres queries, unchanged per spec). Only the orchestrator's\n // claim/dispatch half swaps to BullMQ.\n //\n // #6 — the bullmq backend module is filtered out of drizzle/memory\n // installs (no `bullmq` peer dep, no consumer-side tsc compile of an\n // unused file). The factory below dynamic-imports it via a non-literal\n // specifier so TS treats the module type as `any` and never tries to\n // resolve the absent file on a drizzle/memory consumer.\n const resolved = resolveBullMqConfig(opts.extensions?.bullmq);\n providers.push({ provide: BULLMQ_CONNECTION, useValue: resolved.connection });\n providers.push({ provide: BULLMQ_RESOLVED_CONFIG, useValue: resolved });\n providers.push({\n provide: JOB_ORCHESTRATOR,\n useFactory: async (...args: unknown[]): Promise<object> => {\n const specifier = './job-orchestrator.bullmq-backend';\n const mod = (await import(specifier)) as {\n BullMQJobOrchestrator: new (...args: unknown[]) => object;\n };\n return new mod.BullMQJobOrchestrator(...args);\n },\n // The bullmq orchestrator constructor mirrors DrizzleJobOrchestrator's\n // injection list: DRIZZLE + JOBS_MULTI_TENANT + the resolved BullMQ\n // tokens. Importing token references would force a static dep on the\n // tokens file in this module's import graph; using the existing\n // symbols already in scope is sufficient.\n inject: [DRIZZLE, JOBS_MULTI_TENANT, BULLMQ_CONNECTION, BULLMQ_RESOLVED_CONFIG],\n });\n providers.push({ provide: JOB_RUN_SERVICE, useClass: DrizzleJobRunService });\n providers.push({ provide: JOB_STEP_SERVICE, useClass: DrizzleJobStepService });\n } else {\n providers.push({ provide: JOB_ORCHESTRATOR, useClass: DrizzleJobOrchestrator });\n providers.push({ provide: JOB_RUN_SERVICE, useClass: DrizzleJobRunService });\n providers.push({ provide: JOB_STEP_SERVICE, useClass: DrizzleJobStepService });\n }\n\n const exports = [\n JOB_ORCHESTRATOR,\n JOB_RUN_SERVICE,\n JOB_STEP_SERVICE,\n JOBS_MULTI_TENANT,\n ];\n // BULLMQ-1 — only export the BullMQ tokens when they were actually\n // provided. Nest throws \"exported but not provided\" otherwise. Exported so\n // JobWorkerModule (which imports this module) can read the resolved\n // connection/config to spawn BullMQ workers.\n if (opts.backend === 'bullmq') {\n exports.push(BULLMQ_CONNECTION, BULLMQ_RESOLVED_CONFIG);\n }\n\n return {\n module: JobsDomainModule,\n global: true,\n providers,\n exports,\n };\n }\n}\n","/**\n * NestJS injection tokens\n *\n * Used with @Inject() decorator in concrete repository constructors.\n */\n\n/**\n * Injection token for the Drizzle ORM database client.\n *\n * Usage in concrete repositories:\n * ```typescript\n * constructor(@Inject(DRIZZLE) db: DrizzleClient) { super(db); }\n * ```\n */\nexport const DRIZZLE = 'DRIZZLE' as const;\n\n/**\n * Injection token for the event bus (IEventBus).\n *\n * Optional — only resolved when EventsModule.forRoot() is registered.\n * BaseService uses this with @Optional() to emit lifecycle events\n * without requiring the events subsystem to be installed.\n *\n * Usage in services/use cases:\n * ```typescript\n * @Optional() @Inject(EVENT_BUS) eventBus?: IEventBus\n * ```\n */\nexport const EVENT_BUS = 'EVENT_BUS' as const;\n","/** Canonical package namespace for cross-boundary DI token keys. MUST be a hardcoded\n * constant (NOT derived from package.json) so a vendored copy — which lives inside the\n * CONSUMER's package — produces the identical key and the two copies share the symbol. */\nexport const PKG = '@pattern-stack/codegen';\n// TODO(token-version): if/when a runtime contract version is adopted, inject it HERE only\n// (e.g. `${PKG}#${ABI}.${area}.${name}`) — this helper is the single chokepoint.\nexport const tokenKey = (area: string, name: string): string => `${PKG}.${area}.${name}`;\n","/**\n * Injection tokens for the job orchestration domain layer (ADR-022, JOB-2).\n *\n * Consumer code injects these symbols via `@Inject(JOB_ORCHESTRATOR)` etc.;\n * concrete backends (JOB-3 Drizzle, JOB-4 Memory) provide the implementations\n * through `JobsDomainModule.forRoot({ backend })` in JOB-5.\n *\n * Each token is a namespaced `Symbol.for(...)` (ADR-037, via `tokenKey()`) —\n * distinct per key, so Nest's DI lookup is unambiguous, AND matching by VALUE\n * across import boundaries so the package and a (legacy) vendored runtime copy\n * resolve to the same symbol.\n */\nimport { tokenKey } from '../token-key';\n\nexport const JOB_ORCHESTRATOR = Symbol.for(tokenKey('jobs', 'orchestrator'));\nexport const JOB_RUN_SERVICE = Symbol.for(tokenKey('jobs', 'run-service'));\nexport const JOB_STEP_SERVICE = Symbol.for(tokenKey('jobs', 'step-service'));\n\n/**\n * Multi-tenancy opt-in flag (JOB-8). Bound to the boolean passed in via\n * `JobsDomainModule.forRoot({ multiTenant })`, defaulting to `false`.\n *\n * When `true`, the four service-layer backends (Drizzle + Memory orchestrator\n * and run-service) enforce `tenantId` on every mutating / targeted-read call:\n * `start`, `cancel`, `listForScope`, `cancelForScope`, `rescheduleForScope`.\n * Missing (`undefined`) `tenantId` throws `MissingTenantIdError`; explicit\n * `null` opts into cross-tenant background work and passes through.\n *\n * The JobWorker claim loop is **cross-tenant by design** — the worker has no\n * tenant context; `tenantId` is populated at write time and enforced on\n * targeted reads. See docs/specs/JOB-8.md.\n */\nexport const JOBS_MULTI_TENANT = Symbol.for(tokenKey('jobs', 'multi-tenant'));\n","/**\n * DrizzleJobOrchestrator — Postgres-backed implementation of\n * `IJobOrchestrator` (ADR-022, JOB-3).\n *\n * Single-layer architecture: `start` writes a single `job_run` row; the\n * `JobWorker` polling loop claims it directly via `FOR UPDATE SKIP LOCKED`.\n * No `job_queue` table, no executor port. See `docs/specs/JOB-3.md`.\n */\nimport { randomUUID } from 'node:crypto';\nimport { Inject, Injectable, Logger } from '@nestjs/common';\nimport { and, desc, eq, gt, inArray, isNotNull, ne, notInArray, sql } from 'drizzle-orm';\nimport type { DrizzleClient } from '../../types/drizzle';\nimport type { DrizzleTransaction } from '../events/event-bus.protocol';\nimport { DRIZZLE } from '../../constants/tokens';\nimport {\n jobRuns,\n jobs,\n type JobDefinitionRow,\n type JobRunRow,\n} from './job-orchestration.schema';\nimport type {\n CancelOptions,\n IJobOrchestrator,\n JobPoolDef,\n JobRun,\n JobUpsertEntry,\n StartOptions,\n} from './job-orchestrator.protocol';\nimport {\n JobCollisionError,\n JobNotReplayableError,\n JobTemplateFieldMissingError,\n JobTypeNotFoundError,\n MissingTenantIdError,\n} from './jobs-errors';\nimport { jobSteps } from './job-orchestration.schema';\nimport { JOBS_MULTI_TENANT } from './jobs-domain.tokens';\n\n/**\n * Terminal statuses — transitions into these are final. Used by `cancel`\n * (to short-circuit idempotently) and by `replay` (as the guard gate).\n */\nexport const TERMINAL_STATUSES = [\n 'completed',\n 'failed',\n 'timed_out',\n 'canceled',\n] as const;\ntype TerminalStatus = (typeof TERMINAL_STATUSES)[number];\ntype JobRunStatus = JobRunRow['status'];\n\n/** Statuses excluded from dedupe window matches per ADR-022. */\nconst DEDUPE_EXCLUDED_STATUSES: JobRunStatus[] = ['canceled', 'failed'];\n/** Statuses that count as in-flight for concurrency collision checks. */\nconst IN_FLIGHT_STATUSES: JobRunStatus[] = ['pending', 'running'];\n\n/**\n * Substitute `{{field}}` placeholders against the input payload.\n *\n * Implementation decision (JOB-3, 2026-04-19): simple `{{field}}` single-key\n * substitution, no dotted paths, no Mustache/Handlebars dependency. A missing\n * field throws `JobTemplateFieldMissingError` synchronously — cheaper than\n * discovering the misconfiguration at claim time.\n */\nexport function evaluateKeyTemplate(\n template: string,\n input: Record<string, unknown>,\n): string {\n return template.replace(/\\{\\{\\s*([a-zA-Z0-9_]+)\\s*\\}\\}/g, (_match, field: string) => {\n const value = input[field];\n if (value === undefined || value === null) {\n throw new JobTemplateFieldMissingError(template, field);\n }\n return String(value);\n });\n}\n\n@Injectable()\nexport class DrizzleJobOrchestrator implements IJobOrchestrator {\n // TODO(logging-subsystem): swap to ILogger once ADR-028 lands\n private readonly logger = new Logger(DrizzleJobOrchestrator.name);\n\n constructor(\n @Inject(DRIZZLE) private readonly db: DrizzleClient,\n @Inject(JOBS_MULTI_TENANT) private readonly multiTenant: boolean,\n ) {}\n\n /**\n * JOB-8 — resolve `tenantId` for a mutating / targeted-read call.\n * Returns the tenant value that should be written to the row (or compared\n * against in a WHERE clause). When `multiTenant` is off, the column is\n * forced to `null` regardless of what callers pass. When on, `undefined`\n * throws; `null` and strings pass through untouched.\n */\n private resolveTenantId(\n method: string,\n tenantId: string | null | undefined,\n ): string | null {\n if (!this.multiTenant) return null;\n if (tenantId === undefined) throw new MissingTenantIdError(method);\n return tenantId;\n }\n\n // ==========================================================================\n // start\n // ==========================================================================\n\n async start(\n type: string,\n input: unknown,\n opts: StartOptions = {},\n tx?: DrizzleTransaction,\n ): Promise<JobRun> {\n const payload = (input ?? {}) as Record<string, unknown>;\n\n // JOB-8 — resolve tenant gate up front so `multi_tenant=true` +\n // undefined surfaces before any row is touched.\n const tenantId = this.resolveTenantId('start', opts.tenantId);\n\n // BRIDGE-7: thread the optional caller tx through every read/write\n // in this method so EventFlowService.publishAndStart can bundle the\n // outbox insert, the eager job_run insert, and (for Case B) the\n // bridge_delivery pre-write into a single transaction.\n const client = (tx ?? this.db) as DrizzleClient;\n\n // 1a. Load job definition.\n const [def] = await client\n .select()\n .from(jobs)\n .where(eq(jobs.type, type))\n .limit(1);\n if (!def) throw new JobTypeNotFoundError(type);\n const definition = def as JobDefinitionRow;\n\n // 1b. Dedupe check.\n if (definition.dedupeKeyTemplate && definition.dedupeWindowMs) {\n const dedupeKey = evaluateKeyTemplate(definition.dedupeKeyTemplate, payload);\n const windowStart = new Date(Date.now() - definition.dedupeWindowMs);\n const existing = await client\n .select()\n .from(jobRuns)\n .where(\n and(\n eq(jobRuns.jobType, type),\n eq(jobRuns.dedupeKey, dedupeKey),\n gt(jobRuns.createdAt, windowStart),\n // status NOT IN ('canceled', 'failed')\n notInStatus(DEDUPE_EXCLUDED_STATUSES),\n ),\n )\n .orderBy(desc(jobRuns.createdAt))\n .limit(1);\n if (existing.length > 0) {\n return existing[0] as JobRun;\n }\n }\n\n // 1c. Concurrency collision check.\n let concurrencyKey: string | null = null;\n if (definition.concurrencyKeyTemplate) {\n concurrencyKey = evaluateKeyTemplate(\n definition.concurrencyKeyTemplate,\n payload,\n );\n const inFlight = await client\n .select()\n .from(jobRuns)\n .where(\n and(\n eq(jobRuns.concurrencyKey, concurrencyKey),\n inArray(jobRuns.status, IN_FLIGHT_STATUSES),\n ),\n )\n .limit(1);\n if (inFlight.length > 0) {\n const incumbent = inFlight[0] as JobRun;\n switch (definition.collisionMode) {\n case 'reject':\n throw new JobCollisionError(type, concurrencyKey, incumbent);\n case 'replace':\n // JOB-8 — thread the incumbent's own tenantId through the\n // internal cascade. Without this, every `replace`-collision\n // start() under multiTenant=true throws MissingTenantIdError\n // from the inner cancel() call instead of cancelling the\n // incumbent. Mirrors the memory backend's `cancelLocked(\n // incumbent.id, ..., incumbent.tenantId)` pattern.\n await this.cancel(incumbent.id, {\n cascade: true,\n reason: 'replaced',\n tenantId: incumbent.tenantId,\n });\n break;\n case 'queue':\n // Fall through — row is inserted; claim query gates it until\n // the incumbent transitions (see JobWorker.processRun queue gate).\n break;\n }\n }\n }\n\n // 1d. Resolve id + rootRunId, INSERT.\n const newId = randomUUID();\n let rootRunId: string = newId;\n if (opts.parentRunId) {\n const [parent] = await client\n .select({ rootRunId: jobRuns.rootRunId })\n .from(jobRuns)\n .where(eq(jobRuns.id, opts.parentRunId))\n .limit(1);\n if (!parent) {\n throw new Error(\n `parentRunId ${opts.parentRunId} does not reference an existing job_run`,\n );\n }\n rootRunId = parent.rootRunId;\n }\n\n const dedupeKey =\n definition.dedupeKeyTemplate\n ? evaluateKeyTemplate(definition.dedupeKeyTemplate, payload)\n : null;\n\n const [inserted] = await client\n .insert(jobRuns)\n .values({\n id: newId,\n jobType: type,\n jobVersion: definition.version,\n parentRunId: opts.parentRunId ?? null,\n rootRunId,\n parentClosePolicy: opts.parentClosePolicy ?? 'terminate',\n scopeEntityType: opts.scope?.entityType ?? null,\n scopeEntityId: opts.scope?.entityId ?? null,\n tenantId,\n tags: opts.tags ?? {},\n pool: opts.pool ?? definition.pool,\n priority: opts.priority ?? definition.priorityDefault,\n concurrencyKey,\n dedupeKey,\n status: 'pending',\n input: payload,\n output: null,\n error: null,\n triggerSource: opts.triggerSource ?? 'manual',\n triggerRef: opts.triggerRef ?? null,\n runAt: opts.runAt ?? new Date(),\n startedAt: null,\n finishedAt: null,\n claimedAt: null,\n attempts: 0,\n })\n .returning();\n\n return inserted as JobRun;\n }\n\n // ==========================================================================\n // cancel\n // ==========================================================================\n\n async cancel(runId: string, opts: CancelOptions = {}): Promise<void> {\n // JOB-8 — resolve tenant gate up front (strict undefined-throws).\n const tenantId = this.resolveTenantId('cancel', opts.tenantId);\n\n // Load target.\n const [target] = await this.db\n .select()\n .from(jobRuns)\n .where(eq(jobRuns.id, runId))\n .limit(1);\n if (!target) return;\n // JOB-8 — cross-tenant cancel is a silent no-op (no existence leak).\n if (this.multiTenant && target.tenantId !== tenantId) return;\n if (TERMINAL_STATUSES.includes(target.status as TerminalStatus)) {\n return; // idempotent\n }\n\n // Atomic transition, guarded against concurrent terminal moves.\n const [cancelled] = await this.db\n .update(jobRuns)\n .set({\n status: 'canceled',\n finishedAt: new Date(),\n updatedAt: new Date(),\n })\n .where(\n and(eq(jobRuns.id, runId), notInStatus([...TERMINAL_STATUSES])),\n )\n .returning();\n\n if (!cancelled) return; // lost the race; already terminal\n\n if (opts.cascade === false) return;\n\n // Fetch descendants and branch on parent_close_policy.\n const descendants = await this.db\n .select()\n .from(jobRuns)\n .where(\n and(\n eq(jobRuns.rootRunId, target.rootRunId),\n ne(jobRuns.id, runId),\n notInStatus([...TERMINAL_STATUSES]),\n ),\n );\n\n for (const child of descendants) {\n const policy = (child as JobRunRow).parentClosePolicy;\n if (policy === 'abandon') continue;\n // 'terminate' | 'cancel' — both transition the child to canceled.\n await this.db\n .update(jobRuns)\n .set({\n status: 'canceled',\n finishedAt: new Date(),\n updatedAt: new Date(),\n })\n .where(\n and(\n eq(jobRuns.id, (child as JobRunRow).id),\n notInStatus([...TERMINAL_STATUSES]),\n ),\n );\n }\n\n void opts.reason; // reserved for future audit logging\n }\n\n // ==========================================================================\n // replay\n // ==========================================================================\n\n async replay(runId: string): Promise<JobRun> {\n // Load target + its job definition (we need replay_from).\n const [target] = await this.db\n .select()\n .from(jobRuns)\n .where(eq(jobRuns.id, runId))\n .limit(1);\n if (!target) {\n throw new Error(`replay: run ${runId} not found`);\n }\n const run = target as JobRunRow;\n if (!TERMINAL_STATUSES.includes(run.status as TerminalStatus)) {\n throw new JobNotReplayableError(runId, run.status);\n }\n\n const [def] = await this.db\n .select()\n .from(jobs)\n .where(eq(jobs.type, run.jobType))\n .limit(1);\n if (!def) throw new JobTypeNotFoundError(run.jobType);\n const mode = (def as JobDefinitionRow).replayFrom;\n\n // Atomic: step reset + run reset must commit together.\n const result = await this.db.transaction(async (tx) => {\n if (mode === 'scratch') {\n await tx.delete(jobSteps).where(eq(jobSteps.jobRunId, runId));\n } else if (mode === 'last_step') {\n // Delete only non-completed step rows — completed steps stay memoised.\n await tx\n .delete(jobSteps)\n .where(\n and(eq(jobSteps.jobRunId, runId), ne(jobSteps.status, 'completed')),\n );\n } else {\n // 'last_checkpoint' — Phase 1 has no explicit checkpoint markers, so\n // behaviour collapses to `last_step`. See docs/specs/JOB-3.md\n // \"Implementation Decisions\" — planned divergence in a later phase.\n await tx\n .delete(jobSteps)\n .where(\n and(eq(jobSteps.jobRunId, runId), ne(jobSteps.status, 'completed')),\n );\n }\n\n const [updated] = await tx\n .update(jobRuns)\n .set({\n status: 'pending',\n attempts: 0,\n runAt: new Date(),\n startedAt: null,\n finishedAt: null,\n claimedAt: null,\n error: null,\n output: null,\n updatedAt: new Date(),\n })\n .where(eq(jobRuns.id, runId))\n .returning();\n return updated as JobRunRow;\n });\n\n return result as JobRun;\n }\n\n // ==========================================================================\n // upsertJobRows — boot-time materialisation of `job` definitions\n // ==========================================================================\n\n /**\n * Hash-gated `INSERT … ON CONFLICT (type) DO UPDATE … WHERE` per Q3\n * resolution (2026-04-19): the `UPDATE` branch executes only when one\n * of the persisted metadata fields differs from the incoming payload;\n * `version` bumps only on real change; concurrent boots with identical\n * content are idempotent no-ops.\n *\n * Why this shape (not `DO NOTHING`, not advisory locks):\n * - `DO NOTHING` would let an old-version instance leave a stale row\n * that a new-version instance can't overwrite during a rolling deploy.\n * - Advisory locks add latency and leak risk under crashes.\n * - The `WHERE … IS DISTINCT FROM …` clause makes the conditional\n * atomic — no read-modify-write race on `version` between concurrent\n * boots.\n *\n * Orphan detection: a single `SELECT type FROM job WHERE type NOT IN (...)`\n * returns the types present in DB but absent from `entries`. Caller (boot\n * validator) decides whether to throw `BootValidationError`.\n */\n async upsertJobRows(\n entries: JobUpsertEntry[],\n poolConfig: ReadonlyMap<string, JobPoolDef>,\n ): Promise<{ orphaned: string[] }> {\n void poolConfig; // pool validation is the module's responsibility; orchestrator just persists\n\n for (const entry of entries) {\n const meta = entry.meta;\n const pool = meta.pool ?? 'batch';\n const retryPolicy = meta.retry ?? {\n attempts: 1,\n backoff: 'fixed' as const,\n baseMs: 0,\n };\n const concurrencyKeyTemplate =\n (meta.concurrency as { key?: unknown } | undefined)?.key;\n const concurrencyKeyTemplateStr =\n typeof concurrencyKeyTemplate === 'string' ? concurrencyKeyTemplate : null;\n const collisionMode =\n (meta.concurrency?.collisionMode as JobDefinitionRow['collisionMode']) ??\n 'queue';\n const dedupeKeyTemplate =\n (meta.dedupe as { key?: unknown } | undefined)?.key;\n const dedupeKeyTemplateStr =\n typeof dedupeKeyTemplate === 'string' ? dedupeKeyTemplate : null;\n const dedupeWindowMs = meta.dedupe?.windowMs ?? null;\n const timeoutMs = meta.timeoutMs ?? null;\n const replayFrom = meta.replayFrom ?? 'last_checkpoint';\n const scopeEntityType = meta.scope?.entity ?? null;\n // Q3 resolution: priority_default and replay_from are part of the\n // hashed metadata even though they aren't currently set via decorator\n // metadata above (priority_default has no `@JobHandler` field yet).\n // Default to 0 to keep UPDATE branch quiet across deploys.\n const priorityDefault = 0;\n\n // Hash-gated upsert: every metadata column appears in the WHERE clause\n // so the UPDATE branch only fires on a real change. `version` bumps\n // exactly when the WHERE matches.\n await this.db\n .insert(jobs)\n .values({\n type: entry.type,\n version: 1,\n pool,\n scopeEntityType,\n retryPolicy,\n timeoutMs,\n concurrencyKeyTemplate: concurrencyKeyTemplateStr,\n collisionMode,\n dedupeKeyTemplate: dedupeKeyTemplateStr,\n dedupeWindowMs,\n priorityDefault,\n replayFrom,\n })\n .onConflictDoUpdate({\n target: jobs.type,\n set: {\n pool: sql`EXCLUDED.pool`,\n scopeEntityType: sql`EXCLUDED.scope_entity_type`,\n retryPolicy: sql`EXCLUDED.retry_policy`,\n timeoutMs: sql`EXCLUDED.timeout_ms`,\n concurrencyKeyTemplate: sql`EXCLUDED.concurrency_key_template`,\n collisionMode: sql`EXCLUDED.collision_mode`,\n dedupeKeyTemplate: sql`EXCLUDED.dedupe_key_template`,\n dedupeWindowMs: sql`EXCLUDED.dedupe_window_ms`,\n priorityDefault: sql`EXCLUDED.priority_default`,\n replayFrom: sql`EXCLUDED.replay_from`,\n version: sql`${jobs.version} + 1`,\n updatedAt: sql`now()`,\n },\n // The hash gate: every field listed in the Q3 resolution appears\n // here. `IS DISTINCT FROM` is the null-safe inequality operator;\n // jsonb cast to text gives stable comparison without invoking a\n // dedicated hash column (avoids a JOB-1 schema migration).\n setWhere: sql`\n ${jobs.pool} IS DISTINCT FROM EXCLUDED.pool OR\n ${jobs.retryPolicy}::text IS DISTINCT FROM EXCLUDED.retry_policy::text OR\n ${jobs.timeoutMs} IS DISTINCT FROM EXCLUDED.timeout_ms OR\n ${jobs.concurrencyKeyTemplate} IS DISTINCT FROM EXCLUDED.concurrency_key_template OR\n ${jobs.collisionMode} IS DISTINCT FROM EXCLUDED.collision_mode OR\n ${jobs.dedupeKeyTemplate} IS DISTINCT FROM EXCLUDED.dedupe_key_template OR\n ${jobs.dedupeWindowMs} IS DISTINCT FROM EXCLUDED.dedupe_window_ms OR\n ${jobs.priorityDefault} IS DISTINCT FROM EXCLUDED.priority_default OR\n ${jobs.replayFrom} IS DISTINCT FROM EXCLUDED.replay_from OR\n ${jobs.scopeEntityType} IS DISTINCT FROM EXCLUDED.scope_entity_type\n `,\n });\n }\n\n // Orphan detection: any `job` row whose type is not in the registry.\n const types = entries.map((e) => e.type);\n const orphans =\n types.length === 0\n ? await this.db.select({ type: jobs.type }).from(jobs)\n : await this.db\n .select({ type: jobs.type })\n .from(jobs)\n .where(notInArray(jobs.type, types));\n\n return { orphaned: orphans.map((o) => o.type) };\n }\n}\n\n// ─── Helpers ────────────────────────────────────────────────────────────────\n\nfunction notInStatus(statuses: JobRunStatus[]) {\n // Drizzle's inArray composes with `not` via negation helper; use raw sql\n // to stay readable. `inArray` + `.not()` isn't idiomatic in 0.45.\n const negated = statuses.map((s) => ne(jobRuns.status, s));\n return and(...negated);\n}\n\n// `isNotNull` + `gt` imports are retained for potential future use; silence\n// unused-import lint by re-exporting via `void`.\nvoid isNotNull;\n","/**\n * Drizzle schema for the job orchestration domain (ADR-022).\n *\n * Three tables model the lifecycle of a durable job:\n * - `job` — definitions keyed by handler type (e.g. 'onboarding').\n * - `job_run` — one row per attempt to execute a job; worker claims\n * rows directly via SELECT ... FOR UPDATE SKIP LOCKED.\n * - `job_step` — individual steps within a run; memoises output for replay.\n *\n * Phase 1 ships only this layer. There is no `job_queue` table, no executor\n * port — see ADR-022 and `.claude/skills/jobs/SKILL.md` for the rationale.\n */\nimport {\n pgEnum,\n pgTable,\n uuid,\n text,\n jsonb,\n integer,\n timestamp,\n index,\n uniqueIndex,\n} from 'drizzle-orm/pg-core';\nimport { sql } from 'drizzle-orm';\nimport type { InferSelectModel } from 'drizzle-orm';\n\n// ─── Internal $type<> helpers ───────────────────────────────────────────────\n// Annotation types for jsonb columns only. JOB-2 defines the public protocol\n// types; these remain private to this file.\n\ntype RetryPolicy = {\n attempts: number;\n backoff: 'fixed' | 'exponential';\n baseMs: number;\n nonRetryableErrors?: string[];\n};\n\ntype JobRunError = {\n message: string;\n stack?: string;\n retryable: boolean;\n attempt: number;\n};\n\n// ─── Enums ──────────────────────────────────────────────────────────────────\n\nexport const jobRunStatusEnum = pgEnum('job_run_status', [\n 'pending',\n 'running',\n 'waiting',\n 'completed',\n 'failed',\n 'timed_out',\n 'canceled',\n]);\n\n// extended in ADR-027: tool_call | llm_call | wait | checkpoint | message\nexport const jobStepKindEnum = pgEnum('job_step_kind', ['task']);\n\nexport const jobStepStatusEnum = pgEnum('job_step_status', [\n 'pending',\n 'running',\n 'completed',\n 'failed',\n 'skipped',\n]);\n\nexport const collisionModeEnum = pgEnum('job_collision_mode', [\n 'queue',\n 'reject',\n 'replace',\n]);\n\nexport const replayFromEnum = pgEnum('job_replay_from', [\n 'scratch',\n 'last_step',\n 'last_checkpoint',\n]);\n\nexport const parentClosePolicyEnum = pgEnum('job_parent_close_policy', [\n 'terminate',\n 'cancel',\n 'abandon',\n]);\n\n// Phase 3 placeholder — see ADR-025\nexport const waitKindEnum = pgEnum('job_wait_kind', ['signal']);\n\n// Phase 2 may add more sources; requires Atlas migration\nexport const triggerSourceEnum = pgEnum('job_trigger_source', [\n 'manual',\n 'schedule',\n 'event',\n 'parent',\n]);\n\n// ─── job ────────────────────────────────────────────────────────────────────\n\nexport const jobs = pgTable('job', {\n type: text('type').primaryKey(),\n version: integer('version').notNull().default(1),\n pool: text('pool').notNull(),\n scopeEntityType: text('scope_entity_type'),\n retryPolicy: jsonb('retry_policy').notNull().$type<RetryPolicy>(),\n timeoutMs: integer('timeout_ms'),\n concurrencyKeyTemplate: text('concurrency_key_template'),\n collisionMode: collisionModeEnum('collision_mode').notNull().default('queue'),\n dedupeKeyTemplate: text('dedupe_key_template'),\n dedupeWindowMs: integer('dedupe_window_ms'),\n priorityDefault: integer('priority_default').notNull().default(0),\n replayFrom: replayFromEnum('replay_from').notNull().default('last_checkpoint'),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),\n});\n\nexport type JobDefinitionRow = InferSelectModel<typeof jobs>;\n\n// ─── job_run ────────────────────────────────────────────────────────────────\n\nexport const jobRuns = pgTable(\n 'job_run',\n {\n id: uuid('id').primaryKey().defaultRandom(),\n jobType: text('job_type').notNull().references(() => jobs.type),\n jobVersion: integer('job_version').notNull(),\n parentRunId: uuid('parent_run_id').references((): any => jobRuns.id),\n /**\n * Service generates `id` client-side via randomUUID() and sets\n * root_run_id = id for root runs (single INSERT, no self-FK race).\n */\n rootRunId: uuid('root_run_id').notNull(),\n parentClosePolicy: parentClosePolicyEnum('parent_close_policy')\n .notNull()\n .default('terminate'),\n scopeEntityType: text('scope_entity_type'),\n scopeEntityId: text('scope_entity_id'),\n tenantId: text('tenant_id'),\n tags: jsonb('tags').notNull().default({}).$type<Record<string, string>>(),\n pool: text('pool').notNull(),\n priority: integer('priority').notNull().default(0),\n concurrencyKey: text('concurrency_key'),\n dedupeKey: text('dedupe_key'),\n status: jobRunStatusEnum('status').notNull().default('pending'),\n input: jsonb('input').notNull().$type<Record<string, unknown>>(),\n output: jsonb('output').$type<Record<string, unknown>>(),\n error: jsonb('error').$type<JobRunError>(),\n triggerSource: triggerSourceEnum('trigger_source').notNull(),\n triggerRef: text('trigger_ref'),\n runAt: timestamp('run_at', { withTimezone: true }).notNull().defaultNow(),\n startedAt: timestamp('started_at', { withTimezone: true }),\n finishedAt: timestamp('finished_at', { withTimezone: true }),\n claimedAt: timestamp('claimed_at', { withTimezone: true }),\n attempts: integer('attempts').notNull().default(0),\n // Phase 3 placeholder — see ADR-025\n waitKind: waitKindEnum('wait_kind'),\n // Phase 3 placeholder — see ADR-025\n resumeToken: text('resume_token'),\n // Phase 3 placeholder — see ADR-025\n waitDeadline: timestamp('wait_deadline', { withTimezone: true }),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),\n },\n (t) => ({\n /** Claim query: ORDER BY priority DESC, run_at ASC. */\n idxJobRunClaim: index('idx_job_run_claim').on(t.status, t.pool, t.runAt),\n /** Tree traversal / cascade cancel. */\n idxJobRunRoot: index('idx_job_run_root').on(t.rootRunId),\n /** listForScope query. */\n idxJobRunScope: index('idx_job_run_scope').on(t.scopeEntityType, t.scopeEntityId),\n /** Idempotency collapse — partial index. */\n idxJobRunDedupe: index('idx_job_run_dedupe')\n .on(t.jobType, t.dedupeKey)\n .where(sql`${t.dedupeKey} IS NOT NULL`),\n /** Collision check — partial index. */\n idxJobRunConcurrency: index('idx_job_run_concurrency')\n .on(t.concurrencyKey)\n .where(\n sql`${t.concurrencyKey} IS NOT NULL AND ${t.status} IN ('pending','running')`,\n ),\n }),\n);\n\nexport type JobRunRow = InferSelectModel<typeof jobRuns>;\n\n// ─── job_step ───────────────────────────────────────────────────────────────\n\nexport const jobSteps = pgTable(\n 'job_step',\n {\n id: uuid('id').primaryKey().defaultRandom(),\n jobRunId: uuid('job_run_id').notNull().references(() => jobRuns.id),\n stepId: text('step_id').notNull(),\n kind: jobStepKindEnum('kind').notNull().default('task'),\n /**\n * Monotonic within run. integer (max ~2B per run) is sufficient —\n * downgraded from ADR-022's bigint; revisit only if a single run\n * ever exceeds 2 billion steps.\n */\n seq: integer('seq').notNull(),\n status: jobStepStatusEnum('status').notNull().default('pending'),\n input: jsonb('input').$type<Record<string, unknown>>(),\n /** Memoised on success for replay. */\n output: jsonb('output').$type<Record<string, unknown>>(),\n error: jsonb('error').$type<JobRunError>(),\n attempts: integer('attempts').notNull().default(0),\n startedAt: timestamp('started_at', { withTimezone: true }),\n finishedAt: timestamp('finished_at', { withTimezone: true }),\n },\n (t) => ({\n /** No duplicate step IDs per run. */\n idxJobStepRunStep: uniqueIndex('idx_job_step_run_step').on(t.jobRunId, t.stepId),\n /** Ordered timeline reads. */\n idxJobStepTimeline: index('idx_job_step_timeline').on(t.jobRunId, t.seq),\n }),\n);\n\nexport type JobStepRow = InferSelectModel<typeof jobSteps>;\n","/**\n * Typed errors for the job orchestration domain (ADR-022, JOB-3).\n *\n * All thrown by the Drizzle orchestrator (and mirrored by the Memory\n * backend in JOB-4). They exist as classes so consumers can `instanceof`\n * them in catch blocks and exception filters can map them to HTTP codes.\n */\nimport type { JobRun } from './job-orchestrator.protocol';\n\n/**\n * `start(type, …)` was called for a job type that has no row in the `job`\n * table. At runtime this usually means the handler was not decorated or the\n * boot validator (JOB-5) has not registered it yet.\n */\nexport class JobTypeNotFoundError extends Error {\n override readonly name = 'JobTypeNotFoundError';\n constructor(public readonly jobType: string) {\n super(`No job definition registered for type '${jobType}'.`);\n }\n}\n\n/**\n * Thrown by `start` when `collision_mode === 'reject'` and a non-terminal\n * run with the same `concurrency_key` already exists. Carries the incumbent\n * so callers can surface its id or subscribe to its completion event.\n */\nexport class JobCollisionError extends Error {\n override readonly name = 'JobCollisionError';\n constructor(\n public readonly jobType: string,\n public readonly concurrencyKey: string,\n public readonly incumbent: JobRun,\n ) {\n super(\n `Job type '${jobType}' has an in-flight run with concurrency_key ` +\n `'${concurrencyKey}' (incumbent ${incumbent.id}); collision_mode=reject.`,\n );\n }\n}\n\n/**\n * `replay` was called on a run that is not in a replayable terminal state\n * (i.e. still `pending` / `running` / `waiting`). Replay always spawns\n * fresh execution and therefore requires the source run to be settled.\n */\nexport class JobNotReplayableError extends Error {\n override readonly name = 'JobNotReplayableError';\n constructor(\n public readonly runId: string,\n public readonly currentStatus: string,\n ) {\n super(\n `Run ${runId} is not replayable from status '${currentStatus}'. ` +\n `Only 'completed', 'failed', 'timed_out', and 'canceled' are eligible.`,\n );\n }\n}\n\n/**\n * A `concurrency_key_template` or `dedupe_key_template` referenced a field\n * that is not present on the input payload. Caught at `start` time so the\n * caller sees the misconfiguration synchronously rather than at claim time.\n */\nexport class JobTemplateFieldMissingError extends Error {\n override readonly name = 'JobTemplateFieldMissingError';\n constructor(\n public readonly template: string,\n public readonly field: string,\n ) {\n super(\n `Template '${template}' references input field '${field}' which is ` +\n `missing or undefined on the payload.`,\n );\n }\n}\n\n/**\n * Thrown by the four multi-tenant-aware service-layer backends (JOB-8)\n * when `JobsDomainModule` was configured with `multiTenant: true` but the\n * caller did not pass a `tenantId` in the relevant options object.\n *\n * **Strict enforcement rationale (resolved 2026-04-18).** Cross-tenant data\n * leakage is the worst class of bug a multi-tenant system can ship; surfacing\n * the misuse loudly at the call site (rather than silently defaulting to\n * `null` or to the \"last tenant seen\") prevents both accidental global\n * writes and sneaky reads that return a union of tenants.\n *\n * - `undefined` `tenantId` → throw this error.\n * - Explicit `null` `tenantId` → passes; opts the call into cross-tenant\n * background work (e.g. a nightly housekeeping job that must scan all\n * tenants). The row is persisted with `tenant_id = NULL`.\n */\nexport class MissingTenantIdError extends Error {\n override readonly name = 'MissingTenantIdError';\n constructor(public readonly method: string) {\n super(\n `MissingTenantIdError: JobsDomainModule was configured with ` +\n `multiTenant=true but ${method} was called without tenantId ` +\n `(undefined). Pass an explicit tenantId, or pass null for ` +\n `cross-tenant work.`,\n );\n }\n}\n\n/**\n * Thrown by `JobWorkerModule.onModuleInit` (Drizzle backend only) when the\n * `job` table contains type rows for which no `@JobHandler` is registered\n * in the running process. Surfaces every orphaned type at once so a single\n * boot tells the operator everything to clean up.\n *\n * Skipped entirely in memory mode (Q4 resolution 2026-04-19) — the memory\n * backend has no DB rows to validate; `MemoryJobOrchestrator.start()`\n * throws `JobTypeNotFoundError` synchronously for unknown types instead.\n */\nexport class BootValidationError extends Error {\n override readonly name = 'BootValidationError';\n constructor(public readonly missingHandlers: string[]) {\n super(\n `BootValidationError: ${missingHandlers.length} orphaned job type(s) ` +\n `in 'job' table with no matching @JobHandler in the running process: ` +\n `[${missingHandlers.join(', ')}]. Either register the handler(s) or ` +\n `remove the rows.`,\n );\n }\n}\n\n/**\n * Thrown by `JobWorkerModule.onModuleInit` when one or more `@JobHandler`\n * classes target a `reserved: true` pool from the resolved pool config\n * (the three `events_*` pools are reserved for the events subsystem\n * outbox drain). Listing every offender on a single boot avoids the\n * fix-one-restart-fix-next loop.\n */\nexport class ReservedPoolViolationError extends Error {\n override readonly name = 'ReservedPoolViolationError';\n constructor(\n public readonly offenders: ReadonlyArray<{\n handlerClass: string;\n pool: string;\n }>,\n ) {\n super(\n `ReservedPoolViolationError: ${offenders.length} @JobHandler(s) target ` +\n `reserved pools — reserved pools are framework-only:\\n` +\n offenders\n .map((o) => ` - ${o.handlerClass} → pool='${o.pool}'`)\n .join('\\n'),\n );\n }\n}\n","/**\n * DrizzleJobRunService — scope-oriented reads and bulk operations against\n * `job_run` (ADR-022, JOB-3).\n *\n * Separate from the orchestrator because the access pattern differs: this\n * service scans by `(scope_entity_type, scope_entity_id)` via\n * `idx_job_run_scope`, whereas orchestrator mutates individual runs by id.\n */\nimport { Inject, Injectable } from '@nestjs/common';\nimport { and, asc, desc, eq, gte, inArray, isNull, lt, or, sql } from 'drizzle-orm';\nimport type { DrizzleClient } from '../../types/drizzle';\nimport { DRIZZLE } from '../../constants/tokens';\nimport { jobRuns, type JobRunRow } from './job-orchestration.schema';\nimport type { JobRun } from './job-orchestrator.protocol';\nimport type {\n IJobRunService,\n ListForScopeOptions,\n CancelForScopeOptions,\n RescheduleForScopeOptions,\n PoolStatusCount,\n JobRunFailure,\n ListJobRunsQuery,\n JobRunPage,\n JobRunSummary,\n} from './job-run-service.protocol';\nimport {\n clampLimit,\n decodeKeysetCursor,\n encodeKeysetCursor,\n toJobRunSummary,\n} from './job-run-keyset-cursor';\nimport type { IJobOrchestrator } from './job-orchestrator.protocol';\nimport { JOB_ORCHESTRATOR, JOBS_MULTI_TENANT } from './jobs-domain.tokens';\nimport { MissingTenantIdError } from './jobs-errors';\n\nconst NON_TERMINAL_STATUSES: JobRunRow['status'][] = [\n 'pending',\n 'running',\n 'waiting',\n];\n\n@Injectable()\nexport class DrizzleJobRunService implements IJobRunService {\n constructor(\n @Inject(DRIZZLE) private readonly db: DrizzleClient,\n @Inject(JOB_ORCHESTRATOR) private readonly orchestrator: IJobOrchestrator,\n @Inject(JOBS_MULTI_TENANT) private readonly multiTenant: boolean,\n ) {}\n\n /**\n * JOB-8 — produce the tenant WHERE fragment (or `null` to opt out).\n * Returns `null` when multi-tenancy is off (caller skips the predicate).\n * Throws `MissingTenantIdError` when on + `undefined`.\n * When on + explicit `null`, filters `tenant_id IS NULL`.\n */\n private tenantCondition(\n method: string,\n tenantId: string | null | undefined,\n ) {\n if (!this.multiTenant) return null;\n if (tenantId === undefined) throw new MissingTenantIdError(method);\n return tenantId === null\n ? isNull(jobRuns.tenantId)\n : eq(jobRuns.tenantId, tenantId);\n }\n\n async listForScope(\n entityType: string,\n entityId: string,\n opts: ListForScopeOptions = {},\n ): Promise<JobRun[]> {\n const conditions = [\n eq(jobRuns.scopeEntityType, entityType),\n eq(jobRuns.scopeEntityId, entityId),\n ];\n const tenantCond = this.tenantCondition('listForScope', opts.tenantId);\n if (tenantCond) conditions.push(tenantCond);\n if (opts.status) {\n if (Array.isArray(opts.status)) {\n conditions.push(inArray(jobRuns.status, opts.status));\n } else {\n conditions.push(eq(jobRuns.status, opts.status));\n }\n }\n if (opts.jobType) {\n conditions.push(eq(jobRuns.jobType, opts.jobType));\n }\n\n const orderCol = (() => {\n switch (opts.orderBy) {\n case 'created_at asc':\n return asc(jobRuns.createdAt);\n case 'run_at desc':\n return desc(jobRuns.runAt);\n case 'run_at asc':\n return asc(jobRuns.runAt);\n case 'created_at desc':\n default:\n return desc(jobRuns.createdAt);\n }\n })();\n\n let q = this.db\n .select()\n .from(jobRuns)\n .where(and(...conditions))\n .orderBy(orderCol)\n .$dynamic();\n\n if (typeof opts.limit === 'number') {\n q = q.limit(opts.limit);\n }\n if (typeof opts.offset === 'number') {\n q = q.offset(opts.offset);\n }\n\n const rows = await q;\n return rows as JobRun[];\n }\n\n async cancelForScope(\n entityType: string,\n entityId: string,\n opts: CancelForScopeOptions = {},\n ): Promise<void> {\n const tenantCond = this.tenantCondition('cancelForScope', opts.tenantId);\n const conditions = [\n eq(jobRuns.scopeEntityType, entityType),\n eq(jobRuns.scopeEntityId, entityId),\n inArray(jobRuns.status, NON_TERMINAL_STATUSES),\n ];\n if (tenantCond) conditions.push(tenantCond);\n\n const rows = await this.db\n .select({ id: jobRuns.id })\n .from(jobRuns)\n .where(and(...conditions));\n\n for (const { id } of rows) {\n // Propagate the tenant gate into cascade-cancel. The scope query has\n // already narrowed to this tenant; passing `tenantId` through keeps\n // the orchestrator's per-row guard consistent under multi-tenant mode.\n await this.orchestrator.cancel(id, {\n cascade: true,\n tenantId: opts.tenantId,\n });\n }\n }\n\n async rescheduleForScope(\n entityType: string,\n entityId: string,\n newRunAt: Date,\n opts: RescheduleForScopeOptions = {},\n ): Promise<void> {\n const tenantCond = this.tenantCondition('rescheduleForScope', opts.tenantId);\n const conditions = [\n eq(jobRuns.scopeEntityType, entityType),\n eq(jobRuns.scopeEntityId, entityId),\n eq(jobRuns.status, 'pending'),\n ];\n if (tenantCond) conditions.push(tenantCond);\n\n await this.db\n .update(jobRuns)\n .set({ runAt: newRunAt, updatedAt: new Date() })\n .where(and(...conditions));\n }\n\n async countByPoolAndStatus(\n tenantId?: string | null,\n ): Promise<PoolStatusCount[]> {\n const tenantCond = this.tenantCondition('countByPoolAndStatus', tenantId);\n const rows = await this.db\n .select({\n pool: jobRuns.pool,\n status: jobRuns.status,\n count: sql<number>`count(*)::int`.as('count'),\n })\n .from(jobRuns)\n .where(tenantCond ?? undefined)\n .groupBy(jobRuns.pool, jobRuns.status);\n\n return rows.map((r) => ({\n pool: r.pool,\n status: r.status,\n count: Number(r.count),\n }));\n }\n\n async listRecentFailed(\n limit: number,\n tenantId?: string | null,\n ): Promise<JobRunFailure[]> {\n const conditions = [eq(jobRuns.status, 'failed' as const)];\n const tenantCond = this.tenantCondition('listRecentFailed', tenantId);\n if (tenantCond) conditions.push(tenantCond);\n\n const rows = await this.db\n .select()\n .from(jobRuns)\n .where(and(...conditions))\n .orderBy(desc(jobRuns.finishedAt), desc(jobRuns.updatedAt))\n .limit(limit);\n\n return rows.map((r) => ({\n runId: r.id,\n jobType: r.jobType,\n pool: r.pool,\n scopeEntityType: r.scopeEntityType,\n scopeEntityId: r.scopeEntityId,\n tenantId: r.tenantId,\n attempts: r.attempts,\n errorMessage: r.error?.message ?? null,\n failedAt: r.finishedAt ?? r.updatedAt,\n createdAt: r.createdAt,\n }));\n }\n\n async listJobRuns(query: ListJobRunsQuery = {}): Promise<JobRunPage> {\n const limit = clampLimit(query.limit);\n const conditions = [];\n\n const tenantCond = this.tenantCondition('listJobRuns', query.tenantId);\n if (tenantCond) conditions.push(tenantCond);\n if (query.poolId) conditions.push(eq(jobRuns.pool, query.poolId));\n if (query.rootRunId) conditions.push(eq(jobRuns.rootRunId, query.rootRunId));\n if (query.status) conditions.push(eq(jobRuns.status, query.status));\n if (query.since) conditions.push(gte(jobRuns.createdAt, query.since));\n\n // Keyset seek: WHERE (created_at, id) < (cursorCreatedAt, cursorId),\n // expanded into a SARGable OR so the same `created_at` index is used.\n if (query.cursor) {\n const keyset = decodeKeysetCursor(query.cursor);\n if (keyset) {\n conditions.push(\n or(\n lt(jobRuns.createdAt, keyset.createdAt),\n and(\n eq(jobRuns.createdAt, keyset.createdAt),\n lt(jobRuns.id, keyset.id),\n ),\n )!,\n );\n }\n }\n\n // Fetch one extra row to determine whether a next page exists without a\n // separate COUNT.\n const rows = await this.db\n .select()\n .from(jobRuns)\n .where(conditions.length > 0 ? and(...conditions) : undefined)\n .orderBy(desc(jobRuns.createdAt), desc(jobRuns.id))\n .limit(limit + 1);\n\n const hasMore = rows.length > limit;\n const page = hasMore ? rows.slice(0, limit) : rows;\n const items = page.map(toJobRunSummary);\n const last = page[page.length - 1];\n const nextCursor =\n hasMore && last\n ? encodeKeysetCursor({ createdAt: last.createdAt, id: last.id })\n : null;\n\n return { items, nextCursor };\n }\n\n /**\n * Internal helper used by cascade paths (not on the public protocol).\n * Exposed as a public method on the concrete class so infrastructure\n * code (cascade tests, debug tools) can call it without a cast.\n */\n async findByRootRunId(rootRunId: string): Promise<JobRun[]> {\n const rows = await this.db\n .select()\n .from(jobRuns)\n .where(eq(jobRuns.rootRunId, rootRunId));\n return rows as JobRun[];\n }\n}\n","/**\n * Keyset (seek) cursor codec for `IJobRunService.listJobRuns` (OBS-LIST-1).\n *\n * The list is ordered `created_at DESC, id DESC`. The cursor encodes the\n * `(createdAt, id)` of the last row on the previous page so the next page\n * can seek with `WHERE (created_at, id) < (cursorCreatedAt, cursorId)`\n * rather than an `OFFSET`. Keyset pagination stays O(log n) on deep pages\n * and is stable as new rows arrive at the head.\n *\n * The cursor is opaque to consumers: a base64url-encoded JSON tuple. Shape\n * is an implementation detail — never parse it outside this module.\n *\n * Also hosts `toJobRunSummary`, the single `JobRunRow → JobRunSummary`\n * projection shared by both backends so the narrow shape stays in sync.\n */\nimport type { JobRunRow } from './job-orchestration.schema';\nimport type { JobRunSummary } from './job-run-service.protocol';\n\nexport interface JobRunKeyset {\n /** `created_at` of the last row on the previous page. */\n createdAt: Date;\n /** `id` (UUID) tie-break of the last row on the previous page. */\n id: string;\n}\n\n/** Default page size when `limit` is omitted. */\nexport const DEFAULT_LIST_LIMIT = 50;\n/** Hard upper bound on page size to keep a single read bounded. */\nexport const MAX_LIST_LIMIT = 200;\n\n/** Clamp a caller-supplied `limit` into `[1, MAX_LIST_LIMIT]`. */\nexport function clampLimit(limit: number | undefined): number {\n if (typeof limit !== 'number' || !Number.isFinite(limit)) {\n return DEFAULT_LIST_LIMIT;\n }\n const floored = Math.floor(limit);\n if (floored < 1) return 1;\n if (floored > MAX_LIST_LIMIT) return MAX_LIST_LIMIT;\n return floored;\n}\n\nexport function encodeKeysetCursor(keyset: JobRunKeyset): string {\n const tuple = [keyset.createdAt.toISOString(), keyset.id];\n return Buffer.from(JSON.stringify(tuple), 'utf8').toString('base64url');\n}\n\n/**\n * Decode an opaque cursor back into its `(createdAt, id)` keyset. Returns\n * `null` for a malformed cursor so callers can treat garbage input as\n * \"start from the beginning\" rather than throwing on user-supplied data.\n */\nexport function decodeKeysetCursor(cursor: string): JobRunKeyset | null {\n try {\n const json = Buffer.from(cursor, 'base64url').toString('utf8');\n const parsed = JSON.parse(json) as unknown;\n if (!Array.isArray(parsed) || parsed.length !== 2) return null;\n const [iso, id] = parsed;\n if (typeof iso !== 'string' || typeof id !== 'string') return null;\n const createdAt = new Date(iso);\n if (Number.isNaN(createdAt.getTime())) return null;\n return { createdAt, id };\n } catch {\n return null;\n }\n}\n\n/**\n * Project a raw `job_run` row into the narrow `JobRunSummary` shape exposed\n * by `listJobRuns`. `errorMessage` is pulled from the jsonb `error.message`.\n */\nexport function toJobRunSummary(r: JobRunRow): JobRunSummary {\n return {\n runId: r.id,\n rootRunId: r.rootRunId,\n parentRunId: r.parentRunId,\n triggerSource: r.triggerSource,\n triggerRef: r.triggerRef,\n jobType: r.jobType,\n pool: r.pool,\n status: r.status,\n scopeEntityType: r.scopeEntityType,\n scopeEntityId: r.scopeEntityId,\n tenantId: r.tenantId,\n attempts: r.attempts,\n errorMessage: r.error?.message ?? null,\n runAt: r.runAt,\n startedAt: r.startedAt,\n finishedAt: r.finishedAt,\n createdAt: r.createdAt,\n };\n}\n","/**\n * DrizzleJobStepService — upsert + lookup on `job_step` for replay-safe\n * memoization (ADR-022, JOB-3).\n *\n * `recordStep` upserts on the `(job_run_id, step_id)` unique index — each\n * step row is written as `running` first, then transitioned to a terminal\n * state (`completed` / `failed` / `skipped`). `findStep` is the hot path\n * that `ctx.step()` consults on every invocation; null on miss.\n */\nimport { Inject, Injectable } from '@nestjs/common';\nimport { and, eq } from 'drizzle-orm';\nimport type { DrizzleClient } from '../../types/drizzle';\nimport { DRIZZLE } from '../../constants/tokens';\nimport { jobSteps, type JobStepRow } from './job-orchestration.schema';\nimport type {\n IJobStepService,\n JobStep,\n RecordStepInput,\n} from './job-step-service.protocol';\n\n@Injectable()\nexport class DrizzleJobStepService implements IJobStepService {\n constructor(@Inject(DRIZZLE) private readonly db: DrizzleClient) {}\n\n async recordStep(input: RecordStepInput): Promise<JobStep> {\n const values = {\n jobRunId: input.jobRunId,\n stepId: input.stepId,\n kind: input.kind,\n seq: input.seq,\n status: input.status,\n input: (input.input ?? null) as Record<string, unknown> | null,\n output: (input.output ?? null) as Record<string, unknown> | null,\n error: input.error ?? null,\n attempts: input.attempts ?? 0,\n startedAt: input.startedAt ?? null,\n finishedAt: input.finishedAt ?? null,\n };\n\n const [row] = await this.db\n .insert(jobSteps)\n .values(values)\n .onConflictDoUpdate({\n target: [jobSteps.jobRunId, jobSteps.stepId],\n set: {\n status: values.status,\n output: values.output,\n error: values.error,\n finishedAt: values.finishedAt,\n attempts: values.attempts,\n },\n })\n .returning();\n\n return row as JobStep;\n }\n\n async findStep(runId: string, stepId: string): Promise<JobStep | null> {\n const [row] = await this.db\n .select()\n .from(jobSteps)\n .where(and(eq(jobSteps.jobRunId, runId), eq(jobSteps.stepId, stepId)))\n .limit(1);\n return ((row as JobStepRow | undefined) ?? null) as JobStep | null;\n }\n}\n","/**\n * MemoryJobOrchestrator — in-process implementation of `IJobOrchestrator`\n * (ADR-022, JOB-4).\n *\n * Exists solely for the unit test suite: reproduces the Drizzle backend's\n * observable behaviour (claim ordering, collision modes, dedupe collapse,\n * memoization cache, replay row-clearing, cascade cancel) without a\n * database. Not production — the single-process mutex is a substitute for\n * Postgres' `FOR UPDATE SKIP LOCKED`; acceptable non-parity is listed in\n * `docs/specs/JOB-4.md` (fsync, query perf, multi-process claim).\n *\n * The `MemoryJobStore` is shared with `MemoryJobRunService` /\n * `MemoryJobStepService` — all three services mutate the same Maps under\n * the orchestrator's mutex.\n */\nimport { randomUUID } from 'node:crypto';\nimport { Inject, Injectable, Logger, Optional } from '@nestjs/common';\nimport { ModuleRef } from '@nestjs/core';\nimport type {\n JobDefinitionRow,\n JobRunRow,\n} from './job-orchestration.schema';\nimport type {\n CancelOptions,\n IJobOrchestrator,\n JobPoolDef,\n JobRun,\n JobUpsertEntry,\n StartOptions,\n} from './job-orchestrator.protocol';\nimport type {\n JobContext,\n JobHandlerBase,\n JobHandlerMeta,\n RetryPolicy,\n SpawnChildOptions,\n StepOptions,\n} from './job-handler.base';\nimport { ParentClosePolicy } from './job-handler.base';\nimport {\n JobCollisionError,\n JobNotReplayableError,\n JobTemplateFieldMissingError,\n JobTypeNotFoundError,\n MissingTenantIdError,\n} from './jobs-errors';\nimport { MemoryJobStore } from './memory-job-store';\nimport { MemoryJobStepService } from './job-step-service.memory-backend';\nimport { JOBS_MULTI_TENANT } from './jobs-domain.tokens';\n\n/**\n * Sentinel `run_at` for runs that lost the `queue` collision — they stay\n * unclaimable until the incumbent transitions terminal and the orchestrator\n * advances their `run_at` back to `now()`. Mirrors the Drizzle backend's\n * `claim-time gate` behaviour without requiring a separate claim query.\n */\nconst QUEUED_RUN_AT = new Date(8_640_000_000_000_000); // \"distant future\"\nconst TERMINAL_STATUSES: JobRunRow['status'][] = [\n 'completed',\n 'failed',\n 'timed_out',\n 'canceled',\n];\nconst DEDUPE_EXCLUDED_STATUSES: JobRunRow['status'][] = ['canceled', 'failed'];\nconst IN_FLIGHT_STATUSES: JobRunRow['status'][] = ['pending', 'running'];\n\nfunction isTerminal(status: JobRunRow['status']): boolean {\n return TERMINAL_STATUSES.includes(status);\n}\n\n/**\n * Mirror of `evaluateKeyTemplate` in the Drizzle backend. Kept private here\n * rather than exported so the memory backend has no dependency on the\n * Drizzle module.\n */\nfunction evaluateKeyTemplate(\n template: string,\n input: Record<string, unknown>,\n): string {\n return template.replace(\n /\\{\\{\\s*([a-zA-Z0-9_]+)\\s*\\}\\}/g,\n (_m, field: string) => {\n const value = input[field];\n if (value === undefined || value === null) {\n throw new JobTemplateFieldMissingError(template, field);\n }\n return String(value);\n },\n );\n}\n\n/**\n * Single-promise-chain mutex. Every mutating op on the store goes through\n * `run(...)` so two concurrent `start` calls observe the same sequential\n * consistency Postgres gives us via `FOR UPDATE SKIP LOCKED`. Error\n * swallowing on the chain pointer prevents one failed call from poisoning\n * the queue for subsequent callers.\n *\n * Kept private to this file on purpose — the spec explicitly forbids\n * exporting this; it exists only for the memory backend's internal\n * serialisation.\n */\nclass PromiseMutex {\n private queue: Promise<void> = Promise.resolve();\n\n async run<T>(fn: () => Promise<T>): Promise<T> {\n const next = this.queue.then(() => fn());\n // Swallow errors on the chain pointer so a throwing `fn` doesn't\n // permanently reject every future caller.\n this.queue = next.then(\n () => undefined,\n () => undefined,\n );\n return next;\n }\n}\n\n/** Handler registry entry — class + frozen metadata. */\ninterface HandlerRegistration {\n type: string;\n meta: JobHandlerMeta<unknown>;\n handlerClass: new (...args: unknown[]) => JobHandlerBase<unknown>;\n}\n\n@Injectable()\nexport class MemoryJobOrchestrator implements IJobOrchestrator {\n private readonly logger = new Logger(MemoryJobOrchestrator.name);\n private readonly mutex = new PromiseMutex();\n private readonly handlerRegistry = new Map<string, HandlerRegistration>();\n\n /**\n * `runId → dependent runId[]` — when a run with `concurrencyKey = K`\n * blocks on an incumbent, its id is added here under the incumbent's id.\n * On incumbent terminal transition we advance every dependent's `runAt`\n * back to `now()` so it becomes claimable.\n */\n private readonly queueBlockers = new Map<string, string[]>();\n\n constructor(\n private readonly store: MemoryJobStore,\n private readonly stepService: MemoryJobStepService,\n @Inject(JOBS_MULTI_TENANT) private readonly multiTenant: boolean,\n @Optional() private readonly moduleRef?: ModuleRef,\n ) {}\n\n /**\n * JOB-8 — mirror of the Drizzle backend's `resolveTenantId`. Returns the\n * value to stamp on `tenant_id` / compare against in memory predicates.\n * Off → always `null`. On + `undefined` → throw. On + `null`/string → pass.\n */\n private resolveTenantId(\n method: string,\n tenantId: string | null | undefined,\n ): string | null {\n if (!this.multiTenant) return null;\n if (tenantId === undefined) throw new MissingTenantIdError(method);\n return tenantId;\n }\n\n // ==========================================================================\n // registerHandler — replaces Drizzle's `job` table upsert\n // ==========================================================================\n\n /**\n * Populate the in-memory job definition row plus handler class lookup.\n * Called by `JobWorkerModule.onModuleInit` in memory mode, or directly by\n * unit tests that want to seed the registry without NestJS.\n */\n registerHandler<TInput>(\n type: string,\n meta: JobHandlerMeta<TInput>,\n handlerClass: new (...args: unknown[]) => JobHandlerBase<TInput>,\n ): void {\n const concurrencyKeyTemplate =\n (meta.concurrency as { key?: string } | undefined)?.key ?? null;\n const dedupeKeyTemplate =\n (meta.dedupe as { key?: string } | undefined)?.key ?? null;\n const dedupeWindowMs = meta.dedupe?.windowMs ?? null;\n const now = new Date();\n\n const def: JobDefinitionRow = {\n type,\n version: 1,\n pool: meta.pool ?? 'batch',\n scopeEntityType: meta.scope?.entity ?? null,\n retryPolicy: meta.retry ?? {\n attempts: 1,\n backoff: 'fixed',\n baseMs: 0,\n },\n timeoutMs: meta.timeoutMs ?? null,\n concurrencyKeyTemplate:\n typeof concurrencyKeyTemplate === 'string' ? concurrencyKeyTemplate : null,\n collisionMode:\n (meta.concurrency?.collisionMode as JobDefinitionRow['collisionMode']) ??\n 'queue',\n dedupeKeyTemplate:\n typeof dedupeKeyTemplate === 'string' ? dedupeKeyTemplate : null,\n dedupeWindowMs,\n priorityDefault: 0,\n replayFrom: meta.replayFrom ?? 'last_checkpoint',\n createdAt: now,\n updatedAt: now,\n };\n\n this.store.jobs.set(type, def);\n this.handlerRegistry.set(type, {\n type,\n meta: meta as JobHandlerMeta<unknown>,\n handlerClass: handlerClass as unknown as new (\n ...args: unknown[]\n ) => JobHandlerBase<unknown>,\n });\n }\n\n /** Test helper — look up a registered handler without exposing the map. */\n getHandlerRegistration(type: string): HandlerRegistration | undefined {\n return this.handlerRegistry.get(type);\n }\n\n /**\n * Boot-time upsert per `IJobOrchestrator.upsertJobRows`. Memory backend\n * just funnels each entry through `registerHandler`. The validator is\n * skipped entirely in memory mode (Q4 resolution 2026-04-19), so the\n * orphaned list is always empty — there are no DB rows to compare against.\n */\n async upsertJobRows(\n entries: JobUpsertEntry[],\n poolConfig: ReadonlyMap<string, JobPoolDef>,\n ): Promise<{ orphaned: string[] }> {\n void poolConfig; // pool validation is the module's responsibility\n for (const entry of entries) {\n this.registerHandler(\n entry.type,\n entry.meta as JobHandlerMeta<unknown>,\n entry.handlerClass as new (...args: unknown[]) => JobHandlerBase<unknown>,\n );\n }\n return { orphaned: [] };\n }\n\n // ==========================================================================\n // start\n // ==========================================================================\n\n async start(\n type: string,\n input: unknown,\n opts: StartOptions = {},\n // BRIDGE-7: signature parity with Drizzle backend. The memory backend\n // has no real transactions (its \"atomic\" boundary is a process-wide\n // mutex acquired by the body below), so the parameter is intentionally\n // ignored. Accepting it lets EventFlowService unit tests exercise the\n // same code path without two stub orchestrators.\n _tx?: unknown,\n ): Promise<JobRun> {\n // JOB-8 — resolve tenant gate outside the mutex so the error throws\n // synchronously-ish from the caller's stack rather than via the mutex's\n // deferred chain (matches Drizzle backend's pre-transaction guard).\n const tenantId = this.resolveTenantId('start', opts.tenantId);\n\n return this.mutex.run(async () => {\n const payload = (input ?? {}) as Record<string, unknown>;\n const definition = this.store.jobs.get(type);\n if (!definition) throw new JobTypeNotFoundError(type);\n\n // 1. Dedupe — return existing non-excluded run within the window.\n if (definition.dedupeKeyTemplate && definition.dedupeWindowMs) {\n const dedupeKey = evaluateKeyTemplate(\n definition.dedupeKeyTemplate,\n payload,\n );\n const windowStart = Date.now() - definition.dedupeWindowMs;\n const existing = this.findDedupeCandidate(type, dedupeKey, windowStart);\n if (existing) return existing;\n }\n\n // 2. Concurrency collision check.\n let concurrencyKey: string | null = null;\n let queueBlockedBy: string | null = null;\n if (definition.concurrencyKeyTemplate) {\n concurrencyKey = evaluateKeyTemplate(\n definition.concurrencyKeyTemplate,\n payload,\n );\n const incumbent = this.findInFlightByConcurrencyKey(concurrencyKey);\n if (incumbent) {\n switch (definition.collisionMode) {\n case 'reject':\n throw new JobCollisionError(type, concurrencyKey, incumbent);\n case 'replace':\n // Cancel incumbent (cascading children). Must happen inside\n // the mutex — call the internal helper, not public `cancel()`\n // (public `cancel` would re-enter the mutex and deadlock).\n // Internal replace path sidesteps the tenant gate — it uses\n // the incumbent's own tenant (same concurrency key implies\n // same tenant in practice, but the gate is bypassed via\n // `incumbent.tenantId` to avoid accidental cross-tenant\n // MissingTenantIdError bubbling from the user's `start` call).\n this.cancelLocked(\n incumbent.id,\n { cascade: true, reason: 'replaced' },\n incumbent.tenantId,\n );\n break;\n case 'queue':\n queueBlockedBy = incumbent.id;\n break;\n }\n }\n }\n\n // 3. Resolve lineage.\n const newId = randomUUID();\n let rootRunId: string = newId;\n if (opts.parentRunId) {\n const parent = this.store.runs.get(opts.parentRunId);\n if (!parent) {\n throw new Error(\n `parentRunId ${opts.parentRunId} does not reference an existing job_run`,\n );\n }\n rootRunId = parent.rootRunId;\n }\n\n // 4. Compute dedupe key for the persisted row (separate from dedupe\n // short-circuit above — we store it even when no prior run matched\n // so future dedupe checks see it).\n const dedupeKey = definition.dedupeKeyTemplate\n ? evaluateKeyTemplate(definition.dedupeKeyTemplate, payload)\n : null;\n\n const now = new Date();\n const runAt = queueBlockedBy\n ? QUEUED_RUN_AT\n : (opts.runAt ?? now);\n\n const row: JobRunRow = {\n id: newId,\n jobType: type,\n jobVersion: definition.version,\n parentRunId: opts.parentRunId ?? null,\n rootRunId,\n parentClosePolicy: opts.parentClosePolicy ?? 'terminate',\n scopeEntityType: opts.scope?.entityType ?? null,\n scopeEntityId: opts.scope?.entityId ?? null,\n tenantId,\n tags: opts.tags ?? {},\n pool: opts.pool ?? definition.pool,\n priority: opts.priority ?? definition.priorityDefault,\n concurrencyKey,\n dedupeKey,\n status: 'pending',\n input: payload,\n output: null,\n error: null,\n triggerSource: opts.triggerSource ?? 'manual',\n triggerRef: opts.triggerRef ?? null,\n runAt,\n startedAt: null,\n finishedAt: null,\n claimedAt: null,\n attempts: 0,\n waitKind: null,\n resumeToken: null,\n waitDeadline: null,\n createdAt: now,\n updatedAt: now,\n };\n\n this.store.runs.set(newId, row);\n if (queueBlockedBy) {\n const list = this.queueBlockers.get(queueBlockedBy) ?? [];\n list.push(newId);\n this.queueBlockers.set(queueBlockedBy, list);\n }\n return row;\n });\n }\n\n // ==========================================================================\n // cancel\n // ==========================================================================\n\n async cancel(runId: string, opts: CancelOptions = {}): Promise<void> {\n // JOB-8 — strict tenant gate outside the mutex (matches Drizzle path).\n const tenantId = this.resolveTenantId('cancel', opts.tenantId);\n await this.mutex.run(async () => {\n this.cancelLocked(runId, opts, tenantId);\n });\n }\n\n /**\n * Internal cancel that assumes the caller already holds the mutex.\n * Synchronous because all store ops are in-memory. Idempotent.\n *\n * `tenantForGate` is the already-validated tenant id (or `null`). When\n * non-null it gates the initial cancellation to that tenant's run; the\n * cascade step then sweeps descendants on the same `rootRunId` without\n * re-checking — children of a tenant-gated parent always share the\n * tenant (enforced at `start` time).\n */\n private cancelLocked(\n runId: string,\n opts: CancelOptions,\n tenantForGate: string | null,\n ): void {\n const run = this.store.runs.get(runId);\n if (!run) return;\n // JOB-8 — cross-tenant cancel is silent no-op.\n if (this.multiTenant && run.tenantId !== tenantForGate) return;\n if (isTerminal(run.status)) return;\n\n const now = new Date();\n\n // Collect descendants up front so Cancel-policy parents can wait on\n // children (their `finished_at` is set after children transition).\n const descendants =\n opts.cascade === false\n ? []\n : Array.from(this.store.runs.values()).filter(\n (r) =>\n r.rootRunId === run.rootRunId &&\n r.id !== runId &&\n !isTerminal(r.status),\n );\n\n // Group by policy stored on the child.\n const terminateChildren = descendants.filter(\n (d) => d.parentClosePolicy === ParentClosePolicy.Terminate,\n );\n const cancelChildren = descendants.filter(\n (d) => d.parentClosePolicy === ParentClosePolicy.Cancel,\n );\n // 'abandon' → do nothing.\n\n // Terminate policy: cancel children, then parent.\n for (const child of terminateChildren) {\n this.transitionToCanceled(child.id, now);\n }\n\n // Cancel policy: cancel children first, then parent (so parent's\n // finished_at is set only after children transitioned).\n for (const child of cancelChildren) {\n this.transitionToCanceled(child.id, now);\n }\n\n this.transitionToCanceled(runId, now);\n\n void opts.reason; // reserved for future audit logging\n }\n\n private transitionToCanceled(runId: string, at: Date): void {\n const run = this.store.runs.get(runId);\n if (!run) return;\n if (isTerminal(run.status)) return;\n const next: JobRunRow = {\n ...run,\n status: 'canceled',\n finishedAt: at,\n updatedAt: at,\n };\n this.store.runs.set(runId, next);\n this.unblockQueuedDependents(runId);\n }\n\n /**\n * When `runId` transitions to a terminal state, advance every dependent\n * `queue`-blocked run's `run_at` back to `now()` so `claimNext` picks\n * them up.\n */\n private unblockQueuedDependents(runId: string): void {\n const dependents = this.queueBlockers.get(runId);\n if (!dependents || dependents.length === 0) return;\n const now = new Date();\n for (const dep of dependents) {\n const depRun = this.store.runs.get(dep);\n if (!depRun) continue;\n if (depRun.status !== 'pending') continue;\n this.store.runs.set(dep, { ...depRun, runAt: now, updatedAt: now });\n }\n this.queueBlockers.delete(runId);\n }\n\n // ==========================================================================\n // claimNext — consumed by JobWorker in memory mode (tests exercise directly)\n // ==========================================================================\n\n async claimNext(pool: string): Promise<JobRunRow | null> {\n return this.mutex.run(async () => {\n const now = Date.now();\n const candidates = Array.from(this.store.runs.values()).filter(\n (r) =>\n r.status === 'pending' &&\n r.pool === pool &&\n r.runAt.getTime() <= now,\n );\n if (candidates.length === 0) return null;\n\n // ORDER BY priority DESC, run_at ASC (Drizzle parity).\n candidates.sort((a, b) => {\n if (a.priority !== b.priority) return b.priority - a.priority;\n return a.runAt.getTime() - b.runAt.getTime();\n });\n\n const winner = candidates[0]!;\n const claimedAt = new Date();\n const next: JobRunRow = {\n ...winner,\n status: 'running',\n claimedAt,\n startedAt: claimedAt,\n updatedAt: claimedAt,\n };\n this.store.runs.set(winner.id, next);\n return next;\n });\n }\n\n // ==========================================================================\n // replay\n // ==========================================================================\n\n async replay(runId: string): Promise<JobRun> {\n return this.mutex.run(async () => {\n const run = this.store.runs.get(runId);\n if (!run) throw new Error(`replay: run ${runId} not found`);\n if (!isTerminal(run.status)) {\n throw new JobNotReplayableError(runId, run.status);\n }\n const def = this.store.jobs.get(run.jobType);\n if (!def) throw new JobTypeNotFoundError(run.jobType);\n\n const mode = def.replayFrom;\n if (mode === 'scratch') {\n this.stepService.clearStepsForRun(runId);\n } else {\n // `last_step` and `last_checkpoint` collapse to the same semantic\n // in Phase 1 — delete non-completed rows, preserve memoized ones.\n // Matches the Drizzle backend exactly (see JOB-3 notes).\n this.stepService.clearIncompleteSteps(runId);\n }\n\n const now = new Date();\n const next: JobRunRow = {\n ...run,\n status: 'pending',\n attempts: 0,\n runAt: now,\n startedAt: null,\n finishedAt: null,\n claimedAt: null,\n error: null,\n output: null,\n updatedAt: now,\n };\n this.store.runs.set(runId, next);\n return next;\n });\n }\n\n // ==========================================================================\n // tick — used by unit tests + memory-mode JobWorker\n // ==========================================================================\n\n /**\n * Execute a single claimed run to completion, retry, or failure. Not on\n * `IJobOrchestrator` — it's the memory equivalent of the Drizzle\n * `JobWorker.processRun` code path. The unit tests drive it directly so\n * they can assert memoization across ticks without spinning up a worker.\n */\n async tick(runId: string): Promise<void> {\n // We load state outside the mutex because handler execution cannot\n // hold the serialisation lock — `fn()` inside `ctx.step` can call back\n // into `start` / `spawnChild` which would deadlock. Mutation points\n // (recordStep, status transition) go through the services or the\n // orchestrator entry points and re-enter the mutex there.\n const run = this.store.runs.get(runId);\n if (!run) throw new Error(`tick: run ${runId} not found`);\n if (run.status !== 'running') {\n throw new Error(\n `tick: run ${runId} must be 'running' (got '${run.status}')`,\n );\n }\n\n const registration = this.handlerRegistry.get(run.jobType);\n if (!registration) {\n await this.markFailed(run, new Error(\n `No handler registered for jobType='${run.jobType}'`,\n ), (run.attempts ?? 0) + 1);\n return;\n }\n const meta = registration.meta;\n const HandlerClass = registration.handlerClass;\n // Match the Drizzle backend: resolve the handler through Nest's\n // ModuleRef so `@Inject` constructor params work. ModuleRef is\n // @Optional() — zero-dep test stubs that construct this orchestrator\n // manually still hit the legacy `new HandlerClass()` path.\n // `get({ strict: false })` (not `create()`) — the handler must be a\n // provider in its owning module so cross-module @Inject dependencies\n // resolve. See job-worker.ts for the full rationale.\n const handler = this.moduleRef\n ? (this.moduleRef.get(\n HandlerClass as unknown as new (...args: unknown[]) => unknown,\n { strict: false },\n ) as JobHandlerBase<unknown>)\n : new HandlerClass();\n\n const ctx: JobContext<unknown> = {\n input: run.input,\n run: run as JobRun,\n step: this.makeStepFn(run),\n spawnChild: this.makeSpawnFn(run),\n logger: new Logger(`JobRun:${run.id}`),\n };\n\n const attemptsBefore = run.attempts ?? 0;\n try {\n const output = (await handler.run(ctx)) as Record<string, unknown> | undefined;\n await this.markCompleted(run, output ?? {}, attemptsBefore + 1);\n } catch (err) {\n const policy = meta.retry;\n const decision = classifyError(err, policy, attemptsBefore);\n const nextAttempts = attemptsBefore + 1;\n if (decision === 'retry' && policy) {\n const delay = computeBackoff(policy, nextAttempts);\n await this.rescheduleForRetry(run, err, nextAttempts, delay);\n } else {\n await this.markFailed(run, err, nextAttempts);\n }\n }\n }\n\n private makeStepFn(run: JobRunRow) {\n return async <TOutput>(\n stepId: string,\n fn: () => Promise<TOutput>,\n _opts?: StepOptions,\n ): Promise<TOutput> => {\n void _opts;\n const existing = await this.stepService.findStep(run.id, stepId);\n if (existing?.status === 'completed') {\n return existing.output as TOutput;\n }\n const seq = this.nextStepSeq(run.id);\n const startedAt = new Date();\n const nextAttempts = (existing?.attempts ?? 0) + 1;\n await this.stepService.recordStep({\n jobRunId: run.id,\n stepId,\n kind: 'task',\n seq,\n status: 'running',\n startedAt,\n attempts: nextAttempts,\n });\n try {\n const output = await fn();\n await this.stepService.recordStep({\n jobRunId: run.id,\n stepId,\n kind: 'task',\n seq,\n status: 'completed',\n output: output as Record<string, unknown> | undefined,\n finishedAt: new Date(),\n attempts: nextAttempts,\n });\n return output;\n } catch (err) {\n await this.stepService.recordStep({\n jobRunId: run.id,\n stepId,\n kind: 'task',\n seq,\n status: 'failed',\n error: serialiseError(err, nextAttempts, false),\n finishedAt: new Date(),\n attempts: nextAttempts,\n });\n throw err;\n }\n };\n }\n\n private makeSpawnFn(run: JobRunRow) {\n return async (\n type: string,\n input: unknown,\n opts?: SpawnChildOptions,\n ): Promise<JobRun> => {\n return this.start(type, input, {\n parentRunId: run.id,\n parentClosePolicy: opts?.closePolicy,\n runAt: opts?.runAt,\n priority: opts?.priority,\n tags: opts?.tags,\n triggerSource: 'parent',\n triggerRef: run.id,\n });\n };\n }\n\n private nextStepSeq(runId: string): number {\n const rows = this.store.steps.get(runId);\n if (!rows || rows.length === 0) return 1;\n let max = 0;\n for (const r of rows) if (r.seq > max) max = r.seq;\n return max + 1;\n }\n\n private async markCompleted(\n run: JobRunRow,\n output: Record<string, unknown>,\n attempts: number,\n ): Promise<void> {\n await this.mutex.run(async () => {\n const current = this.store.runs.get(run.id);\n if (!current || isTerminal(current.status)) return;\n const now = new Date();\n this.store.runs.set(run.id, {\n ...current,\n status: 'completed',\n output,\n finishedAt: now,\n updatedAt: now,\n attempts,\n });\n this.unblockQueuedDependents(run.id);\n });\n }\n\n private async markFailed(\n run: JobRunRow,\n err: unknown,\n attempts: number,\n ): Promise<void> {\n await this.mutex.run(async () => {\n const current = this.store.runs.get(run.id);\n if (!current || isTerminal(current.status)) return;\n const now = new Date();\n this.store.runs.set(run.id, {\n ...current,\n status: 'failed',\n finishedAt: now,\n updatedAt: now,\n attempts,\n error: serialiseError(err, attempts, false),\n });\n this.unblockQueuedDependents(run.id);\n });\n\n // parent_close_policy = 'terminate' cascade mirrors the Drizzle worker\n // (cancel runs outside its own terminal transition). We pass the run's\n // own `tenantId` so the cancel passes the multi-tenant gate — this is\n // system-internal cascade, not a user-initiated call.\n if (run.parentClosePolicy === 'terminate') {\n try {\n await this.cancel(run.id, {\n cascade: true,\n reason: 'parent-failed',\n tenantId: run.tenantId,\n });\n } catch (cascadeErr) {\n this.logger.warn(\n `cascade on failed run ${run.id}: ${(cascadeErr as Error).message}`,\n );\n }\n }\n }\n\n private async rescheduleForRetry(\n run: JobRunRow,\n err: unknown,\n attempts: number,\n delayMs: number,\n ): Promise<void> {\n await this.mutex.run(async () => {\n const current = this.store.runs.get(run.id);\n if (!current || isTerminal(current.status)) return;\n const now = new Date();\n this.store.runs.set(run.id, {\n ...current,\n status: 'pending',\n attempts,\n runAt: new Date(Date.now() + delayMs),\n startedAt: null,\n claimedAt: null,\n updatedAt: now,\n error: serialiseError(err, attempts, true),\n });\n });\n }\n\n // ==========================================================================\n // Internal queries — used by start / cancel\n // ==========================================================================\n\n private findDedupeCandidate(\n jobType: string,\n dedupeKey: string,\n windowStartMs: number,\n ): JobRunRow | null {\n let best: JobRunRow | null = null;\n for (const r of this.store.runs.values()) {\n if (r.jobType !== jobType) continue;\n if (r.dedupeKey !== dedupeKey) continue;\n if (DEDUPE_EXCLUDED_STATUSES.includes(r.status)) continue;\n if (r.createdAt.getTime() <= windowStartMs) continue;\n if (!best || r.createdAt.getTime() > best.createdAt.getTime()) {\n best = r;\n }\n }\n return best;\n }\n\n private findInFlightByConcurrencyKey(key: string): JobRunRow | null {\n for (const r of this.store.runs.values()) {\n if (r.concurrencyKey !== key) continue;\n if (!IN_FLIGHT_STATUSES.includes(r.status)) continue;\n return r;\n }\n return null;\n }\n}\n\n// ─── Pure helpers (mirrored from JobWorker so memory mode is standalone) ────\n\nfunction classifyError(\n err: unknown,\n policy: RetryPolicy | undefined,\n currentAttempts: number,\n): 'retry' | 'fail' {\n if (!policy) return 'fail';\n const errObj = err as { name?: string; code?: string } | undefined;\n const name = errObj?.name;\n const code = errObj?.code;\n const nonRetryable = policy.nonRetryableErrors ?? [];\n if (nonRetryable.some((n) => n === name || n === code)) return 'fail';\n if (currentAttempts + 1 >= policy.attempts) return 'fail';\n return 'retry';\n}\n\nfunction computeBackoff(policy: RetryPolicy, attempts: number): number {\n const base = Math.max(policy.baseMs, 0);\n if (policy.backoff === 'fixed') return base;\n const exponent = Math.max(attempts - 1, 0);\n if (exponent >= 53) return Number.MAX_SAFE_INTEGER;\n const raw = base * Math.pow(2, exponent);\n if (!Number.isFinite(raw) || raw >= Number.MAX_SAFE_INTEGER) {\n return Number.MAX_SAFE_INTEGER;\n }\n return raw;\n}\n\nfunction serialiseError(err: unknown, attempt: number, retryable: boolean) {\n const e = err as { message?: string; stack?: string } | undefined;\n return {\n message: (e?.message ?? String(err)) as string,\n stack: e?.stack,\n retryable,\n attempt,\n };\n}\n","/**\n * Handler base class, JobContext, @JobHandler decorator, and policy types\n * for the job orchestration domain (ADR-022, JOB-2).\n *\n * User-authored jobs subclass `JobHandlerBase<TInput, TOutput>` and decorate\n * the class with `@JobHandler<TInput>('job_type', meta)`. The decorator\n * 1. stores metadata via `Reflect.defineMetadata` so Nest's reflector can\n * pick it up at module boot, and\n * 2. populates `JOB_HANDLER_REGISTRY` — a module-singleton map consumed by\n * `JobWorkerModule` (JOB-5) to materialise `job` rows and resolve\n * handler classes during claim/execute.\n *\n * No runtime orchestration lives here; this file is a pure type + decorator\n * surface so downstream PRs (JOB-3..JOB-5) can implement against a stable\n * shape.\n */\n// TODO(logging-subsystem): swap to ILogger once ADR-028 lands\nimport type { Logger } from '@nestjs/common';\nimport { tokenKey } from '../token-key';\nimport type { EventOfType, EventTypeName } from '../events/generated/types';\nimport type { JobRun } from './job-orchestrator.protocol';\n\n// ─── ParentClosePolicy ──────────────────────────────────────────────────────\n\n/**\n * What happens to running child runs when a parent enters a terminal state.\n * Stored on the child at spawn; changes to the parent after spawn do NOT\n * retroactively rewrite children.\n */\nexport enum ParentClosePolicy {\n Terminate = 'terminate',\n Cancel = 'cancel',\n Abandon = 'abandon',\n}\n\n// ─── Policy types ───────────────────────────────────────────────────────────\n\nexport interface RetryPolicy {\n attempts: number;\n backoff: 'fixed' | 'exponential';\n baseMs: number;\n nonRetryableErrors?: string[];\n}\n\nexport interface ConcurrencyPolicy<TInput> {\n key: (input: TInput) => string;\n collisionMode: 'queue' | 'reject' | 'replace';\n}\n\nexport interface DedupePolicy<TInput> {\n key: (input: TInput) => string;\n windowMs: number;\n}\n\n/**\n * Declarative scope reference. `TScope` is parameterised so JOB-7 can narrow\n * `entity` to the generated `ScopeEntityType` union at the call site without\n * modifying this file (OQ-1 resolution, 2026-04-20).\n */\nexport interface ScopeRef<TInput, TScope extends string = string> {\n entity: TScope;\n from: (input: TInput) => string;\n}\n\n/**\n * Bridge trigger authoring shape (BRIDGE-6 follow-up — BRIDGE-6 shipped the\n * generator + runtime for `@JobHandler({ triggers })` but never added the\n * authoring field to this type; the generator's tests scan source as strings,\n * so a real decorator was never compiled and the gap went uncaught).\n *\n * Declared on `@JobHandler({ triggers })`; the codegen bridge-registry\n * generator (`src/cli/shared/bridge-registry-generator.ts`) scans these from\n * source and emits `bridge/generated/registry.ts`, validating each `event`\n * against the generated `eventRegistry` at `gen-all`. The distributed union\n * narrows `map`/`when` per `event`, so callbacks are typed against the event\n * payload (ADR-023, \"typed against PayloadOfType<T>\").\n *\n * Typed against events' generated types — the same `import type` coupling the\n * bridge already has (erased at runtime). `jobs` must NOT import `bridge`, so\n * the post-gen `BridgeTriggerEntry` is deliberately not referenced here;\n * `triggerId`/`jobType` are computed by the generator, not authored.\n */\nexport type JobTrigger<TInput> = {\n [T in EventTypeName]: {\n /** Event type that fires this trigger. Validated against `eventRegistry`. */\n event: T;\n /** Maps the event to the job input. Inlined verbatim into the registry. */\n map: (event: EventOfType<T>) => TInput;\n /** Optional guard; `false` → wrapper records `status='skipped'`. */\n when?: (event: EventOfType<T>) => boolean;\n };\n}[EventTypeName];\n\nexport interface JobHandlerMeta<TInput> {\n pool?: string;\n scope?: ScopeRef<TInput>;\n retry?: RetryPolicy;\n concurrency?: ConcurrencyPolicy<TInput>;\n dedupe?: DedupePolicy<TInput>;\n timeoutMs?: number;\n replayFrom?: 'scratch' | 'last_step' | 'last_checkpoint';\n /**\n * Bridge triggers (ADR-023 Tier 3). Codegen scans these into `bridgeRegistry`;\n * the framework `BridgeDeliveryHandler` starts this job per matched event.\n * Absent for jobs started directly or via `IEventFlow.publishAndStart`.\n */\n triggers?: readonly JobTrigger<TInput>[];\n}\n\n// ─── Runtime option shapes ──────────────────────────────────────────────────\n\nexport interface StepOptions {\n retry?: RetryPolicy;\n timeoutMs?: number;\n}\n\nexport interface SpawnChildOptions {\n closePolicy?: ParentClosePolicy;\n runAt?: Date;\n priority?: number;\n tags?: Record<string, string>;\n}\n\n// ─── JobContext ─────────────────────────────────────────────────────────────\n\nexport interface JobContext<TInput> {\n readonly input: TInput;\n readonly run: JobRun;\n step<TOutput>(\n stepId: string,\n fn: () => Promise<TOutput>,\n opts?: StepOptions,\n ): Promise<TOutput>;\n spawnChild(type: string, input: unknown, opts?: SpawnChildOptions): Promise<JobRun>;\n readonly logger: Logger;\n // NOT in Phase 1 — deferred to ADR-025:\n // waitFor(kind, token, opts)\n // signal(token, payload)\n // sleep(ms)\n}\n\n// ─── JobHandlerBase ─────────────────────────────────────────────────────────\n\nexport abstract class JobHandlerBase<TInput, TOutput = unknown> {\n abstract run(ctx: JobContext<TInput>): Promise<TOutput>;\n}\n\n// ─── Registry + decorator ───────────────────────────────────────────────────\n\n/**\n * Module-singleton map keyed by job type. Populated by the `@JobHandler`\n * decorator at class definition time; consumed by `JobWorkerModule` (JOB-5)\n * to upsert `job` rows and resolve handler classes during claim/execute.\n */\nexport const JOB_HANDLER_REGISTRY = new Map<\n string,\n {\n type: string;\n meta: JobHandlerMeta<unknown>;\n handlerClass: new (...args: unknown[]) => JobHandlerBase<unknown>;\n }\n>();\n\n// ADR-037: namespaced `Symbol.for(...)` (via `tokenKey()`) so the reflection-metadata\n// key matches by value across import boundaries (the @JobHandler decorator and the\n// reader may resolve different runtime copies). Distinct from the DI tokens but\n// subject to the same dual-package identity hazard.\nexport const JOB_HANDLER_METADATA_KEY = Symbol.for(tokenKey('jobs', 'handler-metadata'));\n\n/**\n * Class decorator that registers a handler with the job type, the full\n * metadata shape, and the target class constructor.\n *\n * Duplicate-type behaviour (OQ-3, resolved 2026-04-18):\n * - `NODE_ENV === 'production'` → throw; silent overwrite in prod is a\n * correctness bug.\n * - `NODE_ENV === 'test'` → silent overwrite (tests intentionally\n * re-register handlers).\n * - otherwise (dev) → `console.warn` + overwrite. `console`\n * is used intentionally instead of the Nest `Logger` — decorators run\n * at module-load time before any Nest container exists.\n */\nexport function JobHandler<TInput>(\n type: string,\n meta: JobHandlerMeta<TInput>,\n): ClassDecorator {\n return (target) => {\n if (JOB_HANDLER_REGISTRY.has(type)) {\n const env = process.env.NODE_ENV;\n if (env === 'production') {\n throw new Error(\n `[JobHandler] Duplicate registration for job type '${type}'. ` +\n `Each @JobHandler must declare a unique type.`,\n );\n }\n if (env !== 'test') {\n // eslint-disable-next-line no-console\n console.warn(\n `[JobHandler] Duplicate registration for job type '${type}'. ` +\n `Overwriting previous handler — this is almost certainly a bug.`,\n );\n }\n }\n\n Reflect.defineMetadata(JOB_HANDLER_METADATA_KEY, { type, meta }, target);\n JOB_HANDLER_REGISTRY.set(type, {\n type,\n meta: meta as JobHandlerMeta<unknown>,\n handlerClass: target as unknown as new (\n ...args: unknown[]\n ) => JobHandlerBase<unknown>,\n });\n };\n}\n\n// ─── HandlerRegistry — read helpers consumed by JobWorkerModule (JOB-5) ─────\n\n/**\n * Single entry shape returned by `HandlerRegistry.getAll()` / `.get()` and\n * exposed to `JobWorkerModule.onModuleInit` for boot-time upserts.\n *\n * Structurally compatible with `IJobOrchestrator.upsertJobRows`'s\n * `JobUpsertEntry` so the worker module can pass entries through verbatim\n * without re-mapping.\n */\nexport interface HandlerRegistryEntry {\n type: string;\n meta: JobHandlerMeta<unknown>;\n handlerClass: new (...args: unknown[]) => JobHandlerBase<unknown>;\n}\n\n/**\n * Read facade over `JOB_HANDLER_REGISTRY`. The decorator's write path is\n * unchanged; this namespace exists so consumers (the worker module, tests)\n * don't import the raw `Map` and accidentally mutate it.\n */\nexport namespace HandlerRegistry {\n /** All registered entries in insertion order. */\n export function getAll(): HandlerRegistryEntry[] {\n return Array.from(JOB_HANDLER_REGISTRY.values());\n }\n\n /** Lookup by job type, or `undefined` if no `@JobHandler` is registered. */\n export function get(type: string): HandlerRegistryEntry | undefined {\n return JOB_HANDLER_REGISTRY.get(type);\n }\n}\n","/**\n * MemoryJobRunService — scope-oriented queries and bulk ops over the\n * in-memory run store (ADR-022, JOB-4).\n *\n * Mirrors `DrizzleJobRunService` but scans `MemoryJobStore.runs.values()`.\n * Cancel delegates back to the orchestrator so cascade semantics stay in\n * one place.\n */\nimport { Inject, Injectable } from '@nestjs/common';\nimport type { JobRunRow } from './job-orchestration.schema';\nimport type { JobRun } from './job-orchestrator.protocol';\nimport type {\n IJobRunService,\n ListForScopeOptions,\n CancelForScopeOptions,\n RescheduleForScopeOptions,\n PoolStatusCount,\n JobRunFailure,\n ListJobRunsQuery,\n JobRunPage,\n} from './job-run-service.protocol';\nimport {\n clampLimit,\n decodeKeysetCursor,\n encodeKeysetCursor,\n toJobRunSummary,\n} from './job-run-keyset-cursor';\nimport type { IJobOrchestrator } from './job-orchestrator.protocol';\nimport { JOB_ORCHESTRATOR, JOBS_MULTI_TENANT } from './jobs-domain.tokens';\nimport { MissingTenantIdError } from './jobs-errors';\nimport { MemoryJobStore } from './memory-job-store';\n\nconst NON_TERMINAL_STATUSES: JobRunRow['status'][] = [\n 'pending',\n 'running',\n 'waiting',\n];\n\n@Injectable()\nexport class MemoryJobRunService implements IJobRunService {\n constructor(\n private readonly store: MemoryJobStore,\n @Inject(JOB_ORCHESTRATOR) private readonly orchestrator: IJobOrchestrator,\n @Inject(JOBS_MULTI_TENANT) private readonly multiTenant: boolean,\n ) {}\n\n /**\n * JOB-8 — produce a per-row predicate for the tenant gate.\n * Returns `null` when multi-tenancy is off (caller doesn't check).\n * Throws when on + `undefined`; matches `tenant_id IS NULL` on explicit\n * `null` to support cross-tenant background work.\n */\n private tenantPredicate(\n method: string,\n tenantId: string | null | undefined,\n ): ((r: JobRunRow) => boolean) | null {\n if (!this.multiTenant) return null;\n if (tenantId === undefined) throw new MissingTenantIdError(method);\n return (r) => r.tenantId === tenantId;\n }\n\n async listForScope(\n entityType: string,\n entityId: string,\n opts: ListForScopeOptions = {},\n ): Promise<JobRun[]> {\n const statusFilter = opts.status\n ? Array.isArray(opts.status)\n ? new Set(opts.status)\n : new Set([opts.status])\n : null;\n const tenantCheck = this.tenantPredicate('listForScope', opts.tenantId);\n\n const rows: JobRunRow[] = [];\n for (const r of this.store.runs.values()) {\n if (r.scopeEntityType !== entityType) continue;\n if (r.scopeEntityId !== entityId) continue;\n if (statusFilter && !statusFilter.has(r.status)) continue;\n if (opts.jobType && r.jobType !== opts.jobType) continue;\n if (tenantCheck && !tenantCheck(r)) continue;\n rows.push(r);\n }\n\n const orderBy = opts.orderBy ?? 'created_at desc';\n rows.sort((a, b) => compareBy(a, b, orderBy));\n\n const offset = opts.offset ?? 0;\n const limit = opts.limit;\n const sliced =\n typeof limit === 'number' ? rows.slice(offset, offset + limit) : rows.slice(offset);\n return sliced as JobRun[];\n }\n\n async cancelForScope(\n entityType: string,\n entityId: string,\n opts: CancelForScopeOptions = {},\n ): Promise<void> {\n const tenantCheck = this.tenantPredicate('cancelForScope', opts.tenantId);\n\n const ids: string[] = [];\n for (const r of this.store.runs.values()) {\n if (r.scopeEntityType !== entityType) continue;\n if (r.scopeEntityId !== entityId) continue;\n if (!NON_TERMINAL_STATUSES.includes(r.status)) continue;\n if (tenantCheck && !tenantCheck(r)) continue;\n ids.push(r.id);\n }\n for (const id of ids) {\n // Propagate the tenant gate through the orchestrator's cancel so the\n // internal per-row guard passes (no surprise MissingTenantIdError\n // once the scope query has already narrowed to this tenant).\n await this.orchestrator.cancel(id, {\n cascade: true,\n tenantId: opts.tenantId,\n });\n }\n }\n\n async rescheduleForScope(\n entityType: string,\n entityId: string,\n newRunAt: Date,\n opts: RescheduleForScopeOptions = {},\n ): Promise<void> {\n const tenantCheck = this.tenantPredicate('rescheduleForScope', opts.tenantId);\n for (const r of this.store.runs.values()) {\n if (r.scopeEntityType !== entityType) continue;\n if (r.scopeEntityId !== entityId) continue;\n if (r.status !== 'pending') continue;\n if (tenantCheck && !tenantCheck(r)) continue;\n this.store.runs.set(r.id, {\n ...r,\n runAt: newRunAt,\n updatedAt: new Date(),\n });\n }\n }\n\n async countByPoolAndStatus(\n tenantId?: string | null,\n ): Promise<PoolStatusCount[]> {\n const tenantCheck = this.tenantPredicate('countByPoolAndStatus', tenantId);\n const map = new Map<string, PoolStatusCount>();\n for (const r of this.store.runs.values()) {\n if (tenantCheck && !tenantCheck(r)) continue;\n const key = `${r.pool}\\0${r.status}`;\n const cur = map.get(key);\n if (cur) {\n cur.count += 1;\n } else {\n map.set(key, { pool: r.pool, status: r.status, count: 1 });\n }\n }\n return Array.from(map.values());\n }\n\n async listRecentFailed(\n limit: number,\n tenantId?: string | null,\n ): Promise<JobRunFailure[]> {\n const tenantCheck = this.tenantPredicate('listRecentFailed', tenantId);\n const failed: JobRunRow[] = [];\n for (const r of this.store.runs.values()) {\n if (r.status !== 'failed') continue;\n if (tenantCheck && !tenantCheck(r)) continue;\n failed.push(r);\n }\n failed.sort((a, b) => {\n const at = (a.finishedAt ?? a.updatedAt).getTime();\n const bt = (b.finishedAt ?? b.updatedAt).getTime();\n return bt - at;\n });\n return failed.slice(0, limit).map((r) => ({\n runId: r.id,\n jobType: r.jobType,\n pool: r.pool,\n scopeEntityType: r.scopeEntityType,\n scopeEntityId: r.scopeEntityId,\n tenantId: r.tenantId,\n attempts: r.attempts,\n errorMessage: r.error?.message ?? null,\n failedAt: r.finishedAt ?? r.updatedAt,\n createdAt: r.createdAt,\n }));\n }\n\n async listJobRuns(query: ListJobRunsQuery = {}): Promise<JobRunPage> {\n const limit = clampLimit(query.limit);\n const tenantCheck = this.tenantPredicate('listJobRuns', query.tenantId);\n const keyset = query.cursor ? decodeKeysetCursor(query.cursor) : null;\n\n const matched: JobRunRow[] = [];\n for (const r of this.store.runs.values()) {\n if (tenantCheck && !tenantCheck(r)) continue;\n if (query.poolId && r.pool !== query.poolId) continue;\n if (query.rootRunId && r.rootRunId !== query.rootRunId) continue;\n if (query.status && r.status !== query.status) continue;\n if (query.since && r.createdAt.getTime() < query.since.getTime()) continue;\n matched.push(r);\n }\n\n // Order created_at DESC, id DESC to match the Drizzle backend's keyset.\n matched.sort((a, b) => {\n const dt = b.createdAt.getTime() - a.createdAt.getTime();\n if (dt !== 0) return dt;\n return a.id < b.id ? 1 : a.id > b.id ? -1 : 0;\n });\n\n // Keyset seek: drop everything at/after the cursor's (created_at, id).\n const seeked = keyset\n ? matched.filter((r) => {\n const ct = r.createdAt.getTime();\n const kt = keyset.createdAt.getTime();\n if (ct < kt) return true;\n if (ct > kt) return false;\n return r.id < keyset.id;\n })\n : matched;\n\n const hasMore = seeked.length > limit;\n const page = hasMore ? seeked.slice(0, limit) : seeked;\n const items = page.map(toJobRunSummary);\n const last = page[page.length - 1];\n const nextCursor =\n hasMore && last\n ? encodeKeysetCursor({ createdAt: last.createdAt, id: last.id })\n : null;\n\n return { items, nextCursor };\n }\n\n /**\n * Direct lookup. Not on the protocol — concrete-class convenience for\n * tests. Matches `DrizzleJobRunService.findByRootRunId` in spirit; both\n * are debug / test helpers that sidestep the orchestrator.\n */\n findById(runId: string): JobRun | null {\n return (this.store.runs.get(runId) ?? null) as JobRun | null;\n }\n\n /** Public counterpart to the Drizzle backend's `findByRootRunId` helper. */\n findByRootRunId(rootRunId: string): JobRun[] {\n const out: JobRunRow[] = [];\n for (const r of this.store.runs.values()) {\n if (r.rootRunId === rootRunId) out.push(r);\n }\n return out as JobRun[];\n }\n}\n\nfunction compareBy(\n a: JobRunRow,\n b: JobRunRow,\n order: Exclude<ListForScopeOptions['orderBy'], undefined>,\n): number {\n switch (order) {\n case 'created_at asc':\n return a.createdAt.getTime() - b.createdAt.getTime();\n case 'run_at desc':\n return b.runAt.getTime() - a.runAt.getTime();\n case 'run_at asc':\n return a.runAt.getTime() - b.runAt.getTime();\n case 'created_at desc':\n default:\n return b.createdAt.getTime() - a.createdAt.getTime();\n }\n}\n","/**\n * MemoryJobStepService — in-memory implementation of `IJobStepService`\n * (ADR-022, JOB-4).\n *\n * Mirrors `DrizzleJobStepService` but against plain Maps. `findStep` only\n * returns `completed` rows (memoization cache hit); anything non-completed\n * is invisible so the ctx.step fn re-runs on replay / retry exactly like\n * the Drizzle backend (which deletes non-completed rows on replay).\n */\nimport { randomUUID } from 'node:crypto';\nimport { Injectable } from '@nestjs/common';\nimport type { JobStepRow } from './job-orchestration.schema';\nimport type {\n IJobStepService,\n JobStep,\n RecordStepInput,\n} from './job-step-service.protocol';\nimport { MemoryJobStore } from './memory-job-store';\n\n@Injectable()\nexport class MemoryJobStepService implements IJobStepService {\n constructor(private readonly store: MemoryJobStore) {}\n\n async findStep(runId: string, stepId: string): Promise<JobStep | null> {\n const rows = this.store.steps.get(runId);\n if (!rows) return null;\n const match = rows.find(\n (r) => r.stepId === stepId && r.status === 'completed',\n );\n return (match ?? null) as JobStep | null;\n }\n\n async recordStep(input: RecordStepInput): Promise<JobStep> {\n const rows = this.getOrCreateRows(input.jobRunId);\n const existingIdx = rows.findIndex((r) => r.stepId === input.stepId);\n\n const normalisedInput =\n (input.input ?? null) as Record<string, unknown> | null;\n const normalisedOutput =\n (input.output ?? null) as Record<string, unknown> | null;\n\n if (existingIdx >= 0) {\n const prev = rows[existingIdx]!;\n const next: JobStepRow = {\n ...prev,\n status: input.status,\n input: normalisedInput ?? prev.input,\n output: normalisedOutput ?? prev.output,\n error: input.error ?? prev.error,\n attempts: input.attempts ?? prev.attempts,\n startedAt: input.startedAt ?? prev.startedAt,\n finishedAt: input.finishedAt ?? prev.finishedAt,\n };\n rows[existingIdx] = next;\n return next as JobStep;\n }\n\n const seq = input.seq ?? this.nextSeq(rows);\n const row: JobStepRow = {\n id: randomUUID(),\n jobRunId: input.jobRunId,\n stepId: input.stepId,\n kind: input.kind,\n seq,\n status: input.status,\n input: normalisedInput,\n output: normalisedOutput,\n error: input.error ?? null,\n attempts: input.attempts ?? 0,\n startedAt: input.startedAt ?? null,\n finishedAt: input.finishedAt ?? null,\n };\n rows.push(row);\n return row as JobStep;\n }\n\n /**\n * Replay helper — wipe every step row for a run. Mirrors the `scratch`\n * replay mode of the Drizzle backend (`DELETE FROM job_step WHERE job_run_id = …`).\n */\n clearStepsForRun(runId: string): void {\n this.store.steps.delete(runId);\n }\n\n /**\n * Remove every non-`completed` row for the run. Memoized (`completed`)\n * rows are preserved — this is the `last_checkpoint` / `last_step`\n * semantics the Drizzle backend implements via\n * `DELETE … WHERE status != 'completed'`. Both replay modes route here\n * (Phase 1 collapses `last_step` onto this behaviour; see JOB-3 notes).\n */\n clearIncompleteSteps(runId: string): void {\n const rows = this.store.steps.get(runId);\n if (!rows) return;\n const kept = rows.filter((r) => r.status === 'completed');\n if (kept.length === 0) {\n this.store.steps.delete(runId);\n } else {\n this.store.steps.set(runId, kept);\n }\n }\n\n private getOrCreateRows(runId: string): JobStepRow[] {\n let rows = this.store.steps.get(runId);\n if (!rows) {\n rows = [];\n this.store.steps.set(runId, rows);\n }\n return rows;\n }\n\n private nextSeq(rows: JobStepRow[]): number {\n let max = 0;\n for (const r of rows) {\n if (r.seq > max) max = r.seq;\n }\n return max + 1;\n }\n}\n","/**\n * MemoryJobStore — the shared in-memory backing for the three memory-backend\n * services (ADR-022, JOB-4).\n *\n * Plain class, not `@Injectable()`. Wired as a `useValue` provider in\n * JOB-5's `JobsDomainModule.forRoot({ backend: 'memory' })` so unit tests\n * can keep a direct reference for `beforeEach` resets.\n *\n * All three memory services receive the same `MemoryJobStore` instance via\n * constructor injection; the store owns mutable state, the services are\n * stateless mutators.\n */\nimport type {\n JobDefinitionRow,\n JobRunRow,\n JobStepRow,\n} from './job-orchestration.schema';\n\nexport class MemoryJobStore {\n /** Runs keyed by `id` (single source of truth for status/scope/lineage). */\n readonly runs: Map<string, JobRunRow> = new Map();\n\n /** Steps keyed by `job_run_id`; array order matches insertion order. */\n readonly steps: Map<string, JobStepRow[]> = new Map();\n\n /** Job definitions keyed by `type` — memory mirror of the `job` table. */\n readonly jobs: Map<string, JobDefinitionRow> = new Map();\n\n /** Reset everything. Tests call this in `beforeEach`. */\n clear(): void {\n this.runs.clear();\n this.steps.clear();\n this.jobs.clear();\n }\n}\n","/**\n * Pool config loader for the job orchestration domain (ADR-022, JOB-5).\n *\n * Reads `codegen.config.yaml: jobs.pools` from `process.cwd()` (or an\n * explicit `configPath` for tests), merges user-defined pools onto the five\n * framework defaults, and returns the resolved `Map<string, PoolDefinition>`\n * consumed by `JobWorkerModule.onModuleInit` and `JobsDomainModule`'s\n * config-validator surface.\n *\n * Invariants:\n * - User cannot flip `reserved: true` on a framework pool — silently\n * preserved. The three `events_*` pools are reserved infrastructure\n * for the events outbox drain.\n * - User-defined pools cannot set `reserved: true` — `reserved` is\n * framework-only metadata.\n * - Missing `codegen.config.yaml` is not an error; loader returns the\n * framework defaults verbatim.\n *\n * Result is cached at module scope after first call so repeated reads (e.g.\n * a worker module + a one-off scaffold validator in the same process) hit\n * the same parse. Tests that pass `configPath` skip the cache and isolate.\n */\nimport { existsSync, readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { parse as parseYaml } from 'yaml';\n\nexport interface PoolDefinition {\n /** Routing identifier — reused as the per-pool worker queue name. */\n queue: string;\n /** Max parallel in-flight `processRun` calls for this pool's worker. */\n concurrency: number;\n /** `true` ⇒ user `@JobHandler` may not target it. Framework-only. */\n reserved: boolean;\n /** Free-text annotation surfaced in admin UIs / logs. */\n description?: string;\n}\n\nexport type PoolConfig = Map<string, PoolDefinition>;\n\n/**\n * Five framework defaults. Three reserved `events_*` pools drain the\n * `IEventBus` outbox (one per `DomainEvent.direction`); `interactive` and\n * `batch` are user-default pools (`batch` is the `@JobHandler` default\n * when no `pool` is specified).\n */\nexport const FRAMEWORK_POOLS: Readonly<Record<string, PoolDefinition>> = Object.freeze({\n events_inbound: Object.freeze({\n queue: 'jobs-events-inbound',\n concurrency: 20,\n reserved: true,\n description: 'Inbound events drain (events subsystem outbox).',\n }),\n events_change: Object.freeze({\n queue: 'jobs-events-change',\n concurrency: 30,\n reserved: true,\n description: 'Change events drain (events subsystem outbox).',\n }),\n events_outbound: Object.freeze({\n queue: 'jobs-events-outbound',\n concurrency: 10,\n reserved: true,\n description: 'Outbound events drain (events subsystem outbox).',\n }),\n interactive: Object.freeze({\n queue: 'jobs-interactive',\n concurrency: 20,\n reserved: false,\n description: 'User-facing latency-sensitive jobs.',\n }),\n batch: Object.freeze({\n queue: 'jobs-batch',\n concurrency: 5,\n reserved: false,\n description: 'Default pool for background jobs.',\n }),\n});\n\n/** Names of the framework reserved pools. Cheap inline lookup for the worker. */\nexport const RESERVED_POOL_NAMES: ReadonlySet<string> = new Set(\n Object.entries(FRAMEWORK_POOLS)\n .filter(([, def]) => def.reserved)\n .map(([name]) => name),\n);\n\n/**\n * Cache by absolute config path. The `cwd` default is normalised before\n * lookup so two callers passing the same path share the cache; explicit\n * test-only paths cache separately.\n */\nconst cache = new Map<string, PoolConfig>();\n\n/**\n * Reset the loader cache. Test-only — not exported from the package\n * `index.ts`. Useful for tests that mutate `process.cwd()` between cases.\n */\nexport function _resetPoolConfigCacheForTests(): void {\n cache.clear();\n}\n\n/**\n * Resolve the merged pool config.\n *\n * @param configPath optional absolute or cwd-relative path; defaults to\n * `${process.cwd()}/codegen.config.yaml`.\n */\nexport function loadPoolConfig(configPath?: string): PoolConfig {\n const resolved = resolve(configPath ?? `${process.cwd()}/codegen.config.yaml`);\n const cached = cache.get(resolved);\n if (cached) return cached;\n\n const merged = new Map<string, PoolDefinition>();\n // Seed with framework defaults first — they always take precedence on\n // `reserved` and provide defaults for `queue` / `concurrency` if user\n // overrides only some fields.\n for (const [name, def] of Object.entries(FRAMEWORK_POOLS)) {\n merged.set(name, { ...def });\n }\n\n if (!existsSync(resolved)) {\n cache.set(resolved, merged);\n return merged;\n }\n\n let raw: unknown;\n try {\n raw = parseYaml(readFileSync(resolved, 'utf8'));\n } catch (err) {\n throw new Error(\n `pool-config.loader: failed to parse YAML at ${resolved}: ${(err as Error).message}`,\n );\n }\n\n const userPools = extractUserPools(raw);\n for (const [name, userDef] of Object.entries(userPools)) {\n const existing = merged.get(name);\n if (existing) {\n // Framework pool — user may tweak concurrency + description but\n // cannot flip `reserved`. `queue` is frozen too (reserved framework\n // pools' queue identifiers are part of the cross-subsystem contract\n // with the events outbox drain).\n const next: PoolDefinition = {\n queue: existing.queue,\n concurrency:\n typeof userDef.concurrency === 'number'\n ? userDef.concurrency\n : existing.concurrency,\n reserved: existing.reserved,\n description: userDef.description ?? existing.description,\n };\n merged.set(name, next);\n continue;\n }\n // User-defined pool. Validate required fields; reject reserved.\n if (typeof userDef.queue !== 'string' || userDef.queue.length === 0) {\n throw new Error(\n `pool-config.loader: pool '${name}' must declare a non-empty 'queue'.`,\n );\n }\n if (typeof userDef.concurrency !== 'number' || userDef.concurrency <= 0) {\n throw new Error(\n `pool-config.loader: pool '${name}' must declare a positive 'concurrency'.`,\n );\n }\n if (userDef.reserved === true) {\n throw new Error(\n `pool-config.loader: user-defined pool '${name}' cannot set ` +\n `'reserved: true' — reserved is framework-only.`,\n );\n }\n merged.set(name, {\n queue: userDef.queue,\n concurrency: userDef.concurrency,\n reserved: false,\n description: userDef.description,\n });\n }\n\n cache.set(resolved, merged);\n return merged;\n}\n\n/**\n * Names of every non-reserved pool in the resolved config. The default\n * worker activation set when `JobWorkerModuleOptions.pools` is omitted —\n * the worker process never claims the reserved `events_*` pools by\n * default; those are bound by the events subsystem's outbox bridge.\n */\nexport function allNonReservedPoolNames(config: PoolConfig): string[] {\n const out: string[] = [];\n for (const [name, def] of config) {\n if (!def.reserved) out.push(name);\n }\n return out;\n}\n\n/**\n * Names of **every** pool in the resolved config, reserved `events_*` lanes\n * included. The activation set for a standalone worker booted with\n * `JobWorkerModule.forRoot({ allPools: true })` (BULLMQ-1 Phase 1) — the\n * single worker process drains both user pools and the bridge's reserved\n * pools so wrapper `job_run` rows are never stranded.\n */\nexport function allPoolNames(config: PoolConfig): string[] {\n return [...config.keys()];\n}\n\n// ─── internals ──────────────────────────────────────────────────────────────\n\ninterface UserPoolShape {\n queue?: string;\n concurrency?: number;\n reserved?: boolean;\n description?: string;\n}\n\nfunction extractUserPools(raw: unknown): Record<string, UserPoolShape> {\n if (!raw || typeof raw !== 'object') return {};\n const jobs = (raw as { jobs?: unknown }).jobs;\n if (!jobs || typeof jobs !== 'object') return {};\n const pools = (jobs as { pools?: unknown }).pools;\n if (!pools || typeof pools !== 'object') return {};\n const out: Record<string, UserPoolShape> = {};\n for (const [name, def] of Object.entries(pools as Record<string, unknown>)) {\n if (!def || typeof def !== 'object') continue;\n out[name] = def as UserPoolShape;\n }\n return out;\n}\n","/**\n * BullMQ backend configuration surface (BULLMQ-1, ADR-022 extension slot).\n *\n * The core `IJobOrchestrator` contract is backend-agnostic; everything in\n * this file is BullMQ-specific and lives behind the\n * `jobs.extensions.bullmq.*` config namespace (CLAUDE.md core/extension\n * protocol). The Drizzle backend never reads any of it.\n */\nimport { tokenKey } from '../token-key';\nimport { loadPoolConfig, type PoolConfig } from './pool-config.loader';\n\n/**\n * #6 — Structural mirror of BullMQ's `ConnectionOptions`. Declared locally\n * so this config file (which ships into EVERY jobs install, drizzle or\n * bullmq) does NOT need the `bullmq` peer dep resolved by the consumer's\n * tsc. The bullmq backend internally casts to the real `ConnectionOptions`\n * — that file is only vendored when `--backend bullmq` is selected\n * (see `backendFileFilter`).\n *\n * Accepts the `{ url }` shape this resolver emits, plus the host/port/\n * password/db form BullMQ also accepts, with an open index for any extra\n * ioredis options consumers may flow through.\n */\nexport type BullMqConnectionOptions = {\n url?: string;\n host?: string;\n port?: number;\n password?: string;\n db?: number;\n [key: string]: unknown;\n};\n\n/**\n * Typed shape of `codegen.config.yaml: jobs.extensions.bullmq`. Snake_case\n * because it mirrors the YAML the consumer authors.\n *\n * ```yaml\n * jobs:\n * backend: bullmq\n * extensions:\n * bullmq:\n * redis_url: redis://localhost:6379 # or env REDIS_URL\n * queue_prefix: myapp # optional namespace (ADR-022 OQ)\n * bull_board:\n * enabled: true\n * mount_path: /api/admin/queues\n * ```\n */\nexport interface BullMqExtensionsConfig {\n /**\n * Redis/Valkey connection URL. When omitted, the runtime resolves\n * `process.env.REDIS_URL`, then falls back to `redis://localhost:6379`.\n */\n redis_url?: string;\n /**\n * Optional queue-name prefix to avoid collisions when several codegen apps\n * share one Redis (ADR-022 §\"BullMQ queue naming collisions\"). Applied to\n * every pool queue alias.\n */\n queue_prefix?: string;\n /**\n * Bull Board dashboard — opt-in extension (not core). Mounting is the\n * consumer's responsibility (it needs the consumer's Express/Nest adapter +\n * admin auth); we only carry the config. See README + spec §Extensions.\n */\n bull_board?: {\n enabled: boolean;\n mount_path?: string;\n };\n}\n\n/**\n * The runtime form after `redis_url`/env resolution. This is what the\n * orchestrator + worker actually consume.\n */\nexport interface BullMqResolvedConfig {\n connection: BullMqConnectionOptions;\n queuePrefix?: string;\n bullBoard?: { enabled: boolean; mountPath: string };\n}\n\n// ADR-037: namespaced `Symbol.for(...)` (via `tokenKey()`) — matches by value\n// across runtime copies.\n/** DI token for the resolved BullMQ `ConnectionOptions` (ioredis-compatible). */\nexport const BULLMQ_CONNECTION = Symbol.for(tokenKey('jobs', 'bullmq-connection'));\n\n/** DI token for the full resolved BullMQ config (prefix + bull board). */\nexport const BULLMQ_RESOLVED_CONFIG = Symbol.for(tokenKey('jobs', 'bullmq-resolved-config'));\n\nconst DEFAULT_REDIS_URL = 'redis://localhost:6379';\nconst DEFAULT_BULL_BOARD_MOUNT = '/admin/queues';\n\n/**\n * Resolve the BullMQ runtime config from the extension block.\n *\n * Precedence for the connection URL:\n * 1. explicit `extensions.bullmq.redis_url`\n * 2. `process.env.REDIS_URL`\n * 3. `redis://localhost:6379`\n *\n * Returns a `{ url }` connection shape — BullMQ/ioredis accept a URL string\n * via the `{ url }` ConnectionOptions form.\n */\nexport function resolveBullMqConfig(\n ext: BullMqExtensionsConfig | undefined,\n): BullMqResolvedConfig {\n const url =\n ext?.redis_url ?? process.env.REDIS_URL ?? DEFAULT_REDIS_URL;\n\n const resolved: BullMqResolvedConfig = {\n connection: { url },\n queuePrefix: ext?.queue_prefix,\n };\n if (ext?.bull_board?.enabled) {\n resolved.bullBoard = {\n enabled: true,\n mountPath: ext.bull_board.mount_path ?? DEFAULT_BULL_BOARD_MOUNT,\n };\n }\n return resolved;\n}\n\n/**\n * Resolve the BullMQ queue name for a *logical pool name*. The orchestrator\n * and worker MUST agree on this mapping or jobs are enqueued onto a queue\n * nobody consumes. Both derive it identically:\n *\n * 1. Look up the pool's `queue` alias (e.g. `jobs-batch`) in the resolved\n * pool config — the same alias `JobWorkerModule.onModuleInit` logs and\n * that the BullMQ `Worker` binds to.\n * 2. Fall back to the logical pool name when the pool is unknown (defensive;\n * still a stable, colon-free identifier).\n * 3. Apply the optional `queue_prefix` namespace for multi-app Redis\n * sharing — `:` is fine in the *queue name* (it is only forbidden in the\n * `jobId`, hence the sha1 there).\n *\n * `poolConfig` defaults to the cached `loadPoolConfig()` so callers that only\n * hold the logical pool name (the orchestrator) don't need to thread the map.\n */\nexport function resolvePoolQueueName(\n pool: string,\n config: BullMqResolvedConfig | null | undefined,\n poolConfig: PoolConfig = loadPoolConfig(),\n): string {\n const alias = poolConfig.get(pool)?.queue ?? pool;\n const prefix = config?.queuePrefix;\n return prefix ? `${prefix}:${alias}` : alias;\n}\n"],"mappings":"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","names":["sql","dedupeKey","sql","Inject","Injectable","and","desc","eq","inArray","sql","eq","inArray","desc","and","sql","Injectable","Inject","Inject","Injectable","and","eq","and","eq","Injectable","Inject","randomUUID","Inject","Injectable","Logger","HandlerRegistry","TERMINAL_STATUSES","DEDUPE_EXCLUDED_STATUSES","IN_FLIGHT_STATUSES","evaluateKeyTemplate","Logger","dedupeKey","randomUUID","Injectable","Inject","Inject","Injectable","NON_TERMINAL_STATUSES","Injectable","Inject","randomUUID","Injectable","randomUUID","Injectable"]}