@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.
- package/dist/{job-orchestrator.protocol-CHOEqBDk.d.ts → job-orchestrator.protocol-CARhMLCO.d.ts} +1 -1
- package/dist/runtime/subsystems/analytics/analytics.module.js +6 -2
- package/dist/runtime/subsystems/analytics/analytics.module.js.map +1 -1
- package/dist/runtime/subsystems/analytics/analytics.tokens.d.ts +0 -11
- package/dist/runtime/subsystems/analytics/analytics.tokens.js +6 -2
- package/dist/runtime/subsystems/analytics/analytics.tokens.js.map +1 -1
- package/dist/runtime/subsystems/analytics/cube-backend.js +6 -2
- package/dist/runtime/subsystems/analytics/cube-backend.js.map +1 -1
- package/dist/runtime/subsystems/analytics/index.js +6 -2
- package/dist/runtime/subsystems/analytics/index.js.map +1 -1
- package/dist/runtime/subsystems/auth/auth.module.js +12 -6
- package/dist/runtime/subsystems/auth/auth.module.js.map +1 -1
- package/dist/runtime/subsystems/auth/auth.tokens.d.ts +0 -28
- package/dist/runtime/subsystems/auth/auth.tokens.js +12 -8
- package/dist/runtime/subsystems/auth/auth.tokens.js.map +1 -1
- package/dist/runtime/subsystems/auth/controllers/auth.controller.js +12 -5
- package/dist/runtime/subsystems/auth/controllers/auth.controller.js.map +1 -1
- package/dist/runtime/subsystems/auth/index.js +12 -8
- package/dist/runtime/subsystems/auth/index.js.map +1 -1
- package/dist/runtime/subsystems/auth/middleware/requester-context.js +12 -1
- package/dist/runtime/subsystems/auth/middleware/requester-context.js.map +1 -1
- package/dist/runtime/subsystems/bridge/bridge-delivery-handler.d.ts +1 -1
- package/dist/runtime/subsystems/bridge/bridge-delivery-handler.js +10 -2
- package/dist/runtime/subsystems/bridge/bridge-delivery-handler.js.map +1 -1
- package/dist/runtime/subsystems/bridge/bridge-outbox-drain-hook.js +10 -2
- package/dist/runtime/subsystems/bridge/bridge-outbox-drain-hook.js.map +1 -1
- package/dist/runtime/subsystems/bridge/bridge.module.d.ts +1 -1
- package/dist/runtime/subsystems/bridge/bridge.module.js +14 -9
- package/dist/runtime/subsystems/bridge/bridge.module.js.map +1 -1
- package/dist/runtime/subsystems/bridge/bridge.protocol.d.ts +1 -1
- package/dist/runtime/subsystems/bridge/event-flow.service.d.ts +1 -1
- package/dist/runtime/subsystems/bridge/event-flow.service.js +9 -1
- package/dist/runtime/subsystems/bridge/event-flow.service.js.map +1 -1
- package/dist/runtime/subsystems/bridge/index.d.ts +1 -1
- package/dist/runtime/subsystems/bridge/index.js +14 -9
- package/dist/runtime/subsystems/bridge/index.js.map +1 -1
- package/dist/runtime/subsystems/cache/cache.drizzle-backend.js +6 -1
- package/dist/runtime/subsystems/cache/cache.drizzle-backend.js.map +1 -1
- package/dist/runtime/subsystems/cache/cache.memory-backend.js +6 -1
- package/dist/runtime/subsystems/cache/cache.memory-backend.js.map +1 -1
- package/dist/runtime/subsystems/cache/cache.module.js +6 -2
- package/dist/runtime/subsystems/cache/cache.module.js.map +1 -1
- package/dist/runtime/subsystems/cache/cache.tokens.d.ts +0 -10
- package/dist/runtime/subsystems/cache/cache.tokens.js +6 -2
- package/dist/runtime/subsystems/cache/cache.tokens.js.map +1 -1
- package/dist/runtime/subsystems/cache/index.js +6 -2
- package/dist/runtime/subsystems/cache/index.js.map +1 -1
- package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js +5 -0
- package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js.map +1 -1
- package/dist/runtime/subsystems/events/event-bus.memory-backend.js +5 -0
- package/dist/runtime/subsystems/events/event-bus.memory-backend.js.map +1 -1
- package/dist/runtime/subsystems/events/event-bus.redis-backend.js +5 -1
- package/dist/runtime/subsystems/events/event-bus.redis-backend.js.map +1 -1
- package/dist/runtime/subsystems/events/events.module.js +5 -1
- package/dist/runtime/subsystems/events/events.module.js.map +1 -1
- package/dist/runtime/subsystems/events/events.tokens.d.ts +5 -11
- package/dist/runtime/subsystems/events/events.tokens.js +5 -1
- package/dist/runtime/subsystems/events/events.tokens.js.map +1 -1
- package/dist/runtime/subsystems/events/generated/bus.js +5 -0
- package/dist/runtime/subsystems/events/generated/bus.js.map +1 -1
- package/dist/runtime/subsystems/events/generated/index.js +5 -0
- package/dist/runtime/subsystems/events/generated/index.js.map +1 -1
- package/dist/runtime/subsystems/events/index.js +5 -1
- package/dist/runtime/subsystems/events/index.js.map +1 -1
- package/dist/runtime/subsystems/index.d.ts +3 -3
- package/dist/runtime/subsystems/index.js +34 -26
- package/dist/runtime/subsystems/index.js.map +1 -1
- package/dist/runtime/subsystems/integration/incremental-read.d.ts +35 -8
- package/dist/runtime/subsystems/integration/incremental-read.js +9 -6
- package/dist/runtime/subsystems/integration/incremental-read.js.map +1 -1
- package/dist/runtime/subsystems/integration/index.d.ts +1 -1
- package/dist/runtime/subsystems/integration/index.js +9 -6
- package/dist/runtime/subsystems/integration/index.js.map +1 -1
- package/dist/runtime/subsystems/jobs/bullmq.config.d.ts +0 -9
- package/dist/runtime/subsystems/jobs/bullmq.config.js +6 -2
- package/dist/runtime/subsystems/jobs/bullmq.config.js.map +1 -1
- package/dist/runtime/subsystems/jobs/index.d.ts +1 -1
- package/dist/runtime/subsystems/jobs/index.js +13 -9
- package/dist/runtime/subsystems/jobs/index.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-handler.base.d.ts +1 -1
- package/dist/runtime/subsystems/jobs/job-handler.base.js +5 -1
- package/dist/runtime/subsystems/jobs/job-handler.base.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.d.ts +1 -1
- package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.js +10 -3
- package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-orchestrator.drizzle-backend.d.ts +1 -1
- package/dist/runtime/subsystems/jobs/job-orchestrator.drizzle-backend.js +8 -1
- package/dist/runtime/subsystems/jobs/job-orchestrator.drizzle-backend.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-orchestrator.memory-backend.d.ts +1 -1
- package/dist/runtime/subsystems/jobs/job-orchestrator.memory-backend.js +9 -1
- package/dist/runtime/subsystems/jobs/job-orchestrator.memory-backend.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-orchestrator.protocol.d.ts +1 -1
- package/dist/runtime/subsystems/jobs/job-run-keyset-cursor.d.ts +1 -1
- package/dist/runtime/subsystems/jobs/job-run-service.drizzle-backend.d.ts +1 -1
- package/dist/runtime/subsystems/jobs/job-run-service.drizzle-backend.js +8 -2
- package/dist/runtime/subsystems/jobs/job-run-service.drizzle-backend.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-run-service.memory-backend.d.ts +1 -1
- package/dist/runtime/subsystems/jobs/job-run-service.memory-backend.js +8 -2
- package/dist/runtime/subsystems/jobs/job-run-service.memory-backend.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-run-service.protocol.d.ts +1 -1
- package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.d.ts +1 -1
- package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.js +5 -0
- package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-worker.d.ts +1 -1
- package/dist/runtime/subsystems/jobs/job-worker.js +10 -4
- package/dist/runtime/subsystems/jobs/job-worker.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-worker.module.d.ts +5 -2
- package/dist/runtime/subsystems/jobs/job-worker.module.js +13 -8
- package/dist/runtime/subsystems/jobs/job-worker.module.js.map +1 -1
- package/dist/runtime/subsystems/jobs/jobs-domain.module.js +11 -6
- package/dist/runtime/subsystems/jobs/jobs-domain.module.js.map +1 -1
- package/dist/runtime/subsystems/jobs/jobs-domain.tokens.d.ts +0 -11
- package/dist/runtime/subsystems/jobs/jobs-domain.tokens.js +8 -4
- package/dist/runtime/subsystems/jobs/jobs-domain.tokens.js.map +1 -1
- package/dist/runtime/subsystems/jobs/jobs-errors.d.ts +1 -1
- package/dist/runtime/subsystems/observability/index.d.ts +1 -1
- package/dist/runtime/subsystems/observability/index.js +9 -1
- package/dist/runtime/subsystems/observability/index.js.map +1 -1
- package/dist/runtime/subsystems/observability/observability.module.js +9 -1
- package/dist/runtime/subsystems/observability/observability.module.js.map +1 -1
- package/dist/runtime/subsystems/observability/observability.protocol.d.ts +1 -1
- package/dist/runtime/subsystems/observability/observability.service.d.ts +1 -1
- package/dist/runtime/subsystems/observability/observability.service.js +9 -1
- package/dist/runtime/subsystems/observability/observability.service.js.map +1 -1
- package/dist/runtime/subsystems/observability/reporters/bridge-metrics.reporter.d.ts +1 -1
- package/dist/runtime/subsystems/observability/reporters/index.d.ts +1 -1
- package/dist/runtime/subsystems/storage/index.js +5 -1
- package/dist/runtime/subsystems/storage/index.js.map +1 -1
- package/dist/runtime/subsystems/storage/storage.module.js +5 -1
- package/dist/runtime/subsystems/storage/storage.module.js.map +1 -1
- package/dist/runtime/subsystems/storage/storage.tokens.d.ts +0 -8
- package/dist/runtime/subsystems/storage/storage.tokens.js +5 -1
- package/dist/runtime/subsystems/storage/storage.tokens.js.map +1 -1
- package/dist/runtime/subsystems/token-key.d.ts +7 -0
- package/dist/runtime/subsystems/token-key.js +8 -0
- package/dist/runtime/subsystems/token-key.js.map +1 -0
- package/dist/src/cli/index.js +362 -233
- package/dist/src/cli/index.js.map +1 -1
- package/package.json +5 -1
- package/runtime/subsystems/analytics/analytics.tokens.ts +6 -2
- package/runtime/subsystems/auth/auth.tokens.ts +15 -8
- package/runtime/subsystems/cache/cache.tokens.ts +7 -2
- package/runtime/subsystems/events/events.tokens.ts +8 -1
- package/runtime/subsystems/index.ts +6 -1
- package/runtime/subsystems/integration/incremental-read.ts +43 -9
- package/runtime/subsystems/integration/index.ts +1 -0
- package/runtime/subsystems/jobs/bullmq.config.ts +5 -2
- package/runtime/subsystems/jobs/job-handler.base.ts +6 -1
- package/runtime/subsystems/jobs/job-worker.module.ts +5 -1
- package/runtime/subsystems/jobs/job-worker.ts +4 -1
- package/runtime/subsystems/jobs/jobs-domain.tokens.ts +10 -7
- package/runtime/subsystems/storage/storage.tokens.ts +6 -1
- package/runtime/subsystems/token-key.ts +7 -0
- package/src/config/runtime-mode.mjs +82 -0
- package/templates/entity/new/backend/modules/core/integration-source.ejs.t +3 -2
- package/templates/entity/new/clean-lite-ps/controller.ejs.t +1 -1
- package/templates/entity/new/clean-lite-ps/module.ejs.t +1 -1
- package/templates/entity/new/clean-lite-ps/prompt-extension.js +8 -2
- package/templates/entity/new/clean-lite-ps/repository.ejs.t +4 -4
- package/templates/entity/new/clean-lite-ps/service.ejs.t +4 -4
- package/templates/entity/new/prompt.js +49 -10
|
@@ -1,14 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Injection tokens for the job orchestration domain layer (ADR-022, JOB-2).
|
|
3
|
-
*
|
|
4
|
-
* Consumer code injects these symbols via `@Inject(JOB_ORCHESTRATOR)` etc.;
|
|
5
|
-
* concrete backends (JOB-3 Drizzle, JOB-4 Memory) provide the implementations
|
|
6
|
-
* through `JobsDomainModule.forRoot({ backend })` in JOB-5.
|
|
7
|
-
*
|
|
8
|
-
* Each token is a unique `Symbol` — guaranteed distinct from every other
|
|
9
|
-
* Symbol at runtime, which is exactly the uniqueness guarantee Nest's DI
|
|
10
|
-
* container relies on for token-based lookup.
|
|
11
|
-
*/
|
|
12
1
|
declare const JOB_ORCHESTRATOR: unique symbol;
|
|
13
2
|
declare const JOB_RUN_SERVICE: unique symbol;
|
|
14
3
|
declare const JOB_STEP_SERVICE: unique symbol;
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
// runtime/subsystems/token-key.ts
|
|
2
|
+
var PKG = "@pattern-stack/codegen";
|
|
3
|
+
var tokenKey = (area, name) => `${PKG}.${area}.${name}`;
|
|
4
|
+
|
|
1
5
|
// runtime/subsystems/jobs/jobs-domain.tokens.ts
|
|
2
|
-
var JOB_ORCHESTRATOR =
|
|
3
|
-
var JOB_RUN_SERVICE =
|
|
4
|
-
var JOB_STEP_SERVICE =
|
|
5
|
-
var JOBS_MULTI_TENANT =
|
|
6
|
+
var JOB_ORCHESTRATOR = Symbol.for(tokenKey("jobs", "orchestrator"));
|
|
7
|
+
var JOB_RUN_SERVICE = Symbol.for(tokenKey("jobs", "run-service"));
|
|
8
|
+
var JOB_STEP_SERVICE = Symbol.for(tokenKey("jobs", "step-service"));
|
|
9
|
+
var JOBS_MULTI_TENANT = Symbol.for(tokenKey("jobs", "multi-tenant"));
|
|
6
10
|
export {
|
|
7
11
|
JOBS_MULTI_TENANT,
|
|
8
12
|
JOB_ORCHESTRATOR,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../runtime/subsystems/jobs/jobs-domain.tokens.ts"],"sourcesContent":["/**\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
|
|
1
|
+
{"version":3,"sources":["../../../../runtime/subsystems/token-key.ts","../../../../runtime/subsystems/jobs/jobs-domain.tokens.ts"],"sourcesContent":["/** 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"],"mappings":";AAGO,IAAM,MAAM;AAGZ,IAAM,WAAW,CAAC,MAAc,SAAyB,GAAG,GAAG,IAAI,IAAI,IAAI,IAAI;;;ACQ/E,IAAM,mBAAmB,OAAO,IAAI,SAAS,QAAQ,cAAc,CAAC;AACpE,IAAM,kBAAkB,OAAO,IAAI,SAAS,QAAQ,aAAa,CAAC;AAClE,IAAM,mBAAmB,OAAO,IAAI,SAAS,QAAQ,cAAc,CAAC;AAgBpE,IAAM,oBAAoB,OAAO,IAAI,SAAS,QAAQ,cAAc,CAAC;","names":[]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as JobRun } from '../../../job-orchestrator.protocol-
|
|
1
|
+
import { i as JobRun } from '../../../job-orchestrator.protocol-CARhMLCO.js';
|
|
2
2
|
import '../events/event-bus.protocol.js';
|
|
3
3
|
import '../../types/drizzle.js';
|
|
4
4
|
import 'drizzle-orm/node-postgres';
|
|
@@ -14,7 +14,7 @@ import '../../types/drizzle.js';
|
|
|
14
14
|
import 'drizzle-orm/node-postgres';
|
|
15
15
|
import '../integration/integration-field-diff.protocol.js';
|
|
16
16
|
import 'zod';
|
|
17
|
-
import '../../../job-orchestrator.protocol-
|
|
17
|
+
import '../../../job-orchestrator.protocol-CARhMLCO.js';
|
|
18
18
|
import '../jobs/job-orchestration.schema.js';
|
|
19
19
|
import 'drizzle-orm/pg-core';
|
|
20
20
|
import 'drizzle-orm';
|
|
@@ -22,11 +22,19 @@ import { Module } from "@nestjs/common";
|
|
|
22
22
|
// runtime/subsystems/observability/observability.service.ts
|
|
23
23
|
import { Inject, Injectable, Optional } from "@nestjs/common";
|
|
24
24
|
|
|
25
|
+
// runtime/subsystems/token-key.ts
|
|
26
|
+
var PKG = "@pattern-stack/codegen";
|
|
27
|
+
var tokenKey = (area, name) => `${PKG}.${area}.${name}`;
|
|
28
|
+
|
|
25
29
|
// runtime/subsystems/jobs/jobs-domain.tokens.ts
|
|
26
|
-
var
|
|
30
|
+
var JOB_ORCHESTRATOR = Symbol.for(tokenKey("jobs", "orchestrator"));
|
|
31
|
+
var JOB_RUN_SERVICE = Symbol.for(tokenKey("jobs", "run-service"));
|
|
32
|
+
var JOB_STEP_SERVICE = Symbol.for(tokenKey("jobs", "step-service"));
|
|
33
|
+
var JOBS_MULTI_TENANT = Symbol.for(tokenKey("jobs", "multi-tenant"));
|
|
27
34
|
|
|
28
35
|
// runtime/subsystems/events/events.tokens.ts
|
|
29
36
|
var EVENT_READ_PORT = "EVENT_READ_PORT";
|
|
37
|
+
var REDIS_URL = Symbol.for(tokenKey("events", "redis-url"));
|
|
30
38
|
|
|
31
39
|
// runtime/subsystems/bridge/bridge.tokens.ts
|
|
32
40
|
var BRIDGE_DELIVERY_REPO = "BRIDGE_DELIVERY_REPO";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../runtime/subsystems/observability/observability.tokens.ts","../../../../runtime/subsystems/observability/observability.module.ts","../../../../runtime/subsystems/observability/observability.service.ts","../../../../runtime/subsystems/jobs/jobs-domain.tokens.ts","../../../../runtime/subsystems/events/events.tokens.ts","../../../../runtime/subsystems/bridge/bridge.tokens.ts","../../../../runtime/subsystems/integration/integration.tokens.ts","../../../../runtime/subsystems/observability/reporters/bridge-metrics.reporter.ts","../../../../runtime/subsystems/observability/observability-errors.ts"],"sourcesContent":["/**\n * Observability combiner subsystem — DI tokens (ADR-025, OBS-5).\n *\n * String constants (not Symbols), matching the events / bridge / integration\n * convention. The jobs subsystem uses Symbols for its analogous tokens;\n * observability stays internally consistent with its sibling combiner\n * (bridge) because the two are structurally paired (ADR-025).\n *\n * Usage in consumers:\n * ```ts\n * constructor(@Inject(OBSERVABILITY) private readonly obs: IObservability) {}\n * ```\n */\n\n/**\n * Token for the `IObservability` composer facade (OBS-5). Resolves to the\n * single `ObservabilityService` instance registered by\n * `ObservabilityModule.forRoot(...)`.\n */\nexport const OBSERVABILITY = 'OBSERVABILITY' as const;\n\n/**\n * Token for the resolved `ObservabilityModuleOptions` object. Provided by\n * `ObservabilityModule.forRoot(...)`. Reserved for phase 2 — the current\n * options shape is empty; OBS-6 will extend it with a `reporters` field.\n */\nexport const OBSERVABILITY_MODULE_OPTIONS = 'OBSERVABILITY_MODULE_OPTIONS' as const;\n","/**\n * ObservabilityModule — combiner subsystem (ADR-025, OBS-5, OBS-6).\n *\n * Composes the jobs, bridge, and integration read ports into a single\n * `IObservability` facade. Owned by no sibling subsystem; it consumes\n * their tokens via DI, which the consumer app wires by registering the\n * sibling modules in the right order (like BridgeModule — the named\n * precedent in ADR-025).\n *\n * Consumer wiring (register AFTER the composed sibling modules):\n * ```ts\n * @Module({\n * imports: [\n * EventsModule.forRoot({ backend: 'drizzle' }),\n * JobsDomainModule.forRoot({ backend: 'drizzle' }),\n * BridgeModule.forRoot({ backend: 'drizzle' }),\n * IntegrationModule.forRoot({ backend: 'drizzle' }),\n * ObservabilityModule.forRoot({\n * reporters: {\n * bridgeMetrics: {\n * enabled: true,\n * intervalMs: 60_000,\n * windowHours: 1,\n * },\n * },\n * }),\n * ],\n * })\n * class AppModule {}\n * ```\n *\n * # No `backend` option — intentional\n *\n * Unlike ADR-008 infrastructure subsystems (events / jobs / cache /\n * storage), observability is a combiner per ADR-025 and owns no durable\n * state. The \"backend\" is whichever backends the composed subsystems are\n * running — portability is inherited, not declared. See ADR-025 §4 (when\n * to pick combiner vs. infrastructure) and\n * `.claude/skills/observability/SKILL.md` §1.\n *\n * # Graceful sibling absence\n *\n * The consumed sibling tokens are `@Optional()` inside\n * `ObservabilityService`. An app that only installed a subset of the\n * composed subsystems can still register `ObservabilityModule`; the\n * methods whose sibling is missing return empty shapes.\n *\n * # Reporters (OBS-6)\n *\n * Internal consumers of the `OBSERVABILITY` facade — auto-registered\n * when the matching `reporters.*` key is present and enabled. Reporters\n * are NOT added to `exports`; they are a module-internal concern.\n * Consumers configure them via options; they never import the reporter\n * classes directly.\n */\nimport { Module, type DynamicModule, type Provider } from '@nestjs/common';\n\nimport {\n OBSERVABILITY,\n OBSERVABILITY_MODULE_OPTIONS,\n} from './observability.tokens';\nimport { ObservabilityService } from './observability.service';\nimport { BridgeMetricsReporter } from './reporters/bridge-metrics.reporter';\n\n/**\n * Config for the bridge-delivery sampler (OBS-6). All fields are required\n * when `enabled: true` — the config is declarative and explicit; there\n * are no hidden defaults. Lives on the module (not the reporter file) so\n * the module's options type stays the single authoritative schema for\n * everything `forRoot` accepts.\n */\nexport interface BridgeMetricsReporterConfig {\n /** Master switch. Reporter is only registered as a provider when this\n * is `true` (see `forRoot`). */\n enabled: boolean;\n /** Sampling period in ms. Must be `> 0` when `enabled: true`. */\n intervalMs: number;\n /** Trailing window (hours) passed to `getBridgeDeliveryHistogram`.\n * Must be `> 0` when `enabled: true`. */\n windowHours: number;\n /** Forwarded verbatim to `IObservability.getBridgeDeliveryHistogram`.\n * - `undefined` — sibling default semantics\n * - `null` — explicit cross-tenant match\n * - `string` — filter to that single tenant */\n tenantId?: string | null;\n}\n\n/**\n * Named-map of reporter configs. Named, not array, so consumers can toggle\n * individual reporters by key without juggling order and so future reporters\n * can be added without breaking existing configs.\n */\nexport interface ObservabilityReportersOptions {\n bridgeMetrics?: BridgeMetricsReporterConfig;\n}\n\n/**\n * Options for `ObservabilityModule.forRoot()`. Currently only `reporters`;\n * room to grow (sampling, exporters) without changing the module signature.\n */\nexport interface ObservabilityModuleOptions {\n reporters?: ObservabilityReportersOptions;\n}\n\n@Module({})\nexport class ObservabilityModule {\n static forRoot(options: ObservabilityModuleOptions = {}): DynamicModule {\n const providers: Provider[] = [\n // Expose the resolved options so internal reporters can read their\n // own config via `OBSERVABILITY_MODULE_OPTIONS`.\n { provide: OBSERVABILITY_MODULE_OPTIONS, useValue: options },\n // Register the concrete class as the canonical instance.\n ObservabilityService,\n // OBSERVABILITY token points at the same instance — consumers inject\n // the token, not the class, per ADR-025 §Shape (index.ts does NOT\n // export `ObservabilityService`).\n { provide: OBSERVABILITY, useExisting: ObservabilityService },\n ];\n\n // Reporters: auto-registered when enabled, not added to `exports`.\n // They are internal consumers of OBSERVABILITY; consumers configure\n // them via options rather than importing the classes.\n if (options.reporters?.bridgeMetrics?.enabled === true) {\n providers.push(BridgeMetricsReporter);\n }\n\n return {\n module: ObservabilityModule,\n global: true,\n providers,\n exports: [OBSERVABILITY, OBSERVABILITY_MODULE_OPTIONS],\n };\n }\n}\n","/**\n * ObservabilityService — `IObservability` combiner implementation\n * (ADR-025, OBS-5).\n *\n * Composes read methods across the jobs, bridge, and integration subsystems via\n * DI. Owns no state, no schema, no SQL. Every method is a one-line\n * delegation to the sibling port that already encodes the semantics.\n *\n * # Missing-port degradation\n *\n * Every sibling is injected with `@Optional()`. When the consumer's app\n * has not wired a given subsystem, the corresponding field is `undefined`\n * and the delegating method returns an empty shape:\n * - array methods return `[]`\n * - `getBridgeDeliveryHistogram` returns `{ pending: 0, delivered: 0,\n * skipped: 0, failed: 0 }` (matches the bridge protocol's fixed-keys\n * contract so consumers can render a 4-row chart unconditionally).\n *\n * Graceful absence is the whole point of the combiner pattern (ADR-025\n * §Shape, constraint 3) — a consumer that only installed the jobs\n * subsystem can still inject `OBSERVABILITY` and get useful job reads\n * without wiring the rest.\n *\n * # Multi-tenancy\n *\n * `tenantId` passes VERBATIM from the public method to the owning port.\n * `ObservabilityService` never re-implements tenant filtering. See\n * `.claude/skills/observability/SKILL.md` §3 and ADR-025.\n */\nimport { Inject, Injectable, Optional } from '@nestjs/common';\n\nimport { JOB_RUN_SERVICE } from '../jobs/jobs-domain.tokens';\nimport type {\n IJobRunService,\n JobRunFailure,\n JobRunPage,\n JobRunSummary,\n ListJobRunsQuery,\n PoolStatusCount,\n} from '../jobs/job-run-service.protocol';\n\nimport { EVENT_READ_PORT } from '../events/events.tokens';\nimport type {\n EventPage,\n EventSummary,\n IEventReadPort,\n ListEventsQuery,\n} from '../events/event-read.protocol';\n\nimport { BRIDGE_DELIVERY_REPO } from '../bridge/bridge.tokens';\nimport type { IJobBridge, StatusHistogram } from '../bridge/bridge.protocol';\n\nimport { INTEGRATION_CURSOR_STORE, INTEGRATION_RUN_RECORDER } from '../integration/integration.tokens';\nimport type {\n IIntegrationRunRecorder,\n IntegrationRunSummary,\n} from '../integration/integration-run-recorder.protocol';\nimport type {\n CursorSnapshot,\n ICursorStore,\n} from '../integration/integration-cursor-store.protocol';\n\nimport type {\n CorrelationTimeline,\n CorrelationTimelineEntry,\n IObservability,\n} from './observability.protocol';\n\n/**\n * Safety bound on how many pages the correlation timeline will walk when\n * draining a sibling port. A single run tree producing more than\n * 50 pages × default page size of correlated rows is pathological; cap to\n * keep the stitch bounded rather than unbounded-loop on bad data.\n */\nconst MAX_TIMELINE_PAGES = 50;\n\n@Injectable()\nexport class ObservabilityService implements IObservability {\n /**\n * All-zero histogram used when the bridge subsystem is absent. Matches\n * the bridge protocol's \"fixed keys, zero-filled\" contract so consumers\n * never branch on presence.\n */\n private static readonly EMPTY_HISTOGRAM: StatusHistogram = {\n pending: 0,\n delivered: 0,\n skipped: 0,\n failed: 0,\n };\n\n /** Empty page used when a sibling read port is absent. */\n private static readonly EMPTY_JOB_RUN_PAGE: JobRunPage = {\n items: [],\n nextCursor: null,\n };\n private static readonly EMPTY_EVENT_PAGE: EventPage = {\n items: [],\n nextCursor: null,\n };\n\n constructor(\n @Optional()\n @Inject(JOB_RUN_SERVICE)\n private readonly jobRuns?: IJobRunService,\n @Optional()\n @Inject(BRIDGE_DELIVERY_REPO)\n private readonly bridge?: IJobBridge,\n @Optional()\n @Inject(INTEGRATION_RUN_RECORDER)\n private readonly integrationRuns?: IIntegrationRunRecorder,\n @Optional()\n @Inject(INTEGRATION_CURSOR_STORE)\n private readonly cursors?: ICursorStore,\n @Optional()\n @Inject(EVENT_READ_PORT)\n private readonly events?: IEventReadPort | null,\n ) {}\n\n async getPoolDepths(tenantId?: string | null): Promise<PoolStatusCount[]> {\n if (!this.jobRuns) return [];\n return this.jobRuns.countByPoolAndStatus(tenantId);\n }\n\n async getRecentFailedJobs(\n limit: number,\n tenantId?: string | null,\n ): Promise<JobRunFailure[]> {\n if (!this.jobRuns) return [];\n return this.jobRuns.listRecentFailed(limit, tenantId);\n }\n\n async getBridgeDeliveryHistogram(\n windowHours: number,\n tenantId?: string | null,\n ): Promise<StatusHistogram> {\n if (!this.bridge) return { ...ObservabilityService.EMPTY_HISTOGRAM };\n return this.bridge.getStatusHistogram(windowHours, tenantId);\n }\n\n async getRecentIntegrationRuns(\n limit: number,\n subscriptionId?: string,\n tenantId?: string | null,\n ): Promise<IntegrationRunSummary[]> {\n if (!this.integrationRuns) return [];\n return this.integrationRuns.listRecent(limit, subscriptionId, tenantId);\n }\n\n async getCursors(tenantId?: string | null): Promise<CursorSnapshot[]> {\n if (!this.cursors) return [];\n return this.cursors.listAll(tenantId);\n }\n\n async listJobRuns(query?: ListJobRunsQuery): Promise<JobRunPage> {\n if (!this.jobRuns) {\n return { ...ObservabilityService.EMPTY_JOB_RUN_PAGE };\n }\n return this.jobRuns.listJobRuns(query);\n }\n\n async listEvents(query?: ListEventsQuery): Promise<EventPage> {\n if (!this.events) {\n return { ...ObservabilityService.EMPTY_EVENT_PAGE };\n }\n return this.events.listEvents(query);\n }\n\n async getCorrelationTimeline(\n rootRunId: string,\n tenantId?: string | null,\n ): Promise<CorrelationTimeline> {\n const runs = await this.collectRuns(rootRunId, tenantId);\n const events = await this.collectEvents(rootRunId, tenantId);\n\n const entries: CorrelationTimelineEntry[] = [\n ...runs.map(\n (run): CorrelationTimelineEntry => ({\n kind: 'job_run',\n at: run.createdAt,\n run,\n }),\n ),\n ...events.map(\n (event): CorrelationTimelineEntry => ({\n kind: 'event',\n at: event.occurredAt,\n event,\n }),\n ),\n ];\n\n // Ascending chronological order. Stable tie-break: job runs before\n // events at the same instant (the run that emits an event precedes it).\n entries.sort((a, b) => {\n const dt = a.at.getTime() - b.at.getTime();\n if (dt !== 0) return dt;\n if (a.kind === b.kind) return 0;\n return a.kind === 'job_run' ? -1 : 1;\n });\n\n const startedAt = entries.length > 0 ? entries[0]!.at : null;\n const lastActivityAt =\n entries.length > 0 ? entries[entries.length - 1]!.at : null;\n\n return {\n rootRunId,\n entries,\n summary: {\n runCount: runs.length,\n eventCount: events.length,\n startedAt,\n lastActivityAt,\n },\n };\n }\n\n /**\n * Drain every `job_run` sharing `rootRunId` by walking the keyset cursor.\n * Empty when the jobs subsystem is absent.\n */\n private async collectRuns(\n rootRunId: string,\n tenantId?: string | null,\n ): Promise<JobRunSummary[]> {\n if (!this.jobRuns) return [];\n const out: JobRunSummary[] = [];\n let cursor: string | undefined;\n for (let page = 0; page < MAX_TIMELINE_PAGES; page += 1) {\n const result = await this.jobRuns.listJobRuns({\n rootRunId,\n tenantId,\n cursor,\n });\n out.push(...result.items);\n if (!result.nextCursor) break;\n cursor = result.nextCursor;\n }\n return out;\n }\n\n /**\n * Drain every `domain_event` whose `metadata.rootRunId` matches by walking\n * the keyset cursor. Empty when the events read port is absent.\n */\n private async collectEvents(\n rootRunId: string,\n tenantId?: string | null,\n ): Promise<EventSummary[]> {\n if (!this.events) return [];\n const out: EventSummary[] = [];\n let cursor: string | undefined;\n for (let page = 0; page < MAX_TIMELINE_PAGES; page += 1) {\n const result = await this.events.listEvents({\n rootRunId,\n tenantId,\n cursor,\n });\n out.push(...result.items);\n if (!result.nextCursor) break;\n cursor = result.nextCursor;\n }\n return out;\n }\n}\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 * 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 * Integration subsystem — DI tokens\n *\n * String constants (not Symbols) so they match by value across import\n * boundaries — same convention as the events subsystem (`EVENT_BUS`). The\n * jobs subsystem uses Symbols for its analogous tokens; events and integration\n * stay internally consistent with strings.\n *\n * Usage in use cases:\n * ```ts\n * constructor(\n * @Inject(INTEGRATION_CHANGE_SOURCE) private readonly source: IChangeSource<CanonicalOpportunity>,\n * @Inject(INTEGRATION_CURSOR_STORE) private readonly cursors: ICursorStore,\n * @Inject(INTEGRATION_FIELD_DIFFER) private readonly differ: IFieldDiffer<CanonicalOpportunity>,\n * @Inject(INTEGRATION_SINK) private readonly sink: IIntegrationSink<CanonicalOpportunity>,\n * @Inject(INTEGRATION_RUN_RECORDER) private readonly recorder: IIntegrationRunRecorder,\n * ) {}\n * ```\n *\n * Concrete bindings are registered by `IntegrationModule.forRoot(...)` (SYNC-6).\n */\n\nexport const INTEGRATION_CHANGE_SOURCE = 'INTEGRATION_CHANGE_SOURCE' as const;\nexport const INTEGRATION_CURSOR_STORE = 'INTEGRATION_CURSOR_STORE' as const;\nexport const INTEGRATION_FIELD_DIFFER = 'INTEGRATION_FIELD_DIFFER' as const;\nexport const INTEGRATION_SINK = 'INTEGRATION_SINK' as const;\n\n/**\n * Run-recorder token (SYNC-5). Backed by `IIntegrationRunRecorder`. Drizzle impl\n * lands in SYNC-4; tests provide inline fakes.\n */\nexport const INTEGRATION_RUN_RECORDER = 'INTEGRATION_RUN_RECORDER' as const;\n\n/**\n * Injection token for the resolved `IntegrationModuleOptions` object (SYNC-6).\n *\n * Backends that need to observe module configuration (e.g. `multiTenant`\n * flag, pool filters) inject via this token. Provided automatically by\n * `IntegrationModule.forRoot(...)` / `IntegrationModule.forRootAsync(...)`.\n */\nexport const INTEGRATION_MODULE_OPTIONS = 'INTEGRATION_MODULE_OPTIONS' as const;\n\n/**\n * Injection token for the resolved multi-tenancy flag (SYNC-6).\n *\n * Provided by `IntegrationModule.forRoot(...)` as `options.multiTenant ?? false`.\n * Consumed by `ExecuteIntegrationUseCase` to enforce the tenantId-is-required rule.\n */\nexport const INTEGRATION_MULTI_TENANT = 'INTEGRATION_MULTI_TENANT' as const;\n\n/**\n * Injection token for the entity-keyed `IEntityChangeSourceRegistry` (C7,\n * #336). Bound to the codegen-emitted aggregator that folds per-provider\n * adapter contributions into one registry (RFC-0001 §3, emitted by Track D\n * D3/D4).\n *\n * A string constant, not `Symbol.for(...)`, to match this subsystem's token\n * convention (see file header). The originating issue's code block proposed a\n * `Symbol.for('@pattern-stack/codegen.entity-change-source-registry')` key,\n * predating the sync→integration consolidation onto string tokens; kept as a\n * string here for internal consistency with the other INTEGRATION_* tokens.\n */\nexport const ENTITY_CHANGE_SOURCE_REGISTRY = 'ENTITY_CHANGE_SOURCE_REGISTRY' as const;\n","/**\n * BridgeMetricsReporter — internal consumer of `IObservability`\n * (ADR-025, OBS-6).\n *\n * Periodically samples `getBridgeDeliveryHistogram` from the observability\n * facade and emits one log line per tick. Auto-registered by\n * `ObservabilityModule.forRoot()` when\n * `options.reporters.bridgeMetrics.enabled === true`. Consumers configure\n * via options; they never import this class. Not exported from the\n * module's `exports` array — internal.\n *\n * # Invariants (enforced by skill + ADR-025)\n *\n * - Injects ONLY `OBSERVABILITY` + `OBSERVABILITY_MODULE_OPTIONS`. Never\n * `BRIDGE_DELIVERY_REPO` or any other sibling token. Reporters are\n * consumers of the composed facade, not parallel composers.\n * - Never reaches into sibling tables or extends `IObservability`.\n * - Errors isolated per-tick (logged, never rethrown) so a transient\n * sibling failure does not kill the interval.\n * - `tenantId` passes VERBATIM to the facade — observability owns tenant\n * semantics, reporters don't re-implement them.\n *\n * # Lifecycle\n *\n * - `onModuleInit` — eager first-tick, then `setInterval`. Handle is\n * `.unref()`-ed when supported so the loop never blocks node shutdown.\n * - `onModuleDestroy` — `clearInterval` + null the handle. Idempotent.\n *\n * No `@nestjs/schedule` dependency — raw `setInterval` keeps the runtime\n * footprint minimal and avoids pulling a decorator framework in for a\n * single loop.\n */\nimport {\n Inject,\n Injectable,\n Logger,\n type OnModuleDestroy,\n type OnModuleInit,\n} from '@nestjs/common';\n\nimport type { IObservability } from '../observability.protocol';\nimport {\n OBSERVABILITY,\n OBSERVABILITY_MODULE_OPTIONS,\n} from '../observability.tokens';\n// Type-only imports — the module imports this file as a value for DI\n// registration, and this file imports config types back. Keeping the\n// back-edge type-only prevents a runtime circular-import.\nimport type {\n BridgeMetricsReporterConfig,\n ObservabilityModuleOptions,\n} from '../observability.module';\n\n@Injectable()\nexport class BridgeMetricsReporter implements OnModuleInit, OnModuleDestroy {\n private readonly logger = new Logger(BridgeMetricsReporter.name);\n private handle: ReturnType<typeof setInterval> | null = null;\n private readonly config: BridgeMetricsReporterConfig | undefined;\n\n constructor(\n @Inject(OBSERVABILITY) private readonly observability: IObservability,\n @Inject(OBSERVABILITY_MODULE_OPTIONS) options: ObservabilityModuleOptions,\n ) {\n this.config = options.reporters?.bridgeMetrics;\n }\n\n onModuleInit(): void {\n if (!this.config || !this.config.enabled) {\n this.logger.log('BridgeMetricsReporter disabled');\n return;\n }\n if (this.config.intervalMs <= 0 || this.config.windowHours <= 0) {\n this.logger.warn(\n `invalid config; not starting: intervalMs=${this.config.intervalMs} windowHours=${this.config.windowHours}`,\n );\n return;\n }\n // Eager first-tick so consumers see data immediately on boot, without\n // waiting `intervalMs` for the first sample.\n void this.runOnce();\n this.handle = setInterval(() => {\n void this.runOnce();\n }, this.config.intervalMs);\n // `.unref()` lets the node process exit even if the interval is still\n // scheduled — important in short-lived CLI/test contexts. Guarded\n // because browser-shimmed timers may not expose it.\n if (typeof this.handle.unref === 'function') this.handle.unref();\n }\n\n onModuleDestroy(): void {\n if (this.handle !== null) {\n clearInterval(this.handle);\n this.handle = null;\n }\n }\n\n /**\n * Single sample. Public so tests and future ops tooling can trigger a\n * sample on demand without waiting for the interval. Errors are caught\n * and logged here — never rethrown — so a transient sibling failure\n * does not kill subsequent ticks.\n */\n async runOnce(): Promise<void> {\n if (!this.config || !this.config.enabled) return;\n try {\n const h = await this.observability.getBridgeDeliveryHistogram(\n this.config.windowHours,\n this.config.tenantId,\n );\n this.logger.log(\n `bridge-delivery window=${this.config.windowHours}h tenant=${this.config.tenantId ?? 'default'} pending=${h.pending} delivered=${h.delivered} skipped=${h.skipped} failed=${h.failed}`,\n );\n } catch (err) {\n this.logger.error(\n 'BridgeMetricsReporter runOnce failed',\n err instanceof Error ? err.stack : String(err),\n );\n }\n }\n}\n","/**\n * Base class for observability-specific errors.\n *\n * Phase-1 `IObservability` methods do not throw — missing sibling ports\n * degrade to empty shapes (see `ObservabilityService`). This class exists\n * so future extensions (OBS-6 reporter misconfiguration, phase-2 Drizzle\n * extensions for `pg_stat_*` sampling, etc.) have a named base without\n * churning the barrel when they land.\n */\nexport class ObservabilityError extends Error {\n constructor(\n message: string,\n override readonly cause?: unknown,\n ) {\n super(message);\n this.name = 'ObservabilityError';\n }\n}\n"],"mappings":"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","names":["Inject","Injectable","Injectable","Inject"]}
|
|
1
|
+
{"version":3,"sources":["../../../../runtime/subsystems/observability/observability.tokens.ts","../../../../runtime/subsystems/observability/observability.module.ts","../../../../runtime/subsystems/observability/observability.service.ts","../../../../runtime/subsystems/token-key.ts","../../../../runtime/subsystems/jobs/jobs-domain.tokens.ts","../../../../runtime/subsystems/events/events.tokens.ts","../../../../runtime/subsystems/bridge/bridge.tokens.ts","../../../../runtime/subsystems/integration/integration.tokens.ts","../../../../runtime/subsystems/observability/reporters/bridge-metrics.reporter.ts","../../../../runtime/subsystems/observability/observability-errors.ts"],"sourcesContent":["/**\n * Observability combiner subsystem — DI tokens (ADR-025, OBS-5).\n *\n * String constants (not Symbols), matching the events / bridge / integration\n * convention. The jobs subsystem uses Symbols for its analogous tokens;\n * observability stays internally consistent with its sibling combiner\n * (bridge) because the two are structurally paired (ADR-025).\n *\n * Usage in consumers:\n * ```ts\n * constructor(@Inject(OBSERVABILITY) private readonly obs: IObservability) {}\n * ```\n */\n\n/**\n * Token for the `IObservability` composer facade (OBS-5). Resolves to the\n * single `ObservabilityService` instance registered by\n * `ObservabilityModule.forRoot(...)`.\n */\nexport const OBSERVABILITY = 'OBSERVABILITY' as const;\n\n/**\n * Token for the resolved `ObservabilityModuleOptions` object. Provided by\n * `ObservabilityModule.forRoot(...)`. Reserved for phase 2 — the current\n * options shape is empty; OBS-6 will extend it with a `reporters` field.\n */\nexport const OBSERVABILITY_MODULE_OPTIONS = 'OBSERVABILITY_MODULE_OPTIONS' as const;\n","/**\n * ObservabilityModule — combiner subsystem (ADR-025, OBS-5, OBS-6).\n *\n * Composes the jobs, bridge, and integration read ports into a single\n * `IObservability` facade. Owned by no sibling subsystem; it consumes\n * their tokens via DI, which the consumer app wires by registering the\n * sibling modules in the right order (like BridgeModule — the named\n * precedent in ADR-025).\n *\n * Consumer wiring (register AFTER the composed sibling modules):\n * ```ts\n * @Module({\n * imports: [\n * EventsModule.forRoot({ backend: 'drizzle' }),\n * JobsDomainModule.forRoot({ backend: 'drizzle' }),\n * BridgeModule.forRoot({ backend: 'drizzle' }),\n * IntegrationModule.forRoot({ backend: 'drizzle' }),\n * ObservabilityModule.forRoot({\n * reporters: {\n * bridgeMetrics: {\n * enabled: true,\n * intervalMs: 60_000,\n * windowHours: 1,\n * },\n * },\n * }),\n * ],\n * })\n * class AppModule {}\n * ```\n *\n * # No `backend` option — intentional\n *\n * Unlike ADR-008 infrastructure subsystems (events / jobs / cache /\n * storage), observability is a combiner per ADR-025 and owns no durable\n * state. The \"backend\" is whichever backends the composed subsystems are\n * running — portability is inherited, not declared. See ADR-025 §4 (when\n * to pick combiner vs. infrastructure) and\n * `.claude/skills/observability/SKILL.md` §1.\n *\n * # Graceful sibling absence\n *\n * The consumed sibling tokens are `@Optional()` inside\n * `ObservabilityService`. An app that only installed a subset of the\n * composed subsystems can still register `ObservabilityModule`; the\n * methods whose sibling is missing return empty shapes.\n *\n * # Reporters (OBS-6)\n *\n * Internal consumers of the `OBSERVABILITY` facade — auto-registered\n * when the matching `reporters.*` key is present and enabled. Reporters\n * are NOT added to `exports`; they are a module-internal concern.\n * Consumers configure them via options; they never import the reporter\n * classes directly.\n */\nimport { Module, type DynamicModule, type Provider } from '@nestjs/common';\n\nimport {\n OBSERVABILITY,\n OBSERVABILITY_MODULE_OPTIONS,\n} from './observability.tokens';\nimport { ObservabilityService } from './observability.service';\nimport { BridgeMetricsReporter } from './reporters/bridge-metrics.reporter';\n\n/**\n * Config for the bridge-delivery sampler (OBS-6). All fields are required\n * when `enabled: true` — the config is declarative and explicit; there\n * are no hidden defaults. Lives on the module (not the reporter file) so\n * the module's options type stays the single authoritative schema for\n * everything `forRoot` accepts.\n */\nexport interface BridgeMetricsReporterConfig {\n /** Master switch. Reporter is only registered as a provider when this\n * is `true` (see `forRoot`). */\n enabled: boolean;\n /** Sampling period in ms. Must be `> 0` when `enabled: true`. */\n intervalMs: number;\n /** Trailing window (hours) passed to `getBridgeDeliveryHistogram`.\n * Must be `> 0` when `enabled: true`. */\n windowHours: number;\n /** Forwarded verbatim to `IObservability.getBridgeDeliveryHistogram`.\n * - `undefined` — sibling default semantics\n * - `null` — explicit cross-tenant match\n * - `string` — filter to that single tenant */\n tenantId?: string | null;\n}\n\n/**\n * Named-map of reporter configs. Named, not array, so consumers can toggle\n * individual reporters by key without juggling order and so future reporters\n * can be added without breaking existing configs.\n */\nexport interface ObservabilityReportersOptions {\n bridgeMetrics?: BridgeMetricsReporterConfig;\n}\n\n/**\n * Options for `ObservabilityModule.forRoot()`. Currently only `reporters`;\n * room to grow (sampling, exporters) without changing the module signature.\n */\nexport interface ObservabilityModuleOptions {\n reporters?: ObservabilityReportersOptions;\n}\n\n@Module({})\nexport class ObservabilityModule {\n static forRoot(options: ObservabilityModuleOptions = {}): DynamicModule {\n const providers: Provider[] = [\n // Expose the resolved options so internal reporters can read their\n // own config via `OBSERVABILITY_MODULE_OPTIONS`.\n { provide: OBSERVABILITY_MODULE_OPTIONS, useValue: options },\n // Register the concrete class as the canonical instance.\n ObservabilityService,\n // OBSERVABILITY token points at the same instance — consumers inject\n // the token, not the class, per ADR-025 §Shape (index.ts does NOT\n // export `ObservabilityService`).\n { provide: OBSERVABILITY, useExisting: ObservabilityService },\n ];\n\n // Reporters: auto-registered when enabled, not added to `exports`.\n // They are internal consumers of OBSERVABILITY; consumers configure\n // them via options rather than importing the classes.\n if (options.reporters?.bridgeMetrics?.enabled === true) {\n providers.push(BridgeMetricsReporter);\n }\n\n return {\n module: ObservabilityModule,\n global: true,\n providers,\n exports: [OBSERVABILITY, OBSERVABILITY_MODULE_OPTIONS],\n };\n }\n}\n","/**\n * ObservabilityService — `IObservability` combiner implementation\n * (ADR-025, OBS-5).\n *\n * Composes read methods across the jobs, bridge, and integration subsystems via\n * DI. Owns no state, no schema, no SQL. Every method is a one-line\n * delegation to the sibling port that already encodes the semantics.\n *\n * # Missing-port degradation\n *\n * Every sibling is injected with `@Optional()`. When the consumer's app\n * has not wired a given subsystem, the corresponding field is `undefined`\n * and the delegating method returns an empty shape:\n * - array methods return `[]`\n * - `getBridgeDeliveryHistogram` returns `{ pending: 0, delivered: 0,\n * skipped: 0, failed: 0 }` (matches the bridge protocol's fixed-keys\n * contract so consumers can render a 4-row chart unconditionally).\n *\n * Graceful absence is the whole point of the combiner pattern (ADR-025\n * §Shape, constraint 3) — a consumer that only installed the jobs\n * subsystem can still inject `OBSERVABILITY` and get useful job reads\n * without wiring the rest.\n *\n * # Multi-tenancy\n *\n * `tenantId` passes VERBATIM from the public method to the owning port.\n * `ObservabilityService` never re-implements tenant filtering. See\n * `.claude/skills/observability/SKILL.md` §3 and ADR-025.\n */\nimport { Inject, Injectable, Optional } from '@nestjs/common';\n\nimport { JOB_RUN_SERVICE } from '../jobs/jobs-domain.tokens';\nimport type {\n IJobRunService,\n JobRunFailure,\n JobRunPage,\n JobRunSummary,\n ListJobRunsQuery,\n PoolStatusCount,\n} from '../jobs/job-run-service.protocol';\n\nimport { EVENT_READ_PORT } from '../events/events.tokens';\nimport type {\n EventPage,\n EventSummary,\n IEventReadPort,\n ListEventsQuery,\n} from '../events/event-read.protocol';\n\nimport { BRIDGE_DELIVERY_REPO } from '../bridge/bridge.tokens';\nimport type { IJobBridge, StatusHistogram } from '../bridge/bridge.protocol';\n\nimport { INTEGRATION_CURSOR_STORE, INTEGRATION_RUN_RECORDER } from '../integration/integration.tokens';\nimport type {\n IIntegrationRunRecorder,\n IntegrationRunSummary,\n} from '../integration/integration-run-recorder.protocol';\nimport type {\n CursorSnapshot,\n ICursorStore,\n} from '../integration/integration-cursor-store.protocol';\n\nimport type {\n CorrelationTimeline,\n CorrelationTimelineEntry,\n IObservability,\n} from './observability.protocol';\n\n/**\n * Safety bound on how many pages the correlation timeline will walk when\n * draining a sibling port. A single run tree producing more than\n * 50 pages × default page size of correlated rows is pathological; cap to\n * keep the stitch bounded rather than unbounded-loop on bad data.\n */\nconst MAX_TIMELINE_PAGES = 50;\n\n@Injectable()\nexport class ObservabilityService implements IObservability {\n /**\n * All-zero histogram used when the bridge subsystem is absent. Matches\n * the bridge protocol's \"fixed keys, zero-filled\" contract so consumers\n * never branch on presence.\n */\n private static readonly EMPTY_HISTOGRAM: StatusHistogram = {\n pending: 0,\n delivered: 0,\n skipped: 0,\n failed: 0,\n };\n\n /** Empty page used when a sibling read port is absent. */\n private static readonly EMPTY_JOB_RUN_PAGE: JobRunPage = {\n items: [],\n nextCursor: null,\n };\n private static readonly EMPTY_EVENT_PAGE: EventPage = {\n items: [],\n nextCursor: null,\n };\n\n constructor(\n @Optional()\n @Inject(JOB_RUN_SERVICE)\n private readonly jobRuns?: IJobRunService,\n @Optional()\n @Inject(BRIDGE_DELIVERY_REPO)\n private readonly bridge?: IJobBridge,\n @Optional()\n @Inject(INTEGRATION_RUN_RECORDER)\n private readonly integrationRuns?: IIntegrationRunRecorder,\n @Optional()\n @Inject(INTEGRATION_CURSOR_STORE)\n private readonly cursors?: ICursorStore,\n @Optional()\n @Inject(EVENT_READ_PORT)\n private readonly events?: IEventReadPort | null,\n ) {}\n\n async getPoolDepths(tenantId?: string | null): Promise<PoolStatusCount[]> {\n if (!this.jobRuns) return [];\n return this.jobRuns.countByPoolAndStatus(tenantId);\n }\n\n async getRecentFailedJobs(\n limit: number,\n tenantId?: string | null,\n ): Promise<JobRunFailure[]> {\n if (!this.jobRuns) return [];\n return this.jobRuns.listRecentFailed(limit, tenantId);\n }\n\n async getBridgeDeliveryHistogram(\n windowHours: number,\n tenantId?: string | null,\n ): Promise<StatusHistogram> {\n if (!this.bridge) return { ...ObservabilityService.EMPTY_HISTOGRAM };\n return this.bridge.getStatusHistogram(windowHours, tenantId);\n }\n\n async getRecentIntegrationRuns(\n limit: number,\n subscriptionId?: string,\n tenantId?: string | null,\n ): Promise<IntegrationRunSummary[]> {\n if (!this.integrationRuns) return [];\n return this.integrationRuns.listRecent(limit, subscriptionId, tenantId);\n }\n\n async getCursors(tenantId?: string | null): Promise<CursorSnapshot[]> {\n if (!this.cursors) return [];\n return this.cursors.listAll(tenantId);\n }\n\n async listJobRuns(query?: ListJobRunsQuery): Promise<JobRunPage> {\n if (!this.jobRuns) {\n return { ...ObservabilityService.EMPTY_JOB_RUN_PAGE };\n }\n return this.jobRuns.listJobRuns(query);\n }\n\n async listEvents(query?: ListEventsQuery): Promise<EventPage> {\n if (!this.events) {\n return { ...ObservabilityService.EMPTY_EVENT_PAGE };\n }\n return this.events.listEvents(query);\n }\n\n async getCorrelationTimeline(\n rootRunId: string,\n tenantId?: string | null,\n ): Promise<CorrelationTimeline> {\n const runs = await this.collectRuns(rootRunId, tenantId);\n const events = await this.collectEvents(rootRunId, tenantId);\n\n const entries: CorrelationTimelineEntry[] = [\n ...runs.map(\n (run): CorrelationTimelineEntry => ({\n kind: 'job_run',\n at: run.createdAt,\n run,\n }),\n ),\n ...events.map(\n (event): CorrelationTimelineEntry => ({\n kind: 'event',\n at: event.occurredAt,\n event,\n }),\n ),\n ];\n\n // Ascending chronological order. Stable tie-break: job runs before\n // events at the same instant (the run that emits an event precedes it).\n entries.sort((a, b) => {\n const dt = a.at.getTime() - b.at.getTime();\n if (dt !== 0) return dt;\n if (a.kind === b.kind) return 0;\n return a.kind === 'job_run' ? -1 : 1;\n });\n\n const startedAt = entries.length > 0 ? entries[0]!.at : null;\n const lastActivityAt =\n entries.length > 0 ? entries[entries.length - 1]!.at : null;\n\n return {\n rootRunId,\n entries,\n summary: {\n runCount: runs.length,\n eventCount: events.length,\n startedAt,\n lastActivityAt,\n },\n };\n }\n\n /**\n * Drain every `job_run` sharing `rootRunId` by walking the keyset cursor.\n * Empty when the jobs subsystem is absent.\n */\n private async collectRuns(\n rootRunId: string,\n tenantId?: string | null,\n ): Promise<JobRunSummary[]> {\n if (!this.jobRuns) return [];\n const out: JobRunSummary[] = [];\n let cursor: string | undefined;\n for (let page = 0; page < MAX_TIMELINE_PAGES; page += 1) {\n const result = await this.jobRuns.listJobRuns({\n rootRunId,\n tenantId,\n cursor,\n });\n out.push(...result.items);\n if (!result.nextCursor) break;\n cursor = result.nextCursor;\n }\n return out;\n }\n\n /**\n * Drain every `domain_event` whose `metadata.rootRunId` matches by walking\n * the keyset cursor. Empty when the events read port is absent.\n */\n private async collectEvents(\n rootRunId: string,\n tenantId?: string | null,\n ): Promise<EventSummary[]> {\n if (!this.events) return [];\n const out: EventSummary[] = [];\n let cursor: string | undefined;\n for (let page = 0; page < MAX_TIMELINE_PAGES; page += 1) {\n const result = await this.events.listEvents({\n rootRunId,\n tenantId,\n cursor,\n });\n out.push(...result.items);\n if (!result.nextCursor) break;\n cursor = result.nextCursor;\n }\n return out;\n }\n}\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 * 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 */\nimport { tokenKey } from '../token-key';\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 *\n * ADR-037: namespaced `Symbol.for(...)` (via `tokenKey()`) so it matches by value\n * across runtime copies (the sibling string tokens above are already value-stable).\n * Note the jobs subsystem defines its own `REDIS_URL`-equivalent; this is the\n * events one.\n */\nexport const REDIS_URL = Symbol.for(tokenKey('events', '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 * Integration subsystem — DI tokens\n *\n * String constants (not Symbols) so they match by value across import\n * boundaries — same convention as the events subsystem (`EVENT_BUS`). The\n * jobs subsystem uses Symbols for its analogous tokens; events and integration\n * stay internally consistent with strings.\n *\n * Usage in use cases:\n * ```ts\n * constructor(\n * @Inject(INTEGRATION_CHANGE_SOURCE) private readonly source: IChangeSource<CanonicalOpportunity>,\n * @Inject(INTEGRATION_CURSOR_STORE) private readonly cursors: ICursorStore,\n * @Inject(INTEGRATION_FIELD_DIFFER) private readonly differ: IFieldDiffer<CanonicalOpportunity>,\n * @Inject(INTEGRATION_SINK) private readonly sink: IIntegrationSink<CanonicalOpportunity>,\n * @Inject(INTEGRATION_RUN_RECORDER) private readonly recorder: IIntegrationRunRecorder,\n * ) {}\n * ```\n *\n * Concrete bindings are registered by `IntegrationModule.forRoot(...)` (SYNC-6).\n */\n\nexport const INTEGRATION_CHANGE_SOURCE = 'INTEGRATION_CHANGE_SOURCE' as const;\nexport const INTEGRATION_CURSOR_STORE = 'INTEGRATION_CURSOR_STORE' as const;\nexport const INTEGRATION_FIELD_DIFFER = 'INTEGRATION_FIELD_DIFFER' as const;\nexport const INTEGRATION_SINK = 'INTEGRATION_SINK' as const;\n\n/**\n * Run-recorder token (SYNC-5). Backed by `IIntegrationRunRecorder`. Drizzle impl\n * lands in SYNC-4; tests provide inline fakes.\n */\nexport const INTEGRATION_RUN_RECORDER = 'INTEGRATION_RUN_RECORDER' as const;\n\n/**\n * Injection token for the resolved `IntegrationModuleOptions` object (SYNC-6).\n *\n * Backends that need to observe module configuration (e.g. `multiTenant`\n * flag, pool filters) inject via this token. Provided automatically by\n * `IntegrationModule.forRoot(...)` / `IntegrationModule.forRootAsync(...)`.\n */\nexport const INTEGRATION_MODULE_OPTIONS = 'INTEGRATION_MODULE_OPTIONS' as const;\n\n/**\n * Injection token for the resolved multi-tenancy flag (SYNC-6).\n *\n * Provided by `IntegrationModule.forRoot(...)` as `options.multiTenant ?? false`.\n * Consumed by `ExecuteIntegrationUseCase` to enforce the tenantId-is-required rule.\n */\nexport const INTEGRATION_MULTI_TENANT = 'INTEGRATION_MULTI_TENANT' as const;\n\n/**\n * Injection token for the entity-keyed `IEntityChangeSourceRegistry` (C7,\n * #336). Bound to the codegen-emitted aggregator that folds per-provider\n * adapter contributions into one registry (RFC-0001 §3, emitted by Track D\n * D3/D4).\n *\n * A string constant, not `Symbol.for(...)`, to match this subsystem's token\n * convention (see file header). The originating issue's code block proposed a\n * `Symbol.for('@pattern-stack/codegen.entity-change-source-registry')` key,\n * predating the sync→integration consolidation onto string tokens; kept as a\n * string here for internal consistency with the other INTEGRATION_* tokens.\n */\nexport const ENTITY_CHANGE_SOURCE_REGISTRY = 'ENTITY_CHANGE_SOURCE_REGISTRY' as const;\n","/**\n * BridgeMetricsReporter — internal consumer of `IObservability`\n * (ADR-025, OBS-6).\n *\n * Periodically samples `getBridgeDeliveryHistogram` from the observability\n * facade and emits one log line per tick. Auto-registered by\n * `ObservabilityModule.forRoot()` when\n * `options.reporters.bridgeMetrics.enabled === true`. Consumers configure\n * via options; they never import this class. Not exported from the\n * module's `exports` array — internal.\n *\n * # Invariants (enforced by skill + ADR-025)\n *\n * - Injects ONLY `OBSERVABILITY` + `OBSERVABILITY_MODULE_OPTIONS`. Never\n * `BRIDGE_DELIVERY_REPO` or any other sibling token. Reporters are\n * consumers of the composed facade, not parallel composers.\n * - Never reaches into sibling tables or extends `IObservability`.\n * - Errors isolated per-tick (logged, never rethrown) so a transient\n * sibling failure does not kill the interval.\n * - `tenantId` passes VERBATIM to the facade — observability owns tenant\n * semantics, reporters don't re-implement them.\n *\n * # Lifecycle\n *\n * - `onModuleInit` — eager first-tick, then `setInterval`. Handle is\n * `.unref()`-ed when supported so the loop never blocks node shutdown.\n * - `onModuleDestroy` — `clearInterval` + null the handle. Idempotent.\n *\n * No `@nestjs/schedule` dependency — raw `setInterval` keeps the runtime\n * footprint minimal and avoids pulling a decorator framework in for a\n * single loop.\n */\nimport {\n Inject,\n Injectable,\n Logger,\n type OnModuleDestroy,\n type OnModuleInit,\n} from '@nestjs/common';\n\nimport type { IObservability } from '../observability.protocol';\nimport {\n OBSERVABILITY,\n OBSERVABILITY_MODULE_OPTIONS,\n} from '../observability.tokens';\n// Type-only imports — the module imports this file as a value for DI\n// registration, and this file imports config types back. Keeping the\n// back-edge type-only prevents a runtime circular-import.\nimport type {\n BridgeMetricsReporterConfig,\n ObservabilityModuleOptions,\n} from '../observability.module';\n\n@Injectable()\nexport class BridgeMetricsReporter implements OnModuleInit, OnModuleDestroy {\n private readonly logger = new Logger(BridgeMetricsReporter.name);\n private handle: ReturnType<typeof setInterval> | null = null;\n private readonly config: BridgeMetricsReporterConfig | undefined;\n\n constructor(\n @Inject(OBSERVABILITY) private readonly observability: IObservability,\n @Inject(OBSERVABILITY_MODULE_OPTIONS) options: ObservabilityModuleOptions,\n ) {\n this.config = options.reporters?.bridgeMetrics;\n }\n\n onModuleInit(): void {\n if (!this.config || !this.config.enabled) {\n this.logger.log('BridgeMetricsReporter disabled');\n return;\n }\n if (this.config.intervalMs <= 0 || this.config.windowHours <= 0) {\n this.logger.warn(\n `invalid config; not starting: intervalMs=${this.config.intervalMs} windowHours=${this.config.windowHours}`,\n );\n return;\n }\n // Eager first-tick so consumers see data immediately on boot, without\n // waiting `intervalMs` for the first sample.\n void this.runOnce();\n this.handle = setInterval(() => {\n void this.runOnce();\n }, this.config.intervalMs);\n // `.unref()` lets the node process exit even if the interval is still\n // scheduled — important in short-lived CLI/test contexts. Guarded\n // because browser-shimmed timers may not expose it.\n if (typeof this.handle.unref === 'function') this.handle.unref();\n }\n\n onModuleDestroy(): void {\n if (this.handle !== null) {\n clearInterval(this.handle);\n this.handle = null;\n }\n }\n\n /**\n * Single sample. Public so tests and future ops tooling can trigger a\n * sample on demand without waiting for the interval. Errors are caught\n * and logged here — never rethrown — so a transient sibling failure\n * does not kill subsequent ticks.\n */\n async runOnce(): Promise<void> {\n if (!this.config || !this.config.enabled) return;\n try {\n const h = await this.observability.getBridgeDeliveryHistogram(\n this.config.windowHours,\n this.config.tenantId,\n );\n this.logger.log(\n `bridge-delivery window=${this.config.windowHours}h tenant=${this.config.tenantId ?? 'default'} pending=${h.pending} delivered=${h.delivered} skipped=${h.skipped} failed=${h.failed}`,\n );\n } catch (err) {\n this.logger.error(\n 'BridgeMetricsReporter runOnce failed',\n err instanceof Error ? err.stack : String(err),\n );\n }\n }\n}\n","/**\n * Base class for observability-specific errors.\n *\n * Phase-1 `IObservability` methods do not throw — missing sibling ports\n * degrade to empty shapes (see `ObservabilityService`). This class exists\n * so future extensions (OBS-6 reporter misconfiguration, phase-2 Drizzle\n * extensions for `pg_stat_*` sampling, etc.) have a named base without\n * churning the barrel when they land.\n */\nexport class ObservabilityError extends Error {\n constructor(\n message: string,\n override readonly cause?: unknown,\n ) {\n super(message);\n this.name = 'ObservabilityError';\n }\n}\n"],"mappings":"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","names":["Inject","Injectable","Injectable","Inject"]}
|
|
@@ -22,11 +22,19 @@ var OBSERVABILITY_MODULE_OPTIONS = "OBSERVABILITY_MODULE_OPTIONS";
|
|
|
22
22
|
// runtime/subsystems/observability/observability.service.ts
|
|
23
23
|
import { Inject, Injectable, Optional } from "@nestjs/common";
|
|
24
24
|
|
|
25
|
+
// runtime/subsystems/token-key.ts
|
|
26
|
+
var PKG = "@pattern-stack/codegen";
|
|
27
|
+
var tokenKey = (area, name) => `${PKG}.${area}.${name}`;
|
|
28
|
+
|
|
25
29
|
// runtime/subsystems/jobs/jobs-domain.tokens.ts
|
|
26
|
-
var
|
|
30
|
+
var JOB_ORCHESTRATOR = Symbol.for(tokenKey("jobs", "orchestrator"));
|
|
31
|
+
var JOB_RUN_SERVICE = Symbol.for(tokenKey("jobs", "run-service"));
|
|
32
|
+
var JOB_STEP_SERVICE = Symbol.for(tokenKey("jobs", "step-service"));
|
|
33
|
+
var JOBS_MULTI_TENANT = Symbol.for(tokenKey("jobs", "multi-tenant"));
|
|
27
34
|
|
|
28
35
|
// runtime/subsystems/events/events.tokens.ts
|
|
29
36
|
var EVENT_READ_PORT = "EVENT_READ_PORT";
|
|
37
|
+
var REDIS_URL = Symbol.for(tokenKey("events", "redis-url"));
|
|
30
38
|
|
|
31
39
|
// runtime/subsystems/bridge/bridge.tokens.ts
|
|
32
40
|
var BRIDGE_DELIVERY_REPO = "BRIDGE_DELIVERY_REPO";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../runtime/subsystems/observability/observability.module.ts","../../../../runtime/subsystems/observability/observability.tokens.ts","../../../../runtime/subsystems/observability/observability.service.ts","../../../../runtime/subsystems/jobs/jobs-domain.tokens.ts","../../../../runtime/subsystems/events/events.tokens.ts","../../../../runtime/subsystems/bridge/bridge.tokens.ts","../../../../runtime/subsystems/integration/integration.tokens.ts","../../../../runtime/subsystems/observability/reporters/bridge-metrics.reporter.ts"],"sourcesContent":["/**\n * ObservabilityModule — combiner subsystem (ADR-025, OBS-5, OBS-6).\n *\n * Composes the jobs, bridge, and integration read ports into a single\n * `IObservability` facade. Owned by no sibling subsystem; it consumes\n * their tokens via DI, which the consumer app wires by registering the\n * sibling modules in the right order (like BridgeModule — the named\n * precedent in ADR-025).\n *\n * Consumer wiring (register AFTER the composed sibling modules):\n * ```ts\n * @Module({\n * imports: [\n * EventsModule.forRoot({ backend: 'drizzle' }),\n * JobsDomainModule.forRoot({ backend: 'drizzle' }),\n * BridgeModule.forRoot({ backend: 'drizzle' }),\n * IntegrationModule.forRoot({ backend: 'drizzle' }),\n * ObservabilityModule.forRoot({\n * reporters: {\n * bridgeMetrics: {\n * enabled: true,\n * intervalMs: 60_000,\n * windowHours: 1,\n * },\n * },\n * }),\n * ],\n * })\n * class AppModule {}\n * ```\n *\n * # No `backend` option — intentional\n *\n * Unlike ADR-008 infrastructure subsystems (events / jobs / cache /\n * storage), observability is a combiner per ADR-025 and owns no durable\n * state. The \"backend\" is whichever backends the composed subsystems are\n * running — portability is inherited, not declared. See ADR-025 §4 (when\n * to pick combiner vs. infrastructure) and\n * `.claude/skills/observability/SKILL.md` §1.\n *\n * # Graceful sibling absence\n *\n * The consumed sibling tokens are `@Optional()` inside\n * `ObservabilityService`. An app that only installed a subset of the\n * composed subsystems can still register `ObservabilityModule`; the\n * methods whose sibling is missing return empty shapes.\n *\n * # Reporters (OBS-6)\n *\n * Internal consumers of the `OBSERVABILITY` facade — auto-registered\n * when the matching `reporters.*` key is present and enabled. Reporters\n * are NOT added to `exports`; they are a module-internal concern.\n * Consumers configure them via options; they never import the reporter\n * classes directly.\n */\nimport { Module, type DynamicModule, type Provider } from '@nestjs/common';\n\nimport {\n OBSERVABILITY,\n OBSERVABILITY_MODULE_OPTIONS,\n} from './observability.tokens';\nimport { ObservabilityService } from './observability.service';\nimport { BridgeMetricsReporter } from './reporters/bridge-metrics.reporter';\n\n/**\n * Config for the bridge-delivery sampler (OBS-6). All fields are required\n * when `enabled: true` — the config is declarative and explicit; there\n * are no hidden defaults. Lives on the module (not the reporter file) so\n * the module's options type stays the single authoritative schema for\n * everything `forRoot` accepts.\n */\nexport interface BridgeMetricsReporterConfig {\n /** Master switch. Reporter is only registered as a provider when this\n * is `true` (see `forRoot`). */\n enabled: boolean;\n /** Sampling period in ms. Must be `> 0` when `enabled: true`. */\n intervalMs: number;\n /** Trailing window (hours) passed to `getBridgeDeliveryHistogram`.\n * Must be `> 0` when `enabled: true`. */\n windowHours: number;\n /** Forwarded verbatim to `IObservability.getBridgeDeliveryHistogram`.\n * - `undefined` — sibling default semantics\n * - `null` — explicit cross-tenant match\n * - `string` — filter to that single tenant */\n tenantId?: string | null;\n}\n\n/**\n * Named-map of reporter configs. Named, not array, so consumers can toggle\n * individual reporters by key without juggling order and so future reporters\n * can be added without breaking existing configs.\n */\nexport interface ObservabilityReportersOptions {\n bridgeMetrics?: BridgeMetricsReporterConfig;\n}\n\n/**\n * Options for `ObservabilityModule.forRoot()`. Currently only `reporters`;\n * room to grow (sampling, exporters) without changing the module signature.\n */\nexport interface ObservabilityModuleOptions {\n reporters?: ObservabilityReportersOptions;\n}\n\n@Module({})\nexport class ObservabilityModule {\n static forRoot(options: ObservabilityModuleOptions = {}): DynamicModule {\n const providers: Provider[] = [\n // Expose the resolved options so internal reporters can read their\n // own config via `OBSERVABILITY_MODULE_OPTIONS`.\n { provide: OBSERVABILITY_MODULE_OPTIONS, useValue: options },\n // Register the concrete class as the canonical instance.\n ObservabilityService,\n // OBSERVABILITY token points at the same instance — consumers inject\n // the token, not the class, per ADR-025 §Shape (index.ts does NOT\n // export `ObservabilityService`).\n { provide: OBSERVABILITY, useExisting: ObservabilityService },\n ];\n\n // Reporters: auto-registered when enabled, not added to `exports`.\n // They are internal consumers of OBSERVABILITY; consumers configure\n // them via options rather than importing the classes.\n if (options.reporters?.bridgeMetrics?.enabled === true) {\n providers.push(BridgeMetricsReporter);\n }\n\n return {\n module: ObservabilityModule,\n global: true,\n providers,\n exports: [OBSERVABILITY, OBSERVABILITY_MODULE_OPTIONS],\n };\n }\n}\n","/**\n * Observability combiner subsystem — DI tokens (ADR-025, OBS-5).\n *\n * String constants (not Symbols), matching the events / bridge / integration\n * convention. The jobs subsystem uses Symbols for its analogous tokens;\n * observability stays internally consistent with its sibling combiner\n * (bridge) because the two are structurally paired (ADR-025).\n *\n * Usage in consumers:\n * ```ts\n * constructor(@Inject(OBSERVABILITY) private readonly obs: IObservability) {}\n * ```\n */\n\n/**\n * Token for the `IObservability` composer facade (OBS-5). Resolves to the\n * single `ObservabilityService` instance registered by\n * `ObservabilityModule.forRoot(...)`.\n */\nexport const OBSERVABILITY = 'OBSERVABILITY' as const;\n\n/**\n * Token for the resolved `ObservabilityModuleOptions` object. Provided by\n * `ObservabilityModule.forRoot(...)`. Reserved for phase 2 — the current\n * options shape is empty; OBS-6 will extend it with a `reporters` field.\n */\nexport const OBSERVABILITY_MODULE_OPTIONS = 'OBSERVABILITY_MODULE_OPTIONS' as const;\n","/**\n * ObservabilityService — `IObservability` combiner implementation\n * (ADR-025, OBS-5).\n *\n * Composes read methods across the jobs, bridge, and integration subsystems via\n * DI. Owns no state, no schema, no SQL. Every method is a one-line\n * delegation to the sibling port that already encodes the semantics.\n *\n * # Missing-port degradation\n *\n * Every sibling is injected with `@Optional()`. When the consumer's app\n * has not wired a given subsystem, the corresponding field is `undefined`\n * and the delegating method returns an empty shape:\n * - array methods return `[]`\n * - `getBridgeDeliveryHistogram` returns `{ pending: 0, delivered: 0,\n * skipped: 0, failed: 0 }` (matches the bridge protocol's fixed-keys\n * contract so consumers can render a 4-row chart unconditionally).\n *\n * Graceful absence is the whole point of the combiner pattern (ADR-025\n * §Shape, constraint 3) — a consumer that only installed the jobs\n * subsystem can still inject `OBSERVABILITY` and get useful job reads\n * without wiring the rest.\n *\n * # Multi-tenancy\n *\n * `tenantId` passes VERBATIM from the public method to the owning port.\n * `ObservabilityService` never re-implements tenant filtering. See\n * `.claude/skills/observability/SKILL.md` §3 and ADR-025.\n */\nimport { Inject, Injectable, Optional } from '@nestjs/common';\n\nimport { JOB_RUN_SERVICE } from '../jobs/jobs-domain.tokens';\nimport type {\n IJobRunService,\n JobRunFailure,\n JobRunPage,\n JobRunSummary,\n ListJobRunsQuery,\n PoolStatusCount,\n} from '../jobs/job-run-service.protocol';\n\nimport { EVENT_READ_PORT } from '../events/events.tokens';\nimport type {\n EventPage,\n EventSummary,\n IEventReadPort,\n ListEventsQuery,\n} from '../events/event-read.protocol';\n\nimport { BRIDGE_DELIVERY_REPO } from '../bridge/bridge.tokens';\nimport type { IJobBridge, StatusHistogram } from '../bridge/bridge.protocol';\n\nimport { INTEGRATION_CURSOR_STORE, INTEGRATION_RUN_RECORDER } from '../integration/integration.tokens';\nimport type {\n IIntegrationRunRecorder,\n IntegrationRunSummary,\n} from '../integration/integration-run-recorder.protocol';\nimport type {\n CursorSnapshot,\n ICursorStore,\n} from '../integration/integration-cursor-store.protocol';\n\nimport type {\n CorrelationTimeline,\n CorrelationTimelineEntry,\n IObservability,\n} from './observability.protocol';\n\n/**\n * Safety bound on how many pages the correlation timeline will walk when\n * draining a sibling port. A single run tree producing more than\n * 50 pages × default page size of correlated rows is pathological; cap to\n * keep the stitch bounded rather than unbounded-loop on bad data.\n */\nconst MAX_TIMELINE_PAGES = 50;\n\n@Injectable()\nexport class ObservabilityService implements IObservability {\n /**\n * All-zero histogram used when the bridge subsystem is absent. Matches\n * the bridge protocol's \"fixed keys, zero-filled\" contract so consumers\n * never branch on presence.\n */\n private static readonly EMPTY_HISTOGRAM: StatusHistogram = {\n pending: 0,\n delivered: 0,\n skipped: 0,\n failed: 0,\n };\n\n /** Empty page used when a sibling read port is absent. */\n private static readonly EMPTY_JOB_RUN_PAGE: JobRunPage = {\n items: [],\n nextCursor: null,\n };\n private static readonly EMPTY_EVENT_PAGE: EventPage = {\n items: [],\n nextCursor: null,\n };\n\n constructor(\n @Optional()\n @Inject(JOB_RUN_SERVICE)\n private readonly jobRuns?: IJobRunService,\n @Optional()\n @Inject(BRIDGE_DELIVERY_REPO)\n private readonly bridge?: IJobBridge,\n @Optional()\n @Inject(INTEGRATION_RUN_RECORDER)\n private readonly integrationRuns?: IIntegrationRunRecorder,\n @Optional()\n @Inject(INTEGRATION_CURSOR_STORE)\n private readonly cursors?: ICursorStore,\n @Optional()\n @Inject(EVENT_READ_PORT)\n private readonly events?: IEventReadPort | null,\n ) {}\n\n async getPoolDepths(tenantId?: string | null): Promise<PoolStatusCount[]> {\n if (!this.jobRuns) return [];\n return this.jobRuns.countByPoolAndStatus(tenantId);\n }\n\n async getRecentFailedJobs(\n limit: number,\n tenantId?: string | null,\n ): Promise<JobRunFailure[]> {\n if (!this.jobRuns) return [];\n return this.jobRuns.listRecentFailed(limit, tenantId);\n }\n\n async getBridgeDeliveryHistogram(\n windowHours: number,\n tenantId?: string | null,\n ): Promise<StatusHistogram> {\n if (!this.bridge) return { ...ObservabilityService.EMPTY_HISTOGRAM };\n return this.bridge.getStatusHistogram(windowHours, tenantId);\n }\n\n async getRecentIntegrationRuns(\n limit: number,\n subscriptionId?: string,\n tenantId?: string | null,\n ): Promise<IntegrationRunSummary[]> {\n if (!this.integrationRuns) return [];\n return this.integrationRuns.listRecent(limit, subscriptionId, tenantId);\n }\n\n async getCursors(tenantId?: string | null): Promise<CursorSnapshot[]> {\n if (!this.cursors) return [];\n return this.cursors.listAll(tenantId);\n }\n\n async listJobRuns(query?: ListJobRunsQuery): Promise<JobRunPage> {\n if (!this.jobRuns) {\n return { ...ObservabilityService.EMPTY_JOB_RUN_PAGE };\n }\n return this.jobRuns.listJobRuns(query);\n }\n\n async listEvents(query?: ListEventsQuery): Promise<EventPage> {\n if (!this.events) {\n return { ...ObservabilityService.EMPTY_EVENT_PAGE };\n }\n return this.events.listEvents(query);\n }\n\n async getCorrelationTimeline(\n rootRunId: string,\n tenantId?: string | null,\n ): Promise<CorrelationTimeline> {\n const runs = await this.collectRuns(rootRunId, tenantId);\n const events = await this.collectEvents(rootRunId, tenantId);\n\n const entries: CorrelationTimelineEntry[] = [\n ...runs.map(\n (run): CorrelationTimelineEntry => ({\n kind: 'job_run',\n at: run.createdAt,\n run,\n }),\n ),\n ...events.map(\n (event): CorrelationTimelineEntry => ({\n kind: 'event',\n at: event.occurredAt,\n event,\n }),\n ),\n ];\n\n // Ascending chronological order. Stable tie-break: job runs before\n // events at the same instant (the run that emits an event precedes it).\n entries.sort((a, b) => {\n const dt = a.at.getTime() - b.at.getTime();\n if (dt !== 0) return dt;\n if (a.kind === b.kind) return 0;\n return a.kind === 'job_run' ? -1 : 1;\n });\n\n const startedAt = entries.length > 0 ? entries[0]!.at : null;\n const lastActivityAt =\n entries.length > 0 ? entries[entries.length - 1]!.at : null;\n\n return {\n rootRunId,\n entries,\n summary: {\n runCount: runs.length,\n eventCount: events.length,\n startedAt,\n lastActivityAt,\n },\n };\n }\n\n /**\n * Drain every `job_run` sharing `rootRunId` by walking the keyset cursor.\n * Empty when the jobs subsystem is absent.\n */\n private async collectRuns(\n rootRunId: string,\n tenantId?: string | null,\n ): Promise<JobRunSummary[]> {\n if (!this.jobRuns) return [];\n const out: JobRunSummary[] = [];\n let cursor: string | undefined;\n for (let page = 0; page < MAX_TIMELINE_PAGES; page += 1) {\n const result = await this.jobRuns.listJobRuns({\n rootRunId,\n tenantId,\n cursor,\n });\n out.push(...result.items);\n if (!result.nextCursor) break;\n cursor = result.nextCursor;\n }\n return out;\n }\n\n /**\n * Drain every `domain_event` whose `metadata.rootRunId` matches by walking\n * the keyset cursor. Empty when the events read port is absent.\n */\n private async collectEvents(\n rootRunId: string,\n tenantId?: string | null,\n ): Promise<EventSummary[]> {\n if (!this.events) return [];\n const out: EventSummary[] = [];\n let cursor: string | undefined;\n for (let page = 0; page < MAX_TIMELINE_PAGES; page += 1) {\n const result = await this.events.listEvents({\n rootRunId,\n tenantId,\n cursor,\n });\n out.push(...result.items);\n if (!result.nextCursor) break;\n cursor = result.nextCursor;\n }\n return out;\n }\n}\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 * 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 * Integration subsystem — DI tokens\n *\n * String constants (not Symbols) so they match by value across import\n * boundaries — same convention as the events subsystem (`EVENT_BUS`). The\n * jobs subsystem uses Symbols for its analogous tokens; events and integration\n * stay internally consistent with strings.\n *\n * Usage in use cases:\n * ```ts\n * constructor(\n * @Inject(INTEGRATION_CHANGE_SOURCE) private readonly source: IChangeSource<CanonicalOpportunity>,\n * @Inject(INTEGRATION_CURSOR_STORE) private readonly cursors: ICursorStore,\n * @Inject(INTEGRATION_FIELD_DIFFER) private readonly differ: IFieldDiffer<CanonicalOpportunity>,\n * @Inject(INTEGRATION_SINK) private readonly sink: IIntegrationSink<CanonicalOpportunity>,\n * @Inject(INTEGRATION_RUN_RECORDER) private readonly recorder: IIntegrationRunRecorder,\n * ) {}\n * ```\n *\n * Concrete bindings are registered by `IntegrationModule.forRoot(...)` (SYNC-6).\n */\n\nexport const INTEGRATION_CHANGE_SOURCE = 'INTEGRATION_CHANGE_SOURCE' as const;\nexport const INTEGRATION_CURSOR_STORE = 'INTEGRATION_CURSOR_STORE' as const;\nexport const INTEGRATION_FIELD_DIFFER = 'INTEGRATION_FIELD_DIFFER' as const;\nexport const INTEGRATION_SINK = 'INTEGRATION_SINK' as const;\n\n/**\n * Run-recorder token (SYNC-5). Backed by `IIntegrationRunRecorder`. Drizzle impl\n * lands in SYNC-4; tests provide inline fakes.\n */\nexport const INTEGRATION_RUN_RECORDER = 'INTEGRATION_RUN_RECORDER' as const;\n\n/**\n * Injection token for the resolved `IntegrationModuleOptions` object (SYNC-6).\n *\n * Backends that need to observe module configuration (e.g. `multiTenant`\n * flag, pool filters) inject via this token. Provided automatically by\n * `IntegrationModule.forRoot(...)` / `IntegrationModule.forRootAsync(...)`.\n */\nexport const INTEGRATION_MODULE_OPTIONS = 'INTEGRATION_MODULE_OPTIONS' as const;\n\n/**\n * Injection token for the resolved multi-tenancy flag (SYNC-6).\n *\n * Provided by `IntegrationModule.forRoot(...)` as `options.multiTenant ?? false`.\n * Consumed by `ExecuteIntegrationUseCase` to enforce the tenantId-is-required rule.\n */\nexport const INTEGRATION_MULTI_TENANT = 'INTEGRATION_MULTI_TENANT' as const;\n\n/**\n * Injection token for the entity-keyed `IEntityChangeSourceRegistry` (C7,\n * #336). Bound to the codegen-emitted aggregator that folds per-provider\n * adapter contributions into one registry (RFC-0001 §3, emitted by Track D\n * D3/D4).\n *\n * A string constant, not `Symbol.for(...)`, to match this subsystem's token\n * convention (see file header). The originating issue's code block proposed a\n * `Symbol.for('@pattern-stack/codegen.entity-change-source-registry')` key,\n * predating the sync→integration consolidation onto string tokens; kept as a\n * string here for internal consistency with the other INTEGRATION_* tokens.\n */\nexport const ENTITY_CHANGE_SOURCE_REGISTRY = 'ENTITY_CHANGE_SOURCE_REGISTRY' as const;\n","/**\n * BridgeMetricsReporter — internal consumer of `IObservability`\n * (ADR-025, OBS-6).\n *\n * Periodically samples `getBridgeDeliveryHistogram` from the observability\n * facade and emits one log line per tick. Auto-registered by\n * `ObservabilityModule.forRoot()` when\n * `options.reporters.bridgeMetrics.enabled === true`. Consumers configure\n * via options; they never import this class. Not exported from the\n * module's `exports` array — internal.\n *\n * # Invariants (enforced by skill + ADR-025)\n *\n * - Injects ONLY `OBSERVABILITY` + `OBSERVABILITY_MODULE_OPTIONS`. Never\n * `BRIDGE_DELIVERY_REPO` or any other sibling token. Reporters are\n * consumers of the composed facade, not parallel composers.\n * - Never reaches into sibling tables or extends `IObservability`.\n * - Errors isolated per-tick (logged, never rethrown) so a transient\n * sibling failure does not kill the interval.\n * - `tenantId` passes VERBATIM to the facade — observability owns tenant\n * semantics, reporters don't re-implement them.\n *\n * # Lifecycle\n *\n * - `onModuleInit` — eager first-tick, then `setInterval`. Handle is\n * `.unref()`-ed when supported so the loop never blocks node shutdown.\n * - `onModuleDestroy` — `clearInterval` + null the handle. Idempotent.\n *\n * No `@nestjs/schedule` dependency — raw `setInterval` keeps the runtime\n * footprint minimal and avoids pulling a decorator framework in for a\n * single loop.\n */\nimport {\n Inject,\n Injectable,\n Logger,\n type OnModuleDestroy,\n type OnModuleInit,\n} from '@nestjs/common';\n\nimport type { IObservability } from '../observability.protocol';\nimport {\n OBSERVABILITY,\n OBSERVABILITY_MODULE_OPTIONS,\n} from '../observability.tokens';\n// Type-only imports — the module imports this file as a value for DI\n// registration, and this file imports config types back. Keeping the\n// back-edge type-only prevents a runtime circular-import.\nimport type {\n BridgeMetricsReporterConfig,\n ObservabilityModuleOptions,\n} from '../observability.module';\n\n@Injectable()\nexport class BridgeMetricsReporter implements OnModuleInit, OnModuleDestroy {\n private readonly logger = new Logger(BridgeMetricsReporter.name);\n private handle: ReturnType<typeof setInterval> | null = null;\n private readonly config: BridgeMetricsReporterConfig | undefined;\n\n constructor(\n @Inject(OBSERVABILITY) private readonly observability: IObservability,\n @Inject(OBSERVABILITY_MODULE_OPTIONS) options: ObservabilityModuleOptions,\n ) {\n this.config = options.reporters?.bridgeMetrics;\n }\n\n onModuleInit(): void {\n if (!this.config || !this.config.enabled) {\n this.logger.log('BridgeMetricsReporter disabled');\n return;\n }\n if (this.config.intervalMs <= 0 || this.config.windowHours <= 0) {\n this.logger.warn(\n `invalid config; not starting: intervalMs=${this.config.intervalMs} windowHours=${this.config.windowHours}`,\n );\n return;\n }\n // Eager first-tick so consumers see data immediately on boot, without\n // waiting `intervalMs` for the first sample.\n void this.runOnce();\n this.handle = setInterval(() => {\n void this.runOnce();\n }, this.config.intervalMs);\n // `.unref()` lets the node process exit even if the interval is still\n // scheduled — important in short-lived CLI/test contexts. Guarded\n // because browser-shimmed timers may not expose it.\n if (typeof this.handle.unref === 'function') this.handle.unref();\n }\n\n onModuleDestroy(): void {\n if (this.handle !== null) {\n clearInterval(this.handle);\n this.handle = null;\n }\n }\n\n /**\n * Single sample. Public so tests and future ops tooling can trigger a\n * sample on demand without waiting for the interval. Errors are caught\n * and logged here — never rethrown — so a transient sibling failure\n * does not kill subsequent ticks.\n */\n async runOnce(): Promise<void> {\n if (!this.config || !this.config.enabled) return;\n try {\n const h = await this.observability.getBridgeDeliveryHistogram(\n this.config.windowHours,\n this.config.tenantId,\n );\n this.logger.log(\n `bridge-delivery window=${this.config.windowHours}h tenant=${this.config.tenantId ?? 'default'} pending=${h.pending} delivered=${h.delivered} skipped=${h.skipped} failed=${h.failed}`,\n );\n } catch (err) {\n this.logger.error(\n 'BridgeMetricsReporter runOnce failed',\n err instanceof Error ? err.stack : String(err),\n );\n }\n }\n}\n"],"mappings":"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","names":["Inject","Injectable","Injectable","Inject"]}
|
|
1
|
+
{"version":3,"sources":["../../../../runtime/subsystems/observability/observability.module.ts","../../../../runtime/subsystems/observability/observability.tokens.ts","../../../../runtime/subsystems/observability/observability.service.ts","../../../../runtime/subsystems/token-key.ts","../../../../runtime/subsystems/jobs/jobs-domain.tokens.ts","../../../../runtime/subsystems/events/events.tokens.ts","../../../../runtime/subsystems/bridge/bridge.tokens.ts","../../../../runtime/subsystems/integration/integration.tokens.ts","../../../../runtime/subsystems/observability/reporters/bridge-metrics.reporter.ts"],"sourcesContent":["/**\n * ObservabilityModule — combiner subsystem (ADR-025, OBS-5, OBS-6).\n *\n * Composes the jobs, bridge, and integration read ports into a single\n * `IObservability` facade. Owned by no sibling subsystem; it consumes\n * their tokens via DI, which the consumer app wires by registering the\n * sibling modules in the right order (like BridgeModule — the named\n * precedent in ADR-025).\n *\n * Consumer wiring (register AFTER the composed sibling modules):\n * ```ts\n * @Module({\n * imports: [\n * EventsModule.forRoot({ backend: 'drizzle' }),\n * JobsDomainModule.forRoot({ backend: 'drizzle' }),\n * BridgeModule.forRoot({ backend: 'drizzle' }),\n * IntegrationModule.forRoot({ backend: 'drizzle' }),\n * ObservabilityModule.forRoot({\n * reporters: {\n * bridgeMetrics: {\n * enabled: true,\n * intervalMs: 60_000,\n * windowHours: 1,\n * },\n * },\n * }),\n * ],\n * })\n * class AppModule {}\n * ```\n *\n * # No `backend` option — intentional\n *\n * Unlike ADR-008 infrastructure subsystems (events / jobs / cache /\n * storage), observability is a combiner per ADR-025 and owns no durable\n * state. The \"backend\" is whichever backends the composed subsystems are\n * running — portability is inherited, not declared. See ADR-025 §4 (when\n * to pick combiner vs. infrastructure) and\n * `.claude/skills/observability/SKILL.md` §1.\n *\n * # Graceful sibling absence\n *\n * The consumed sibling tokens are `@Optional()` inside\n * `ObservabilityService`. An app that only installed a subset of the\n * composed subsystems can still register `ObservabilityModule`; the\n * methods whose sibling is missing return empty shapes.\n *\n * # Reporters (OBS-6)\n *\n * Internal consumers of the `OBSERVABILITY` facade — auto-registered\n * when the matching `reporters.*` key is present and enabled. Reporters\n * are NOT added to `exports`; they are a module-internal concern.\n * Consumers configure them via options; they never import the reporter\n * classes directly.\n */\nimport { Module, type DynamicModule, type Provider } from '@nestjs/common';\n\nimport {\n OBSERVABILITY,\n OBSERVABILITY_MODULE_OPTIONS,\n} from './observability.tokens';\nimport { ObservabilityService } from './observability.service';\nimport { BridgeMetricsReporter } from './reporters/bridge-metrics.reporter';\n\n/**\n * Config for the bridge-delivery sampler (OBS-6). All fields are required\n * when `enabled: true` — the config is declarative and explicit; there\n * are no hidden defaults. Lives on the module (not the reporter file) so\n * the module's options type stays the single authoritative schema for\n * everything `forRoot` accepts.\n */\nexport interface BridgeMetricsReporterConfig {\n /** Master switch. Reporter is only registered as a provider when this\n * is `true` (see `forRoot`). */\n enabled: boolean;\n /** Sampling period in ms. Must be `> 0` when `enabled: true`. */\n intervalMs: number;\n /** Trailing window (hours) passed to `getBridgeDeliveryHistogram`.\n * Must be `> 0` when `enabled: true`. */\n windowHours: number;\n /** Forwarded verbatim to `IObservability.getBridgeDeliveryHistogram`.\n * - `undefined` — sibling default semantics\n * - `null` — explicit cross-tenant match\n * - `string` — filter to that single tenant */\n tenantId?: string | null;\n}\n\n/**\n * Named-map of reporter configs. Named, not array, so consumers can toggle\n * individual reporters by key without juggling order and so future reporters\n * can be added without breaking existing configs.\n */\nexport interface ObservabilityReportersOptions {\n bridgeMetrics?: BridgeMetricsReporterConfig;\n}\n\n/**\n * Options for `ObservabilityModule.forRoot()`. Currently only `reporters`;\n * room to grow (sampling, exporters) without changing the module signature.\n */\nexport interface ObservabilityModuleOptions {\n reporters?: ObservabilityReportersOptions;\n}\n\n@Module({})\nexport class ObservabilityModule {\n static forRoot(options: ObservabilityModuleOptions = {}): DynamicModule {\n const providers: Provider[] = [\n // Expose the resolved options so internal reporters can read their\n // own config via `OBSERVABILITY_MODULE_OPTIONS`.\n { provide: OBSERVABILITY_MODULE_OPTIONS, useValue: options },\n // Register the concrete class as the canonical instance.\n ObservabilityService,\n // OBSERVABILITY token points at the same instance — consumers inject\n // the token, not the class, per ADR-025 §Shape (index.ts does NOT\n // export `ObservabilityService`).\n { provide: OBSERVABILITY, useExisting: ObservabilityService },\n ];\n\n // Reporters: auto-registered when enabled, not added to `exports`.\n // They are internal consumers of OBSERVABILITY; consumers configure\n // them via options rather than importing the classes.\n if (options.reporters?.bridgeMetrics?.enabled === true) {\n providers.push(BridgeMetricsReporter);\n }\n\n return {\n module: ObservabilityModule,\n global: true,\n providers,\n exports: [OBSERVABILITY, OBSERVABILITY_MODULE_OPTIONS],\n };\n }\n}\n","/**\n * Observability combiner subsystem — DI tokens (ADR-025, OBS-5).\n *\n * String constants (not Symbols), matching the events / bridge / integration\n * convention. The jobs subsystem uses Symbols for its analogous tokens;\n * observability stays internally consistent with its sibling combiner\n * (bridge) because the two are structurally paired (ADR-025).\n *\n * Usage in consumers:\n * ```ts\n * constructor(@Inject(OBSERVABILITY) private readonly obs: IObservability) {}\n * ```\n */\n\n/**\n * Token for the `IObservability` composer facade (OBS-5). Resolves to the\n * single `ObservabilityService` instance registered by\n * `ObservabilityModule.forRoot(...)`.\n */\nexport const OBSERVABILITY = 'OBSERVABILITY' as const;\n\n/**\n * Token for the resolved `ObservabilityModuleOptions` object. Provided by\n * `ObservabilityModule.forRoot(...)`. Reserved for phase 2 — the current\n * options shape is empty; OBS-6 will extend it with a `reporters` field.\n */\nexport const OBSERVABILITY_MODULE_OPTIONS = 'OBSERVABILITY_MODULE_OPTIONS' as const;\n","/**\n * ObservabilityService — `IObservability` combiner implementation\n * (ADR-025, OBS-5).\n *\n * Composes read methods across the jobs, bridge, and integration subsystems via\n * DI. Owns no state, no schema, no SQL. Every method is a one-line\n * delegation to the sibling port that already encodes the semantics.\n *\n * # Missing-port degradation\n *\n * Every sibling is injected with `@Optional()`. When the consumer's app\n * has not wired a given subsystem, the corresponding field is `undefined`\n * and the delegating method returns an empty shape:\n * - array methods return `[]`\n * - `getBridgeDeliveryHistogram` returns `{ pending: 0, delivered: 0,\n * skipped: 0, failed: 0 }` (matches the bridge protocol's fixed-keys\n * contract so consumers can render a 4-row chart unconditionally).\n *\n * Graceful absence is the whole point of the combiner pattern (ADR-025\n * §Shape, constraint 3) — a consumer that only installed the jobs\n * subsystem can still inject `OBSERVABILITY` and get useful job reads\n * without wiring the rest.\n *\n * # Multi-tenancy\n *\n * `tenantId` passes VERBATIM from the public method to the owning port.\n * `ObservabilityService` never re-implements tenant filtering. See\n * `.claude/skills/observability/SKILL.md` §3 and ADR-025.\n */\nimport { Inject, Injectable, Optional } from '@nestjs/common';\n\nimport { JOB_RUN_SERVICE } from '../jobs/jobs-domain.tokens';\nimport type {\n IJobRunService,\n JobRunFailure,\n JobRunPage,\n JobRunSummary,\n ListJobRunsQuery,\n PoolStatusCount,\n} from '../jobs/job-run-service.protocol';\n\nimport { EVENT_READ_PORT } from '../events/events.tokens';\nimport type {\n EventPage,\n EventSummary,\n IEventReadPort,\n ListEventsQuery,\n} from '../events/event-read.protocol';\n\nimport { BRIDGE_DELIVERY_REPO } from '../bridge/bridge.tokens';\nimport type { IJobBridge, StatusHistogram } from '../bridge/bridge.protocol';\n\nimport { INTEGRATION_CURSOR_STORE, INTEGRATION_RUN_RECORDER } from '../integration/integration.tokens';\nimport type {\n IIntegrationRunRecorder,\n IntegrationRunSummary,\n} from '../integration/integration-run-recorder.protocol';\nimport type {\n CursorSnapshot,\n ICursorStore,\n} from '../integration/integration-cursor-store.protocol';\n\nimport type {\n CorrelationTimeline,\n CorrelationTimelineEntry,\n IObservability,\n} from './observability.protocol';\n\n/**\n * Safety bound on how many pages the correlation timeline will walk when\n * draining a sibling port. A single run tree producing more than\n * 50 pages × default page size of correlated rows is pathological; cap to\n * keep the stitch bounded rather than unbounded-loop on bad data.\n */\nconst MAX_TIMELINE_PAGES = 50;\n\n@Injectable()\nexport class ObservabilityService implements IObservability {\n /**\n * All-zero histogram used when the bridge subsystem is absent. Matches\n * the bridge protocol's \"fixed keys, zero-filled\" contract so consumers\n * never branch on presence.\n */\n private static readonly EMPTY_HISTOGRAM: StatusHistogram = {\n pending: 0,\n delivered: 0,\n skipped: 0,\n failed: 0,\n };\n\n /** Empty page used when a sibling read port is absent. */\n private static readonly EMPTY_JOB_RUN_PAGE: JobRunPage = {\n items: [],\n nextCursor: null,\n };\n private static readonly EMPTY_EVENT_PAGE: EventPage = {\n items: [],\n nextCursor: null,\n };\n\n constructor(\n @Optional()\n @Inject(JOB_RUN_SERVICE)\n private readonly jobRuns?: IJobRunService,\n @Optional()\n @Inject(BRIDGE_DELIVERY_REPO)\n private readonly bridge?: IJobBridge,\n @Optional()\n @Inject(INTEGRATION_RUN_RECORDER)\n private readonly integrationRuns?: IIntegrationRunRecorder,\n @Optional()\n @Inject(INTEGRATION_CURSOR_STORE)\n private readonly cursors?: ICursorStore,\n @Optional()\n @Inject(EVENT_READ_PORT)\n private readonly events?: IEventReadPort | null,\n ) {}\n\n async getPoolDepths(tenantId?: string | null): Promise<PoolStatusCount[]> {\n if (!this.jobRuns) return [];\n return this.jobRuns.countByPoolAndStatus(tenantId);\n }\n\n async getRecentFailedJobs(\n limit: number,\n tenantId?: string | null,\n ): Promise<JobRunFailure[]> {\n if (!this.jobRuns) return [];\n return this.jobRuns.listRecentFailed(limit, tenantId);\n }\n\n async getBridgeDeliveryHistogram(\n windowHours: number,\n tenantId?: string | null,\n ): Promise<StatusHistogram> {\n if (!this.bridge) return { ...ObservabilityService.EMPTY_HISTOGRAM };\n return this.bridge.getStatusHistogram(windowHours, tenantId);\n }\n\n async getRecentIntegrationRuns(\n limit: number,\n subscriptionId?: string,\n tenantId?: string | null,\n ): Promise<IntegrationRunSummary[]> {\n if (!this.integrationRuns) return [];\n return this.integrationRuns.listRecent(limit, subscriptionId, tenantId);\n }\n\n async getCursors(tenantId?: string | null): Promise<CursorSnapshot[]> {\n if (!this.cursors) return [];\n return this.cursors.listAll(tenantId);\n }\n\n async listJobRuns(query?: ListJobRunsQuery): Promise<JobRunPage> {\n if (!this.jobRuns) {\n return { ...ObservabilityService.EMPTY_JOB_RUN_PAGE };\n }\n return this.jobRuns.listJobRuns(query);\n }\n\n async listEvents(query?: ListEventsQuery): Promise<EventPage> {\n if (!this.events) {\n return { ...ObservabilityService.EMPTY_EVENT_PAGE };\n }\n return this.events.listEvents(query);\n }\n\n async getCorrelationTimeline(\n rootRunId: string,\n tenantId?: string | null,\n ): Promise<CorrelationTimeline> {\n const runs = await this.collectRuns(rootRunId, tenantId);\n const events = await this.collectEvents(rootRunId, tenantId);\n\n const entries: CorrelationTimelineEntry[] = [\n ...runs.map(\n (run): CorrelationTimelineEntry => ({\n kind: 'job_run',\n at: run.createdAt,\n run,\n }),\n ),\n ...events.map(\n (event): CorrelationTimelineEntry => ({\n kind: 'event',\n at: event.occurredAt,\n event,\n }),\n ),\n ];\n\n // Ascending chronological order. Stable tie-break: job runs before\n // events at the same instant (the run that emits an event precedes it).\n entries.sort((a, b) => {\n const dt = a.at.getTime() - b.at.getTime();\n if (dt !== 0) return dt;\n if (a.kind === b.kind) return 0;\n return a.kind === 'job_run' ? -1 : 1;\n });\n\n const startedAt = entries.length > 0 ? entries[0]!.at : null;\n const lastActivityAt =\n entries.length > 0 ? entries[entries.length - 1]!.at : null;\n\n return {\n rootRunId,\n entries,\n summary: {\n runCount: runs.length,\n eventCount: events.length,\n startedAt,\n lastActivityAt,\n },\n };\n }\n\n /**\n * Drain every `job_run` sharing `rootRunId` by walking the keyset cursor.\n * Empty when the jobs subsystem is absent.\n */\n private async collectRuns(\n rootRunId: string,\n tenantId?: string | null,\n ): Promise<JobRunSummary[]> {\n if (!this.jobRuns) return [];\n const out: JobRunSummary[] = [];\n let cursor: string | undefined;\n for (let page = 0; page < MAX_TIMELINE_PAGES; page += 1) {\n const result = await this.jobRuns.listJobRuns({\n rootRunId,\n tenantId,\n cursor,\n });\n out.push(...result.items);\n if (!result.nextCursor) break;\n cursor = result.nextCursor;\n }\n return out;\n }\n\n /**\n * Drain every `domain_event` whose `metadata.rootRunId` matches by walking\n * the keyset cursor. Empty when the events read port is absent.\n */\n private async collectEvents(\n rootRunId: string,\n tenantId?: string | null,\n ): Promise<EventSummary[]> {\n if (!this.events) return [];\n const out: EventSummary[] = [];\n let cursor: string | undefined;\n for (let page = 0; page < MAX_TIMELINE_PAGES; page += 1) {\n const result = await this.events.listEvents({\n rootRunId,\n tenantId,\n cursor,\n });\n out.push(...result.items);\n if (!result.nextCursor) break;\n cursor = result.nextCursor;\n }\n return out;\n }\n}\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 * 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 */\nimport { tokenKey } from '../token-key';\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 *\n * ADR-037: namespaced `Symbol.for(...)` (via `tokenKey()`) so it matches by value\n * across runtime copies (the sibling string tokens above are already value-stable).\n * Note the jobs subsystem defines its own `REDIS_URL`-equivalent; this is the\n * events one.\n */\nexport const REDIS_URL = Symbol.for(tokenKey('events', '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 * Integration subsystem — DI tokens\n *\n * String constants (not Symbols) so they match by value across import\n * boundaries — same convention as the events subsystem (`EVENT_BUS`). The\n * jobs subsystem uses Symbols for its analogous tokens; events and integration\n * stay internally consistent with strings.\n *\n * Usage in use cases:\n * ```ts\n * constructor(\n * @Inject(INTEGRATION_CHANGE_SOURCE) private readonly source: IChangeSource<CanonicalOpportunity>,\n * @Inject(INTEGRATION_CURSOR_STORE) private readonly cursors: ICursorStore,\n * @Inject(INTEGRATION_FIELD_DIFFER) private readonly differ: IFieldDiffer<CanonicalOpportunity>,\n * @Inject(INTEGRATION_SINK) private readonly sink: IIntegrationSink<CanonicalOpportunity>,\n * @Inject(INTEGRATION_RUN_RECORDER) private readonly recorder: IIntegrationRunRecorder,\n * ) {}\n * ```\n *\n * Concrete bindings are registered by `IntegrationModule.forRoot(...)` (SYNC-6).\n */\n\nexport const INTEGRATION_CHANGE_SOURCE = 'INTEGRATION_CHANGE_SOURCE' as const;\nexport const INTEGRATION_CURSOR_STORE = 'INTEGRATION_CURSOR_STORE' as const;\nexport const INTEGRATION_FIELD_DIFFER = 'INTEGRATION_FIELD_DIFFER' as const;\nexport const INTEGRATION_SINK = 'INTEGRATION_SINK' as const;\n\n/**\n * Run-recorder token (SYNC-5). Backed by `IIntegrationRunRecorder`. Drizzle impl\n * lands in SYNC-4; tests provide inline fakes.\n */\nexport const INTEGRATION_RUN_RECORDER = 'INTEGRATION_RUN_RECORDER' as const;\n\n/**\n * Injection token for the resolved `IntegrationModuleOptions` object (SYNC-6).\n *\n * Backends that need to observe module configuration (e.g. `multiTenant`\n * flag, pool filters) inject via this token. Provided automatically by\n * `IntegrationModule.forRoot(...)` / `IntegrationModule.forRootAsync(...)`.\n */\nexport const INTEGRATION_MODULE_OPTIONS = 'INTEGRATION_MODULE_OPTIONS' as const;\n\n/**\n * Injection token for the resolved multi-tenancy flag (SYNC-6).\n *\n * Provided by `IntegrationModule.forRoot(...)` as `options.multiTenant ?? false`.\n * Consumed by `ExecuteIntegrationUseCase` to enforce the tenantId-is-required rule.\n */\nexport const INTEGRATION_MULTI_TENANT = 'INTEGRATION_MULTI_TENANT' as const;\n\n/**\n * Injection token for the entity-keyed `IEntityChangeSourceRegistry` (C7,\n * #336). Bound to the codegen-emitted aggregator that folds per-provider\n * adapter contributions into one registry (RFC-0001 §3, emitted by Track D\n * D3/D4).\n *\n * A string constant, not `Symbol.for(...)`, to match this subsystem's token\n * convention (see file header). The originating issue's code block proposed a\n * `Symbol.for('@pattern-stack/codegen.entity-change-source-registry')` key,\n * predating the sync→integration consolidation onto string tokens; kept as a\n * string here for internal consistency with the other INTEGRATION_* tokens.\n */\nexport const ENTITY_CHANGE_SOURCE_REGISTRY = 'ENTITY_CHANGE_SOURCE_REGISTRY' as const;\n","/**\n * BridgeMetricsReporter — internal consumer of `IObservability`\n * (ADR-025, OBS-6).\n *\n * Periodically samples `getBridgeDeliveryHistogram` from the observability\n * facade and emits one log line per tick. Auto-registered by\n * `ObservabilityModule.forRoot()` when\n * `options.reporters.bridgeMetrics.enabled === true`. Consumers configure\n * via options; they never import this class. Not exported from the\n * module's `exports` array — internal.\n *\n * # Invariants (enforced by skill + ADR-025)\n *\n * - Injects ONLY `OBSERVABILITY` + `OBSERVABILITY_MODULE_OPTIONS`. Never\n * `BRIDGE_DELIVERY_REPO` or any other sibling token. Reporters are\n * consumers of the composed facade, not parallel composers.\n * - Never reaches into sibling tables or extends `IObservability`.\n * - Errors isolated per-tick (logged, never rethrown) so a transient\n * sibling failure does not kill the interval.\n * - `tenantId` passes VERBATIM to the facade — observability owns tenant\n * semantics, reporters don't re-implement them.\n *\n * # Lifecycle\n *\n * - `onModuleInit` — eager first-tick, then `setInterval`. Handle is\n * `.unref()`-ed when supported so the loop never blocks node shutdown.\n * - `onModuleDestroy` — `clearInterval` + null the handle. Idempotent.\n *\n * No `@nestjs/schedule` dependency — raw `setInterval` keeps the runtime\n * footprint minimal and avoids pulling a decorator framework in for a\n * single loop.\n */\nimport {\n Inject,\n Injectable,\n Logger,\n type OnModuleDestroy,\n type OnModuleInit,\n} from '@nestjs/common';\n\nimport type { IObservability } from '../observability.protocol';\nimport {\n OBSERVABILITY,\n OBSERVABILITY_MODULE_OPTIONS,\n} from '../observability.tokens';\n// Type-only imports — the module imports this file as a value for DI\n// registration, and this file imports config types back. Keeping the\n// back-edge type-only prevents a runtime circular-import.\nimport type {\n BridgeMetricsReporterConfig,\n ObservabilityModuleOptions,\n} from '../observability.module';\n\n@Injectable()\nexport class BridgeMetricsReporter implements OnModuleInit, OnModuleDestroy {\n private readonly logger = new Logger(BridgeMetricsReporter.name);\n private handle: ReturnType<typeof setInterval> | null = null;\n private readonly config: BridgeMetricsReporterConfig | undefined;\n\n constructor(\n @Inject(OBSERVABILITY) private readonly observability: IObservability,\n @Inject(OBSERVABILITY_MODULE_OPTIONS) options: ObservabilityModuleOptions,\n ) {\n this.config = options.reporters?.bridgeMetrics;\n }\n\n onModuleInit(): void {\n if (!this.config || !this.config.enabled) {\n this.logger.log('BridgeMetricsReporter disabled');\n return;\n }\n if (this.config.intervalMs <= 0 || this.config.windowHours <= 0) {\n this.logger.warn(\n `invalid config; not starting: intervalMs=${this.config.intervalMs} windowHours=${this.config.windowHours}`,\n );\n return;\n }\n // Eager first-tick so consumers see data immediately on boot, without\n // waiting `intervalMs` for the first sample.\n void this.runOnce();\n this.handle = setInterval(() => {\n void this.runOnce();\n }, this.config.intervalMs);\n // `.unref()` lets the node process exit even if the interval is still\n // scheduled — important in short-lived CLI/test contexts. Guarded\n // because browser-shimmed timers may not expose it.\n if (typeof this.handle.unref === 'function') this.handle.unref();\n }\n\n onModuleDestroy(): void {\n if (this.handle !== null) {\n clearInterval(this.handle);\n this.handle = null;\n }\n }\n\n /**\n * Single sample. Public so tests and future ops tooling can trigger a\n * sample on demand without waiting for the interval. Errors are caught\n * and logged here — never rethrown — so a transient sibling failure\n * does not kill subsequent ticks.\n */\n async runOnce(): Promise<void> {\n if (!this.config || !this.config.enabled) return;\n try {\n const h = await this.observability.getBridgeDeliveryHistogram(\n this.config.windowHours,\n this.config.tenantId,\n );\n this.logger.log(\n `bridge-delivery window=${this.config.windowHours}h tenant=${this.config.tenantId ?? 'default'} pending=${h.pending} delivered=${h.delivered} skipped=${h.skipped} failed=${h.failed}`,\n );\n } catch (err) {\n this.logger.error(\n 'BridgeMetricsReporter runOnce failed',\n err instanceof Error ? err.stack : String(err),\n );\n }\n }\n}\n"],"mappings":"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","names":["Inject","Injectable","Injectable","Inject"]}
|
|
@@ -3,7 +3,7 @@ import { ListEventsQuery, EventPage, EventSummary } from '../events/event-read.p
|
|
|
3
3
|
import { StatusHistogram } from '../bridge/bridge.protocol.js';
|
|
4
4
|
import { IntegrationRunSummary } from '../integration/integration-run-recorder.protocol.js';
|
|
5
5
|
import { CursorSnapshot } from '../integration/integration-cursor-store.protocol.js';
|
|
6
|
-
import '../../../job-orchestrator.protocol-
|
|
6
|
+
import '../../../job-orchestrator.protocol-CARhMLCO.js';
|
|
7
7
|
import '../events/event-bus.protocol.js';
|
|
8
8
|
import '../../types/drizzle.js';
|
|
9
9
|
import 'drizzle-orm/node-postgres';
|
|
@@ -4,7 +4,7 @@ import { IJobBridge, StatusHistogram } from '../bridge/bridge.protocol.js';
|
|
|
4
4
|
import { IIntegrationRunRecorder, IntegrationRunSummary } from '../integration/integration-run-recorder.protocol.js';
|
|
5
5
|
import { ICursorStore, CursorSnapshot } from '../integration/integration-cursor-store.protocol.js';
|
|
6
6
|
import { IObservability, CorrelationTimeline } from './observability.protocol.js';
|
|
7
|
-
import '../../../job-orchestrator.protocol-
|
|
7
|
+
import '../../../job-orchestrator.protocol-CARhMLCO.js';
|
|
8
8
|
import '../events/event-bus.protocol.js';
|
|
9
9
|
import '../../types/drizzle.js';
|
|
10
10
|
import 'drizzle-orm/node-postgres';
|
|
@@ -15,11 +15,19 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
15
15
|
// runtime/subsystems/observability/observability.service.ts
|
|
16
16
|
import { Inject, Injectable, Optional } from "@nestjs/common";
|
|
17
17
|
|
|
18
|
+
// runtime/subsystems/token-key.ts
|
|
19
|
+
var PKG = "@pattern-stack/codegen";
|
|
20
|
+
var tokenKey = (area, name) => `${PKG}.${area}.${name}`;
|
|
21
|
+
|
|
18
22
|
// runtime/subsystems/jobs/jobs-domain.tokens.ts
|
|
19
|
-
var
|
|
23
|
+
var JOB_ORCHESTRATOR = Symbol.for(tokenKey("jobs", "orchestrator"));
|
|
24
|
+
var JOB_RUN_SERVICE = Symbol.for(tokenKey("jobs", "run-service"));
|
|
25
|
+
var JOB_STEP_SERVICE = Symbol.for(tokenKey("jobs", "step-service"));
|
|
26
|
+
var JOBS_MULTI_TENANT = Symbol.for(tokenKey("jobs", "multi-tenant"));
|
|
20
27
|
|
|
21
28
|
// runtime/subsystems/events/events.tokens.ts
|
|
22
29
|
var EVENT_READ_PORT = "EVENT_READ_PORT";
|
|
30
|
+
var REDIS_URL = Symbol.for(tokenKey("events", "redis-url"));
|
|
23
31
|
|
|
24
32
|
// runtime/subsystems/bridge/bridge.tokens.ts
|
|
25
33
|
var BRIDGE_DELIVERY_REPO = "BRIDGE_DELIVERY_REPO";
|