@pattern-stack/codegen 0.8.1 → 0.9.1

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 (120) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/{job-orchestrator.protocol-BwsBd37o.d.ts → job-orchestrator.protocol-CHOEqBDk.d.ts} +36 -1
  3. package/dist/runtime/subsystems/bridge/bridge-delivery-handler.d.ts +2 -2
  4. package/dist/runtime/subsystems/bridge/bridge-delivery-handler.js.map +1 -1
  5. package/dist/runtime/subsystems/bridge/bridge-outbox-drain-hook.js.map +1 -1
  6. package/dist/runtime/subsystems/bridge/bridge.module.d.ts +5 -1
  7. package/dist/runtime/subsystems/bridge/bridge.module.js +930 -275
  8. package/dist/runtime/subsystems/bridge/bridge.module.js.map +1 -1
  9. package/dist/runtime/subsystems/bridge/event-flow.service.d.ts +1 -1
  10. package/dist/runtime/subsystems/bridge/event-flow.service.js.map +1 -1
  11. package/dist/runtime/subsystems/bridge/index.d.ts +4 -1
  12. package/dist/runtime/subsystems/bridge/index.js +837 -182
  13. package/dist/runtime/subsystems/bridge/index.js.map +1 -1
  14. package/dist/runtime/subsystems/events/event-bus.drizzle-backend.d.ts +3 -1
  15. package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js +92 -1
  16. package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js.map +1 -1
  17. package/dist/runtime/subsystems/events/event-bus.memory-backend.d.ts +3 -1
  18. package/dist/runtime/subsystems/events/event-bus.memory-backend.js +99 -0
  19. package/dist/runtime/subsystems/events/event-bus.memory-backend.js.map +1 -1
  20. package/dist/runtime/subsystems/events/event-bus.redis-backend.js.map +1 -1
  21. package/dist/runtime/subsystems/events/event-keyset-cursor.d.ts +32 -0
  22. package/dist/runtime/subsystems/events/event-keyset-cursor.js +38 -0
  23. package/dist/runtime/subsystems/events/event-keyset-cursor.js.map +1 -0
  24. package/dist/runtime/subsystems/events/event-read.protocol.d.ts +94 -0
  25. package/dist/runtime/subsystems/events/event-read.protocol.js +9 -0
  26. package/dist/runtime/subsystems/events/event-read.protocol.js.map +1 -0
  27. package/dist/runtime/subsystems/events/events.module.js +177 -3
  28. package/dist/runtime/subsystems/events/events.module.js.map +1 -1
  29. package/dist/runtime/subsystems/events/events.tokens.d.ts +16 -1
  30. package/dist/runtime/subsystems/events/events.tokens.js +2 -0
  31. package/dist/runtime/subsystems/events/events.tokens.js.map +1 -1
  32. package/dist/runtime/subsystems/events/generated/bus.js.map +1 -1
  33. package/dist/runtime/subsystems/events/generated/index.js.map +1 -1
  34. package/dist/runtime/subsystems/events/index.d.ts +2 -1
  35. package/dist/runtime/subsystems/events/index.js +178 -3
  36. package/dist/runtime/subsystems/events/index.js.map +1 -1
  37. package/dist/runtime/subsystems/index.d.ts +3 -2
  38. package/dist/runtime/subsystems/index.js +1194 -264
  39. package/dist/runtime/subsystems/index.js.map +1 -1
  40. package/dist/runtime/subsystems/jobs/bullmq.config.d.ts +98 -0
  41. package/dist/runtime/subsystems/jobs/bullmq.config.js +143 -0
  42. package/dist/runtime/subsystems/jobs/bullmq.config.js.map +1 -0
  43. package/dist/runtime/subsystems/jobs/index.d.ts +8 -3
  44. package/dist/runtime/subsystems/jobs/index.js +861 -201
  45. package/dist/runtime/subsystems/jobs/index.js.map +1 -1
  46. package/dist/runtime/subsystems/jobs/job-handler.base.d.ts +2 -1
  47. package/dist/runtime/subsystems/jobs/job-handler.base.js.map +1 -1
  48. package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.d.ts +108 -0
  49. package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.js +922 -0
  50. package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.js.map +1 -0
  51. package/dist/runtime/subsystems/jobs/job-orchestrator.drizzle-backend.d.ts +2 -1
  52. package/dist/runtime/subsystems/jobs/job-orchestrator.memory-backend.d.ts +2 -1
  53. package/dist/runtime/subsystems/jobs/job-orchestrator.memory-backend.js.map +1 -1
  54. package/dist/runtime/subsystems/jobs/job-orchestrator.protocol.d.ts +2 -1
  55. package/dist/runtime/subsystems/jobs/job-run-keyset-cursor.d.ts +53 -0
  56. package/dist/runtime/subsystems/jobs/job-run-keyset-cursor.js +57 -0
  57. package/dist/runtime/subsystems/jobs/job-run-keyset-cursor.js.map +1 -0
  58. package/dist/runtime/subsystems/jobs/job-run-service.drizzle-backend.d.ts +4 -2
  59. package/dist/runtime/subsystems/jobs/job-run-service.drizzle-backend.js +81 -1
  60. package/dist/runtime/subsystems/jobs/job-run-service.drizzle-backend.js.map +1 -1
  61. package/dist/runtime/subsystems/jobs/job-run-service.memory-backend.d.ts +4 -2
  62. package/dist/runtime/subsystems/jobs/job-run-service.memory-backend.js +81 -0
  63. package/dist/runtime/subsystems/jobs/job-run-service.memory-backend.js.map +1 -1
  64. package/dist/runtime/subsystems/jobs/job-run-service.protocol.d.ts +76 -2
  65. package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.d.ts +49 -0
  66. package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.js +374 -0
  67. package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.js.map +1 -0
  68. package/dist/runtime/subsystems/jobs/job-worker.d.ts +2 -1
  69. package/dist/runtime/subsystems/jobs/job-worker.js.map +1 -1
  70. package/dist/runtime/subsystems/jobs/job-worker.module.d.ts +44 -5
  71. package/dist/runtime/subsystems/jobs/job-worker.module.js +832 -178
  72. package/dist/runtime/subsystems/jobs/job-worker.module.js.map +1 -1
  73. package/dist/runtime/subsystems/jobs/jobs-domain.module.d.ts +10 -1
  74. package/dist/runtime/subsystems/jobs/jobs-domain.module.js +519 -20
  75. package/dist/runtime/subsystems/jobs/jobs-domain.module.js.map +1 -1
  76. package/dist/runtime/subsystems/jobs/jobs-errors.d.ts +2 -1
  77. package/dist/runtime/subsystems/jobs/pool-config.loader.d.ts +9 -1
  78. package/dist/runtime/subsystems/jobs/pool-config.loader.js +4 -0
  79. package/dist/runtime/subsystems/jobs/pool-config.loader.js.map +1 -1
  80. package/dist/runtime/subsystems/observability/index.d.ts +4 -3
  81. package/dist/runtime/subsystems/observability/index.js +109 -2
  82. package/dist/runtime/subsystems/observability/index.js.map +1 -1
  83. package/dist/runtime/subsystems/observability/observability.module.js +109 -2
  84. package/dist/runtime/subsystems/observability/observability.module.js.map +1 -1
  85. package/dist/runtime/subsystems/observability/observability.protocol.d.ts +64 -3
  86. package/dist/runtime/subsystems/observability/observability.service.d.ts +22 -4
  87. package/dist/runtime/subsystems/observability/observability.service.js +109 -2
  88. package/dist/runtime/subsystems/observability/observability.service.js.map +1 -1
  89. package/dist/runtime/subsystems/observability/reporters/bridge-metrics.reporter.d.ts +3 -2
  90. package/dist/runtime/subsystems/observability/reporters/index.d.ts +3 -2
  91. package/dist/src/cli/index.js +30 -6
  92. package/dist/src/cli/index.js.map +1 -1
  93. package/package.json +5 -1
  94. package/runtime/subsystems/bridge/bridge.module.ts +5 -0
  95. package/runtime/subsystems/events/event-bus.drizzle-backend.ts +109 -3
  96. package/runtime/subsystems/events/event-bus.memory-backend.ts +103 -1
  97. package/runtime/subsystems/events/event-keyset-cursor.ts +59 -0
  98. package/runtime/subsystems/events/event-read.protocol.ts +97 -0
  99. package/runtime/subsystems/events/events.module.ts +18 -2
  100. package/runtime/subsystems/events/events.tokens.ts +16 -0
  101. package/runtime/subsystems/events/index.ts +7 -0
  102. package/runtime/subsystems/jobs/bullmq.config.ts +125 -0
  103. package/runtime/subsystems/jobs/index.ts +22 -0
  104. package/runtime/subsystems/jobs/job-handler.base.ts +36 -0
  105. package/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.ts +381 -0
  106. package/runtime/subsystems/jobs/job-run-keyset-cursor.ts +88 -0
  107. package/runtime/subsystems/jobs/job-run-service.drizzle-backend.ts +59 -1
  108. package/runtime/subsystems/jobs/job-run-service.memory-backend.ts +53 -0
  109. package/runtime/subsystems/jobs/job-run-service.protocol.ts +77 -0
  110. package/runtime/subsystems/jobs/job-worker.bullmq-backend.ts +311 -0
  111. package/runtime/subsystems/jobs/job-worker.module.ts +124 -10
  112. package/runtime/subsystems/jobs/jobs-domain.module.ts +40 -21
  113. package/runtime/subsystems/jobs/pool-config.loader.ts +11 -0
  114. package/runtime/subsystems/observability/index.ts +8 -0
  115. package/runtime/subsystems/observability/observability.protocol.ts +76 -0
  116. package/runtime/subsystems/observability/observability.service.ts +148 -1
  117. package/templates/entity/new/clean-lite-ps/prompt-extension.js +14 -12
  118. package/templates/relationship/new/prompt.js +8 -5
  119. package/templates/subsystem/jobs/worker.ejs.t +30 -7
  120. package/templates/subsystem/sync/sync-audit.schema.ejs.t +12 -16
package/CHANGELOG.md CHANGED
@@ -4,6 +4,35 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.9.0] — 2026-05-25
8
+
9
+ Bundles four merged PRs (none carried a version bump): the BullMQ backend and
10
+ observability list-reads (features), plus two consumer-facing type-check fixes.
11
+
12
+ ### Added
13
+
14
+ - **`feat(jobs)` — BullMQ `IJobOrchestrator` backend (BULLMQ-1, #385).** A second
15
+ orchestrator backend behind the existing core contract, plus the Phase-1 prep:
16
+ the bridge reserved-pool guard is revived (`JOB_WORKER_MODULE_OPTIONS` export),
17
+ a `JobWorkerModule.forRoot({ allPools })` option, and a bridge/events-aware
18
+ standalone `worker.ts` template.
19
+ - **`feat(observability)` — row-level list reads in the combiner (OBS-LIST-1,
20
+ #384).** `listJobRuns` / `listEvents` / `getCorrelationTimeline` on the
21
+ observability combiner + composing ports (drizzle + memory).
22
+
23
+ ### Fixed
24
+
25
+ - **`fix(subsystems)` — type-check under a `multi_tenant:false`, no-events
26
+ consumer (#383).** The sync schema template now always emits `tenant_id`
27
+ (the runtime sync code references it unconditionally; `SYNC_MULTI_TENANT`
28
+ gates enforcement, not the column), and the event-codegen generator falls back
29
+ to `EventOfType<T> = DomainEvent` (not `never`) when no events are declared, so
30
+ the bridge `EventFlowService` type-checks. Surfaced by a downstream consumer's
31
+ CI; codegen-patterns' own suite never hit it.
32
+ - **`fix(clean-lite-ps)` — create-DTO nullable fields are also optional (#382).**
33
+ `zodChainForCreate` applied `.nullable()` / `.optional()` mutually
34
+ exclusively, so a nullable, non-required field stayed a required key.
35
+
7
36
  ## [0.8.1] — 2026-05-25
8
37
 
9
38
  Closes the loop on ambient tenant scoping (0.8.0): adds the **RequesterContext
@@ -1,6 +1,7 @@
1
1
  import { DrizzleTransaction } from './runtime/subsystems/events/event-bus.protocol.js';
2
2
  import { JobRunRow } from './runtime/subsystems/jobs/job-orchestration.schema.js';
3
3
  import { Logger } from '@nestjs/common';
4
+ import { EventTypeName, EventOfType } from './runtime/subsystems/events/generated/types.js';
4
5
 
5
6
  /**
6
7
  * Handler base class, JobContext, @JobHandler decorator, and policy types
@@ -52,6 +53,34 @@ interface ScopeRef<TInput, TScope extends string = string> {
52
53
  entity: TScope;
53
54
  from: (input: TInput) => string;
54
55
  }
56
+ /**
57
+ * Bridge trigger authoring shape (BRIDGE-6 follow-up — BRIDGE-6 shipped the
58
+ * generator + runtime for `@JobHandler({ triggers })` but never added the
59
+ * authoring field to this type; the generator's tests scan source as strings,
60
+ * so a real decorator was never compiled and the gap went uncaught).
61
+ *
62
+ * Declared on `@JobHandler({ triggers })`; the codegen bridge-registry
63
+ * generator (`src/cli/shared/bridge-registry-generator.ts`) scans these from
64
+ * source and emits `bridge/generated/registry.ts`, validating each `event`
65
+ * against the generated `eventRegistry` at `gen-all`. The distributed union
66
+ * narrows `map`/`when` per `event`, so callbacks are typed against the event
67
+ * payload (ADR-023, "typed against PayloadOfType<T>").
68
+ *
69
+ * Typed against events' generated types — the same `import type` coupling the
70
+ * bridge already has (erased at runtime). `jobs` must NOT import `bridge`, so
71
+ * the post-gen `BridgeTriggerEntry` is deliberately not referenced here;
72
+ * `triggerId`/`jobType` are computed by the generator, not authored.
73
+ */
74
+ type JobTrigger<TInput> = {
75
+ [T in EventTypeName]: {
76
+ /** Event type that fires this trigger. Validated against `eventRegistry`. */
77
+ event: T;
78
+ /** Maps the event to the job input. Inlined verbatim into the registry. */
79
+ map: (event: EventOfType<T>) => TInput;
80
+ /** Optional guard; `false` → wrapper records `status='skipped'`. */
81
+ when?: (event: EventOfType<T>) => boolean;
82
+ };
83
+ }[EventTypeName];
55
84
  interface JobHandlerMeta<TInput> {
56
85
  pool?: string;
57
86
  scope?: ScopeRef<TInput>;
@@ -60,6 +89,12 @@ interface JobHandlerMeta<TInput> {
60
89
  dedupe?: DedupePolicy<TInput>;
61
90
  timeoutMs?: number;
62
91
  replayFrom?: 'scratch' | 'last_step' | 'last_checkpoint';
92
+ /**
93
+ * Bridge triggers (ADR-023 Tier 3). Codegen scans these into `bridgeRegistry`;
94
+ * the framework `BridgeDeliveryHandler` starts this job per matched event.
95
+ * Absent for jobs started directly or via `IEventFlow.publishAndStart`.
96
+ */
97
+ triggers?: readonly JobTrigger<TInput>[];
63
98
  }
64
99
  interface StepOptions {
65
100
  retry?: RetryPolicy;
@@ -275,4 +310,4 @@ interface IJobOrchestrator {
275
310
  }>;
276
311
  }
277
312
 
278
- export { type CancelOptions as C, type DedupePolicy as D, HandlerRegistry as H, type IJobOrchestrator as I, JOB_HANDLER_METADATA_KEY as J, ParentClosePolicy as P, type RetryPolicy as R, type ScopeRef as S, type ConcurrencyPolicy as a, type HandlerRegistryEntry as b, JOB_HANDLER_REGISTRY as c, type JobContext as d, JobHandler as e, JobHandlerBase as f, type JobHandlerMeta as g, type JobPoolDef as h, type JobRun as i, type JobUpsertEntry as j, type SpawnChildOptions as k, type StartOptions as l, type StepOptions as m };
313
+ export { type CancelOptions as C, type DedupePolicy as D, HandlerRegistry as H, type IJobOrchestrator as I, JOB_HANDLER_METADATA_KEY as J, ParentClosePolicy as P, type RetryPolicy as R, type ScopeRef as S, type ConcurrencyPolicy as a, type HandlerRegistryEntry as b, JOB_HANDLER_REGISTRY as c, type JobContext as d, JobHandler as e, JobHandlerBase as f, type JobHandlerMeta as g, type JobPoolDef as h, type JobRun as i, type JobTrigger as j, type JobUpsertEntry as k, type SpawnChildOptions as l, type StartOptions as m, type StepOptions as n };
@@ -1,13 +1,13 @@
1
- import { f as JobHandlerBase, I as IJobOrchestrator, d as JobContext } from '../../../job-orchestrator.protocol-BwsBd37o.js';
1
+ import { f as JobHandlerBase, I as IJobOrchestrator, d as JobContext } from '../../../job-orchestrator.protocol-CHOEqBDk.js';
2
2
  import { IEventBus } from '../events/event-bus.protocol.js';
3
3
  import { IJobBridge, BridgeRegistry } from './bridge.protocol.js';
4
4
  import '../jobs/job-orchestration.schema.js';
5
5
  import 'drizzle-orm/pg-core';
6
6
  import 'drizzle-orm';
7
7
  import '@nestjs/common';
8
+ import '../events/generated/types.js';
8
9
  import '../../types/drizzle.js';
9
10
  import 'drizzle-orm/node-postgres';
10
- import '../events/generated/types.js';
11
11
  import './bridge-delivery.schema.js';
12
12
 
13
13
  /** Stable canonical job type — referenced by BRIDGE-4 wrapper inserts. */
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../runtime/subsystems/bridge/bridge-delivery-handler.ts","../../../../runtime/subsystems/jobs/jobs-domain.tokens.ts","../../../../runtime/subsystems/jobs/job-handler.base.ts","../../../../runtime/subsystems/events/events.tokens.ts","../../../../runtime/subsystems/bridge/bridge.tokens.ts","../../../../runtime/subsystems/bridge/bridge-errors.ts","../../../../runtime/subsystems/bridge/assert-tenant-id.ts"],"sourcesContent":["/**\n * BridgeDeliveryHandler — the framework `@JobHandler` that runs every\n * bridge-fanout wrapper on the reserved `events_*` pools (BRIDGE-5,\n * ADR-023 §Decision 2 flow diagram).\n *\n * Role: when the outbox drain (BRIDGE-4) inserts a `bridge_delivery + wrapper\n * job_run` pair, the worker that polls the wrapper's pool claims that\n * wrapper and dispatches it to this handler. The handler:\n *\n * 1. Loads the `bridge_delivery` row by `ctx.input.deliveryId`.\n * 2. Looks up the trigger entry in the codegen-emitted `bridgeRegistry`\n * (`runtime/subsystems/bridge/generated/registry.ts`, BRIDGE-6).\n * A missing entry means the trigger was renamed or removed since the\n * delivery row was written; mark `skipped` with\n * `skip_reason='trigger_unregistered'` per ADR-023 §Trigger rename\n * or removal.\n * 3. Re-fetches the authoritative `domain_events` row (`IEventBus.findById`)\n * so `when:` / `map:` callbacks see the committed payload — never a\n * copy that drifted between drain and claim time.\n * 4. Evaluates `entry.when?.(event)`. False ⇒ mark `skipped` with\n * `skip_reason='predicate_false'`.\n * 5. Calls `IJobOrchestrator.start(entry.jobType, entry.map(event), …)`\n * INSIDE `ctx.step('spawn_user_run', …)`. The step memoization is\n * what makes wrapper retries (BRIDGE-1 ledger says no auto-retry past\n * the wrapper's own retry policy, but Phase 1 wrappers DO retry per\n * JOB-3) idempotent — a successful spawn followed by a transient\n * ledger-update failure would otherwise re-spawn on the next attempt.\n * 6. Marks `delivered` with the spawned `runId`.\n *\n * Pool registration: BRIDGE-5 ships ONE `@JobHandler` registration with\n * `pool: 'events_change'` (the default). The wrapper rows the drain\n * inserts carry `pool: events_<direction>` per row, so workers polling\n * `events_inbound` / `events_outbound` claim and dispatch to this same\n * handler class regardless of the metadata pool — the worker filter is on\n * `job_run.pool`, not on `@JobHandler.meta.pool`. BRIDGE-8 confirms the\n * three pools are active and registered at module init. The\n * `@framework/*` job-type prefix exempts this registration from the\n * reserved-pool validator (BRIDGE-5 added that exemption to\n * `job-worker.module.ts`).\n *\n * Tenant threading: when `BRIDGE_MULTI_TENANT=true`, the handler asserts\n * `delivery.tenantId !== undefined` before the spawn (the column is\n * nullable, so explicit `null` is allowed for cross-tenant work — same\n * contract as JOB-8). BRIDGE-8 wires the assertion via the\n * `BRIDGE_MULTI_TENANT` token.\n *\n * Failure path: any throw inside the handler propagates up; the worker's\n * normal retry policy (declared on the `@JobHandler` here as `attempts:\n * 3, backoff: exponential, baseMs: 250`) absorbs transient infra blips.\n * After exhaustion, the wrapper transitions to `failed`; the outer error\n * handler catches and calls `repo.markFailed(...)` so the delivery row\n * reflects the final state. Operators see `bridge_delivery.status='failed'`\n * surface via the `idx_bridge_delivery_status` partial index (BRIDGE-1).\n */\nimport { Inject, Injectable, Logger, Optional } from '@nestjs/common';\n\nimport { JOB_ORCHESTRATOR } from '../jobs/jobs-domain.tokens';\nimport type { IJobOrchestrator } from '../jobs/job-orchestrator.protocol';\nimport {\n JobHandler,\n JobHandlerBase,\n type JobContext,\n} from '../jobs/job-handler.base';\n\nimport { EVENT_BUS } from '../events/events.tokens';\nimport type { IEventBus, DomainEvent } from '../events/event-bus.protocol';\nimport type { EventTypeName } from '../events/generated/types';\n\nimport {\n BRIDGE_DELIVERY_REPO,\n BRIDGE_MULTI_TENANT,\n BRIDGE_REGISTRY,\n} from './bridge.tokens';\nimport type {\n BridgeRegistry,\n BridgeTriggerEntry,\n IJobBridge,\n} from './bridge.protocol';\nimport { assertTenantId } from './assert-tenant-id';\n\n/** Stable canonical job type — referenced by BRIDGE-4 wrapper inserts. */\nexport const BRIDGE_DELIVERY_JOB_TYPE = '@framework/bridge_delivery' as const;\n\n/** Stable canonical step id — referenced for memoization across attempts. */\nconst SPAWN_USER_RUN_STEP = 'spawn_user_run' as const;\n\nexport interface BridgeDeliveryInput {\n /** PK of the `bridge_delivery` row this wrapper services. */\n deliveryId: string;\n}\n\n@Injectable()\n@JobHandler<BridgeDeliveryInput>(BRIDGE_DELIVERY_JOB_TYPE, {\n pool: 'events_change',\n retry: { attempts: 3, backoff: 'exponential', baseMs: 250 },\n replayFrom: 'last_step',\n})\nexport class BridgeDeliveryHandler extends JobHandlerBase<\n BridgeDeliveryInput,\n { runId: string } | { skipped: true; reason: string }\n> {\n private readonly classLogger = new Logger(BridgeDeliveryHandler.name);\n\n constructor(\n @Inject(BRIDGE_DELIVERY_REPO) private readonly repo: IJobBridge,\n @Inject(JOB_ORCHESTRATOR) private readonly orchestrator: IJobOrchestrator,\n @Inject(EVENT_BUS) private readonly events: IEventBus,\n @Inject(BRIDGE_REGISTRY) private readonly registry: BridgeRegistry,\n @Optional()\n @Inject(BRIDGE_MULTI_TENANT)\n private readonly multiTenant: boolean = false,\n ) {\n super();\n }\n\n async run(\n ctx: JobContext<BridgeDeliveryInput>,\n ): Promise<{ runId: string } | { skipped: true; reason: string }> {\n const { deliveryId } = ctx.input;\n\n // Step 1 — locate the delivery row by primary key.\n const delivery = await this.repo.findDeliveryById(deliveryId);\n if (!delivery) {\n // The drain wrote a wrapper job_run but the delivery row is gone\n // (manual ops cleanup, or delete-cascade from the parent event).\n // No row → no work; return without throwing so the wrapper marks\n // completed cleanly.\n this.classLogger.warn(\n `bridge_delivery row '${deliveryId}' not found; wrapper completes ` +\n `without spawning a user job.`,\n );\n return { skipped: true, reason: 'delivery_row_missing' };\n }\n\n // Step 2 — multi-tenancy gate. Site (b) of the three ADR-023\n // §Multi-tenancy enforcement sites; shared helper from BRIDGE-8.\n // The DB always returns string|null, never undefined; this branch\n // exists for the in-memory backend's older test fixtures and to\n // pin the contract in shape-typed tests.\n assertTenantId(\n 'BridgeDeliveryHandler.run',\n this.multiTenant,\n delivery.tenantId,\n );\n\n // Step 3 — load the typed event row.\n const event = await this.events.findById(delivery.eventId);\n if (!event) {\n // FK from bridge_delivery.event_id → domain_events.id should make\n // this impossible at the DB layer, but defensive: if the row is\n // missing we mark skipped, not failed (no work the bridge can do).\n this.classLogger.warn(\n `domain_events row '${delivery.eventId}' missing for delivery ` +\n `'${deliveryId}'; marking skipped.`,\n );\n await this.repo.markSkipped(delivery.id, 'event_row_missing');\n return { skipped: true, reason: 'event_row_missing' };\n }\n\n // Step 4 — registry lookup. Handles trigger rename/removal cleanly.\n const entry = this.findRegistryEntry(event.type, delivery.triggerId);\n if (!entry) {\n await this.repo.markSkipped(delivery.id, 'trigger_unregistered');\n return { skipped: true, reason: 'trigger_unregistered' };\n }\n\n // Step 5 — `when:` predicate.\n if (entry.when && !entry.when(event as never)) {\n await this.repo.markSkipped(delivery.id, 'predicate_false');\n return { skipped: true, reason: 'predicate_false' };\n }\n\n // Step 6 — memoized spawn. `ctx.step` records the result in\n // `job_step` and on retry returns the cached `{ runId }` so a\n // transient failure between `orchestrator.start` and `markDelivered`\n // doesn't double-spawn the user job.\n const input = entry.map(event as never);\n const { runId } = await ctx.step<{ runId: string }>(\n SPAWN_USER_RUN_STEP,\n async () => {\n const run = await this.orchestrator.start(entry.jobType, input, {\n parentRunId: ctx.run.id,\n triggerSource: 'event',\n triggerRef: delivery.eventId,\n tenantId: delivery.tenantId,\n });\n return { runId: run.id };\n },\n );\n\n // Step 7 — ledger transition.\n await this.repo.markDelivered(delivery.id, runId);\n return { runId };\n }\n\n /**\n * Locate the registry entry for `(eventType, triggerId)`. Linear scan\n * over the per-event-type array — N is the number of triggers declared\n * for one event, typically 1–5; the table is not big enough to warrant\n * a secondary index.\n */\n private findRegistryEntry(\n eventType: string,\n triggerId: string,\n ): BridgeTriggerEntry | undefined {\n const candidates =\n this.registry[eventType as EventTypeName] ?? undefined;\n if (!candidates) return undefined;\n return candidates.find((c) => c.triggerId === triggerId) as\n | BridgeTriggerEntry\n | undefined;\n }\n}\n\n/**\n * Re-export for BRIDGE-7 facade Case B and BRIDGE-4 wrapper insert.\n * Single source of truth for the canonical type string keeps refactors\n * in one place.\n */\nexport { BRIDGE_DELIVERY_JOB_TYPE as BridgeDeliveryJobType };\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 * 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 { 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\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\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 * Injection token for the event bus.\n *\n * String constant (not Symbol) so it matches by value across import boundaries.\n * Matches the token in runtime/constants/tokens.ts — both are 'EVENT_BUS'.\n *\n * Usage in use cases:\n * ```typescript\n * constructor(@Inject(EVENT_BUS) private readonly eventBus: IEventBus) {}\n * ```\n */\nexport const EVENT_BUS = 'EVENT_BUS' as const;\n\n/**\n * Injection token for the generated `TypedEventBus` facade.\n *\n * `TypedEventBus` lives in `runtime/subsystems/events/generated/bus.ts` and\n * wraps `IEventBus` with project-specific `AppDomainEvent`-typed `publish<T>()`\n * and `subscribe<T>()`. Use cases inject this token in preference to\n * `EVENT_BUS` when they want compile-time type safety on event shapes.\n *\n * String constant (not Symbol) so it matches by value across import\n * boundaries — same convention as `EVENT_BUS`.\n *\n * Provider registration lands in EVT-6 (EventsModule wiring); the token is\n * declared here so generated code can import it without depending on the\n * still-being-formalised module.\n */\nexport const TYPED_EVENT_BUS = 'TYPED_EVENT_BUS' as const;\n\n/**\n * Injection token for the resolved multi-tenancy flag.\n *\n * Provided by `EventsModule.forRoot(...)` as `options.multiTenant ?? false`.\n * Consumed by `TypedEventBus` to enforce the tenantId-is-required rule at\n * publish time.\n *\n * String constant (not Symbol) so it matches by value across import\n * boundaries — same convention as the other events tokens. (The jobs\n * subsystem uses Symbols for the analogous token; events chose strings\n * from the start and we keep the file internally consistent.)\n */\nexport const EVENTS_MULTI_TENANT = 'EVENTS_MULTI_TENANT' as const;\n\n/**\n * Injection token for the Redis connection URL used by RedisEventBus.\n * Provided automatically by EventsModule.forRoot({ backend: 'redis' }).\n */\nexport const REDIS_URL = Symbol('REDIS_URL');\n\n/**\n * Injection token for the resolved `EventsModuleOptions` object.\n *\n * Provided automatically by `EventsModule.forRoot(...)` /\n * `EventsModule.forRootAsync(...)`. Backends that need to observe module\n * configuration (e.g. `DrizzleEventBus` reading `opts.pools` for\n * pool-filtered drain) inject via this token.\n *\n * String-valued (not `Symbol`) so it matches by value across import\n * boundaries — same convention as `EVENT_BUS`.\n */\nexport const EVENTS_MODULE_OPTIONS = 'EVENTS_MODULE_OPTIONS' as const;\n","/**\n * Injection tokens for the bridge subsystem (ADR-023 Phase 2, BRIDGE-2).\n *\n * String constants (not Symbols) so they match by value across import\n * boundaries — same convention as `EVENT_BUS` / `EVENTS_MULTI_TENANT` in the\n * events subsystem (per EVT-6 §Implementation Notes). The jobs subsystem\n * uses Symbols for its analogous tokens; we keep the bridge file internally\n * consistent with the events convention because the bridge is conceptually\n * downstream of (and imports from) the events module.\n */\n\n/**\n * Token for the `IJobBridge` repo backend (memory in BRIDGE-3, Drizzle in\n * BRIDGE-4). Consumed by `BridgeDeliveryHandler` (BRIDGE-5), the outbox\n * drain (BRIDGE-4 modification), and `EventFlowService` (BRIDGE-7).\n */\nexport const BRIDGE_DELIVERY_REPO = 'BRIDGE_DELIVERY_REPO' as const;\n\n/**\n * Token for the `IEventFlow` facade implementation (BRIDGE-7). Use cases\n * inject this in preference to `EVENT_BUS` / `TYPED_EVENT_BUS` — calling\n * `eventFlow.publish(...)` / `eventFlow.publishAndStart(...)` is the\n * sanctioned authoring surface (ADR-023 §Decision 7).\n */\nexport const EVENT_FLOW = 'EVENT_FLOW' as const;\n\n/**\n * Token for the resolved multi-tenancy flag, provided by\n * `BridgeModule.forRoot({ multiTenant })` in BRIDGE-8. Consumed by\n * `EventFlowService.publishAndStart` (entry), `BridgeDeliveryHandler.handle`\n * (entry), and `DrizzleBridgeDeliveryRepo.insertDelivery` (pre-write) — the\n * three enforcement sites called out in ADR-023 §Multi-tenancy.\n */\nexport const BRIDGE_MULTI_TENANT = 'BRIDGE_MULTI_TENANT' as const;\n\n/**\n * Token for the resolved `BridgeModuleOptions` object. Provided by\n * `BridgeModule.forRoot(...)` / `forRootAsync(...)` in BRIDGE-8.\n * Mirrors `EVENTS_MODULE_OPTIONS` and `JOBS_DOMAIN_OPTIONS` shape — backends\n * inject this when they need to observe additional module configuration\n * (e.g. pool overrides) without each adding a dedicated token.\n */\nexport const BRIDGE_MODULE_OPTIONS = 'BRIDGE_MODULE_OPTIONS' as const;\n\n/**\n * Token for the codegen-emitted `bridgeRegistry` — the\n * `Record<EventTypeName, BridgeTriggerEntry[]>` map that drives\n * outbox-drain trigger lookup (BRIDGE-4) and `EventFlowService` Case B\n * dedup (BRIDGE-7). Provider registration lands in BRIDGE-8; the token is\n * declared here so generated code (BRIDGE-6) can import it without\n * depending on the still-being-formalised module.\n */\nexport const BRIDGE_REGISTRY = 'BRIDGE_REGISTRY' as const;\n\n\n/**\n * Token for the `IBridgeOutboxDrainHook` implementation (BRIDGE-4).\n * Injected `@Optional()` into `DrizzleEventBus` — when the bridge\n * subsystem is not installed the token is undefined and the events\n * outbox drain skips the bridge block entirely (preserves EVT-4\n * baseline behaviour).\n *\n * Resolution order: `BridgeModule.forRoot()` provides this token in\n * BRIDGE-8 alongside the rest of the bridge subsystem. `EventsModule`\n * itself never provides it; the events subsystem stays unaware of the\n * bridge unless the consumer wires it.\n */\nexport const BRIDGE_OUTBOX_DRAIN_HOOK = 'BRIDGE_OUTBOX_DRAIN_HOOK' as const;\n","/**\n * Typed errors for the bridge subsystem (ADR-023 Phase 2, BRIDGE-2).\n *\n * All thrown by the three enforcement sites named in ADR-023 §Multi-tenancy:\n * - `EventFlowService.publishAndStart` entry (BRIDGE-7)\n * - `BridgeDeliveryHandler.handle` entry (BRIDGE-5)\n * - `DrizzleBridgeDeliveryRepo.insertDelivery` pre-write (BRIDGE-4)\n *\n * Same shape as `runtime/subsystems/jobs/jobs-errors.ts` and\n * `runtime/subsystems/events/events-errors.ts` so consumers can catch them\n * with the same exception-filter pattern across all three subsystems.\n */\n\n/**\n * Thrown when `BridgeModule` was configured with `multiTenant: true` but\n * the caller did not pass a `tenantId` at one of the three enforcement\n * sites listed above.\n *\n * **Strict enforcement rationale (mirrors JOB-8 / SYNC-6 stance, locked\n * 2026-04-18 for jobs; same rationale applies here).** Cross-tenant data\n * leakage is the worst class of bug a multi-tenant system can ship;\n * surfacing the misuse loudly at the call site (rather than silently\n * defaulting to `null` or to \"the last tenant seen\") prevents both\n * accidental global 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 * work (e.g. a system housekeeping event with no owning tenant). The\n * `bridge_delivery` row is persisted with `tenant_id = NULL`.\n *\n * The `callSite` constructor argument names which of the three enforcement\n * sites threw — review reports and ops dashboards rely on a stable site\n * name, so use the canonical strings: `'EventFlowService.publishAndStart'`,\n * `'BridgeDeliveryHandler.handle'`,\n * `'DrizzleBridgeDeliveryRepo.insertDelivery'`.\n */\nexport class MissingTenantIdError extends Error {\n override readonly name = 'MissingTenantIdError';\n constructor(public readonly callSite: string) {\n super(\n `MissingTenantIdError: BridgeModule was configured with ` +\n `multiTenant=true but ${callSite} was called without tenantId ` +\n `(undefined). Pass an explicit tenantId, or pass null for ` +\n `cross-tenant work.`,\n );\n }\n}\n\n/**\n * Synthetic error thrown by `MemoryBridgeDeliveryRepo.insertDelivery` when\n * a duplicate `(event_id, trigger_id)` insert hits the simulated UNIQUE\n * constraint (BRIDGE-3).\n *\n * Carries a `constraint` field equal to the Drizzle constraint name\n * declared in BRIDGE-1's schema (`uq_bridge_delivery_event_trigger`) so\n * call sites can branch on the same discriminator regardless of which\n * backend is wired up. This matters because ADR-023 explicitly leans on\n * the constraint as the dedup mechanism in two places — outbox replay\n * and `publishAndStart` Case B — and BRIDGE-4 / BRIDGE-7 will share a\n * type-check path with BRIDGE-3-driven tests.\n *\n * The Drizzle backend (BRIDGE-4) does NOT throw this error: it uses\n * `INSERT … ON CONFLICT (event_id, trigger_id) DO NOTHING RETURNING id`\n * per the BRIDGE-4 spec recommendation, so collisions surface as an empty\n * result set rather than an exception. The error exists so the memory\n * backend can faithfully model the \"duplicate raises\" behaviour for tests\n * that want to assert the constraint actually fires.\n */\n/**\n * Thrown by `BridgeModule.onModuleInit` when `JobWorkerModule` is wired\n * alongside the bridge but its active `pools` list does not include one\n * or more of the three reserved bridge pools (`events_inbound`,\n * `events_change`, `events_outbound`).\n *\n * Without a worker polling those pools, the wrapper `job_run` rows the\n * outbox drain inserts (BRIDGE-4) sit `pending` forever — a silent\n * footgun where `eventFlow.publish(...)` returns success but no user\n * job ever spawns. The boot-time check converts that into a fail-fast.\n *\n * Operators can either (a) add `...BRIDGE_RESERVED_POOLS` to their\n * `JobWorkerModule.forRoot({ pools })` configuration so the same\n * process polls the reserved pools, or (b) run a separate worker\n * process per reserved pool for lane isolation (ADR-022 §Pool\n * isolation).\n */\nexport class BridgeReservedPoolsNotPolledError extends Error {\n override readonly name = 'BridgeReservedPoolsNotPolledError';\n constructor(public readonly missingPools: readonly string[]) {\n super(\n `BridgeModule loaded but JobWorkerModule is not polling reserved ` +\n `pool '${missingPools[0]}'. Add ...BRIDGE_RESERVED_POOLS to your ` +\n `JobWorkerModule.forRoot({ pools }) configuration. Missing pools: ` +\n `${missingPools.join(', ')}. (Bridge-fanout wrappers will sit ` +\n `pending forever without these pollers.)`,\n );\n }\n}\n\nexport class UniqueConstraintError extends Error {\n override readonly name = 'UniqueConstraintError';\n constructor(\n public readonly constraint: string,\n public readonly eventId: string,\n public readonly triggerId: string,\n ) {\n super(\n `UniqueConstraintError: duplicate insert into bridge_delivery for ` +\n `(event_id='${eventId}', trigger_id='${triggerId}') — violates ` +\n `constraint '${constraint}'.`,\n );\n }\n}\n","/**\n * `assertTenantId` — shared multi-tenancy enforcement helper for the\n * bridge subsystem (BRIDGE-8, ADR-023 §Multi-tenancy null-tenantId).\n *\n * Single source of truth for the three enforcement sites named in\n * ADR-023 §Multi-tenancy and the BRIDGE-2 spec:\n *\n * (a) `EventFlowService.publishAndStart` — request-path entry\n * (b) `BridgeDeliveryHandler.run` — wrapper handler entry\n * (c) `DrizzleBridgeDeliveryRepo.insertDelivery` — last-line repo defense\n *\n * Contract (mirrors JOB-8 / SYNC-6 — locked 2026-04-18 for jobs and\n * carried into the bridge here):\n *\n * - `multiTenant === false` → no-op (always passes).\n * - `multiTenant === true`,\n * `tenantId === undefined` → throw `MissingTenantIdError(site)`.\n * - `multiTenant === true`,\n * `tenantId === null` → passes; opts the call into\n * cross-tenant work (system\n * housekeeping, framework events\n * with no owning tenant). Persists\n * to the DB as `tenant_id = NULL`.\n * - `multiTenant === true`,\n * `tenantId` is a string → passes.\n *\n * The strict `undefined`-vs-`null` discrimination is the entire point —\n * silent defaulting is exactly the failure mode that lets cross-tenant\n * leaks ship.\n */\nimport { MissingTenantIdError } from './bridge-errors';\n\n/**\n * Throws `MissingTenantIdError(site)` if `multiTenant === true` and\n * `tenantId === undefined`. Explicit `null` always passes.\n *\n * @param site Canonical site name — one of:\n * `'EventFlowService.publishAndStart'`,\n * `'BridgeDeliveryHandler.run'`,\n * `'DrizzleBridgeDeliveryRepo.insertDelivery'`.\n * Stable strings; ops dashboards / review reports key\n * on these. Use the same string the existing tests\n * and `MissingTenantIdError` JSDoc enumerate.\n * @param multiTenant Resolved `BRIDGE_MULTI_TENANT` flag (from\n * `BridgeModule.forRoot({ multiTenant })`).\n * @param tenantId The tenantId the caller supplied (or didn't).\n */\nexport function assertTenantId(\n site: string,\n multiTenant: boolean,\n tenantId: string | null | undefined,\n): void {\n if (multiTenant && tenantId === undefined) {\n throw new MissingTenantIdError(site);\n }\n // explicit null passes — opts into cross-tenant work\n}\n"],"mappings":"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","names":["HandlerRegistry"]}
1
+ {"version":3,"sources":["../../../../runtime/subsystems/bridge/bridge-delivery-handler.ts","../../../../runtime/subsystems/jobs/jobs-domain.tokens.ts","../../../../runtime/subsystems/jobs/job-handler.base.ts","../../../../runtime/subsystems/events/events.tokens.ts","../../../../runtime/subsystems/bridge/bridge.tokens.ts","../../../../runtime/subsystems/bridge/bridge-errors.ts","../../../../runtime/subsystems/bridge/assert-tenant-id.ts"],"sourcesContent":["/**\n * BridgeDeliveryHandler — the framework `@JobHandler` that runs every\n * bridge-fanout wrapper on the reserved `events_*` pools (BRIDGE-5,\n * ADR-023 §Decision 2 flow diagram).\n *\n * Role: when the outbox drain (BRIDGE-4) inserts a `bridge_delivery + wrapper\n * job_run` pair, the worker that polls the wrapper's pool claims that\n * wrapper and dispatches it to this handler. The handler:\n *\n * 1. Loads the `bridge_delivery` row by `ctx.input.deliveryId`.\n * 2. Looks up the trigger entry in the codegen-emitted `bridgeRegistry`\n * (`runtime/subsystems/bridge/generated/registry.ts`, BRIDGE-6).\n * A missing entry means the trigger was renamed or removed since the\n * delivery row was written; mark `skipped` with\n * `skip_reason='trigger_unregistered'` per ADR-023 §Trigger rename\n * or removal.\n * 3. Re-fetches the authoritative `domain_events` row (`IEventBus.findById`)\n * so `when:` / `map:` callbacks see the committed payload — never a\n * copy that drifted between drain and claim time.\n * 4. Evaluates `entry.when?.(event)`. False ⇒ mark `skipped` with\n * `skip_reason='predicate_false'`.\n * 5. Calls `IJobOrchestrator.start(entry.jobType, entry.map(event), …)`\n * INSIDE `ctx.step('spawn_user_run', …)`. The step memoization is\n * what makes wrapper retries (BRIDGE-1 ledger says no auto-retry past\n * the wrapper's own retry policy, but Phase 1 wrappers DO retry per\n * JOB-3) idempotent — a successful spawn followed by a transient\n * ledger-update failure would otherwise re-spawn on the next attempt.\n * 6. Marks `delivered` with the spawned `runId`.\n *\n * Pool registration: BRIDGE-5 ships ONE `@JobHandler` registration with\n * `pool: 'events_change'` (the default). The wrapper rows the drain\n * inserts carry `pool: events_<direction>` per row, so workers polling\n * `events_inbound` / `events_outbound` claim and dispatch to this same\n * handler class regardless of the metadata pool — the worker filter is on\n * `job_run.pool`, not on `@JobHandler.meta.pool`. BRIDGE-8 confirms the\n * three pools are active and registered at module init. The\n * `@framework/*` job-type prefix exempts this registration from the\n * reserved-pool validator (BRIDGE-5 added that exemption to\n * `job-worker.module.ts`).\n *\n * Tenant threading: when `BRIDGE_MULTI_TENANT=true`, the handler asserts\n * `delivery.tenantId !== undefined` before the spawn (the column is\n * nullable, so explicit `null` is allowed for cross-tenant work — same\n * contract as JOB-8). BRIDGE-8 wires the assertion via the\n * `BRIDGE_MULTI_TENANT` token.\n *\n * Failure path: any throw inside the handler propagates up; the worker's\n * normal retry policy (declared on the `@JobHandler` here as `attempts:\n * 3, backoff: exponential, baseMs: 250`) absorbs transient infra blips.\n * After exhaustion, the wrapper transitions to `failed`; the outer error\n * handler catches and calls `repo.markFailed(...)` so the delivery row\n * reflects the final state. Operators see `bridge_delivery.status='failed'`\n * surface via the `idx_bridge_delivery_status` partial index (BRIDGE-1).\n */\nimport { Inject, Injectable, Logger, Optional } from '@nestjs/common';\n\nimport { JOB_ORCHESTRATOR } from '../jobs/jobs-domain.tokens';\nimport type { IJobOrchestrator } from '../jobs/job-orchestrator.protocol';\nimport {\n JobHandler,\n JobHandlerBase,\n type JobContext,\n} from '../jobs/job-handler.base';\n\nimport { EVENT_BUS } from '../events/events.tokens';\nimport type { IEventBus, DomainEvent } from '../events/event-bus.protocol';\nimport type { EventTypeName } from '../events/generated/types';\n\nimport {\n BRIDGE_DELIVERY_REPO,\n BRIDGE_MULTI_TENANT,\n BRIDGE_REGISTRY,\n} from './bridge.tokens';\nimport type {\n BridgeRegistry,\n BridgeTriggerEntry,\n IJobBridge,\n} from './bridge.protocol';\nimport { assertTenantId } from './assert-tenant-id';\n\n/** Stable canonical job type — referenced by BRIDGE-4 wrapper inserts. */\nexport const BRIDGE_DELIVERY_JOB_TYPE = '@framework/bridge_delivery' as const;\n\n/** Stable canonical step id — referenced for memoization across attempts. */\nconst SPAWN_USER_RUN_STEP = 'spawn_user_run' as const;\n\nexport interface BridgeDeliveryInput {\n /** PK of the `bridge_delivery` row this wrapper services. */\n deliveryId: string;\n}\n\n@Injectable()\n@JobHandler<BridgeDeliveryInput>(BRIDGE_DELIVERY_JOB_TYPE, {\n pool: 'events_change',\n retry: { attempts: 3, backoff: 'exponential', baseMs: 250 },\n replayFrom: 'last_step',\n})\nexport class BridgeDeliveryHandler extends JobHandlerBase<\n BridgeDeliveryInput,\n { runId: string } | { skipped: true; reason: string }\n> {\n private readonly classLogger = new Logger(BridgeDeliveryHandler.name);\n\n constructor(\n @Inject(BRIDGE_DELIVERY_REPO) private readonly repo: IJobBridge,\n @Inject(JOB_ORCHESTRATOR) private readonly orchestrator: IJobOrchestrator,\n @Inject(EVENT_BUS) private readonly events: IEventBus,\n @Inject(BRIDGE_REGISTRY) private readonly registry: BridgeRegistry,\n @Optional()\n @Inject(BRIDGE_MULTI_TENANT)\n private readonly multiTenant: boolean = false,\n ) {\n super();\n }\n\n async run(\n ctx: JobContext<BridgeDeliveryInput>,\n ): Promise<{ runId: string } | { skipped: true; reason: string }> {\n const { deliveryId } = ctx.input;\n\n // Step 1 — locate the delivery row by primary key.\n const delivery = await this.repo.findDeliveryById(deliveryId);\n if (!delivery) {\n // The drain wrote a wrapper job_run but the delivery row is gone\n // (manual ops cleanup, or delete-cascade from the parent event).\n // No row → no work; return without throwing so the wrapper marks\n // completed cleanly.\n this.classLogger.warn(\n `bridge_delivery row '${deliveryId}' not found; wrapper completes ` +\n `without spawning a user job.`,\n );\n return { skipped: true, reason: 'delivery_row_missing' };\n }\n\n // Step 2 — multi-tenancy gate. Site (b) of the three ADR-023\n // §Multi-tenancy enforcement sites; shared helper from BRIDGE-8.\n // The DB always returns string|null, never undefined; this branch\n // exists for the in-memory backend's older test fixtures and to\n // pin the contract in shape-typed tests.\n assertTenantId(\n 'BridgeDeliveryHandler.run',\n this.multiTenant,\n delivery.tenantId,\n );\n\n // Step 3 — load the typed event row.\n const event = await this.events.findById(delivery.eventId);\n if (!event) {\n // FK from bridge_delivery.event_id → domain_events.id should make\n // this impossible at the DB layer, but defensive: if the row is\n // missing we mark skipped, not failed (no work the bridge can do).\n this.classLogger.warn(\n `domain_events row '${delivery.eventId}' missing for delivery ` +\n `'${deliveryId}'; marking skipped.`,\n );\n await this.repo.markSkipped(delivery.id, 'event_row_missing');\n return { skipped: true, reason: 'event_row_missing' };\n }\n\n // Step 4 — registry lookup. Handles trigger rename/removal cleanly.\n const entry = this.findRegistryEntry(event.type, delivery.triggerId);\n if (!entry) {\n await this.repo.markSkipped(delivery.id, 'trigger_unregistered');\n return { skipped: true, reason: 'trigger_unregistered' };\n }\n\n // Step 5 — `when:` predicate.\n if (entry.when && !entry.when(event as never)) {\n await this.repo.markSkipped(delivery.id, 'predicate_false');\n return { skipped: true, reason: 'predicate_false' };\n }\n\n // Step 6 — memoized spawn. `ctx.step` records the result in\n // `job_step` and on retry returns the cached `{ runId }` so a\n // transient failure between `orchestrator.start` and `markDelivered`\n // doesn't double-spawn the user job.\n const input = entry.map(event as never);\n const { runId } = await ctx.step<{ runId: string }>(\n SPAWN_USER_RUN_STEP,\n async () => {\n const run = await this.orchestrator.start(entry.jobType, input, {\n parentRunId: ctx.run.id,\n triggerSource: 'event',\n triggerRef: delivery.eventId,\n tenantId: delivery.tenantId,\n });\n return { runId: run.id };\n },\n );\n\n // Step 7 — ledger transition.\n await this.repo.markDelivered(delivery.id, runId);\n return { runId };\n }\n\n /**\n * Locate the registry entry for `(eventType, triggerId)`. Linear scan\n * over the per-event-type array — N is the number of triggers declared\n * for one event, typically 1–5; the table is not big enough to warrant\n * a secondary index.\n */\n private findRegistryEntry(\n eventType: string,\n triggerId: string,\n ): BridgeTriggerEntry | undefined {\n const candidates =\n this.registry[eventType as EventTypeName] ?? undefined;\n if (!candidates) return undefined;\n return candidates.find((c) => c.triggerId === triggerId) as\n | BridgeTriggerEntry\n | undefined;\n }\n}\n\n/**\n * Re-export for BRIDGE-7 facade Case B and BRIDGE-4 wrapper insert.\n * Single source of truth for the canonical type string keeps refactors\n * in one place.\n */\nexport { BRIDGE_DELIVERY_JOB_TYPE as BridgeDeliveryJobType };\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 * 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 * Injection token for the event bus.\n *\n * String constant (not Symbol) so it matches by value across import boundaries.\n * Matches the token in runtime/constants/tokens.ts — both are 'EVENT_BUS'.\n *\n * Usage in use cases:\n * ```typescript\n * constructor(@Inject(EVENT_BUS) private readonly eventBus: IEventBus) {}\n * ```\n */\nexport const EVENT_BUS = 'EVENT_BUS' as const;\n\n/**\n * Injection token for the read-side `IEventReadPort` over `domain_events`\n * (OBS-LIST-1).\n *\n * Bound by `EventsModule.forRoot` to the same backend instance as\n * `EVENT_BUS` for the `drizzle` and `memory` backends (both implement\n * `IEventReadPort`). The `redis` backend retains no history and therefore\n * does NOT provide this token — consumers composing it (e.g. the\n * observability combiner) inject it `@Optional()` and degrade to empty\n * results.\n *\n * String constant (not Symbol) so it matches by value across import\n * boundaries — same convention as `EVENT_BUS`.\n */\nexport const EVENT_READ_PORT = 'EVENT_READ_PORT' as const;\n\n/**\n * Injection token for the generated `TypedEventBus` facade.\n *\n * `TypedEventBus` lives in `runtime/subsystems/events/generated/bus.ts` and\n * wraps `IEventBus` with project-specific `AppDomainEvent`-typed `publish<T>()`\n * and `subscribe<T>()`. Use cases inject this token in preference to\n * `EVENT_BUS` when they want compile-time type safety on event shapes.\n *\n * String constant (not Symbol) so it matches by value across import\n * boundaries — same convention as `EVENT_BUS`.\n *\n * Provider registration lands in EVT-6 (EventsModule wiring); the token is\n * declared here so generated code can import it without depending on the\n * still-being-formalised module.\n */\nexport const TYPED_EVENT_BUS = 'TYPED_EVENT_BUS' as const;\n\n/**\n * Injection token for the resolved multi-tenancy flag.\n *\n * Provided by `EventsModule.forRoot(...)` as `options.multiTenant ?? false`.\n * Consumed by `TypedEventBus` to enforce the tenantId-is-required rule at\n * publish time.\n *\n * String constant (not Symbol) so it matches by value across import\n * boundaries — same convention as the other events tokens. (The jobs\n * subsystem uses Symbols for the analogous token; events chose strings\n * from the start and we keep the file internally consistent.)\n */\nexport const EVENTS_MULTI_TENANT = 'EVENTS_MULTI_TENANT' as const;\n\n/**\n * Injection token for the Redis connection URL used by RedisEventBus.\n * Provided automatically by EventsModule.forRoot({ backend: 'redis' }).\n */\nexport const REDIS_URL = Symbol('REDIS_URL');\n\n/**\n * Injection token for the resolved `EventsModuleOptions` object.\n *\n * Provided automatically by `EventsModule.forRoot(...)` /\n * `EventsModule.forRootAsync(...)`. Backends that need to observe module\n * configuration (e.g. `DrizzleEventBus` reading `opts.pools` for\n * pool-filtered drain) inject via this token.\n *\n * String-valued (not `Symbol`) so it matches by value across import\n * boundaries — same convention as `EVENT_BUS`.\n */\nexport const EVENTS_MODULE_OPTIONS = 'EVENTS_MODULE_OPTIONS' as const;\n","/**\n * Injection tokens for the bridge subsystem (ADR-023 Phase 2, BRIDGE-2).\n *\n * String constants (not Symbols) so they match by value across import\n * boundaries — same convention as `EVENT_BUS` / `EVENTS_MULTI_TENANT` in the\n * events subsystem (per EVT-6 §Implementation Notes). The jobs subsystem\n * uses Symbols for its analogous tokens; we keep the bridge file internally\n * consistent with the events convention because the bridge is conceptually\n * downstream of (and imports from) the events module.\n */\n\n/**\n * Token for the `IJobBridge` repo backend (memory in BRIDGE-3, Drizzle in\n * BRIDGE-4). Consumed by `BridgeDeliveryHandler` (BRIDGE-5), the outbox\n * drain (BRIDGE-4 modification), and `EventFlowService` (BRIDGE-7).\n */\nexport const BRIDGE_DELIVERY_REPO = 'BRIDGE_DELIVERY_REPO' as const;\n\n/**\n * Token for the `IEventFlow` facade implementation (BRIDGE-7). Use cases\n * inject this in preference to `EVENT_BUS` / `TYPED_EVENT_BUS` — calling\n * `eventFlow.publish(...)` / `eventFlow.publishAndStart(...)` is the\n * sanctioned authoring surface (ADR-023 §Decision 7).\n */\nexport const EVENT_FLOW = 'EVENT_FLOW' as const;\n\n/**\n * Token for the resolved multi-tenancy flag, provided by\n * `BridgeModule.forRoot({ multiTenant })` in BRIDGE-8. Consumed by\n * `EventFlowService.publishAndStart` (entry), `BridgeDeliveryHandler.handle`\n * (entry), and `DrizzleBridgeDeliveryRepo.insertDelivery` (pre-write) — the\n * three enforcement sites called out in ADR-023 §Multi-tenancy.\n */\nexport const BRIDGE_MULTI_TENANT = 'BRIDGE_MULTI_TENANT' as const;\n\n/**\n * Token for the resolved `BridgeModuleOptions` object. Provided by\n * `BridgeModule.forRoot(...)` / `forRootAsync(...)` in BRIDGE-8.\n * Mirrors `EVENTS_MODULE_OPTIONS` and `JOBS_DOMAIN_OPTIONS` shape — backends\n * inject this when they need to observe additional module configuration\n * (e.g. pool overrides) without each adding a dedicated token.\n */\nexport const BRIDGE_MODULE_OPTIONS = 'BRIDGE_MODULE_OPTIONS' as const;\n\n/**\n * Token for the codegen-emitted `bridgeRegistry` — the\n * `Record<EventTypeName, BridgeTriggerEntry[]>` map that drives\n * outbox-drain trigger lookup (BRIDGE-4) and `EventFlowService` Case B\n * dedup (BRIDGE-7). Provider registration lands in BRIDGE-8; the token is\n * declared here so generated code (BRIDGE-6) can import it without\n * depending on the still-being-formalised module.\n */\nexport const BRIDGE_REGISTRY = 'BRIDGE_REGISTRY' as const;\n\n\n/**\n * Token for the `IBridgeOutboxDrainHook` implementation (BRIDGE-4).\n * Injected `@Optional()` into `DrizzleEventBus` — when the bridge\n * subsystem is not installed the token is undefined and the events\n * outbox drain skips the bridge block entirely (preserves EVT-4\n * baseline behaviour).\n *\n * Resolution order: `BridgeModule.forRoot()` provides this token in\n * BRIDGE-8 alongside the rest of the bridge subsystem. `EventsModule`\n * itself never provides it; the events subsystem stays unaware of the\n * bridge unless the consumer wires it.\n */\nexport const BRIDGE_OUTBOX_DRAIN_HOOK = 'BRIDGE_OUTBOX_DRAIN_HOOK' as const;\n","/**\n * Typed errors for the bridge subsystem (ADR-023 Phase 2, BRIDGE-2).\n *\n * All thrown by the three enforcement sites named in ADR-023 §Multi-tenancy:\n * - `EventFlowService.publishAndStart` entry (BRIDGE-7)\n * - `BridgeDeliveryHandler.handle` entry (BRIDGE-5)\n * - `DrizzleBridgeDeliveryRepo.insertDelivery` pre-write (BRIDGE-4)\n *\n * Same shape as `runtime/subsystems/jobs/jobs-errors.ts` and\n * `runtime/subsystems/events/events-errors.ts` so consumers can catch them\n * with the same exception-filter pattern across all three subsystems.\n */\n\n/**\n * Thrown when `BridgeModule` was configured with `multiTenant: true` but\n * the caller did not pass a `tenantId` at one of the three enforcement\n * sites listed above.\n *\n * **Strict enforcement rationale (mirrors JOB-8 / SYNC-6 stance, locked\n * 2026-04-18 for jobs; same rationale applies here).** Cross-tenant data\n * leakage is the worst class of bug a multi-tenant system can ship;\n * surfacing the misuse loudly at the call site (rather than silently\n * defaulting to `null` or to \"the last tenant seen\") prevents both\n * accidental global 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 * work (e.g. a system housekeeping event with no owning tenant). The\n * `bridge_delivery` row is persisted with `tenant_id = NULL`.\n *\n * The `callSite` constructor argument names which of the three enforcement\n * sites threw — review reports and ops dashboards rely on a stable site\n * name, so use the canonical strings: `'EventFlowService.publishAndStart'`,\n * `'BridgeDeliveryHandler.handle'`,\n * `'DrizzleBridgeDeliveryRepo.insertDelivery'`.\n */\nexport class MissingTenantIdError extends Error {\n override readonly name = 'MissingTenantIdError';\n constructor(public readonly callSite: string) {\n super(\n `MissingTenantIdError: BridgeModule was configured with ` +\n `multiTenant=true but ${callSite} was called without tenantId ` +\n `(undefined). Pass an explicit tenantId, or pass null for ` +\n `cross-tenant work.`,\n );\n }\n}\n\n/**\n * Synthetic error thrown by `MemoryBridgeDeliveryRepo.insertDelivery` when\n * a duplicate `(event_id, trigger_id)` insert hits the simulated UNIQUE\n * constraint (BRIDGE-3).\n *\n * Carries a `constraint` field equal to the Drizzle constraint name\n * declared in BRIDGE-1's schema (`uq_bridge_delivery_event_trigger`) so\n * call sites can branch on the same discriminator regardless of which\n * backend is wired up. This matters because ADR-023 explicitly leans on\n * the constraint as the dedup mechanism in two places — outbox replay\n * and `publishAndStart` Case B — and BRIDGE-4 / BRIDGE-7 will share a\n * type-check path with BRIDGE-3-driven tests.\n *\n * The Drizzle backend (BRIDGE-4) does NOT throw this error: it uses\n * `INSERT … ON CONFLICT (event_id, trigger_id) DO NOTHING RETURNING id`\n * per the BRIDGE-4 spec recommendation, so collisions surface as an empty\n * result set rather than an exception. The error exists so the memory\n * backend can faithfully model the \"duplicate raises\" behaviour for tests\n * that want to assert the constraint actually fires.\n */\n/**\n * Thrown by `BridgeModule.onModuleInit` when `JobWorkerModule` is wired\n * alongside the bridge but its active `pools` list does not include one\n * or more of the three reserved bridge pools (`events_inbound`,\n * `events_change`, `events_outbound`).\n *\n * Without a worker polling those pools, the wrapper `job_run` rows the\n * outbox drain inserts (BRIDGE-4) sit `pending` forever — a silent\n * footgun where `eventFlow.publish(...)` returns success but no user\n * job ever spawns. The boot-time check converts that into a fail-fast.\n *\n * Operators can either (a) add `...BRIDGE_RESERVED_POOLS` to their\n * `JobWorkerModule.forRoot({ pools })` configuration so the same\n * process polls the reserved pools, or (b) run a separate worker\n * process per reserved pool for lane isolation (ADR-022 §Pool\n * isolation).\n */\nexport class BridgeReservedPoolsNotPolledError extends Error {\n override readonly name = 'BridgeReservedPoolsNotPolledError';\n constructor(public readonly missingPools: readonly string[]) {\n super(\n `BridgeModule loaded but JobWorkerModule is not polling reserved ` +\n `pool '${missingPools[0]}'. Add ...BRIDGE_RESERVED_POOLS to your ` +\n `JobWorkerModule.forRoot({ pools }) configuration. Missing pools: ` +\n `${missingPools.join(', ')}. (Bridge-fanout wrappers will sit ` +\n `pending forever without these pollers.)`,\n );\n }\n}\n\nexport class UniqueConstraintError extends Error {\n override readonly name = 'UniqueConstraintError';\n constructor(\n public readonly constraint: string,\n public readonly eventId: string,\n public readonly triggerId: string,\n ) {\n super(\n `UniqueConstraintError: duplicate insert into bridge_delivery for ` +\n `(event_id='${eventId}', trigger_id='${triggerId}') — violates ` +\n `constraint '${constraint}'.`,\n );\n }\n}\n","/**\n * `assertTenantId` — shared multi-tenancy enforcement helper for the\n * bridge subsystem (BRIDGE-8, ADR-023 §Multi-tenancy null-tenantId).\n *\n * Single source of truth for the three enforcement sites named in\n * ADR-023 §Multi-tenancy and the BRIDGE-2 spec:\n *\n * (a) `EventFlowService.publishAndStart` — request-path entry\n * (b) `BridgeDeliveryHandler.run` — wrapper handler entry\n * (c) `DrizzleBridgeDeliveryRepo.insertDelivery` — last-line repo defense\n *\n * Contract (mirrors JOB-8 / SYNC-6 — locked 2026-04-18 for jobs and\n * carried into the bridge here):\n *\n * - `multiTenant === false` → no-op (always passes).\n * - `multiTenant === true`,\n * `tenantId === undefined` → throw `MissingTenantIdError(site)`.\n * - `multiTenant === true`,\n * `tenantId === null` → passes; opts the call into\n * cross-tenant work (system\n * housekeeping, framework events\n * with no owning tenant). Persists\n * to the DB as `tenant_id = NULL`.\n * - `multiTenant === true`,\n * `tenantId` is a string → passes.\n *\n * The strict `undefined`-vs-`null` discrimination is the entire point —\n * silent defaulting is exactly the failure mode that lets cross-tenant\n * leaks ship.\n */\nimport { MissingTenantIdError } from './bridge-errors';\n\n/**\n * Throws `MissingTenantIdError(site)` if `multiTenant === true` and\n * `tenantId === undefined`. Explicit `null` always passes.\n *\n * @param site Canonical site name — one of:\n * `'EventFlowService.publishAndStart'`,\n * `'BridgeDeliveryHandler.run'`,\n * `'DrizzleBridgeDeliveryRepo.insertDelivery'`.\n * Stable strings; ops dashboards / review reports key\n * on these. Use the same string the existing tests\n * and `MissingTenantIdError` JSDoc enumerate.\n * @param multiTenant Resolved `BRIDGE_MULTI_TENANT` flag (from\n * `BridgeModule.forRoot({ multiTenant })`).\n * @param tenantId The tenantId the caller supplied (or didn't).\n */\nexport function assertTenantId(\n site: string,\n multiTenant: boolean,\n tenantId: string | null | undefined,\n): void {\n if (multiTenant && tenantId === undefined) {\n throw new MissingTenantIdError(site);\n }\n // explicit null passes — opts into cross-tenant work\n}\n"],"mappings":"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","names":["HandlerRegistry"]}