@pattern-stack/codegen 0.8.1 → 0.9.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/CHANGELOG.md +29 -0
- package/dist/runtime/subsystems/bridge/bridge-delivery-handler.js.map +1 -1
- package/dist/runtime/subsystems/bridge/bridge-outbox-drain-hook.js.map +1 -1
- package/dist/runtime/subsystems/bridge/bridge.module.d.ts +3 -0
- package/dist/runtime/subsystems/bridge/bridge.module.js +930 -275
- package/dist/runtime/subsystems/bridge/bridge.module.js.map +1 -1
- package/dist/runtime/subsystems/bridge/event-flow.service.js.map +1 -1
- package/dist/runtime/subsystems/bridge/index.d.ts +3 -0
- package/dist/runtime/subsystems/bridge/index.js +837 -182
- package/dist/runtime/subsystems/bridge/index.js.map +1 -1
- package/dist/runtime/subsystems/events/event-bus.drizzle-backend.d.ts +3 -1
- package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js +92 -1
- package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js.map +1 -1
- package/dist/runtime/subsystems/events/event-bus.memory-backend.d.ts +3 -1
- package/dist/runtime/subsystems/events/event-bus.memory-backend.js +99 -0
- package/dist/runtime/subsystems/events/event-bus.memory-backend.js.map +1 -1
- package/dist/runtime/subsystems/events/event-bus.redis-backend.js.map +1 -1
- package/dist/runtime/subsystems/events/event-keyset-cursor.d.ts +32 -0
- package/dist/runtime/subsystems/events/event-keyset-cursor.js +38 -0
- package/dist/runtime/subsystems/events/event-keyset-cursor.js.map +1 -0
- package/dist/runtime/subsystems/events/event-read.protocol.d.ts +94 -0
- package/dist/runtime/subsystems/events/event-read.protocol.js +9 -0
- package/dist/runtime/subsystems/events/event-read.protocol.js.map +1 -0
- package/dist/runtime/subsystems/events/events.module.js +177 -3
- package/dist/runtime/subsystems/events/events.module.js.map +1 -1
- package/dist/runtime/subsystems/events/events.tokens.d.ts +16 -1
- package/dist/runtime/subsystems/events/events.tokens.js +2 -0
- package/dist/runtime/subsystems/events/events.tokens.js.map +1 -1
- package/dist/runtime/subsystems/events/generated/bus.js.map +1 -1
- package/dist/runtime/subsystems/events/generated/index.js.map +1 -1
- package/dist/runtime/subsystems/events/index.d.ts +2 -1
- package/dist/runtime/subsystems/events/index.js +178 -3
- package/dist/runtime/subsystems/events/index.js.map +1 -1
- package/dist/runtime/subsystems/index.d.ts +1 -0
- package/dist/runtime/subsystems/index.js +1194 -264
- package/dist/runtime/subsystems/index.js.map +1 -1
- package/dist/runtime/subsystems/jobs/bullmq.config.d.ts +98 -0
- package/dist/runtime/subsystems/jobs/bullmq.config.js +143 -0
- package/dist/runtime/subsystems/jobs/bullmq.config.js.map +1 -0
- package/dist/runtime/subsystems/jobs/index.d.ts +6 -2
- package/dist/runtime/subsystems/jobs/index.js +861 -201
- package/dist/runtime/subsystems/jobs/index.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.d.ts +107 -0
- package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.js +922 -0
- package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.js.map +1 -0
- package/dist/runtime/subsystems/jobs/job-run-keyset-cursor.d.ts +52 -0
- package/dist/runtime/subsystems/jobs/job-run-keyset-cursor.js +57 -0
- package/dist/runtime/subsystems/jobs/job-run-keyset-cursor.js.map +1 -0
- package/dist/runtime/subsystems/jobs/job-run-service.drizzle-backend.d.ts +2 -1
- package/dist/runtime/subsystems/jobs/job-run-service.drizzle-backend.js +81 -1
- 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 +2 -1
- package/dist/runtime/subsystems/jobs/job-run-service.memory-backend.js +81 -0
- 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 +74 -1
- package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.d.ts +48 -0
- package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.js +374 -0
- package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.js.map +1 -0
- package/dist/runtime/subsystems/jobs/job-worker.module.d.ts +42 -4
- package/dist/runtime/subsystems/jobs/job-worker.module.js +832 -178
- package/dist/runtime/subsystems/jobs/job-worker.module.js.map +1 -1
- package/dist/runtime/subsystems/jobs/jobs-domain.module.d.ts +10 -1
- package/dist/runtime/subsystems/jobs/jobs-domain.module.js +519 -20
- package/dist/runtime/subsystems/jobs/jobs-domain.module.js.map +1 -1
- package/dist/runtime/subsystems/jobs/pool-config.loader.d.ts +9 -1
- package/dist/runtime/subsystems/jobs/pool-config.loader.js +4 -0
- package/dist/runtime/subsystems/jobs/pool-config.loader.js.map +1 -1
- package/dist/runtime/subsystems/observability/index.d.ts +4 -3
- package/dist/runtime/subsystems/observability/index.js +109 -2
- package/dist/runtime/subsystems/observability/index.js.map +1 -1
- package/dist/runtime/subsystems/observability/observability.module.js +109 -2
- package/dist/runtime/subsystems/observability/observability.module.js.map +1 -1
- package/dist/runtime/subsystems/observability/observability.protocol.d.ts +63 -2
- package/dist/runtime/subsystems/observability/observability.service.d.ts +21 -3
- package/dist/runtime/subsystems/observability/observability.service.js +109 -2
- package/dist/runtime/subsystems/observability/observability.service.js.map +1 -1
- package/dist/runtime/subsystems/observability/reporters/bridge-metrics.reporter.d.ts +1 -0
- package/dist/runtime/subsystems/observability/reporters/index.d.ts +1 -0
- package/dist/src/cli/index.js +30 -6
- package/dist/src/cli/index.js.map +1 -1
- package/package.json +1 -1
- package/runtime/subsystems/bridge/bridge.module.ts +5 -0
- package/runtime/subsystems/events/event-bus.drizzle-backend.ts +109 -3
- package/runtime/subsystems/events/event-bus.memory-backend.ts +103 -1
- package/runtime/subsystems/events/event-keyset-cursor.ts +59 -0
- package/runtime/subsystems/events/event-read.protocol.ts +97 -0
- package/runtime/subsystems/events/events.module.ts +18 -2
- package/runtime/subsystems/events/events.tokens.ts +16 -0
- package/runtime/subsystems/events/index.ts +7 -0
- package/runtime/subsystems/jobs/bullmq.config.ts +125 -0
- package/runtime/subsystems/jobs/index.ts +22 -0
- package/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.ts +381 -0
- package/runtime/subsystems/jobs/job-run-keyset-cursor.ts +88 -0
- package/runtime/subsystems/jobs/job-run-service.drizzle-backend.ts +59 -1
- package/runtime/subsystems/jobs/job-run-service.memory-backend.ts +53 -0
- package/runtime/subsystems/jobs/job-run-service.protocol.ts +77 -0
- package/runtime/subsystems/jobs/job-worker.bullmq-backend.ts +311 -0
- package/runtime/subsystems/jobs/job-worker.module.ts +124 -10
- package/runtime/subsystems/jobs/jobs-domain.module.ts +40 -21
- package/runtime/subsystems/jobs/pool-config.loader.ts +11 -0
- package/runtime/subsystems/observability/index.ts +8 -0
- package/runtime/subsystems/observability/observability.protocol.ts +76 -0
- package/runtime/subsystems/observability/observability.service.ts +148 -1
- package/templates/entity/new/clean-lite-ps/prompt-extension.js +14 -12
- package/templates/relationship/new/prompt.js +8 -5
- package/templates/subsystem/jobs/worker.ejs.t +30 -7
- package/templates/subsystem/sync/sync-audit.schema.ejs.t +12 -16
|
@@ -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/bridge/bridge.tokens.ts","../../../../runtime/subsystems/sync/sync.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 sync 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 * SyncModule.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 / sync\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 sync 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 PoolStatusCount,\n} from '../jobs/job-run-service.protocol';\n\nimport { BRIDGE_DELIVERY_REPO } from '../bridge/bridge.tokens';\nimport type { IJobBridge, StatusHistogram } from '../bridge/bridge.protocol';\n\nimport { SYNC_CURSOR_STORE, SYNC_RUN_RECORDER } from '../sync/sync.tokens';\nimport type {\n ISyncRunRecorder,\n SyncRunSummary,\n} from '../sync/sync-run-recorder.protocol';\nimport type {\n CursorSnapshot,\n ICursorStore,\n} from '../sync/sync-cursor-store.protocol';\n\nimport type { IObservability } from './observability.protocol';\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 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(SYNC_RUN_RECORDER)\n private readonly syncRuns?: ISyncRunRecorder,\n @Optional()\n @Inject(SYNC_CURSOR_STORE)\n private readonly cursors?: ICursorStore,\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 getRecentSyncRuns(\n limit: number,\n subscriptionId?: string,\n tenantId?: string | null,\n ): Promise<SyncRunSummary[]> {\n if (!this.syncRuns) return [];\n return this.syncRuns.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","/**\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 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 * Sync 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 sync\n * stay internally consistent with strings.\n *\n * Usage in use cases:\n * ```ts\n * constructor(\n * @Inject(SYNC_CHANGE_SOURCE) private readonly source: IChangeSource<CanonicalOpportunity>,\n * @Inject(SYNC_CURSOR_STORE) private readonly cursors: ICursorStore,\n * @Inject(SYNC_FIELD_DIFFER) private readonly differ: IFieldDiffer<CanonicalOpportunity>,\n * @Inject(SYNC_SINK) private readonly sink: ISyncSink<CanonicalOpportunity>,\n * @Inject(SYNC_RUN_RECORDER) private readonly recorder: ISyncRunRecorder,\n * ) {}\n * ```\n *\n * Concrete bindings are registered by `SyncModule.forRoot(...)` (SYNC-6).\n */\n\nexport const SYNC_CHANGE_SOURCE = 'SYNC_CHANGE_SOURCE' as const;\nexport const SYNC_CURSOR_STORE = 'SYNC_CURSOR_STORE' as const;\nexport const SYNC_FIELD_DIFFER = 'SYNC_FIELD_DIFFER' as const;\nexport const SYNC_SINK = 'SYNC_SINK' as const;\n\n/**\n * Run-recorder token (SYNC-5). Backed by `ISyncRunRecorder`. Drizzle impl\n * lands in SYNC-4; tests provide inline fakes.\n */\nexport const SYNC_RUN_RECORDER = 'SYNC_RUN_RECORDER' as const;\n\n/**\n * Injection token for the resolved `SyncModuleOptions` 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 * `SyncModule.forRoot(...)` / `SyncModule.forRootAsync(...)`.\n */\nexport const SYNC_MODULE_OPTIONS = 'SYNC_MODULE_OPTIONS' as const;\n\n/**\n * Injection token for the resolved multi-tenancy flag (SYNC-6).\n *\n * Provided by `SyncModule.forRoot(...)` as `options.multiTenant ?? false`.\n * Consumed by `ExecuteSyncUseCase` to enforce the tenantId-is-required rule.\n */\nexport const SYNC_MULTI_TENANT = 'SYNC_MULTI_TENANT' 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/jobs/jobs-domain.tokens.ts","../../../../runtime/subsystems/events/events.tokens.ts","../../../../runtime/subsystems/bridge/bridge.tokens.ts","../../../../runtime/subsystems/sync/sync.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 sync 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 * SyncModule.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 / sync\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 sync 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 { SYNC_CURSOR_STORE, SYNC_RUN_RECORDER } from '../sync/sync.tokens';\nimport type {\n ISyncRunRecorder,\n SyncRunSummary,\n} from '../sync/sync-run-recorder.protocol';\nimport type {\n CursorSnapshot,\n ICursorStore,\n} from '../sync/sync-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(SYNC_RUN_RECORDER)\n private readonly syncRuns?: ISyncRunRecorder,\n @Optional()\n @Inject(SYNC_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 getRecentSyncRuns(\n limit: number,\n subscriptionId?: string,\n tenantId?: string | null,\n ): Promise<SyncRunSummary[]> {\n if (!this.syncRuns) return [];\n return this.syncRuns.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 * Sync 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 sync\n * stay internally consistent with strings.\n *\n * Usage in use cases:\n * ```ts\n * constructor(\n * @Inject(SYNC_CHANGE_SOURCE) private readonly source: IChangeSource<CanonicalOpportunity>,\n * @Inject(SYNC_CURSOR_STORE) private readonly cursors: ICursorStore,\n * @Inject(SYNC_FIELD_DIFFER) private readonly differ: IFieldDiffer<CanonicalOpportunity>,\n * @Inject(SYNC_SINK) private readonly sink: ISyncSink<CanonicalOpportunity>,\n * @Inject(SYNC_RUN_RECORDER) private readonly recorder: ISyncRunRecorder,\n * ) {}\n * ```\n *\n * Concrete bindings are registered by `SyncModule.forRoot(...)` (SYNC-6).\n */\n\nexport const SYNC_CHANGE_SOURCE = 'SYNC_CHANGE_SOURCE' as const;\nexport const SYNC_CURSOR_STORE = 'SYNC_CURSOR_STORE' as const;\nexport const SYNC_FIELD_DIFFER = 'SYNC_FIELD_DIFFER' as const;\nexport const SYNC_SINK = 'SYNC_SINK' as const;\n\n/**\n * Run-recorder token (SYNC-5). Backed by `ISyncRunRecorder`. Drizzle impl\n * lands in SYNC-4; tests provide inline fakes.\n */\nexport const SYNC_RUN_RECORDER = 'SYNC_RUN_RECORDER' as const;\n\n/**\n * Injection token for the resolved `SyncModuleOptions` 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 * `SyncModule.forRoot(...)` / `SyncModule.forRootAsync(...)`.\n */\nexport const SYNC_MODULE_OPTIONS = 'SYNC_MODULE_OPTIONS' as const;\n\n/**\n * Injection token for the resolved multi-tenancy flag (SYNC-6).\n *\n * Provided by `SyncModule.forRoot(...)` as `options.multiTenant ?? false`.\n * Consumed by `ExecuteSyncUseCase` to enforce the tenantId-is-required rule.\n */\nexport const SYNC_MULTI_TENANT = 'SYNC_MULTI_TENANT' 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,4 +1,5 @@
|
|
|
1
|
-
import { PoolStatusCount, JobRunFailure } from '../jobs/job-run-service.protocol.js';
|
|
1
|
+
import { PoolStatusCount, JobRunFailure, ListJobRunsQuery, JobRunPage, JobRunSummary } from '../jobs/job-run-service.protocol.js';
|
|
2
|
+
import { ListEventsQuery, EventPage, EventSummary } from '../events/event-read.protocol.js';
|
|
2
3
|
import { StatusHistogram } from '../bridge/bridge.protocol.js';
|
|
3
4
|
import { SyncRunSummary } from '../sync/sync-run-recorder.protocol.js';
|
|
4
5
|
import { CursorSnapshot } from '../sync/sync-cursor-store.protocol.js';
|
|
@@ -36,6 +37,39 @@ import 'zod';
|
|
|
36
37
|
* (ADR-025 §Phase-1 scope note; skill §5).
|
|
37
38
|
*/
|
|
38
39
|
|
|
40
|
+
/**
|
|
41
|
+
* One chronological entry in a correlation timeline (OBS-LIST-1). Either a
|
|
42
|
+
* `job_run` or a `domain_event` sharing the same `rootRunId`, tagged with a
|
|
43
|
+
* `kind` discriminator and a single `at` timestamp used for ordering.
|
|
44
|
+
*/
|
|
45
|
+
type CorrelationTimelineEntry = {
|
|
46
|
+
kind: 'job_run';
|
|
47
|
+
at: Date;
|
|
48
|
+
run: JobRunSummary;
|
|
49
|
+
} | {
|
|
50
|
+
kind: 'event';
|
|
51
|
+
at: Date;
|
|
52
|
+
event: EventSummary;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Stitched view of everything correlated to a single `rootRunId`
|
|
56
|
+
* (OBS-LIST-1): the job runs sharing that root plus the domain events whose
|
|
57
|
+
* `metadata.rootRunId` matches, merged into one ascending timeline with a
|
|
58
|
+
* small roll-up summary.
|
|
59
|
+
*/
|
|
60
|
+
interface CorrelationTimeline {
|
|
61
|
+
rootRunId: string;
|
|
62
|
+
/** Ascending by `at`. Job runs ordered by `createdAt`; events by `occurredAt`. */
|
|
63
|
+
entries: CorrelationTimelineEntry[];
|
|
64
|
+
summary: {
|
|
65
|
+
runCount: number;
|
|
66
|
+
eventCount: number;
|
|
67
|
+
/** Earliest `at` across all entries, or `null` when empty. */
|
|
68
|
+
startedAt: Date | null;
|
|
69
|
+
/** Latest `at` across all entries, or `null` when empty. */
|
|
70
|
+
lastActivityAt: Date | null;
|
|
71
|
+
};
|
|
72
|
+
}
|
|
39
73
|
interface IObservability {
|
|
40
74
|
/**
|
|
41
75
|
* Live `(pool, status)` counts across `job_run`. Delegates to
|
|
@@ -74,6 +108,33 @@ interface IObservability {
|
|
|
74
108
|
* Empty array when the sync subsystem is not installed.
|
|
75
109
|
*/
|
|
76
110
|
getCursors(tenantId?: string | null): Promise<CursorSnapshot[]>;
|
|
111
|
+
/**
|
|
112
|
+
* Paginated, filterable `job_run` list (OBS-LIST-1). Delegates to
|
|
113
|
+
* `IJobRunService.listJobRuns`. Keyset pagination on `created_at`.
|
|
114
|
+
*
|
|
115
|
+
* Returns an empty page (`{ items: [], nextCursor: null }`) when the jobs
|
|
116
|
+
* subsystem is not installed.
|
|
117
|
+
*/
|
|
118
|
+
listJobRuns(query?: ListJobRunsQuery): Promise<JobRunPage>;
|
|
119
|
+
/**
|
|
120
|
+
* Paginated, filterable `domain_events` list (OBS-LIST-1). Delegates to
|
|
121
|
+
* `IEventReadPort.listEvents`. Keyset pagination on `occurred_at`.
|
|
122
|
+
*
|
|
123
|
+
* Returns an empty page when the events read port is not installed (e.g.
|
|
124
|
+
* the events subsystem is absent, or its backend is `redis` which retains
|
|
125
|
+
* no history).
|
|
126
|
+
*/
|
|
127
|
+
listEvents(query?: ListEventsQuery): Promise<EventPage>;
|
|
128
|
+
/**
|
|
129
|
+
* Stitch the job runs and domain events sharing a `rootRunId` into a
|
|
130
|
+
* single ascending timeline + summary (OBS-LIST-1). Composes
|
|
131
|
+
* `IJobRunService.listJobRuns` (filtered by the run tree) and
|
|
132
|
+
* `IEventReadPort.listEvents({ rootRunId })`.
|
|
133
|
+
*
|
|
134
|
+
* Returns an empty timeline (zero counts, null bounds) when neither the
|
|
135
|
+
* jobs subsystem nor the events read port is installed.
|
|
136
|
+
*/
|
|
137
|
+
getCorrelationTimeline(rootRunId: string, tenantId?: string | null): Promise<CorrelationTimeline>;
|
|
77
138
|
}
|
|
78
139
|
|
|
79
|
-
export { CursorSnapshot, type IObservability, JobRunFailure, PoolStatusCount, StatusHistogram, SyncRunSummary };
|
|
140
|
+
export { type CorrelationTimeline, type CorrelationTimelineEntry, CursorSnapshot, EventPage, EventSummary, type IObservability, JobRunFailure, JobRunPage, JobRunSummary, ListEventsQuery, ListJobRunsQuery, PoolStatusCount, StatusHistogram, SyncRunSummary };
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { IJobRunService, PoolStatusCount, JobRunFailure } from '../jobs/job-run-service.protocol.js';
|
|
1
|
+
import { IJobRunService, PoolStatusCount, JobRunFailure, ListJobRunsQuery, JobRunPage } from '../jobs/job-run-service.protocol.js';
|
|
2
|
+
import { IEventReadPort, ListEventsQuery, EventPage } from '../events/event-read.protocol.js';
|
|
2
3
|
import { IJobBridge, StatusHistogram } from '../bridge/bridge.protocol.js';
|
|
3
4
|
import { ISyncRunRecorder, SyncRunSummary } from '../sync/sync-run-recorder.protocol.js';
|
|
4
5
|
import { ICursorStore, CursorSnapshot } from '../sync/sync-cursor-store.protocol.js';
|
|
5
|
-
import { IObservability } from './observability.protocol.js';
|
|
6
|
+
import { IObservability, CorrelationTimeline } from './observability.protocol.js';
|
|
6
7
|
import '../../../job-orchestrator.protocol-BwsBd37o.js';
|
|
7
8
|
import '../events/event-bus.protocol.js';
|
|
8
9
|
import '../../types/drizzle.js';
|
|
@@ -21,18 +22,35 @@ declare class ObservabilityService implements IObservability {
|
|
|
21
22
|
private readonly bridge?;
|
|
22
23
|
private readonly syncRuns?;
|
|
23
24
|
private readonly cursors?;
|
|
25
|
+
private readonly events?;
|
|
24
26
|
/**
|
|
25
27
|
* All-zero histogram used when the bridge subsystem is absent. Matches
|
|
26
28
|
* the bridge protocol's "fixed keys, zero-filled" contract so consumers
|
|
27
29
|
* never branch on presence.
|
|
28
30
|
*/
|
|
29
31
|
private static readonly EMPTY_HISTOGRAM;
|
|
30
|
-
|
|
32
|
+
/** Empty page used when a sibling read port is absent. */
|
|
33
|
+
private static readonly EMPTY_JOB_RUN_PAGE;
|
|
34
|
+
private static readonly EMPTY_EVENT_PAGE;
|
|
35
|
+
constructor(jobRuns?: IJobRunService | undefined, bridge?: IJobBridge | undefined, syncRuns?: ISyncRunRecorder | undefined, cursors?: ICursorStore | undefined, events?: (IEventReadPort | null) | undefined);
|
|
31
36
|
getPoolDepths(tenantId?: string | null): Promise<PoolStatusCount[]>;
|
|
32
37
|
getRecentFailedJobs(limit: number, tenantId?: string | null): Promise<JobRunFailure[]>;
|
|
33
38
|
getBridgeDeliveryHistogram(windowHours: number, tenantId?: string | null): Promise<StatusHistogram>;
|
|
34
39
|
getRecentSyncRuns(limit: number, subscriptionId?: string, tenantId?: string | null): Promise<SyncRunSummary[]>;
|
|
35
40
|
getCursors(tenantId?: string | null): Promise<CursorSnapshot[]>;
|
|
41
|
+
listJobRuns(query?: ListJobRunsQuery): Promise<JobRunPage>;
|
|
42
|
+
listEvents(query?: ListEventsQuery): Promise<EventPage>;
|
|
43
|
+
getCorrelationTimeline(rootRunId: string, tenantId?: string | null): Promise<CorrelationTimeline>;
|
|
44
|
+
/**
|
|
45
|
+
* Drain every `job_run` sharing `rootRunId` by walking the keyset cursor.
|
|
46
|
+
* Empty when the jobs subsystem is absent.
|
|
47
|
+
*/
|
|
48
|
+
private collectRuns;
|
|
49
|
+
/**
|
|
50
|
+
* Drain every `domain_event` whose `metadata.rootRunId` matches by walking
|
|
51
|
+
* the keyset cursor. Empty when the events read port is absent.
|
|
52
|
+
*/
|
|
53
|
+
private collectEvents;
|
|
36
54
|
}
|
|
37
55
|
|
|
38
56
|
export { ObservabilityService };
|
|
@@ -18,6 +18,9 @@ import { Inject, Injectable, Optional } from "@nestjs/common";
|
|
|
18
18
|
// runtime/subsystems/jobs/jobs-domain.tokens.ts
|
|
19
19
|
var JOB_RUN_SERVICE = /* @__PURE__ */ Symbol("JOB_RUN_SERVICE");
|
|
20
20
|
|
|
21
|
+
// runtime/subsystems/events/events.tokens.ts
|
|
22
|
+
var EVENT_READ_PORT = "EVENT_READ_PORT";
|
|
23
|
+
|
|
21
24
|
// runtime/subsystems/bridge/bridge.tokens.ts
|
|
22
25
|
var BRIDGE_DELIVERY_REPO = "BRIDGE_DELIVERY_REPO";
|
|
23
26
|
|
|
@@ -26,17 +29,20 @@ var SYNC_CURSOR_STORE = "SYNC_CURSOR_STORE";
|
|
|
26
29
|
var SYNC_RUN_RECORDER = "SYNC_RUN_RECORDER";
|
|
27
30
|
|
|
28
31
|
// runtime/subsystems/observability/observability.service.ts
|
|
32
|
+
var MAX_TIMELINE_PAGES = 50;
|
|
29
33
|
var ObservabilityService = class {
|
|
30
|
-
constructor(jobRuns, bridge, syncRuns, cursors) {
|
|
34
|
+
constructor(jobRuns, bridge, syncRuns, cursors, events) {
|
|
31
35
|
this.jobRuns = jobRuns;
|
|
32
36
|
this.bridge = bridge;
|
|
33
37
|
this.syncRuns = syncRuns;
|
|
34
38
|
this.cursors = cursors;
|
|
39
|
+
this.events = events;
|
|
35
40
|
}
|
|
36
41
|
jobRuns;
|
|
37
42
|
bridge;
|
|
38
43
|
syncRuns;
|
|
39
44
|
cursors;
|
|
45
|
+
events;
|
|
40
46
|
async getPoolDepths(tenantId) {
|
|
41
47
|
if (!this.jobRuns) return [];
|
|
42
48
|
return this.jobRuns.countByPoolAndStatus(tenantId);
|
|
@@ -57,6 +63,96 @@ var ObservabilityService = class {
|
|
|
57
63
|
if (!this.cursors) return [];
|
|
58
64
|
return this.cursors.listAll(tenantId);
|
|
59
65
|
}
|
|
66
|
+
async listJobRuns(query) {
|
|
67
|
+
if (!this.jobRuns) {
|
|
68
|
+
return { ...ObservabilityService.EMPTY_JOB_RUN_PAGE };
|
|
69
|
+
}
|
|
70
|
+
return this.jobRuns.listJobRuns(query);
|
|
71
|
+
}
|
|
72
|
+
async listEvents(query) {
|
|
73
|
+
if (!this.events) {
|
|
74
|
+
return { ...ObservabilityService.EMPTY_EVENT_PAGE };
|
|
75
|
+
}
|
|
76
|
+
return this.events.listEvents(query);
|
|
77
|
+
}
|
|
78
|
+
async getCorrelationTimeline(rootRunId, tenantId) {
|
|
79
|
+
const runs = await this.collectRuns(rootRunId, tenantId);
|
|
80
|
+
const events = await this.collectEvents(rootRunId, tenantId);
|
|
81
|
+
const entries = [
|
|
82
|
+
...runs.map(
|
|
83
|
+
(run) => ({
|
|
84
|
+
kind: "job_run",
|
|
85
|
+
at: run.createdAt,
|
|
86
|
+
run
|
|
87
|
+
})
|
|
88
|
+
),
|
|
89
|
+
...events.map(
|
|
90
|
+
(event) => ({
|
|
91
|
+
kind: "event",
|
|
92
|
+
at: event.occurredAt,
|
|
93
|
+
event
|
|
94
|
+
})
|
|
95
|
+
)
|
|
96
|
+
];
|
|
97
|
+
entries.sort((a, b) => {
|
|
98
|
+
const dt = a.at.getTime() - b.at.getTime();
|
|
99
|
+
if (dt !== 0) return dt;
|
|
100
|
+
if (a.kind === b.kind) return 0;
|
|
101
|
+
return a.kind === "job_run" ? -1 : 1;
|
|
102
|
+
});
|
|
103
|
+
const startedAt = entries.length > 0 ? entries[0].at : null;
|
|
104
|
+
const lastActivityAt = entries.length > 0 ? entries[entries.length - 1].at : null;
|
|
105
|
+
return {
|
|
106
|
+
rootRunId,
|
|
107
|
+
entries,
|
|
108
|
+
summary: {
|
|
109
|
+
runCount: runs.length,
|
|
110
|
+
eventCount: events.length,
|
|
111
|
+
startedAt,
|
|
112
|
+
lastActivityAt
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Drain every `job_run` sharing `rootRunId` by walking the keyset cursor.
|
|
118
|
+
* Empty when the jobs subsystem is absent.
|
|
119
|
+
*/
|
|
120
|
+
async collectRuns(rootRunId, tenantId) {
|
|
121
|
+
if (!this.jobRuns) return [];
|
|
122
|
+
const out = [];
|
|
123
|
+
let cursor;
|
|
124
|
+
for (let page = 0; page < MAX_TIMELINE_PAGES; page += 1) {
|
|
125
|
+
const result = await this.jobRuns.listJobRuns({
|
|
126
|
+
rootRunId,
|
|
127
|
+
tenantId,
|
|
128
|
+
cursor
|
|
129
|
+
});
|
|
130
|
+
out.push(...result.items);
|
|
131
|
+
if (!result.nextCursor) break;
|
|
132
|
+
cursor = result.nextCursor;
|
|
133
|
+
}
|
|
134
|
+
return out;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Drain every `domain_event` whose `metadata.rootRunId` matches by walking
|
|
138
|
+
* the keyset cursor. Empty when the events read port is absent.
|
|
139
|
+
*/
|
|
140
|
+
async collectEvents(rootRunId, tenantId) {
|
|
141
|
+
if (!this.events) return [];
|
|
142
|
+
const out = [];
|
|
143
|
+
let cursor;
|
|
144
|
+
for (let page = 0; page < MAX_TIMELINE_PAGES; page += 1) {
|
|
145
|
+
const result = await this.events.listEvents({
|
|
146
|
+
rootRunId,
|
|
147
|
+
tenantId,
|
|
148
|
+
cursor
|
|
149
|
+
});
|
|
150
|
+
out.push(...result.items);
|
|
151
|
+
if (!result.nextCursor) break;
|
|
152
|
+
cursor = result.nextCursor;
|
|
153
|
+
}
|
|
154
|
+
return out;
|
|
155
|
+
}
|
|
60
156
|
};
|
|
61
157
|
/**
|
|
62
158
|
* All-zero histogram used when the bridge subsystem is absent. Matches
|
|
@@ -69,6 +165,15 @@ __publicField(ObservabilityService, "EMPTY_HISTOGRAM", {
|
|
|
69
165
|
skipped: 0,
|
|
70
166
|
failed: 0
|
|
71
167
|
});
|
|
168
|
+
/** Empty page used when a sibling read port is absent. */
|
|
169
|
+
__publicField(ObservabilityService, "EMPTY_JOB_RUN_PAGE", {
|
|
170
|
+
items: [],
|
|
171
|
+
nextCursor: null
|
|
172
|
+
});
|
|
173
|
+
__publicField(ObservabilityService, "EMPTY_EVENT_PAGE", {
|
|
174
|
+
items: [],
|
|
175
|
+
nextCursor: null
|
|
176
|
+
});
|
|
72
177
|
ObservabilityService = __decorateClass([
|
|
73
178
|
Injectable(),
|
|
74
179
|
__decorateParam(0, Optional()),
|
|
@@ -78,7 +183,9 @@ ObservabilityService = __decorateClass([
|
|
|
78
183
|
__decorateParam(2, Optional()),
|
|
79
184
|
__decorateParam(2, Inject(SYNC_RUN_RECORDER)),
|
|
80
185
|
__decorateParam(3, Optional()),
|
|
81
|
-
__decorateParam(3, Inject(SYNC_CURSOR_STORE))
|
|
186
|
+
__decorateParam(3, Inject(SYNC_CURSOR_STORE)),
|
|
187
|
+
__decorateParam(4, Optional()),
|
|
188
|
+
__decorateParam(4, Inject(EVENT_READ_PORT))
|
|
82
189
|
], ObservabilityService);
|
|
83
190
|
export {
|
|
84
191
|
ObservabilityService
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../runtime/subsystems/observability/observability.service.ts","../../../../runtime/subsystems/jobs/jobs-domain.tokens.ts","../../../../runtime/subsystems/bridge/bridge.tokens.ts","../../../../runtime/subsystems/sync/sync.tokens.ts"],"sourcesContent":["/**\n * ObservabilityService — `IObservability` combiner implementation\n * (ADR-025, OBS-5).\n *\n * Composes read methods across the jobs, bridge, and sync 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 PoolStatusCount,\n} from '../jobs/job-run-service.protocol';\n\nimport { BRIDGE_DELIVERY_REPO } from '../bridge/bridge.tokens';\nimport type { IJobBridge, StatusHistogram } from '../bridge/bridge.protocol';\n\nimport { SYNC_CURSOR_STORE, SYNC_RUN_RECORDER } from '../sync/sync.tokens';\nimport type {\n ISyncRunRecorder,\n SyncRunSummary,\n} from '../sync/sync-run-recorder.protocol';\nimport type {\n CursorSnapshot,\n ICursorStore,\n} from '../sync/sync-cursor-store.protocol';\n\nimport type { IObservability } from './observability.protocol';\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 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(SYNC_RUN_RECORDER)\n private readonly syncRuns?: ISyncRunRecorder,\n @Optional()\n @Inject(SYNC_CURSOR_STORE)\n private readonly cursors?: ICursorStore,\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 getRecentSyncRuns(\n limit: number,\n subscriptionId?: string,\n tenantId?: string | null,\n ): Promise<SyncRunSummary[]> {\n if (!this.syncRuns) return [];\n return this.syncRuns.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","/**\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 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 * Sync 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 sync\n * stay internally consistent with strings.\n *\n * Usage in use cases:\n * ```ts\n * constructor(\n * @Inject(SYNC_CHANGE_SOURCE) private readonly source: IChangeSource<CanonicalOpportunity>,\n * @Inject(SYNC_CURSOR_STORE) private readonly cursors: ICursorStore,\n * @Inject(SYNC_FIELD_DIFFER) private readonly differ: IFieldDiffer<CanonicalOpportunity>,\n * @Inject(SYNC_SINK) private readonly sink: ISyncSink<CanonicalOpportunity>,\n * @Inject(SYNC_RUN_RECORDER) private readonly recorder: ISyncRunRecorder,\n * ) {}\n * ```\n *\n * Concrete bindings are registered by `SyncModule.forRoot(...)` (SYNC-6).\n */\n\nexport const SYNC_CHANGE_SOURCE = 'SYNC_CHANGE_SOURCE' as const;\nexport const SYNC_CURSOR_STORE = 'SYNC_CURSOR_STORE' as const;\nexport const SYNC_FIELD_DIFFER = 'SYNC_FIELD_DIFFER' as const;\nexport const SYNC_SINK = 'SYNC_SINK' as const;\n\n/**\n * Run-recorder token (SYNC-5). Backed by `ISyncRunRecorder`. Drizzle impl\n * lands in SYNC-4; tests provide inline fakes.\n */\nexport const SYNC_RUN_RECORDER = 'SYNC_RUN_RECORDER' as const;\n\n/**\n * Injection token for the resolved `SyncModuleOptions` 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 * `SyncModule.forRoot(...)` / `SyncModule.forRootAsync(...)`.\n */\nexport const SYNC_MODULE_OPTIONS = 'SYNC_MODULE_OPTIONS' as const;\n\n/**\n * Injection token for the resolved multi-tenancy flag (SYNC-6).\n *\n * Provided by `SyncModule.forRoot(...)` as `options.multiTenant ?? false`.\n * Consumed by `ExecuteSyncUseCase` to enforce the tenantId-is-required rule.\n */\nexport const SYNC_MULTI_TENANT = 'SYNC_MULTI_TENANT' as const;\n"],"mappings":";;;;;;;;;;;;;;;AA6BA,SAAS,QAAQ,YAAY,gBAAgB;;;ACjBtC,IAAM,kBAAkB,uBAAO,iBAAiB;;;ACIhD,IAAM,uBAAuB;;;ACO7B,IAAM,oBAAoB;AAQ1B,IAAM,oBAAoB;;;AHuB1B,IAAM,uBAAN,MAAqD;AAAA,EAa1D,YAGmB,SAGA,QAGA,UAGA,SACjB;AAViB;AAGA;AAGA;AAGA;AAAA,EAChB;AAAA,EAVgB;AAAA,EAGA;AAAA,EAGA;AAAA,EAGA;AAAA,EAGnB,MAAM,cAAc,UAAsD;AACxE,QAAI,CAAC,KAAK,QAAS,QAAO,CAAC;AAC3B,WAAO,KAAK,QAAQ,qBAAqB,QAAQ;AAAA,EACnD;AAAA,EAEA,MAAM,oBACJ,OACA,UAC0B;AAC1B,QAAI,CAAC,KAAK,QAAS,QAAO,CAAC;AAC3B,WAAO,KAAK,QAAQ,iBAAiB,OAAO,QAAQ;AAAA,EACtD;AAAA,EAEA,MAAM,2BACJ,aACA,UAC0B;AAC1B,QAAI,CAAC,KAAK,OAAQ,QAAO,EAAE,GAAG,qBAAqB,gBAAgB;AACnE,WAAO,KAAK,OAAO,mBAAmB,aAAa,QAAQ;AAAA,EAC7D;AAAA,EAEA,MAAM,kBACJ,OACA,gBACA,UAC2B;AAC3B,QAAI,CAAC,KAAK,SAAU,QAAO,CAAC;AAC5B,WAAO,KAAK,SAAS,WAAW,OAAO,gBAAgB,QAAQ;AAAA,EACjE;AAAA,EAEA,MAAM,WAAW,UAAqD;AACpE,QAAI,CAAC,KAAK,QAAS,QAAO,CAAC;AAC3B,WAAO,KAAK,QAAQ,QAAQ,QAAQ;AAAA,EACtC;AACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAxDE,cANW,sBAMa,mBAAmC;AAAA,EACzD,SAAS;AAAA,EACT,WAAW;AAAA,EACX,SAAS;AAAA,EACT,QAAQ;AACV;AAXW,uBAAN;AAAA,EADN,WAAW;AAAA,EAeP,4BAAS;AAAA,EACT,0BAAO,eAAe;AAAA,EAEtB,4BAAS;AAAA,EACT,0BAAO,oBAAoB;AAAA,EAE3B,4BAAS;AAAA,EACT,0BAAO,iBAAiB;AAAA,EAExB,4BAAS;AAAA,EACT,0BAAO,iBAAiB;AAAA,GAxBhB;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../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/sync/sync.tokens.ts"],"sourcesContent":["/**\n * ObservabilityService — `IObservability` combiner implementation\n * (ADR-025, OBS-5).\n *\n * Composes read methods across the jobs, bridge, and sync 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 { SYNC_CURSOR_STORE, SYNC_RUN_RECORDER } from '../sync/sync.tokens';\nimport type {\n ISyncRunRecorder,\n SyncRunSummary,\n} from '../sync/sync-run-recorder.protocol';\nimport type {\n CursorSnapshot,\n ICursorStore,\n} from '../sync/sync-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(SYNC_RUN_RECORDER)\n private readonly syncRuns?: ISyncRunRecorder,\n @Optional()\n @Inject(SYNC_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 getRecentSyncRuns(\n limit: number,\n subscriptionId?: string,\n tenantId?: string | null,\n ): Promise<SyncRunSummary[]> {\n if (!this.syncRuns) return [];\n return this.syncRuns.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 * Sync 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 sync\n * stay internally consistent with strings.\n *\n * Usage in use cases:\n * ```ts\n * constructor(\n * @Inject(SYNC_CHANGE_SOURCE) private readonly source: IChangeSource<CanonicalOpportunity>,\n * @Inject(SYNC_CURSOR_STORE) private readonly cursors: ICursorStore,\n * @Inject(SYNC_FIELD_DIFFER) private readonly differ: IFieldDiffer<CanonicalOpportunity>,\n * @Inject(SYNC_SINK) private readonly sink: ISyncSink<CanonicalOpportunity>,\n * @Inject(SYNC_RUN_RECORDER) private readonly recorder: ISyncRunRecorder,\n * ) {}\n * ```\n *\n * Concrete bindings are registered by `SyncModule.forRoot(...)` (SYNC-6).\n */\n\nexport const SYNC_CHANGE_SOURCE = 'SYNC_CHANGE_SOURCE' as const;\nexport const SYNC_CURSOR_STORE = 'SYNC_CURSOR_STORE' as const;\nexport const SYNC_FIELD_DIFFER = 'SYNC_FIELD_DIFFER' as const;\nexport const SYNC_SINK = 'SYNC_SINK' as const;\n\n/**\n * Run-recorder token (SYNC-5). Backed by `ISyncRunRecorder`. Drizzle impl\n * lands in SYNC-4; tests provide inline fakes.\n */\nexport const SYNC_RUN_RECORDER = 'SYNC_RUN_RECORDER' as const;\n\n/**\n * Injection token for the resolved `SyncModuleOptions` 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 * `SyncModule.forRoot(...)` / `SyncModule.forRootAsync(...)`.\n */\nexport const SYNC_MODULE_OPTIONS = 'SYNC_MODULE_OPTIONS' as const;\n\n/**\n * Injection token for the resolved multi-tenancy flag (SYNC-6).\n *\n * Provided by `SyncModule.forRoot(...)` as `options.multiTenant ?? false`.\n * Consumed by `ExecuteSyncUseCase` to enforce the tenantId-is-required rule.\n */\nexport const SYNC_MULTI_TENANT = 'SYNC_MULTI_TENANT' as const;\n"],"mappings":"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","names":[]}
|
|
@@ -9,6 +9,7 @@ import 'drizzle-orm/node-postgres';
|
|
|
9
9
|
import '../../jobs/job-orchestration.schema.js';
|
|
10
10
|
import 'drizzle-orm/pg-core';
|
|
11
11
|
import 'drizzle-orm';
|
|
12
|
+
import '../../events/event-read.protocol.js';
|
|
12
13
|
import '../../bridge/bridge.protocol.js';
|
|
13
14
|
import '../../events/generated/types.js';
|
|
14
15
|
import '../../bridge/bridge-delivery.schema.js';
|
|
@@ -9,6 +9,7 @@ import 'drizzle-orm/node-postgres';
|
|
|
9
9
|
import '../../jobs/job-orchestration.schema.js';
|
|
10
10
|
import 'drizzle-orm/pg-core';
|
|
11
11
|
import 'drizzle-orm';
|
|
12
|
+
import '../../events/event-read.protocol.js';
|
|
12
13
|
import '../../bridge/bridge.protocol.js';
|
|
13
14
|
import '../../events/generated/types.js';
|
|
14
15
|
import '../../bridge/bridge-delivery.schema.js';
|
package/dist/src/cli/index.js
CHANGED
|
@@ -6102,6 +6102,29 @@ function quoteOpts(opts) {
|
|
|
6102
6102
|
if (entries.length === 0) return "";
|
|
6103
6103
|
return "{ " + entries.map(([k, v]) => `${k}: ${typeof v === "string" ? `'${v}'` : String(v)}`).join(", ") + " }";
|
|
6104
6104
|
}
|
|
6105
|
+
function jsonToTs(value) {
|
|
6106
|
+
if (value === null || value === void 0) return "undefined";
|
|
6107
|
+
if (typeof value === "string") return `'${value.replace(/'/g, "\\'")}'`;
|
|
6108
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
6109
|
+
if (Array.isArray(value)) return `[${value.map(jsonToTs).join(", ")}]`;
|
|
6110
|
+
if (typeof value === "object") {
|
|
6111
|
+
const entries = Object.entries(value).filter(
|
|
6112
|
+
([, v]) => v !== void 0
|
|
6113
|
+
);
|
|
6114
|
+
return `{ ${entries.map(([k, v]) => `${k}: ${jsonToTs(v)}`).join(", ")} }`;
|
|
6115
|
+
}
|
|
6116
|
+
return "undefined";
|
|
6117
|
+
}
|
|
6118
|
+
function quoteBullmqDomainOpts(input) {
|
|
6119
|
+
const { backend, multiTenant, bullExt } = input;
|
|
6120
|
+
if (backend !== "bullmq" || !bullExt) {
|
|
6121
|
+
return quoteOpts({ backend, multiTenant });
|
|
6122
|
+
}
|
|
6123
|
+
const parts = [`backend: 'bullmq'`];
|
|
6124
|
+
if (multiTenant) parts.push(`multiTenant: true`);
|
|
6125
|
+
parts.push(`extensions: { bullmq: ${jsonToTs(bullExt)} }`);
|
|
6126
|
+
return `{ ${parts.join(", ")} }`;
|
|
6127
|
+
}
|
|
6105
6128
|
var COMPOSERS = {
|
|
6106
6129
|
events: ({ subsystemsRel, cfg }) => {
|
|
6107
6130
|
const backend = cfg?.backend ?? "drizzle";
|
|
@@ -6122,14 +6145,15 @@ var COMPOSERS = {
|
|
|
6122
6145
|
const imports = [
|
|
6123
6146
|
`import { JobsDomainModule } from '${subsystemsRel}/jobs/jobs-domain.module';`
|
|
6124
6147
|
];
|
|
6125
|
-
const
|
|
6126
|
-
|
|
6127
|
-
];
|
|
6148
|
+
const bullExt = backend === "bullmq" ? cfg?.extensions?.bullmq : void 0;
|
|
6149
|
+
const domainOpts = quoteBullmqDomainOpts({ backend, multiTenant, bullExt });
|
|
6150
|
+
const calls = [` JobsDomainModule.forRoot(${domainOpts}),`];
|
|
6128
6151
|
if (workerMode === "embedded") {
|
|
6129
6152
|
imports.push(
|
|
6130
6153
|
`import { JobWorkerModule } from '${subsystemsRel}/jobs/job-worker.module';`
|
|
6131
6154
|
);
|
|
6132
|
-
|
|
6155
|
+
const workerOpts = backend === "bullmq" ? `{ mode: 'embedded', backend: 'bullmq'${bullExt ? `, domainModuleExtensions: { bullmq: ${jsonToTs(bullExt)} }` : ""} }` : `{ mode: 'embedded' }`;
|
|
6156
|
+
calls.push(` JobWorkerModule.forRoot(${workerOpts}),`);
|
|
6133
6157
|
}
|
|
6134
6158
|
return { imports, calls };
|
|
6135
6159
|
},
|
|
@@ -7273,8 +7297,8 @@ import type { DomainEvent } from '../event-bus.protocol';
|
|
|
7273
7297
|
export type AppDomainEvent = never;
|
|
7274
7298
|
|
|
7275
7299
|
export type EventTypeName = string;
|
|
7276
|
-
export type EventOfType<T extends EventTypeName> =
|
|
7277
|
-
export type PayloadOfType<T extends EventTypeName> =
|
|
7300
|
+
export type EventOfType<T extends EventTypeName> = DomainEvent;
|
|
7301
|
+
export type PayloadOfType<T extends EventTypeName> = DomainEvent['payload'];
|
|
7278
7302
|
`;
|
|
7279
7303
|
}
|
|
7280
7304
|
const chunks = [];
|