@pattern-stack/codegen 0.4.5 → 0.4.6

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.
@@ -354,7 +354,8 @@ __export(bridge_metrics_reporter_exports, {
354
354
  import {
355
355
  Inject as Inject2,
356
356
  Injectable as Injectable3,
357
- Logger
357
+ Logger,
358
+ Optional
358
359
  } from "@nestjs/common";
359
360
  import { and, eq as eq2, gt, sql as sql4 } from "drizzle-orm";
360
361
  var INTERVAL_NAME, DEFAULT_INTERVAL_MS, MIN_INTERVAL_MS, BridgeMetricsReporter;
@@ -368,7 +369,7 @@ var init_bridge_metrics_reporter = __esm({
368
369
  DEFAULT_INTERVAL_MS = 6e4;
369
370
  MIN_INTERVAL_MS = 1e3;
370
371
  BridgeMetricsReporter = class {
371
- constructor(db, scheduler) {
372
+ constructor(db, scheduler = null) {
372
373
  this.db = db;
373
374
  this.scheduler = scheduler;
374
375
  this.intervalMs = this.resolveIntervalMs();
@@ -379,6 +380,14 @@ var init_bridge_metrics_reporter = __esm({
379
380
  logger = new Logger(BridgeMetricsReporter.name);
380
381
  intervalMs;
381
382
  lastTickAt;
383
+ /**
384
+ * Timer handle retained as a field when `SchedulerRegistry` isn't
385
+ * available (optional dep). `SchedulerRegistry` is the Nest-idiomatic
386
+ * home for interval cleanup, but global-module wiring across consumer
387
+ * topologies doesn't always make it injectable here — the fallback
388
+ * keeps the reporter self-sufficient.
389
+ */
390
+ ownedTimer = null;
382
391
  onModuleInit() {
383
392
  this.logger.log(
384
393
  `BridgeMetricsReporter starting (intervalMs=${this.intervalMs}).`
@@ -387,12 +396,20 @@ var init_bridge_metrics_reporter = __esm({
387
396
  void this.tick();
388
397
  }, this.intervalMs);
389
398
  timer.unref?.();
390
- this.scheduler.addInterval(INTERVAL_NAME, timer);
399
+ if (this.scheduler) {
400
+ this.scheduler.addInterval(INTERVAL_NAME, timer);
401
+ } else {
402
+ this.ownedTimer = timer;
403
+ }
391
404
  }
392
405
  onModuleDestroy() {
393
- if (this.scheduler.getIntervals().includes(INTERVAL_NAME)) {
406
+ if (this.scheduler && this.scheduler.getIntervals().includes(INTERVAL_NAME)) {
394
407
  this.scheduler.deleteInterval(INTERVAL_NAME);
395
408
  }
409
+ if (this.ownedTimer !== null) {
410
+ clearInterval(this.ownedTimer);
411
+ this.ownedTimer = null;
412
+ }
396
413
  }
397
414
  /**
398
415
  * Run one sampling tick. Public so tests can drive it deterministically
@@ -474,7 +491,8 @@ var init_bridge_metrics_reporter = __esm({
474
491
  };
475
492
  BridgeMetricsReporter = __decorateClass([
476
493
  Injectable3(),
477
- __decorateParam(0, Inject2(DRIZZLE))
494
+ __decorateParam(0, Inject2(DRIZZLE)),
495
+ __decorateParam(1, Optional())
478
496
  ], BridgeMetricsReporter);
479
497
  }
480
498
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../runtime/constants/tokens.ts","../../../../runtime/subsystems/jobs/job-orchestration.schema.ts","../../../../runtime/subsystems/events/domain-events.schema.ts","../../../../runtime/subsystems/bridge/bridge-delivery.schema.ts","../../../../runtime/subsystems/observability/reporters/bridge-metrics.reporter.ts","../../../../runtime/subsystems/observability/observability.tokens.ts","../../../../runtime/subsystems/observability/observability.module.ts","../../../../runtime/subsystems/observability/observability.drizzle-backend.ts","../../../../runtime/subsystems/sync/sync-audit.schema.ts","../../../../runtime/subsystems/observability/observability.memory-backend.ts","../../../../runtime/subsystems/observability/index.ts"],"sourcesContent":["/**\n * NestJS injection tokens\n *\n * Used with @Inject() decorator in concrete repository constructors.\n */\n\n/**\n * Injection token for the Drizzle ORM database client.\n *\n * Usage in concrete repositories:\n * ```typescript\n * constructor(@Inject(DRIZZLE) db: DrizzleClient) { super(db); }\n * ```\n */\nexport const DRIZZLE = 'DRIZZLE' as const;\n\n/**\n * Injection token for the event bus (IEventBus).\n *\n * Optional — only resolved when EventsModule.forRoot() is registered.\n * BaseService uses this with @Optional() to emit lifecycle events\n * without requiring the events subsystem to be installed.\n *\n * Usage in services/use cases:\n * ```typescript\n * @Optional() @Inject(EVENT_BUS) eventBus?: IEventBus\n * ```\n */\nexport const EVENT_BUS = 'EVENT_BUS' as const;\n","/**\n * Drizzle schema for the job orchestration domain (ADR-022).\n *\n * Three tables model the lifecycle of a durable job:\n * - `job` — definitions keyed by handler type (e.g. 'onboarding').\n * - `job_run` — one row per attempt to execute a job; worker claims\n * rows directly via SELECT ... FOR UPDATE SKIP LOCKED.\n * - `job_step` — individual steps within a run; memoises output for replay.\n *\n * Phase 1 ships only this layer. There is no `job_queue` table, no executor\n * port — see ADR-022 and `.claude/skills/jobs/SKILL.md` for the rationale.\n */\nimport {\n pgEnum,\n pgTable,\n uuid,\n text,\n jsonb,\n integer,\n timestamp,\n index,\n uniqueIndex,\n} from 'drizzle-orm/pg-core';\nimport { sql } from 'drizzle-orm';\nimport type { InferSelectModel } from 'drizzle-orm';\n\n// ─── Internal $type<> helpers ───────────────────────────────────────────────\n// Annotation types for jsonb columns only. JOB-2 defines the public protocol\n// types; these remain private to this file.\n\ntype RetryPolicy = {\n attempts: number;\n backoff: 'fixed' | 'exponential';\n baseMs: number;\n nonRetryableErrors?: string[];\n};\n\ntype JobRunError = {\n message: string;\n stack?: string;\n retryable: boolean;\n attempt: number;\n};\n\n// ─── Enums ──────────────────────────────────────────────────────────────────\n\nexport const jobRunStatusEnum = pgEnum('job_run_status', [\n 'pending',\n 'running',\n 'waiting',\n 'completed',\n 'failed',\n 'timed_out',\n 'canceled',\n]);\n\n// extended in ADR-027: tool_call | llm_call | wait | checkpoint | message\nexport const jobStepKindEnum = pgEnum('job_step_kind', ['task']);\n\nexport const jobStepStatusEnum = pgEnum('job_step_status', [\n 'pending',\n 'running',\n 'completed',\n 'failed',\n 'skipped',\n]);\n\nexport const collisionModeEnum = pgEnum('job_collision_mode', [\n 'queue',\n 'reject',\n 'replace',\n]);\n\nexport const replayFromEnum = pgEnum('job_replay_from', [\n 'scratch',\n 'last_step',\n 'last_checkpoint',\n]);\n\nexport const parentClosePolicyEnum = pgEnum('job_parent_close_policy', [\n 'terminate',\n 'cancel',\n 'abandon',\n]);\n\n// Phase 3 placeholder — see ADR-025\nexport const waitKindEnum = pgEnum('job_wait_kind', ['signal']);\n\n// Phase 2 may add more sources; requires Atlas migration\nexport const triggerSourceEnum = pgEnum('job_trigger_source', [\n 'manual',\n 'schedule',\n 'event',\n 'parent',\n]);\n\n// ─── job ────────────────────────────────────────────────────────────────────\n\nexport const jobs = pgTable('job', {\n type: text('type').primaryKey(),\n version: integer('version').notNull().default(1),\n pool: text('pool').notNull(),\n scopeEntityType: text('scope_entity_type'),\n retryPolicy: jsonb('retry_policy').notNull().$type<RetryPolicy>(),\n timeoutMs: integer('timeout_ms'),\n concurrencyKeyTemplate: text('concurrency_key_template'),\n collisionMode: collisionModeEnum('collision_mode').notNull().default('queue'),\n dedupeKeyTemplate: text('dedupe_key_template'),\n dedupeWindowMs: integer('dedupe_window_ms'),\n priorityDefault: integer('priority_default').notNull().default(0),\n replayFrom: replayFromEnum('replay_from').notNull().default('last_checkpoint'),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),\n});\n\nexport type JobDefinitionRow = InferSelectModel<typeof jobs>;\n\n// ─── job_run ────────────────────────────────────────────────────────────────\n\nexport const jobRuns = pgTable(\n 'job_run',\n {\n id: uuid('id').primaryKey().defaultRandom(),\n jobType: text('job_type').notNull().references(() => jobs.type),\n jobVersion: integer('job_version').notNull(),\n parentRunId: uuid('parent_run_id').references((): any => jobRuns.id),\n /**\n * Service generates `id` client-side via randomUUID() and sets\n * root_run_id = id for root runs (single INSERT, no self-FK race).\n */\n rootRunId: uuid('root_run_id').notNull(),\n parentClosePolicy: parentClosePolicyEnum('parent_close_policy')\n .notNull()\n .default('terminate'),\n scopeEntityType: text('scope_entity_type'),\n scopeEntityId: text('scope_entity_id'),\n tenantId: text('tenant_id'),\n tags: jsonb('tags').notNull().default({}).$type<Record<string, string>>(),\n pool: text('pool').notNull(),\n priority: integer('priority').notNull().default(0),\n concurrencyKey: text('concurrency_key'),\n dedupeKey: text('dedupe_key'),\n status: jobRunStatusEnum('status').notNull().default('pending'),\n input: jsonb('input').notNull().$type<Record<string, unknown>>(),\n output: jsonb('output').$type<Record<string, unknown>>(),\n error: jsonb('error').$type<JobRunError>(),\n triggerSource: triggerSourceEnum('trigger_source').notNull(),\n triggerRef: text('trigger_ref'),\n runAt: timestamp('run_at', { withTimezone: true }).notNull().defaultNow(),\n startedAt: timestamp('started_at', { withTimezone: true }),\n finishedAt: timestamp('finished_at', { withTimezone: true }),\n claimedAt: timestamp('claimed_at', { withTimezone: true }),\n attempts: integer('attempts').notNull().default(0),\n // Phase 3 placeholder — see ADR-025\n waitKind: waitKindEnum('wait_kind'),\n // Phase 3 placeholder — see ADR-025\n resumeToken: text('resume_token'),\n // Phase 3 placeholder — see ADR-025\n waitDeadline: timestamp('wait_deadline', { withTimezone: true }),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),\n },\n (t) => ({\n /** Claim query: ORDER BY priority DESC, run_at ASC. */\n idxJobRunClaim: index('idx_job_run_claim').on(t.status, t.pool, t.runAt),\n /** Tree traversal / cascade cancel. */\n idxJobRunRoot: index('idx_job_run_root').on(t.rootRunId),\n /** listForScope query. */\n idxJobRunScope: index('idx_job_run_scope').on(t.scopeEntityType, t.scopeEntityId),\n /** Idempotency collapse — partial index. */\n idxJobRunDedupe: index('idx_job_run_dedupe')\n .on(t.jobType, t.dedupeKey)\n .where(sql`${t.dedupeKey} IS NOT NULL`),\n /** Collision check — partial index. */\n idxJobRunConcurrency: index('idx_job_run_concurrency')\n .on(t.concurrencyKey)\n .where(\n sql`${t.concurrencyKey} IS NOT NULL AND ${t.status} IN ('pending','running')`,\n ),\n }),\n);\n\nexport type JobRunRow = InferSelectModel<typeof jobRuns>;\n\n// ─── job_step ───────────────────────────────────────────────────────────────\n\nexport const jobSteps = pgTable(\n 'job_step',\n {\n id: uuid('id').primaryKey().defaultRandom(),\n jobRunId: uuid('job_run_id').notNull().references(() => jobRuns.id),\n stepId: text('step_id').notNull(),\n kind: jobStepKindEnum('kind').notNull().default('task'),\n /**\n * Monotonic within run. integer (max ~2B per run) is sufficient —\n * downgraded from ADR-022's bigint; revisit only if a single run\n * ever exceeds 2 billion steps.\n */\n seq: integer('seq').notNull(),\n status: jobStepStatusEnum('status').notNull().default('pending'),\n input: jsonb('input').$type<Record<string, unknown>>(),\n /** Memoised on success for replay. */\n output: jsonb('output').$type<Record<string, unknown>>(),\n error: jsonb('error').$type<JobRunError>(),\n attempts: integer('attempts').notNull().default(0),\n startedAt: timestamp('started_at', { withTimezone: true }),\n finishedAt: timestamp('finished_at', { withTimezone: true }),\n },\n (t) => ({\n /** No duplicate step IDs per run. */\n idxJobStepRunStep: uniqueIndex('idx_job_step_run_step').on(t.jobRunId, t.stepId),\n /** Ordered timeline reads. */\n idxJobStepTimeline: index('idx_job_step_timeline').on(t.jobRunId, t.seq),\n }),\n);\n\nexport type JobStepRow = InferSelectModel<typeof jobSteps>;\n","/**\n * Drizzle schema for the domain_events outbox table.\n *\n * This table backs the DrizzleEventBus. Events are inserted within the\n * same database transaction as the domain write (outbox pattern). A\n * polling process reads unprocessed rows and dispatches to subscribers.\n *\n * First-class routing columns (EVT-1):\n * - `pool` — populated by DrizzleEventBus.publish() (EVT-4); enables\n * pool-filtered drain queries without unpacking metadata JSON.\n * - `direction` — `inbound` | `change` | `outbound`; mirrors the routing\n * dimension used by jobs' reserved `events_inbound` /\n * `events_change` / `events_outbound` pools.\n * - `tenant_id` — conditional: emitted only when `events.multi_tenant: true`\n * in `codegen.config.yaml`. The runtime source declares it\n * unconditionally; EVT-8's scaffold template handles the\n * config-driven include/exclude.\n *\n * The `metadata` JSON column continues to carry these values for protocol\n * stability; the first-class columns are an optimization for drain filtering.\n *\n * Indexes (declared below in the index callback):\n * - (status, occurred_at) — polling drain filter\n * - (aggregate_id, aggregate_type) — event replay per aggregate\n * - (pool, status, occurred_at) — per-pool drain filter (EVT-1)\n */\nimport {\n index,\n jsonb,\n pgTable,\n text,\n timestamp,\n uuid,\n} from 'drizzle-orm/pg-core';\nimport type { InferSelectModel } from 'drizzle-orm';\n\nexport const domainEvents = pgTable(\n 'domain_events',\n {\n id: uuid('id').primaryKey(),\n type: text('type').notNull(),\n aggregateId: text('aggregate_id').notNull(),\n aggregateType: text('aggregate_type').notNull(),\n payload: jsonb('payload').notNull().$type<Record<string, unknown>>(),\n occurredAt: timestamp('occurred_at', { withTimezone: true }).notNull(),\n processedAt: timestamp('processed_at', { withTimezone: true }),\n /** Lifecycle status: pending | processed | failed */\n status: text('status').notNull().default('pending'),\n /** Error message from the last failed dispatch attempt. */\n error: text('error'),\n metadata: jsonb('metadata').$type<Record<string, unknown>>(),\n /** Routing pool (e.g. `events_inbound`, `events_change`, `events_outbound`). Populated by DrizzleEventBus.publish() in EVT-4. */\n pool: text('pool'),\n /** Routing direction: `inbound` | `change` | `outbound`. Populated by DrizzleEventBus.publish() in EVT-4. */\n direction: text('direction'),\n // conditional: emitted only when events.multi_tenant: true\n tenantId: text('tenant_id'),\n },\n (t) => ({\n /** Polling drain filter (existing — promoted from comment to declaration in EVT-1). */\n idxDomainEventsStatusOccurredAt: index('idx_domain_events_status_occurred_at').on(\n t.status,\n t.occurredAt,\n ),\n /** Event replay per aggregate (existing — promoted from comment to declaration in EVT-1). */\n idxDomainEventsAggregate: index('idx_domain_events_aggregate').on(\n t.aggregateId,\n t.aggregateType,\n ),\n /** Per-pool drain filter (EVT-1). Enables DrizzleEventBus to drain a single pool without scanning all events. */\n idxDomainEventsPoolStatusOccurredAt: index(\n 'idx_domain_events_pool_status_occurred_at',\n ).on(t.pool, t.status, t.occurredAt),\n }),\n);\n\nexport type DomainEventRecord = InferSelectModel<typeof domainEvents>;\n","/**\n * Drizzle schema for the `bridge_delivery` ledger (ADR-023 Phase 2, BRIDGE-1).\n *\n * The `bridge_delivery` table is the idempotency ledger for the event-to-job\n * bridge. Every (event, trigger) pair the bridge is asked to spawn produces\n * exactly one row; the `UNIQUE (event_id, trigger_id)` constraint guarantees\n * that:\n *\n * 1. Outbox replays of an event do not double-spawn user job runs — the\n * drain attempts to insert the duplicate, the constraint trips, and the\n * drain skips that trigger.\n * 2. The `IEventFlow.publishAndStart` facade can pre-write a\n * `(status='delivered')` row before the drain runs (Case B from ADR-023\n * §`publishAndStart` + existing `triggers:` collision); the drain then\n * hits UNIQUE on that trigger and skips it while still spawning any\n * other triggers for the same event normally.\n *\n * Status values:\n * - `pending` — wrapper run exists; user job not yet started.\n * - `delivered` — user job started; `user_run_id` populated.\n * - `skipped` — intentional no-op (`when:` returned false, or\n * facade-eager path pre-empted the bridge spawn).\n * - `failed` — wrapper exhausted retry policy; no auto-retry past that\n * (mirrors events outbox stance — ops eyes only).\n *\n * `wrapper_run_id` is **nullable**: the facade-eager path (Case B) pre-writes\n * `bridge_delivery` with no wrapper. The bridge-drain path always populates\n * it.\n *\n * `tenant_id` is emitted **unconditionally and nullable** (per JOB-8\n * 2026-04-20 reversal); enforcement is service-layer (BRIDGE-8) gated on the\n * `BRIDGE_MULTI_TENANT` DI token, not a DB constraint.\n *\n * Indexes:\n * - `bridge_delivery_event_idx` — lookup all deliveries for an event.\n * - `bridge_delivery_status_idx` — partial; ops dashboards filter by\n * `pending | failed`.\n * - `bridge_delivery_user_run_idx` — partial; reverse lookup from a\n * spawned user run back to its delivery row.\n *\n * No service logic, no DI wiring — this is the schema foundation. Backends\n * (memory + drizzle) and the framework handler land in BRIDGE-3 / BRIDGE-4 /\n * BRIDGE-5.\n */\nimport {\n index,\n jsonb,\n pgEnum,\n pgTable,\n text,\n timestamp,\n unique,\n uuid,\n} from 'drizzle-orm/pg-core';\nimport { sql } from 'drizzle-orm';\nimport type { InferSelectModel } from 'drizzle-orm';\n\nimport { domainEvents } from '../events/domain-events.schema';\nimport { jobRuns } from '../jobs/job-orchestration.schema';\n\n// ─── Enum ───────────────────────────────────────────────────────────────────\n\nexport const bridgeDeliveryStatusEnum = pgEnum('bridge_delivery_status', [\n 'pending',\n 'delivered',\n 'skipped',\n 'failed',\n]);\n\n// ─── Table ──────────────────────────────────────────────────────────────────\n\nexport const bridgeDelivery = pgTable(\n 'bridge_delivery',\n {\n id: uuid('id').primaryKey().defaultRandom(),\n /** FK to the source event in the outbox. */\n eventId: uuid('event_id')\n .notNull()\n .references(() => domainEvents.id),\n /**\n * Stable codegen-emitted identifier for the (job, trigger) pair, of the\n * form `<job_type>#<triggerIndex>` (BRIDGE-6). Forms the second half of\n * the UNIQUE idempotency key.\n */\n triggerId: text('trigger_id').notNull(),\n /**\n * Wrapper `job_run.id` (the framework `@framework/bridge_delivery` run\n * that drove this delivery). Nullable: the facade-eager path\n * (`publishAndStart` Case B) pre-writes a delivered row with no wrapper.\n */\n wrapperRunId: uuid('wrapper_run_id').references(() => jobRuns.id),\n /**\n * Spawned user `job_run.id`. Null until status is `delivered`; remains\n * null for `skipped` and `failed` deliveries.\n */\n userRunId: uuid('user_run_id').references(() => jobRuns.id),\n status: bridgeDeliveryStatusEnum('status').notNull().default('pending'),\n /** Populated when status=`skipped` (e.g. `'when_returned_false'`, `'trigger_unregistered'`). */\n skipReason: text('skip_reason'),\n /** Populated when status=`failed`. Mirrors `job_run.error` shape. */\n error: jsonb('error').$type<Record<string, unknown>>(),\n /**\n * Emitted unconditionally and nullable (JOB-8 / SYNC-6 precedent).\n * Enforcement gated on `BRIDGE_MULTI_TENANT` at the service layer\n * (BRIDGE-8); no DB constraint.\n */\n tenantId: text('tenant_id'),\n attemptedAt: timestamp('attempted_at', { withTimezone: true })\n .notNull()\n .defaultNow(),\n deliveredAt: timestamp('delivered_at', { withTimezone: true }),\n },\n (t) => ({\n /**\n * Idempotency ledger. Outbox replays and facade-vs-drain collisions both\n * dedup through this constraint.\n */\n uqBridgeDeliveryEventTrigger: unique('uq_bridge_delivery_event_trigger').on(\n t.eventId,\n t.triggerId,\n ),\n /** Lookup all deliveries for an event (fanout report, debugging). */\n idxBridgeDeliveryEvent: index('idx_bridge_delivery_event').on(t.eventId),\n /**\n * Ops dashboard filter — only the actionable states. Partial index keeps\n * it small at scale (the bulk of rows will be `delivered`).\n */\n idxBridgeDeliveryStatus: index('idx_bridge_delivery_status')\n .on(t.status)\n .where(sql`${t.status} IN ('pending','failed')`),\n /**\n * Reverse lookup from a spawned user run back to its delivery row.\n * Partial — most rows in the bridge ledger but only successful\n * deliveries have a `user_run_id`.\n */\n idxBridgeDeliveryUserRun: index('idx_bridge_delivery_user_run')\n .on(t.userRunId)\n .where(sql`${t.userRunId} IS NOT NULL`),\n }),\n);\n\nexport type BridgeDeliveryRecord = InferSelectModel<typeof bridgeDelivery>;\n","/**\n * BridgeMetricsReporter — periodic structured-log sampler for the\n * `bridge_delivery` ledger.\n *\n * Runs on a timer (default 60s, configurable via\n * `BRIDGE_METRICS_INTERVAL_MS`) and emits ONE `Logger.log` line per tick\n * describing counts of rows that transitioned through each terminal\n * status in the last tick window, grouped by `(status, event_type,\n * skip_reason)`.\n *\n * # Placement\n *\n * Lives under `observability/reporters/` rather than in the bridge\n * subsystem itself because:\n * 1. It's not part of the bridge's functional surface — a reporter is\n * an observability concern composed on top.\n * 2. Future reporters (Prometheus exporter, OTel bridge, etc.) slot in\n * here with no cross-subsystem import churn.\n *\n * # Opt-in via ObservabilityModule\n *\n * The reporter is NOT provided automatically. Opt in via\n * `ObservabilityModule.forRoot({ backend, reporters: { bridgeMetrics: true } })`\n * — the module only registers the reporter when that flag is set, which\n * keeps consumers without the bridge subsystem free of its schema import\n * tax (tree-shaken; see `observability.module.ts` for the gate).\n *\n * # Why a sampler instead of in-handler logs\n *\n * The bridge subsystem writes the `bridge_delivery` ledger directly; adding\n * per-transition log lines inside the handler would double every row at\n * 1:1 cardinality. Aggregating per-tick produces the \"counts per event\n * type of delivered/skipped/failed\" shape that ops dashboards want,\n * without touching the bridge runtime.\n *\n * # Why aggregate-per-tick rather than per-row\n *\n * Deliveries flow at bulk-sync cadence (one event per persisted CRM\n * record). Per-row logs would be noisy and duplicative of the ledger\n * itself; aggregates match the \"counts per event type of\n * delivered/skipped/failed\" operator surface.\n */\nimport {\n Inject,\n Injectable,\n Logger,\n type OnModuleDestroy,\n type OnModuleInit,\n} from '@nestjs/common';\nimport { SchedulerRegistry } from '@nestjs/schedule';\nimport { and, eq, gt, sql } from 'drizzle-orm';\n\nimport { DRIZZLE } from '../../../constants/tokens';\nimport type { DrizzleClient } from '../../../types/drizzle';\nimport { bridgeDelivery } from '../../bridge/bridge-delivery.schema';\nimport { domainEvents } from '../../events/domain-events.schema';\n\nconst INTERVAL_NAME = 'bridge-metrics-tick';\n\n/** Default sampling interval (1 minute). */\nconst DEFAULT_INTERVAL_MS = 60_000;\n\n/** Minimum allowed interval — guards against env misconfig producing a hot loop. */\nconst MIN_INTERVAL_MS = 1_000;\n\nexport interface BridgeMetricsRow {\n status: 'pending' | 'delivered' | 'skipped' | 'failed';\n eventType: string;\n skipReason: string | null;\n count: number;\n}\n\nexport interface BridgeMetricsTick {\n windowStart: Date;\n windowEnd: Date;\n rows: BridgeMetricsRow[];\n}\n\n@Injectable()\nexport class BridgeMetricsReporter implements OnModuleInit, OnModuleDestroy {\n private readonly logger = new Logger(BridgeMetricsReporter.name);\n private readonly intervalMs: number;\n private lastTickAt: Date;\n\n constructor(\n @Inject(DRIZZLE) private readonly db: DrizzleClient,\n private readonly scheduler: SchedulerRegistry,\n ) {\n this.intervalMs = this.resolveIntervalMs();\n // Initialize the window tail at boot so the first tick reports only\n // deliveries that transitioned after the reporter started.\n this.lastTickAt = new Date();\n }\n\n onModuleInit(): void {\n this.logger.log(\n `BridgeMetricsReporter starting (intervalMs=${this.intervalMs}).`,\n );\n const timer = setInterval(() => {\n void this.tick();\n }, this.intervalMs);\n // Allow the process to exit naturally in test runs — setInterval\n // otherwise pins the event loop open.\n timer.unref?.();\n this.scheduler.addInterval(INTERVAL_NAME, timer);\n }\n\n onModuleDestroy(): void {\n if (this.scheduler.getIntervals().includes(INTERVAL_NAME)) {\n this.scheduler.deleteInterval(INTERVAL_NAME);\n }\n }\n\n /**\n * Run one sampling tick. Public so tests can drive it deterministically\n * without waiting on the timer.\n */\n async tick(): Promise<BridgeMetricsTick> {\n const windowStart = this.lastTickAt;\n const windowEnd = new Date();\n this.lastTickAt = windowEnd;\n\n let rows: BridgeMetricsRow[] = [];\n try {\n rows = await this.sample(windowStart, windowEnd);\n } catch (err) {\n this.logger.error(\n `bridge metrics sample failed: ${(err as Error).message}`,\n );\n return { windowStart, windowEnd, rows: [] };\n }\n\n this.emit({ windowStart, windowEnd, rows });\n return { windowStart, windowEnd, rows };\n }\n\n private async sample(\n windowStart: Date,\n windowEnd: Date,\n ): Promise<BridgeMetricsRow[]> {\n // Terminal transitions land `delivered_at` for `delivered`, and leave\n // `attempted_at` as the most recent timestamp for `skipped`/`failed`.\n // Window on COALESCE so terminal skipped/failed rows are captured\n // alongside delivered. Upper edge bounded by windowEnd so a long tick\n // can't double-count rows that transitioned between sample and emit.\n const lastTransition = sql<Date>`COALESCE(${bridgeDelivery.deliveredAt}, ${bridgeDelivery.attemptedAt})`;\n\n const result = await this.db\n .select({\n status: bridgeDelivery.status,\n eventType: domainEvents.type,\n skipReason: bridgeDelivery.skipReason,\n count: sql<number>`COUNT(*)::int`,\n })\n .from(bridgeDelivery)\n .innerJoin(domainEvents, eq(bridgeDelivery.eventId, domainEvents.id))\n .where(\n and(\n gt(lastTransition, windowStart),\n sql`${lastTransition} <= ${windowEnd}`,\n ),\n )\n .groupBy(\n bridgeDelivery.status,\n domainEvents.type,\n bridgeDelivery.skipReason,\n );\n\n return result.map((r) => ({\n status: r.status as BridgeMetricsRow['status'],\n eventType: r.eventType,\n skipReason: r.skipReason,\n count: r.count,\n }));\n }\n\n private emit(tick: BridgeMetricsTick): void {\n if (tick.rows.length === 0) {\n // Heartbeat — confirms the sampler is alive when deliveries are idle.\n // Cheap enough at default 60s cadence; operators rely on this signal\n // to distinguish \"bridge quiet\" from \"reporter dead\".\n this.logger.log(\n `bridge_metrics tick=empty window=[${tick.windowStart.toISOString()}..${tick.windowEnd.toISOString()}]`,\n );\n return;\n }\n\n const totals = tick.rows.reduce(\n (acc, r) => {\n acc[r.status] = (acc[r.status] ?? 0) + r.count;\n return acc;\n },\n {} as Record<string, number>,\n );\n\n const detail = tick.rows\n .map(\n (r) =>\n `${r.eventType}|${r.status}${r.skipReason ? `:${r.skipReason}` : ''}=${r.count}`,\n )\n .join(' ');\n\n this.logger.log(\n `bridge_metrics tick window=[${tick.windowStart.toISOString()}..${tick.windowEnd.toISOString()}] ` +\n `totals=${JSON.stringify(totals)} detail=[${detail}]`,\n );\n }\n\n private resolveIntervalMs(): number {\n const raw = process.env['BRIDGE_METRICS_INTERVAL_MS'];\n if (!raw) return DEFAULT_INTERVAL_MS;\n const parsed = Number.parseInt(raw, 10);\n if (!Number.isFinite(parsed) || parsed < MIN_INTERVAL_MS) {\n new Logger(BridgeMetricsReporter.name).warn(\n `Ignoring BRIDGE_METRICS_INTERVAL_MS='${raw}' (invalid or < ${MIN_INTERVAL_MS}ms); ` +\n `using default ${DEFAULT_INTERVAL_MS}ms.`,\n );\n return DEFAULT_INTERVAL_MS;\n }\n return parsed;\n }\n}\n","/**\n * Injection token for the observability service (ADR-008, 5th subsystem).\n *\n * ```typescript\n * constructor(@Inject(OBSERVABILITY) private readonly obs: IObservabilityService) {}\n * ```\n *\n * Per ADR-008, tokens use `Symbol()` for collision avoidance.\n */\nexport const OBSERVABILITY = Symbol('OBSERVABILITY');\n\n/**\n * Opt-in config token that tells the module whether to register the\n * `BridgeMetricsReporter` sampler. Consumers without the bridge subsystem\n * leave this `false` (the default) so the module doesn't import the\n * reporter's bridge-schema deps.\n */\nexport const OBSERVABILITY_REPORTERS = Symbol('OBSERVABILITY_REPORTERS');\n","/**\n * ObservabilityModule — DynamicModule factory for the observability\n * subsystem (ADR-008, 5th subsystem).\n *\n * Usage in AppModule:\n * ```typescript\n * ObservabilityModule.forRoot({\n * backend: 'drizzle',\n * reporters: { bridgeMetrics: true }, // optional — requires bridge subsystem\n * })\n * ```\n *\n * Usage in tests:\n * ```typescript\n * ObservabilityModule.forRoot({ backend: 'memory' })\n * ```\n *\n * `global: true` means any module that needs `IObservabilityService` can\n * inject `OBSERVABILITY` without importing this module. Register once in\n * AppModule.\n *\n * The drizzle backend requires `DRIZZLE` to be provided globally (e.g.,\n * via DatabaseModule). The memory backend has no dependencies.\n *\n * # Reporters\n *\n * Reporters are orthogonal to backends — they compose on top of either\n * drizzle or memory. The `reporters.bridgeMetrics` flag enables the\n * `BridgeMetricsReporter` sampler. Gated because the reporter imports the\n * bridge + events schemas; consumers without the bridge subsystem should\n * leave it off (the default).\n *\n * `ScheduleModule.forRoot()` is imported conditionally — only when a\n * reporter that needs it is enabled. Keeps the module dependency-light\n * for consumers that only want the read surface.\n */\nimport { type DynamicModule, Module } from '@nestjs/common';\n\nimport { DrizzleObservabilityService } from './observability.drizzle-backend';\nimport { MemoryObservabilityService } from './observability.memory-backend';\nimport {\n OBSERVABILITY,\n OBSERVABILITY_REPORTERS,\n} from './observability.tokens';\n\nexport interface ObservabilityReporterOptions {\n /**\n * Register `BridgeMetricsReporter` — periodic log sampler over\n * `bridge_delivery`. Requires the bridge subsystem (schemas imported\n * transitively). Defaults to `false`.\n */\n bridgeMetrics?: boolean;\n}\n\nexport interface ObservabilityModuleOptions {\n backend: 'drizzle' | 'memory';\n reporters?: ObservabilityReporterOptions;\n}\n\n@Module({})\nexport class ObservabilityModule {\n static forRoot(\n options: ObservabilityModuleOptions = { backend: 'drizzle' },\n ): DynamicModule {\n const ConcreteClass =\n options.backend === 'drizzle'\n ? DrizzleObservabilityService\n : MemoryObservabilityService;\n\n const wantsBridgeMetrics = options.reporters?.bridgeMetrics === true;\n\n const providers: DynamicModule['providers'] = [\n // Register the concrete class as the canonical instance.\n ConcreteClass,\n // OBSERVABILITY token points at the same instance — no duplicate.\n { provide: OBSERVABILITY, useExisting: ConcreteClass },\n // Expose the resolved reporter config for introspection / tests.\n {\n provide: OBSERVABILITY_REPORTERS,\n useValue: options.reporters ?? {},\n },\n ];\n\n const exports: DynamicModule['exports'] = [OBSERVABILITY];\n if (wantsBridgeMetrics) {\n // Lazy-require keeps the reporter file (and its @nestjs/schedule +\n // bridge schema imports) off the hot path for consumers who don't\n // enable the reporter.\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const { BridgeMetricsReporter } = require('./reporters/bridge-metrics.reporter');\n providers.push(BridgeMetricsReporter);\n exports.push(BridgeMetricsReporter);\n }\n\n // ScheduleModule is a PEER dep (optional) — only resolved when a\n // reporter that uses it is enabled. Consumers using the read-only\n // surface (default) are free of the @nestjs/schedule install tax.\n const imports: DynamicModule['imports'] = [];\n if (wantsBridgeMetrics) {\n // Lazy-require: avoids parse-time failure for consumers that haven't\n // installed @nestjs/schedule and don't need reporters.\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const { ScheduleModule } = require('@nestjs/schedule');\n imports.push(ScheduleModule.forRoot());\n }\n\n return {\n module: ObservabilityModule,\n global: true,\n imports,\n providers,\n exports,\n };\n }\n}\n","/**\n * DrizzleObservabilityService — production backend for IObservabilityService.\n *\n * Pure read-only SQL over framework-owned tables:\n * - `job_run` (jobs subsystem)\n * - `bridge_delivery` (bridge subsystem)\n * - `domain_events` (events subsystem)\n * - `sync_runs` (sync subsystem)\n * - `sync_subscriptions` (sync subsystem)\n *\n * No new tables, no background loops, no lifecycle hooks. This is a query\n * facade — each call hits the DB and returns. Rate-limit / dashboard-cadence\n * coordination is the caller's responsibility.\n *\n * # Error behavior\n *\n * Methods throw on DB failure (consistent with the rest of the ADR-008\n * family's write-ish backends). Dashboards and `/dev/status` endpoints are\n * expected to handle the error surface — returning an empty snapshot on a\n * transient DB blip would silently hide \"Postgres is down\" from operators,\n * which is the opposite of what observability is for.\n *\n * # Drizzle-specific extensions (documented per CLAUDE.md core/extensions)\n *\n * Extensions MAY be added to this class that leverage Postgres-specific\n * capability (e.g. `pg_stat_activity` sampling, advisory-lock inspection).\n * Consumers opting into extensions accept backend-specific coupling; the\n * core five methods below stay backend-portable.\n */\nimport { Inject, Injectable } from '@nestjs/common';\nimport { desc, eq, sql } from 'drizzle-orm';\n\nimport { DRIZZLE } from '../../constants/tokens';\nimport type { DrizzleClient } from '../../types/drizzle';\nimport { bridgeDelivery } from '../bridge/bridge-delivery.schema';\nimport { jobRuns } from '../jobs/job-orchestration.schema';\nimport { syncRuns, syncSubscriptions } from '../sync/sync-audit.schema';\nimport type {\n CursorSnapshot,\n IObservabilityService,\n JobRunFailure,\n PoolDepth,\n StatusHistogram,\n SyncRunSummary,\n} from './observability.protocol';\n\n@Injectable()\nexport class DrizzleObservabilityService implements IObservabilityService {\n constructor(@Inject(DRIZZLE) private readonly db: DrizzleClient) {}\n\n async getPoolDepths(): Promise<PoolDepth[]> {\n // Raw SQL: Drizzle's builder drops AS-aliases on bare `sql<>` columns,\n // which the pg driver then can't map back by name. Raw execute with\n // explicit aliases keeps the result shape deterministic.\n const result = await this.db.execute(sql`\n SELECT\n pool AS name,\n COUNT(*) FILTER (WHERE status = 'pending')::int AS pending,\n COUNT(*) FILTER (WHERE status = 'running')::int AS running,\n (percentile_cont(0.95) WITHIN GROUP (\n ORDER BY EXTRACT(EPOCH FROM (now() - claimed_at)) * 1000\n ) FILTER (WHERE status = 'running' AND claimed_at IS NOT NULL))::int\n AS claimed_age_p95_ms\n FROM job_run\n WHERE status IN ('pending','running')\n GROUP BY pool\n ORDER BY pool\n `);\n\n const rows = extractRows<{\n name: string;\n pending: number;\n running: number;\n claimed_age_p95_ms: number | null;\n }>(result);\n\n return rows.map((r) => ({\n name: r.name,\n pending: r.pending,\n running: r.running,\n claimedAgeP95Ms: r.claimed_age_p95_ms,\n }));\n }\n\n async getRecentSyncRuns(\n limit: number,\n integrationId?: string,\n ): Promise<SyncRunSummary[]> {\n // Join to sync_subscriptions to recover integration/adapter/domain so\n // callers don't re-hydrate the subscription row themselves. Upstream\n // sync_runs owns only subscription_id; the enrichment columns live on\n // the subscription side.\n const base = this.db\n .select({\n id: syncRuns.id,\n subscriptionId: syncRuns.subscriptionId,\n integrationId: syncSubscriptions.integrationId,\n adapter: syncSubscriptions.adapter,\n domain: syncSubscriptions.domain,\n direction: syncRuns.direction,\n action: syncRuns.action,\n status: syncRuns.status,\n recordsFound: syncRuns.recordsFound,\n recordsProcessed: syncRuns.recordsProcessed,\n durationMs: syncRuns.durationMs,\n error: syncRuns.error,\n startedAt: syncRuns.startedAt,\n completedAt: syncRuns.completedAt,\n })\n .from(syncRuns)\n .innerJoin(\n syncSubscriptions,\n eq(syncRuns.subscriptionId, syncSubscriptions.id),\n );\n\n const filtered =\n integrationId !== undefined\n ? base.where(eq(syncSubscriptions.integrationId, integrationId))\n : base;\n\n const rows = await filtered.orderBy(desc(syncRuns.startedAt)).limit(limit);\n\n return rows.map((r) => ({\n id: r.id,\n subscriptionId: r.subscriptionId,\n integrationId: r.integrationId,\n adapter: r.adapter,\n domain: r.domain,\n direction: r.direction,\n action: r.action,\n status: r.status,\n recordsFound: r.recordsFound,\n recordsProcessed: r.recordsProcessed,\n durationMs: r.durationMs,\n error: r.error,\n startedAt: r.startedAt,\n completedAt: r.completedAt,\n }));\n }\n\n async getBridgeDeliveryHistogram(\n windowHours: number,\n ): Promise<StatusHistogram> {\n // Window on COALESCE(delivered_at, attempted_at) so terminal skipped/\n // failed rows (which never get delivered_at) are counted alongside\n // delivered rows. The histogram is a flat Record<status, count>.\n const result = await this.db.execute(sql`\n SELECT status, COUNT(*)::int AS count\n FROM bridge_delivery\n WHERE COALESCE(delivered_at, attempted_at) > now() - make_interval(hours => ${windowHours})\n GROUP BY status\n `);\n\n const rows = extractRows<{ status: string; count: number }>(result);\n const hist: StatusHistogram = {};\n for (const r of rows) hist[r.status] = r.count;\n return hist;\n }\n\n async getRecentFailedJobs(limit: number): Promise<JobRunFailure[]> {\n const rows = await this.db\n .select({\n id: jobRuns.id,\n jobType: jobRuns.jobType,\n pool: jobRuns.pool,\n status: jobRuns.status,\n error: jobRuns.error,\n startedAt: jobRuns.startedAt,\n finishedAt: jobRuns.finishedAt,\n attempts: jobRuns.attempts,\n })\n .from(jobRuns)\n .where(eq(jobRuns.status, 'failed'))\n .orderBy(desc(jobRuns.finishedAt))\n .limit(limit);\n\n return rows.map((r) => ({\n id: r.id,\n jobType: r.jobType,\n pool: r.pool,\n status: r.status,\n error: r.error,\n startedAt: r.startedAt,\n finishedAt: r.finishedAt,\n attempts: r.attempts,\n }));\n }\n\n async getCursors(): Promise<CursorSnapshot[]> {\n const rows = await this.db\n .select({\n id: syncSubscriptions.id,\n integrationId: syncSubscriptions.integrationId,\n adapter: syncSubscriptions.adapter,\n domain: syncSubscriptions.domain,\n cursor: syncSubscriptions.cursor,\n lastSyncAt: syncSubscriptions.lastSyncAt,\n })\n .from(syncSubscriptions)\n .where(eq(syncSubscriptions.enabled, true))\n .orderBy(syncSubscriptions.integrationId, syncSubscriptions.domain);\n\n return rows.map((r) => ({\n subscriptionId: r.id,\n integrationId: r.integrationId,\n adapter: r.adapter,\n domain: r.domain,\n lastCursor: r.cursor,\n lastSyncAt: r.lastSyncAt,\n }));\n }\n}\n\n/**\n * Normalize `db.execute()` return shape. `node-postgres` returns `{ rows: [] }`\n * while some pg-compatible drivers return the row array directly.\n */\nfunction extractRows<T>(result: unknown): T[] {\n const maybe = result as { rows?: unknown };\n if (Array.isArray(maybe.rows)) return maybe.rows as T[];\n if (Array.isArray(result)) return result as T[];\n return [];\n}\n","/**\n * Drizzle schema for the sync subsystem audit/observability tables (SYNC-1).\n *\n * Three tables model end-to-end sync observability, keyed by the single port\n * every sync adapter implements (`IChangeSource<T>` from SYNC-2):\n *\n * - `sync_subscriptions` — owns the cursor per\n * `(integration_id, adapter, domain, external_ref)` tuple. Addressed\n * by id by `ICursorStore` (SYNC-3/SYNC-4).\n * - `sync_runs` — per-run audit log: start/complete, status,\n * cursor before/after, counts, direction (inbound|outbound),\n * action (poll|cdc|webhook|manual|writeback).\n * - `sync_run_items` — per-record change log with structured\n * `changed_fields` jsonb enforced by the Zod `FieldDiffSchema`\n * contract (ADR-0003; protocol lives in SYNC-2's\n * sync-field-diff.protocol.ts).\n *\n * Design calls (vs. issue #126 open questions):\n *\n * - `sync_subscriptions` ships in the subsystem (not consumer-owned).\n * Rationale: SYNC-4's `PostgresCursorStore` needs to read/write this\n * table directly; making it consumer-owned would require consumers to\n * hand-wire a shape the backend already knows. The row is addressable\n * by id and scoped by the uniqueness tuple; consumers can still\n * query/list it freely. Same stance as `job_run` being subsystem-\n * owned while remaining consumer-queryable.\n *\n * - `tenant_id` is always emitted on the three tables as nullable text.\n * The `SYNC_MULTI_TENANT` DI flag (SYNC-6) is what enforces the\n * non-null + cross-tenant-isolation contract at the service/orchestrator\n * boundary. This mirrors JOB-1/JOB-8's final shape — runtime guard, not\n * a scaffold-time conditional column. Keeps the schema file uniform\n * across single-tenant and multi-tenant deployments.\n *\n * - `changed_fields` on `sync_run_items` is typed via the Zod-inferred\n * `FieldDiff` shape from SYNC-2 (`{ [fieldName]: { from, to } }`). The\n * recorder service (SYNC-5) validates every write against\n * `FieldDiffSchema.parse` so consumers can rely on the shape.\n */\nimport {\n pgEnum,\n pgTable,\n uuid,\n text,\n jsonb,\n integer,\n boolean,\n timestamp,\n index,\n uniqueIndex,\n} from 'drizzle-orm/pg-core';\nimport type { InferSelectModel } from 'drizzle-orm';\n\nimport type { FieldDiff } from './sync-field-diff.protocol';\n\n// ─── Enums ──────────────────────────────────────────────────────────────────\n\n/**\n * Direction of a sync run relative to local state.\n *\n * - `inbound` — external → local (the common case: SFDC poll → local DB).\n * - `outbound` — local → external (writeback; deferred per epic but the\n * column shape is reserved so future writeback runs share the audit log).\n */\nexport const syncRunDirectionEnum = pgEnum('sync_run_direction', [\n 'inbound',\n 'outbound',\n]);\n\n/**\n * How the run detected upstream changes. Maps 1:1 to the `Change.source`\n * provenance on inbound runs; `manual` captures operator-triggered re-syncs\n * and `writeback` captures outbound runs.\n */\nexport const syncRunActionEnum = pgEnum('sync_run_action', [\n 'poll',\n 'cdc',\n 'webhook',\n 'manual',\n 'writeback',\n]);\n\n/**\n * Lifecycle status of a sync run.\n *\n * - `running` — in-flight; recorder has started but not completed.\n * - `success` — completed with at least one change processed.\n * - `no_changes` — completed cleanly, no upstream changes found.\n * - `failed` — errored before completion; `error` column carries the\n * message. `records_processed` may be non-zero (partial progress).\n */\nexport const syncRunStatusEnum = pgEnum('sync_run_status', [\n 'running',\n 'success',\n 'no_changes',\n 'failed',\n]);\n\n/**\n * Operation applied per record. Mirrors `Change<T>.operation` from SYNC-2,\n * plus the recorder's own `'noop'` for changes that matched existing state.\n */\nexport const syncRunItemOperationEnum = pgEnum('sync_run_item_operation', [\n 'created',\n 'updated',\n 'deleted',\n 'noop',\n]);\n\n/**\n * Per-record status within a run. `skipped` captures loopback-detected echoes\n * of the local system's own writes (see `ILoopbackFingerprintStore` in the\n * epic), which record the external_id but intentionally do not touch local\n * state.\n */\nexport const syncRunItemStatusEnum = pgEnum('sync_run_item_status', [\n 'success',\n 'failed',\n 'skipped',\n]);\n\n// ─── sync_subscriptions ─────────────────────────────────────────────────────\n\n/**\n * One cursor owner per (integration, adapter, domain, external_ref).\n *\n * - `integration_id` — opaque id of the connected account/instance. E.g.\n * the SFDC org id for polling strategies, the GitHub installation id\n * for webhook strategies.\n * - `adapter` — short adapter label, e.g. `'salesforce'`, `'hubspot'`.\n * - `domain` — canonical entity domain this subscription tracks,\n * e.g. `'opportunity'`, `'contact'`.\n * - `external_ref` — optional upstream scope (e.g. a filter id, a\n * webhook subscription id). NULL when the subscription covers the\n * entire domain.\n *\n * The cursor shape is opaque jsonb — strategies type it internally (poll:\n * `{ systemModstamp }`, cdc: `{ replayId }`, webhook: `{ ts }`). Overwritten\n * by `ICursorStore.put(id, cursor)`.\n */\nexport const syncSubscriptions = pgTable(\n 'sync_subscriptions',\n {\n id: uuid('id').primaryKey().defaultRandom(),\n integrationId: text('integration_id').notNull(),\n adapter: text('adapter').notNull(),\n domain: text('domain').notNull(),\n externalRef: text('external_ref'),\n enabled: boolean('enabled').notNull().default(true),\n /**\n * Per-subscription configuration bag. Strategies type it internally;\n * e.g. polling strategies stash `{ batchSize, highWatermark }` here.\n */\n config: jsonb('config').notNull().default({}).$type<Record<string, unknown>>(),\n /**\n * Opaque cursor persisted by `ICursorStore.put()`. NULL until the first\n * successful run advances it.\n */\n cursor: jsonb('cursor').$type<unknown>(),\n lastSyncAt: timestamp('last_sync_at', { withTimezone: true }),\n /** Runtime-enforced when `SYNC_MULTI_TENANT` is true; see SYNC-6. */\n tenantId: text('tenant_id'),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),\n },\n (t) => ({\n /**\n * Composite uniqueness per the epic shape. `external_ref` is nullable;\n * Postgres treats NULLs as distinct in a UNIQUE constraint, which means\n * two rows with the same `(integration_id, adapter, domain)` and NULL\n * external_ref are allowed. That's intentional — a subscription with\n * NULL external_ref covers the full domain, and duplicates there would\n * be a consumer-layer modeling issue, not a schema concern.\n */\n uqSyncSubscriptionTuple: uniqueIndex('uq_sync_subscriptions_tuple').on(\n t.integrationId,\n t.adapter,\n t.domain,\n t.externalRef,\n ),\n /** Scheduling query: list enabled subscriptions ordered by staleness. */\n idxSyncSubscriptionsEnabledLastSync: index(\n 'idx_sync_subscriptions_enabled_last_sync',\n ).on(t.enabled, t.lastSyncAt),\n }),\n);\n\nexport type SyncSubscriptionRow = InferSelectModel<typeof syncSubscriptions>;\n\n// ─── sync_runs ──────────────────────────────────────────────────────────────\n\n/**\n * One row per invocation of `ExecuteSyncUseCase`. `started_at` is set when\n * the recorder opens the run; `completed_at`, `status`, `records_*`,\n * `cursor_after`, and `duration_ms` are filled on completion.\n *\n * `cursor_before` / `cursor_after` carry the opaque cursor snapshots so the\n * run log is fully self-describing — given a run id, an operator can reason\n * about exactly what window was scanned without cross-referencing another\n * table.\n */\nexport const syncRuns = pgTable(\n 'sync_runs',\n {\n id: uuid('id').primaryKey().defaultRandom(),\n subscriptionId: uuid('subscription_id')\n .notNull()\n .references(() => syncSubscriptions.id, { onDelete: 'cascade' }),\n direction: syncRunDirectionEnum('direction').notNull(),\n action: syncRunActionEnum('action').notNull(),\n status: syncRunStatusEnum('status').notNull().default('running'),\n recordsFound: integer('records_found').notNull().default(0),\n recordsProcessed: integer('records_processed').notNull().default(0),\n cursorBefore: jsonb('cursor_before').$type<unknown>(),\n cursorAfter: jsonb('cursor_after').$type<unknown>(),\n durationMs: integer('duration_ms'),\n error: text('error'),\n startedAt: timestamp('started_at', { withTimezone: true })\n .notNull()\n .defaultNow(),\n completedAt: timestamp('completed_at', { withTimezone: true }),\n /** Runtime-enforced when `SYNC_MULTI_TENANT` is true; see SYNC-6. */\n tenantId: text('tenant_id'),\n },\n (t) => ({\n /** Timeline read: \"most recent runs for this subscription\". */\n idxSyncRunsSubscriptionStartedAt: index(\n 'idx_sync_runs_subscription_started_at',\n ).on(t.subscriptionId, t.startedAt),\n /** Stale-run sweeper: \"runs that started > N minutes ago and are still running\". */\n idxSyncRunsStatusStartedAt: index('idx_sync_runs_status_started_at').on(\n t.status,\n t.startedAt,\n ),\n }),\n);\n\nexport type SyncRunRow = InferSelectModel<typeof syncRuns>;\n\n// ─── sync_run_items ─────────────────────────────────────────────────────────\n\n/**\n * One row per upstream change processed within a run. Captures the canonical\n * decision the orchestrator made (`operation` + `status`), the structured\n * per-field diff (`changed_fields`, ADR-0003), and the local row id\n * (`local_id`) for drill-down joins.\n *\n * `changed_fields` is validated at the recorder layer via `FieldDiffSchema`\n * (SYNC-2) — the $type<FieldDiff> annotation here only documents the shape\n * for Drizzle consumers. The runtime enforcement is non-negotiable: downstream\n * drift-detection queries rely on the `{from, to}` shape per field.\n *\n * `title` is an optional human-readable label captured at write time (e.g.\n * `\"Pinnacle opportunity\"`) so run-log UIs don't need to re-hydrate the\n * canonical record.\n */\nexport const syncRunItems = pgTable(\n 'sync_run_items',\n {\n id: uuid('id').primaryKey().defaultRandom(),\n syncRunId: uuid('sync_run_id')\n .notNull()\n .references(() => syncRuns.id, { onDelete: 'cascade' }),\n entityType: text('entity_type').notNull(),\n externalId: text('external_id').notNull(),\n localId: text('local_id'),\n operation: syncRunItemOperationEnum('operation').notNull(),\n status: syncRunItemStatusEnum('status').notNull(),\n /**\n * Structured per-field diff — ADR-0003 shape enforced by\n * `FieldDiffSchema.parse` at the recorder service layer.\n *\n * Shape: `{ [fieldName]: { from: unknown, to: unknown } }`.\n * Empty `{}` for `noop` items; `{ [field]: { from: null, to: <value> } }`\n * for created items; `{ [field]: { from: <value>, to: null } }` for\n * deleted items.\n */\n changedFields: jsonb('changed_fields').notNull().default({}).$type<FieldDiff>(),\n title: text('title'),\n error: text('error'),\n createdAt: timestamp('created_at', { withTimezone: true })\n .notNull()\n .defaultNow(),\n /** Runtime-enforced when `SYNC_MULTI_TENANT` is true; see SYNC-6. */\n tenantId: text('tenant_id'),\n },\n (t) => ({\n /** Ordered timeline within a run. */\n idxSyncRunItemsRunCreatedAt: index('idx_sync_run_items_run_created_at').on(\n t.syncRunId,\n t.createdAt,\n ),\n /** Per-record history: \"every sync that touched opportunity/$extId\". */\n idxSyncRunItemsEntityExternal: index(\n 'idx_sync_run_items_entity_external',\n ).on(t.entityType, t.externalId),\n }),\n);\n\nexport type SyncRunItemRow = InferSelectModel<typeof syncRunItems>;\n","/**\n * MemoryObservabilityService — in-memory test backend for\n * IObservabilityService.\n *\n * Stores snapshot data set by the test harness and returns it verbatim.\n * This is deliberately NOT a replay-from-events simulator — the point of\n * the memory backend is to let tests assert \"when the subsystem returns\n * X, does the caller render Y correctly?\" without standing up Postgres.\n *\n * Tests populate the backend via the `seed*` methods, then exercise the\n * protocol reads. Each seed method replaces (not merges) its slice, which\n * keeps the mental model simple: the backend is a fixture holder.\n *\n * No lifecycle hooks (no background work to manage).\n */\nimport { Injectable } from '@nestjs/common';\nimport type {\n CursorSnapshot,\n IObservabilityService,\n JobRunFailure,\n PoolDepth,\n StatusHistogram,\n SyncRunSummary,\n} from './observability.protocol';\n\n@Injectable()\nexport class MemoryObservabilityService implements IObservabilityService {\n private pools: PoolDepth[] = [];\n private syncRuns: SyncRunSummary[] = [];\n private bridgeHistogram: StatusHistogram = {};\n private failedJobs: JobRunFailure[] = [];\n private cursors: CursorSnapshot[] = [];\n\n // ─── Core contract ─────────────────────────────────────────────────────\n\n async getPoolDepths(): Promise<PoolDepth[]> {\n return [...this.pools];\n }\n\n async getRecentSyncRuns(\n limit: number,\n integrationId?: string,\n ): Promise<SyncRunSummary[]> {\n const filtered =\n integrationId !== undefined\n ? this.syncRuns.filter((r) => r.integrationId === integrationId)\n : this.syncRuns;\n return filtered\n .slice()\n .sort((a, b) => b.startedAt.getTime() - a.startedAt.getTime())\n .slice(0, limit);\n }\n\n async getBridgeDeliveryHistogram(\n _windowHours: number,\n ): Promise<StatusHistogram> {\n // Memory backend ignores the window — tests that care about windowing\n // should seed the histogram for the window they're simulating.\n return { ...this.bridgeHistogram };\n }\n\n async getRecentFailedJobs(limit: number): Promise<JobRunFailure[]> {\n return this.failedJobs\n .slice()\n .sort(\n (a, b) =>\n (b.finishedAt?.getTime() ?? 0) - (a.finishedAt?.getTime() ?? 0),\n )\n .slice(0, limit);\n }\n\n async getCursors(): Promise<CursorSnapshot[]> {\n return [...this.cursors];\n }\n\n // ─── Test seams ────────────────────────────────────────────────────────\n\n /** Replace the pool-depth slice. */\n seedPools(pools: PoolDepth[]): void {\n this.pools = [...pools];\n }\n\n /** Replace the sync-run slice. */\n seedSyncRuns(runs: SyncRunSummary[]): void {\n this.syncRuns = [...runs];\n }\n\n /** Replace the bridge-delivery histogram. */\n seedBridgeHistogram(hist: StatusHistogram): void {\n this.bridgeHistogram = { ...hist };\n }\n\n /** Replace the failed-jobs slice. */\n seedFailedJobs(jobs: JobRunFailure[]): void {\n this.failedJobs = [...jobs];\n }\n\n /** Replace the cursor slice. */\n seedCursors(cursors: CursorSnapshot[]): void {\n this.cursors = [...cursors];\n }\n\n /** Reset every slice — for afterEach hooks. */\n reset(): void {\n this.pools = [];\n this.syncRuns = [];\n this.bridgeHistogram = {};\n this.failedJobs = [];\n this.cursors = [];\n }\n}\n","/**\n * Observability subsystem public API (ADR-008, 5th subsystem).\n *\n * Import token + protocol in services/controllers:\n * ```typescript\n * import { OBSERVABILITY, type IObservabilityService } from '@pattern-stack/codegen/runtime/subsystems/observability';\n * ```\n *\n * Import the module in AppModule:\n * ```typescript\n * import { ObservabilityModule } from '@pattern-stack/codegen/runtime/subsystems/observability';\n * ObservabilityModule.forRoot({ backend: 'drizzle', reporters: { bridgeMetrics: true } })\n * ```\n */\nexport { OBSERVABILITY, OBSERVABILITY_REPORTERS } from './observability.tokens';\nexport type {\n CursorSnapshot,\n IObservabilityService,\n JobRunFailure,\n PoolDepth,\n StatusHistogram,\n SyncRunSummary,\n} from './observability.protocol';\nexport {\n ObservabilityModule,\n type ObservabilityModuleOptions,\n type ObservabilityReporterOptions,\n} from './observability.module';\nexport { DrizzleObservabilityService } from './observability.drizzle-backend';\nexport { MemoryObservabilityService } from './observability.memory-backend';\nexport {\n BridgeMetricsReporter,\n type BridgeMetricsRow,\n type BridgeMetricsTick,\n} from './reporters/bridge-metrics.reporter';\n"],"mappings":"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","names":["index","jsonb","pgTable","text","timestamp","uuid","index","jsonb","pgEnum","pgTable","text","timestamp","uuid","sql","Inject","Injectable","eq","sql","sql","pgEnum","pgTable","uuid","text","jsonb","integer","timestamp","index","uniqueIndex","sql","Injectable","jobs","Injectable","BridgeMetricsReporter"]}
1
+ {"version":3,"sources":["../../../../runtime/constants/tokens.ts","../../../../runtime/subsystems/jobs/job-orchestration.schema.ts","../../../../runtime/subsystems/events/domain-events.schema.ts","../../../../runtime/subsystems/bridge/bridge-delivery.schema.ts","../../../../runtime/subsystems/observability/reporters/bridge-metrics.reporter.ts","../../../../runtime/subsystems/observability/observability.tokens.ts","../../../../runtime/subsystems/observability/observability.module.ts","../../../../runtime/subsystems/observability/observability.drizzle-backend.ts","../../../../runtime/subsystems/sync/sync-audit.schema.ts","../../../../runtime/subsystems/observability/observability.memory-backend.ts","../../../../runtime/subsystems/observability/index.ts"],"sourcesContent":["/**\n * NestJS injection tokens\n *\n * Used with @Inject() decorator in concrete repository constructors.\n */\n\n/**\n * Injection token for the Drizzle ORM database client.\n *\n * Usage in concrete repositories:\n * ```typescript\n * constructor(@Inject(DRIZZLE) db: DrizzleClient) { super(db); }\n * ```\n */\nexport const DRIZZLE = 'DRIZZLE' as const;\n\n/**\n * Injection token for the event bus (IEventBus).\n *\n * Optional — only resolved when EventsModule.forRoot() is registered.\n * BaseService uses this with @Optional() to emit lifecycle events\n * without requiring the events subsystem to be installed.\n *\n * Usage in services/use cases:\n * ```typescript\n * @Optional() @Inject(EVENT_BUS) eventBus?: IEventBus\n * ```\n */\nexport const EVENT_BUS = 'EVENT_BUS' as const;\n","/**\n * Drizzle schema for the job orchestration domain (ADR-022).\n *\n * Three tables model the lifecycle of a durable job:\n * - `job` — definitions keyed by handler type (e.g. 'onboarding').\n * - `job_run` — one row per attempt to execute a job; worker claims\n * rows directly via SELECT ... FOR UPDATE SKIP LOCKED.\n * - `job_step` — individual steps within a run; memoises output for replay.\n *\n * Phase 1 ships only this layer. There is no `job_queue` table, no executor\n * port — see ADR-022 and `.claude/skills/jobs/SKILL.md` for the rationale.\n */\nimport {\n pgEnum,\n pgTable,\n uuid,\n text,\n jsonb,\n integer,\n timestamp,\n index,\n uniqueIndex,\n} from 'drizzle-orm/pg-core';\nimport { sql } from 'drizzle-orm';\nimport type { InferSelectModel } from 'drizzle-orm';\n\n// ─── Internal $type<> helpers ───────────────────────────────────────────────\n// Annotation types for jsonb columns only. JOB-2 defines the public protocol\n// types; these remain private to this file.\n\ntype RetryPolicy = {\n attempts: number;\n backoff: 'fixed' | 'exponential';\n baseMs: number;\n nonRetryableErrors?: string[];\n};\n\ntype JobRunError = {\n message: string;\n stack?: string;\n retryable: boolean;\n attempt: number;\n};\n\n// ─── Enums ──────────────────────────────────────────────────────────────────\n\nexport const jobRunStatusEnum = pgEnum('job_run_status', [\n 'pending',\n 'running',\n 'waiting',\n 'completed',\n 'failed',\n 'timed_out',\n 'canceled',\n]);\n\n// extended in ADR-027: tool_call | llm_call | wait | checkpoint | message\nexport const jobStepKindEnum = pgEnum('job_step_kind', ['task']);\n\nexport const jobStepStatusEnum = pgEnum('job_step_status', [\n 'pending',\n 'running',\n 'completed',\n 'failed',\n 'skipped',\n]);\n\nexport const collisionModeEnum = pgEnum('job_collision_mode', [\n 'queue',\n 'reject',\n 'replace',\n]);\n\nexport const replayFromEnum = pgEnum('job_replay_from', [\n 'scratch',\n 'last_step',\n 'last_checkpoint',\n]);\n\nexport const parentClosePolicyEnum = pgEnum('job_parent_close_policy', [\n 'terminate',\n 'cancel',\n 'abandon',\n]);\n\n// Phase 3 placeholder — see ADR-025\nexport const waitKindEnum = pgEnum('job_wait_kind', ['signal']);\n\n// Phase 2 may add more sources; requires Atlas migration\nexport const triggerSourceEnum = pgEnum('job_trigger_source', [\n 'manual',\n 'schedule',\n 'event',\n 'parent',\n]);\n\n// ─── job ────────────────────────────────────────────────────────────────────\n\nexport const jobs = pgTable('job', {\n type: text('type').primaryKey(),\n version: integer('version').notNull().default(1),\n pool: text('pool').notNull(),\n scopeEntityType: text('scope_entity_type'),\n retryPolicy: jsonb('retry_policy').notNull().$type<RetryPolicy>(),\n timeoutMs: integer('timeout_ms'),\n concurrencyKeyTemplate: text('concurrency_key_template'),\n collisionMode: collisionModeEnum('collision_mode').notNull().default('queue'),\n dedupeKeyTemplate: text('dedupe_key_template'),\n dedupeWindowMs: integer('dedupe_window_ms'),\n priorityDefault: integer('priority_default').notNull().default(0),\n replayFrom: replayFromEnum('replay_from').notNull().default('last_checkpoint'),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),\n});\n\nexport type JobDefinitionRow = InferSelectModel<typeof jobs>;\n\n// ─── job_run ────────────────────────────────────────────────────────────────\n\nexport const jobRuns = pgTable(\n 'job_run',\n {\n id: uuid('id').primaryKey().defaultRandom(),\n jobType: text('job_type').notNull().references(() => jobs.type),\n jobVersion: integer('job_version').notNull(),\n parentRunId: uuid('parent_run_id').references((): any => jobRuns.id),\n /**\n * Service generates `id` client-side via randomUUID() and sets\n * root_run_id = id for root runs (single INSERT, no self-FK race).\n */\n rootRunId: uuid('root_run_id').notNull(),\n parentClosePolicy: parentClosePolicyEnum('parent_close_policy')\n .notNull()\n .default('terminate'),\n scopeEntityType: text('scope_entity_type'),\n scopeEntityId: text('scope_entity_id'),\n tenantId: text('tenant_id'),\n tags: jsonb('tags').notNull().default({}).$type<Record<string, string>>(),\n pool: text('pool').notNull(),\n priority: integer('priority').notNull().default(0),\n concurrencyKey: text('concurrency_key'),\n dedupeKey: text('dedupe_key'),\n status: jobRunStatusEnum('status').notNull().default('pending'),\n input: jsonb('input').notNull().$type<Record<string, unknown>>(),\n output: jsonb('output').$type<Record<string, unknown>>(),\n error: jsonb('error').$type<JobRunError>(),\n triggerSource: triggerSourceEnum('trigger_source').notNull(),\n triggerRef: text('trigger_ref'),\n runAt: timestamp('run_at', { withTimezone: true }).notNull().defaultNow(),\n startedAt: timestamp('started_at', { withTimezone: true }),\n finishedAt: timestamp('finished_at', { withTimezone: true }),\n claimedAt: timestamp('claimed_at', { withTimezone: true }),\n attempts: integer('attempts').notNull().default(0),\n // Phase 3 placeholder — see ADR-025\n waitKind: waitKindEnum('wait_kind'),\n // Phase 3 placeholder — see ADR-025\n resumeToken: text('resume_token'),\n // Phase 3 placeholder — see ADR-025\n waitDeadline: timestamp('wait_deadline', { withTimezone: true }),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),\n },\n (t) => ({\n /** Claim query: ORDER BY priority DESC, run_at ASC. */\n idxJobRunClaim: index('idx_job_run_claim').on(t.status, t.pool, t.runAt),\n /** Tree traversal / cascade cancel. */\n idxJobRunRoot: index('idx_job_run_root').on(t.rootRunId),\n /** listForScope query. */\n idxJobRunScope: index('idx_job_run_scope').on(t.scopeEntityType, t.scopeEntityId),\n /** Idempotency collapse — partial index. */\n idxJobRunDedupe: index('idx_job_run_dedupe')\n .on(t.jobType, t.dedupeKey)\n .where(sql`${t.dedupeKey} IS NOT NULL`),\n /** Collision check — partial index. */\n idxJobRunConcurrency: index('idx_job_run_concurrency')\n .on(t.concurrencyKey)\n .where(\n sql`${t.concurrencyKey} IS NOT NULL AND ${t.status} IN ('pending','running')`,\n ),\n }),\n);\n\nexport type JobRunRow = InferSelectModel<typeof jobRuns>;\n\n// ─── job_step ───────────────────────────────────────────────────────────────\n\nexport const jobSteps = pgTable(\n 'job_step',\n {\n id: uuid('id').primaryKey().defaultRandom(),\n jobRunId: uuid('job_run_id').notNull().references(() => jobRuns.id),\n stepId: text('step_id').notNull(),\n kind: jobStepKindEnum('kind').notNull().default('task'),\n /**\n * Monotonic within run. integer (max ~2B per run) is sufficient —\n * downgraded from ADR-022's bigint; revisit only if a single run\n * ever exceeds 2 billion steps.\n */\n seq: integer('seq').notNull(),\n status: jobStepStatusEnum('status').notNull().default('pending'),\n input: jsonb('input').$type<Record<string, unknown>>(),\n /** Memoised on success for replay. */\n output: jsonb('output').$type<Record<string, unknown>>(),\n error: jsonb('error').$type<JobRunError>(),\n attempts: integer('attempts').notNull().default(0),\n startedAt: timestamp('started_at', { withTimezone: true }),\n finishedAt: timestamp('finished_at', { withTimezone: true }),\n },\n (t) => ({\n /** No duplicate step IDs per run. */\n idxJobStepRunStep: uniqueIndex('idx_job_step_run_step').on(t.jobRunId, t.stepId),\n /** Ordered timeline reads. */\n idxJobStepTimeline: index('idx_job_step_timeline').on(t.jobRunId, t.seq),\n }),\n);\n\nexport type JobStepRow = InferSelectModel<typeof jobSteps>;\n","/**\n * Drizzle schema for the domain_events outbox table.\n *\n * This table backs the DrizzleEventBus. Events are inserted within the\n * same database transaction as the domain write (outbox pattern). A\n * polling process reads unprocessed rows and dispatches to subscribers.\n *\n * First-class routing columns (EVT-1):\n * - `pool` — populated by DrizzleEventBus.publish() (EVT-4); enables\n * pool-filtered drain queries without unpacking metadata JSON.\n * - `direction` — `inbound` | `change` | `outbound`; mirrors the routing\n * dimension used by jobs' reserved `events_inbound` /\n * `events_change` / `events_outbound` pools.\n * - `tenant_id` — conditional: emitted only when `events.multi_tenant: true`\n * in `codegen.config.yaml`. The runtime source declares it\n * unconditionally; EVT-8's scaffold template handles the\n * config-driven include/exclude.\n *\n * The `metadata` JSON column continues to carry these values for protocol\n * stability; the first-class columns are an optimization for drain filtering.\n *\n * Indexes (declared below in the index callback):\n * - (status, occurred_at) — polling drain filter\n * - (aggregate_id, aggregate_type) — event replay per aggregate\n * - (pool, status, occurred_at) — per-pool drain filter (EVT-1)\n */\nimport {\n index,\n jsonb,\n pgTable,\n text,\n timestamp,\n uuid,\n} from 'drizzle-orm/pg-core';\nimport type { InferSelectModel } from 'drizzle-orm';\n\nexport const domainEvents = pgTable(\n 'domain_events',\n {\n id: uuid('id').primaryKey(),\n type: text('type').notNull(),\n aggregateId: text('aggregate_id').notNull(),\n aggregateType: text('aggregate_type').notNull(),\n payload: jsonb('payload').notNull().$type<Record<string, unknown>>(),\n occurredAt: timestamp('occurred_at', { withTimezone: true }).notNull(),\n processedAt: timestamp('processed_at', { withTimezone: true }),\n /** Lifecycle status: pending | processed | failed */\n status: text('status').notNull().default('pending'),\n /** Error message from the last failed dispatch attempt. */\n error: text('error'),\n metadata: jsonb('metadata').$type<Record<string, unknown>>(),\n /** Routing pool (e.g. `events_inbound`, `events_change`, `events_outbound`). Populated by DrizzleEventBus.publish() in EVT-4. */\n pool: text('pool'),\n /** Routing direction: `inbound` | `change` | `outbound`. Populated by DrizzleEventBus.publish() in EVT-4. */\n direction: text('direction'),\n // conditional: emitted only when events.multi_tenant: true\n tenantId: text('tenant_id'),\n },\n (t) => ({\n /** Polling drain filter (existing — promoted from comment to declaration in EVT-1). */\n idxDomainEventsStatusOccurredAt: index('idx_domain_events_status_occurred_at').on(\n t.status,\n t.occurredAt,\n ),\n /** Event replay per aggregate (existing — promoted from comment to declaration in EVT-1). */\n idxDomainEventsAggregate: index('idx_domain_events_aggregate').on(\n t.aggregateId,\n t.aggregateType,\n ),\n /** Per-pool drain filter (EVT-1). Enables DrizzleEventBus to drain a single pool without scanning all events. */\n idxDomainEventsPoolStatusOccurredAt: index(\n 'idx_domain_events_pool_status_occurred_at',\n ).on(t.pool, t.status, t.occurredAt),\n }),\n);\n\nexport type DomainEventRecord = InferSelectModel<typeof domainEvents>;\n","/**\n * Drizzle schema for the `bridge_delivery` ledger (ADR-023 Phase 2, BRIDGE-1).\n *\n * The `bridge_delivery` table is the idempotency ledger for the event-to-job\n * bridge. Every (event, trigger) pair the bridge is asked to spawn produces\n * exactly one row; the `UNIQUE (event_id, trigger_id)` constraint guarantees\n * that:\n *\n * 1. Outbox replays of an event do not double-spawn user job runs — the\n * drain attempts to insert the duplicate, the constraint trips, and the\n * drain skips that trigger.\n * 2. The `IEventFlow.publishAndStart` facade can pre-write a\n * `(status='delivered')` row before the drain runs (Case B from ADR-023\n * §`publishAndStart` + existing `triggers:` collision); the drain then\n * hits UNIQUE on that trigger and skips it while still spawning any\n * other triggers for the same event normally.\n *\n * Status values:\n * - `pending` — wrapper run exists; user job not yet started.\n * - `delivered` — user job started; `user_run_id` populated.\n * - `skipped` — intentional no-op (`when:` returned false, or\n * facade-eager path pre-empted the bridge spawn).\n * - `failed` — wrapper exhausted retry policy; no auto-retry past that\n * (mirrors events outbox stance — ops eyes only).\n *\n * `wrapper_run_id` is **nullable**: the facade-eager path (Case B) pre-writes\n * `bridge_delivery` with no wrapper. The bridge-drain path always populates\n * it.\n *\n * `tenant_id` is emitted **unconditionally and nullable** (per JOB-8\n * 2026-04-20 reversal); enforcement is service-layer (BRIDGE-8) gated on the\n * `BRIDGE_MULTI_TENANT` DI token, not a DB constraint.\n *\n * Indexes:\n * - `bridge_delivery_event_idx` — lookup all deliveries for an event.\n * - `bridge_delivery_status_idx` — partial; ops dashboards filter by\n * `pending | failed`.\n * - `bridge_delivery_user_run_idx` — partial; reverse lookup from a\n * spawned user run back to its delivery row.\n *\n * No service logic, no DI wiring — this is the schema foundation. Backends\n * (memory + drizzle) and the framework handler land in BRIDGE-3 / BRIDGE-4 /\n * BRIDGE-5.\n */\nimport {\n index,\n jsonb,\n pgEnum,\n pgTable,\n text,\n timestamp,\n unique,\n uuid,\n} from 'drizzle-orm/pg-core';\nimport { sql } from 'drizzle-orm';\nimport type { InferSelectModel } from 'drizzle-orm';\n\nimport { domainEvents } from '../events/domain-events.schema';\nimport { jobRuns } from '../jobs/job-orchestration.schema';\n\n// ─── Enum ───────────────────────────────────────────────────────────────────\n\nexport const bridgeDeliveryStatusEnum = pgEnum('bridge_delivery_status', [\n 'pending',\n 'delivered',\n 'skipped',\n 'failed',\n]);\n\n// ─── Table ──────────────────────────────────────────────────────────────────\n\nexport const bridgeDelivery = pgTable(\n 'bridge_delivery',\n {\n id: uuid('id').primaryKey().defaultRandom(),\n /** FK to the source event in the outbox. */\n eventId: uuid('event_id')\n .notNull()\n .references(() => domainEvents.id),\n /**\n * Stable codegen-emitted identifier for the (job, trigger) pair, of the\n * form `<job_type>#<triggerIndex>` (BRIDGE-6). Forms the second half of\n * the UNIQUE idempotency key.\n */\n triggerId: text('trigger_id').notNull(),\n /**\n * Wrapper `job_run.id` (the framework `@framework/bridge_delivery` run\n * that drove this delivery). Nullable: the facade-eager path\n * (`publishAndStart` Case B) pre-writes a delivered row with no wrapper.\n */\n wrapperRunId: uuid('wrapper_run_id').references(() => jobRuns.id),\n /**\n * Spawned user `job_run.id`. Null until status is `delivered`; remains\n * null for `skipped` and `failed` deliveries.\n */\n userRunId: uuid('user_run_id').references(() => jobRuns.id),\n status: bridgeDeliveryStatusEnum('status').notNull().default('pending'),\n /** Populated when status=`skipped` (e.g. `'when_returned_false'`, `'trigger_unregistered'`). */\n skipReason: text('skip_reason'),\n /** Populated when status=`failed`. Mirrors `job_run.error` shape. */\n error: jsonb('error').$type<Record<string, unknown>>(),\n /**\n * Emitted unconditionally and nullable (JOB-8 / SYNC-6 precedent).\n * Enforcement gated on `BRIDGE_MULTI_TENANT` at the service layer\n * (BRIDGE-8); no DB constraint.\n */\n tenantId: text('tenant_id'),\n attemptedAt: timestamp('attempted_at', { withTimezone: true })\n .notNull()\n .defaultNow(),\n deliveredAt: timestamp('delivered_at', { withTimezone: true }),\n },\n (t) => ({\n /**\n * Idempotency ledger. Outbox replays and facade-vs-drain collisions both\n * dedup through this constraint.\n */\n uqBridgeDeliveryEventTrigger: unique('uq_bridge_delivery_event_trigger').on(\n t.eventId,\n t.triggerId,\n ),\n /** Lookup all deliveries for an event (fanout report, debugging). */\n idxBridgeDeliveryEvent: index('idx_bridge_delivery_event').on(t.eventId),\n /**\n * Ops dashboard filter — only the actionable states. Partial index keeps\n * it small at scale (the bulk of rows will be `delivered`).\n */\n idxBridgeDeliveryStatus: index('idx_bridge_delivery_status')\n .on(t.status)\n .where(sql`${t.status} IN ('pending','failed')`),\n /**\n * Reverse lookup from a spawned user run back to its delivery row.\n * Partial — most rows in the bridge ledger but only successful\n * deliveries have a `user_run_id`.\n */\n idxBridgeDeliveryUserRun: index('idx_bridge_delivery_user_run')\n .on(t.userRunId)\n .where(sql`${t.userRunId} IS NOT NULL`),\n }),\n);\n\nexport type BridgeDeliveryRecord = InferSelectModel<typeof bridgeDelivery>;\n","/**\n * BridgeMetricsReporter — periodic structured-log sampler for the\n * `bridge_delivery` ledger.\n *\n * Runs on a timer (default 60s, configurable via\n * `BRIDGE_METRICS_INTERVAL_MS`) and emits ONE `Logger.log` line per tick\n * describing counts of rows that transitioned through each terminal\n * status in the last tick window, grouped by `(status, event_type,\n * skip_reason)`.\n *\n * # Placement\n *\n * Lives under `observability/reporters/` rather than in the bridge\n * subsystem itself because:\n * 1. It's not part of the bridge's functional surface — a reporter is\n * an observability concern composed on top.\n * 2. Future reporters (Prometheus exporter, OTel bridge, etc.) slot in\n * here with no cross-subsystem import churn.\n *\n * # Opt-in via ObservabilityModule\n *\n * The reporter is NOT provided automatically. Opt in via\n * `ObservabilityModule.forRoot({ backend, reporters: { bridgeMetrics: true } })`\n * — the module only registers the reporter when that flag is set, which\n * keeps consumers without the bridge subsystem free of its schema import\n * tax (tree-shaken; see `observability.module.ts` for the gate).\n *\n * # Why a sampler instead of in-handler logs\n *\n * The bridge subsystem writes the `bridge_delivery` ledger directly; adding\n * per-transition log lines inside the handler would double every row at\n * 1:1 cardinality. Aggregating per-tick produces the \"counts per event\n * type of delivered/skipped/failed\" shape that ops dashboards want,\n * without touching the bridge runtime.\n *\n * # Why aggregate-per-tick rather than per-row\n *\n * Deliveries flow at bulk-sync cadence (one event per persisted CRM\n * record). Per-row logs would be noisy and duplicative of the ledger\n * itself; aggregates match the \"counts per event type of\n * delivered/skipped/failed\" operator surface.\n */\nimport {\n Inject,\n Injectable,\n Logger,\n Optional,\n type OnModuleDestroy,\n type OnModuleInit,\n} from '@nestjs/common';\nimport { SchedulerRegistry } from '@nestjs/schedule';\nimport { and, eq, gt, sql } from 'drizzle-orm';\n\nimport { DRIZZLE } from '../../../constants/tokens';\nimport type { DrizzleClient } from '../../../types/drizzle';\nimport { bridgeDelivery } from '../../bridge/bridge-delivery.schema';\nimport { domainEvents } from '../../events/domain-events.schema';\n\nconst INTERVAL_NAME = 'bridge-metrics-tick';\n\n/** Default sampling interval (1 minute). */\nconst DEFAULT_INTERVAL_MS = 60_000;\n\n/** Minimum allowed interval — guards against env misconfig producing a hot loop. */\nconst MIN_INTERVAL_MS = 1_000;\n\nexport interface BridgeMetricsRow {\n status: 'pending' | 'delivered' | 'skipped' | 'failed';\n eventType: string;\n skipReason: string | null;\n count: number;\n}\n\nexport interface BridgeMetricsTick {\n windowStart: Date;\n windowEnd: Date;\n rows: BridgeMetricsRow[];\n}\n\n@Injectable()\nexport class BridgeMetricsReporter implements OnModuleInit, OnModuleDestroy {\n private readonly logger = new Logger(BridgeMetricsReporter.name);\n private readonly intervalMs: number;\n private lastTickAt: Date;\n /**\n * Timer handle retained as a field when `SchedulerRegistry` isn't\n * available (optional dep). `SchedulerRegistry` is the Nest-idiomatic\n * home for interval cleanup, but global-module wiring across consumer\n * topologies doesn't always make it injectable here — the fallback\n * keeps the reporter self-sufficient.\n */\n private ownedTimer: ReturnType<typeof setInterval> | null = null;\n\n constructor(\n @Inject(DRIZZLE) private readonly db: DrizzleClient,\n @Optional() private readonly scheduler: SchedulerRegistry | null = null,\n ) {\n this.intervalMs = this.resolveIntervalMs();\n // Initialize the window tail at boot so the first tick reports only\n // deliveries that transitioned after the reporter started.\n this.lastTickAt = new Date();\n }\n\n onModuleInit(): void {\n this.logger.log(\n `BridgeMetricsReporter starting (intervalMs=${this.intervalMs}).`,\n );\n const timer = setInterval(() => {\n void this.tick();\n }, this.intervalMs);\n // Allow the process to exit naturally in test runs — setInterval\n // otherwise pins the event loop open.\n timer.unref?.();\n\n if (this.scheduler) {\n this.scheduler.addInterval(INTERVAL_NAME, timer);\n } else {\n // Fallback: retain the handle ourselves so onModuleDestroy can\n // clear it without Nest's SchedulerRegistry.\n this.ownedTimer = timer;\n }\n }\n\n onModuleDestroy(): void {\n if (this.scheduler && this.scheduler.getIntervals().includes(INTERVAL_NAME)) {\n this.scheduler.deleteInterval(INTERVAL_NAME);\n }\n if (this.ownedTimer !== null) {\n clearInterval(this.ownedTimer);\n this.ownedTimer = null;\n }\n }\n\n /**\n * Run one sampling tick. Public so tests can drive it deterministically\n * without waiting on the timer.\n */\n async tick(): Promise<BridgeMetricsTick> {\n const windowStart = this.lastTickAt;\n const windowEnd = new Date();\n this.lastTickAt = windowEnd;\n\n let rows: BridgeMetricsRow[] = [];\n try {\n rows = await this.sample(windowStart, windowEnd);\n } catch (err) {\n this.logger.error(\n `bridge metrics sample failed: ${(err as Error).message}`,\n );\n return { windowStart, windowEnd, rows: [] };\n }\n\n this.emit({ windowStart, windowEnd, rows });\n return { windowStart, windowEnd, rows };\n }\n\n private async sample(\n windowStart: Date,\n windowEnd: Date,\n ): Promise<BridgeMetricsRow[]> {\n // Terminal transitions land `delivered_at` for `delivered`, and leave\n // `attempted_at` as the most recent timestamp for `skipped`/`failed`.\n // Window on COALESCE so terminal skipped/failed rows are captured\n // alongside delivered. Upper edge bounded by windowEnd so a long tick\n // can't double-count rows that transitioned between sample and emit.\n const lastTransition = sql<Date>`COALESCE(${bridgeDelivery.deliveredAt}, ${bridgeDelivery.attemptedAt})`;\n\n const result = await this.db\n .select({\n status: bridgeDelivery.status,\n eventType: domainEvents.type,\n skipReason: bridgeDelivery.skipReason,\n count: sql<number>`COUNT(*)::int`,\n })\n .from(bridgeDelivery)\n .innerJoin(domainEvents, eq(bridgeDelivery.eventId, domainEvents.id))\n .where(\n and(\n gt(lastTransition, windowStart),\n sql`${lastTransition} <= ${windowEnd}`,\n ),\n )\n .groupBy(\n bridgeDelivery.status,\n domainEvents.type,\n bridgeDelivery.skipReason,\n );\n\n return result.map((r) => ({\n status: r.status as BridgeMetricsRow['status'],\n eventType: r.eventType,\n skipReason: r.skipReason,\n count: r.count,\n }));\n }\n\n private emit(tick: BridgeMetricsTick): void {\n if (tick.rows.length === 0) {\n // Heartbeat — confirms the sampler is alive when deliveries are idle.\n // Cheap enough at default 60s cadence; operators rely on this signal\n // to distinguish \"bridge quiet\" from \"reporter dead\".\n this.logger.log(\n `bridge_metrics tick=empty window=[${tick.windowStart.toISOString()}..${tick.windowEnd.toISOString()}]`,\n );\n return;\n }\n\n const totals = tick.rows.reduce(\n (acc, r) => {\n acc[r.status] = (acc[r.status] ?? 0) + r.count;\n return acc;\n },\n {} as Record<string, number>,\n );\n\n const detail = tick.rows\n .map(\n (r) =>\n `${r.eventType}|${r.status}${r.skipReason ? `:${r.skipReason}` : ''}=${r.count}`,\n )\n .join(' ');\n\n this.logger.log(\n `bridge_metrics tick window=[${tick.windowStart.toISOString()}..${tick.windowEnd.toISOString()}] ` +\n `totals=${JSON.stringify(totals)} detail=[${detail}]`,\n );\n }\n\n private resolveIntervalMs(): number {\n const raw = process.env['BRIDGE_METRICS_INTERVAL_MS'];\n if (!raw) return DEFAULT_INTERVAL_MS;\n const parsed = Number.parseInt(raw, 10);\n if (!Number.isFinite(parsed) || parsed < MIN_INTERVAL_MS) {\n new Logger(BridgeMetricsReporter.name).warn(\n `Ignoring BRIDGE_METRICS_INTERVAL_MS='${raw}' (invalid or < ${MIN_INTERVAL_MS}ms); ` +\n `using default ${DEFAULT_INTERVAL_MS}ms.`,\n );\n return DEFAULT_INTERVAL_MS;\n }\n return parsed;\n }\n}\n","/**\n * Injection token for the observability service (ADR-008, 5th subsystem).\n *\n * ```typescript\n * constructor(@Inject(OBSERVABILITY) private readonly obs: IObservabilityService) {}\n * ```\n *\n * Per ADR-008, tokens use `Symbol()` for collision avoidance.\n */\nexport const OBSERVABILITY = Symbol('OBSERVABILITY');\n\n/**\n * Opt-in config token that tells the module whether to register the\n * `BridgeMetricsReporter` sampler. Consumers without the bridge subsystem\n * leave this `false` (the default) so the module doesn't import the\n * reporter's bridge-schema deps.\n */\nexport const OBSERVABILITY_REPORTERS = Symbol('OBSERVABILITY_REPORTERS');\n","/**\n * ObservabilityModule — DynamicModule factory for the observability\n * subsystem (ADR-008, 5th subsystem).\n *\n * Usage in AppModule:\n * ```typescript\n * ObservabilityModule.forRoot({\n * backend: 'drizzle',\n * reporters: { bridgeMetrics: true }, // optional — requires bridge subsystem\n * })\n * ```\n *\n * Usage in tests:\n * ```typescript\n * ObservabilityModule.forRoot({ backend: 'memory' })\n * ```\n *\n * `global: true` means any module that needs `IObservabilityService` can\n * inject `OBSERVABILITY` without importing this module. Register once in\n * AppModule.\n *\n * The drizzle backend requires `DRIZZLE` to be provided globally (e.g.,\n * via DatabaseModule). The memory backend has no dependencies.\n *\n * # Reporters\n *\n * Reporters are orthogonal to backends — they compose on top of either\n * drizzle or memory. The `reporters.bridgeMetrics` flag enables the\n * `BridgeMetricsReporter` sampler. Gated because the reporter imports the\n * bridge + events schemas; consumers without the bridge subsystem should\n * leave it off (the default).\n *\n * `ScheduleModule.forRoot()` is imported conditionally — only when a\n * reporter that needs it is enabled. Keeps the module dependency-light\n * for consumers that only want the read surface.\n */\nimport { type DynamicModule, Module } from '@nestjs/common';\n\nimport { DrizzleObservabilityService } from './observability.drizzle-backend';\nimport { MemoryObservabilityService } from './observability.memory-backend';\nimport {\n OBSERVABILITY,\n OBSERVABILITY_REPORTERS,\n} from './observability.tokens';\n\nexport interface ObservabilityReporterOptions {\n /**\n * Register `BridgeMetricsReporter` — periodic log sampler over\n * `bridge_delivery`. Requires the bridge subsystem (schemas imported\n * transitively). Defaults to `false`.\n */\n bridgeMetrics?: boolean;\n}\n\nexport interface ObservabilityModuleOptions {\n backend: 'drizzle' | 'memory';\n reporters?: ObservabilityReporterOptions;\n}\n\n@Module({})\nexport class ObservabilityModule {\n static forRoot(\n options: ObservabilityModuleOptions = { backend: 'drizzle' },\n ): DynamicModule {\n const ConcreteClass =\n options.backend === 'drizzle'\n ? DrizzleObservabilityService\n : MemoryObservabilityService;\n\n const wantsBridgeMetrics = options.reporters?.bridgeMetrics === true;\n\n const providers: DynamicModule['providers'] = [\n // Register the concrete class as the canonical instance.\n ConcreteClass,\n // OBSERVABILITY token points at the same instance — no duplicate.\n { provide: OBSERVABILITY, useExisting: ConcreteClass },\n // Expose the resolved reporter config for introspection / tests.\n {\n provide: OBSERVABILITY_REPORTERS,\n useValue: options.reporters ?? {},\n },\n ];\n\n const exports: DynamicModule['exports'] = [OBSERVABILITY];\n if (wantsBridgeMetrics) {\n // Lazy-require keeps the reporter file (and its @nestjs/schedule +\n // bridge schema imports) off the hot path for consumers who don't\n // enable the reporter.\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const { BridgeMetricsReporter } = require('./reporters/bridge-metrics.reporter');\n providers.push(BridgeMetricsReporter);\n exports.push(BridgeMetricsReporter);\n }\n\n // ScheduleModule is a PEER dep (optional) — only resolved when a\n // reporter that uses it is enabled. Consumers using the read-only\n // surface (default) are free of the @nestjs/schedule install tax.\n const imports: DynamicModule['imports'] = [];\n if (wantsBridgeMetrics) {\n // Lazy-require: avoids parse-time failure for consumers that haven't\n // installed @nestjs/schedule and don't need reporters.\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const { ScheduleModule } = require('@nestjs/schedule');\n imports.push(ScheduleModule.forRoot());\n }\n\n return {\n module: ObservabilityModule,\n global: true,\n imports,\n providers,\n exports,\n };\n }\n}\n","/**\n * DrizzleObservabilityService — production backend for IObservabilityService.\n *\n * Pure read-only SQL over framework-owned tables:\n * - `job_run` (jobs subsystem)\n * - `bridge_delivery` (bridge subsystem)\n * - `domain_events` (events subsystem)\n * - `sync_runs` (sync subsystem)\n * - `sync_subscriptions` (sync subsystem)\n *\n * No new tables, no background loops, no lifecycle hooks. This is a query\n * facade — each call hits the DB and returns. Rate-limit / dashboard-cadence\n * coordination is the caller's responsibility.\n *\n * # Error behavior\n *\n * Methods throw on DB failure (consistent with the rest of the ADR-008\n * family's write-ish backends). Dashboards and `/dev/status` endpoints are\n * expected to handle the error surface — returning an empty snapshot on a\n * transient DB blip would silently hide \"Postgres is down\" from operators,\n * which is the opposite of what observability is for.\n *\n * # Drizzle-specific extensions (documented per CLAUDE.md core/extensions)\n *\n * Extensions MAY be added to this class that leverage Postgres-specific\n * capability (e.g. `pg_stat_activity` sampling, advisory-lock inspection).\n * Consumers opting into extensions accept backend-specific coupling; the\n * core five methods below stay backend-portable.\n */\nimport { Inject, Injectable } from '@nestjs/common';\nimport { desc, eq, sql } from 'drizzle-orm';\n\nimport { DRIZZLE } from '../../constants/tokens';\nimport type { DrizzleClient } from '../../types/drizzle';\nimport { bridgeDelivery } from '../bridge/bridge-delivery.schema';\nimport { jobRuns } from '../jobs/job-orchestration.schema';\nimport { syncRuns, syncSubscriptions } from '../sync/sync-audit.schema';\nimport type {\n CursorSnapshot,\n IObservabilityService,\n JobRunFailure,\n PoolDepth,\n StatusHistogram,\n SyncRunSummary,\n} from './observability.protocol';\n\n@Injectable()\nexport class DrizzleObservabilityService implements IObservabilityService {\n constructor(@Inject(DRIZZLE) private readonly db: DrizzleClient) {}\n\n async getPoolDepths(): Promise<PoolDepth[]> {\n // Raw SQL: Drizzle's builder drops AS-aliases on bare `sql<>` columns,\n // which the pg driver then can't map back by name. Raw execute with\n // explicit aliases keeps the result shape deterministic.\n const result = await this.db.execute(sql`\n SELECT\n pool AS name,\n COUNT(*) FILTER (WHERE status = 'pending')::int AS pending,\n COUNT(*) FILTER (WHERE status = 'running')::int AS running,\n (percentile_cont(0.95) WITHIN GROUP (\n ORDER BY EXTRACT(EPOCH FROM (now() - claimed_at)) * 1000\n ) FILTER (WHERE status = 'running' AND claimed_at IS NOT NULL))::int\n AS claimed_age_p95_ms\n FROM job_run\n WHERE status IN ('pending','running')\n GROUP BY pool\n ORDER BY pool\n `);\n\n const rows = extractRows<{\n name: string;\n pending: number;\n running: number;\n claimed_age_p95_ms: number | null;\n }>(result);\n\n return rows.map((r) => ({\n name: r.name,\n pending: r.pending,\n running: r.running,\n claimedAgeP95Ms: r.claimed_age_p95_ms,\n }));\n }\n\n async getRecentSyncRuns(\n limit: number,\n integrationId?: string,\n ): Promise<SyncRunSummary[]> {\n // Join to sync_subscriptions to recover integration/adapter/domain so\n // callers don't re-hydrate the subscription row themselves. Upstream\n // sync_runs owns only subscription_id; the enrichment columns live on\n // the subscription side.\n const base = this.db\n .select({\n id: syncRuns.id,\n subscriptionId: syncRuns.subscriptionId,\n integrationId: syncSubscriptions.integrationId,\n adapter: syncSubscriptions.adapter,\n domain: syncSubscriptions.domain,\n direction: syncRuns.direction,\n action: syncRuns.action,\n status: syncRuns.status,\n recordsFound: syncRuns.recordsFound,\n recordsProcessed: syncRuns.recordsProcessed,\n durationMs: syncRuns.durationMs,\n error: syncRuns.error,\n startedAt: syncRuns.startedAt,\n completedAt: syncRuns.completedAt,\n })\n .from(syncRuns)\n .innerJoin(\n syncSubscriptions,\n eq(syncRuns.subscriptionId, syncSubscriptions.id),\n );\n\n const filtered =\n integrationId !== undefined\n ? base.where(eq(syncSubscriptions.integrationId, integrationId))\n : base;\n\n const rows = await filtered.orderBy(desc(syncRuns.startedAt)).limit(limit);\n\n return rows.map((r) => ({\n id: r.id,\n subscriptionId: r.subscriptionId,\n integrationId: r.integrationId,\n adapter: r.adapter,\n domain: r.domain,\n direction: r.direction,\n action: r.action,\n status: r.status,\n recordsFound: r.recordsFound,\n recordsProcessed: r.recordsProcessed,\n durationMs: r.durationMs,\n error: r.error,\n startedAt: r.startedAt,\n completedAt: r.completedAt,\n }));\n }\n\n async getBridgeDeliveryHistogram(\n windowHours: number,\n ): Promise<StatusHistogram> {\n // Window on COALESCE(delivered_at, attempted_at) so terminal skipped/\n // failed rows (which never get delivered_at) are counted alongside\n // delivered rows. The histogram is a flat Record<status, count>.\n const result = await this.db.execute(sql`\n SELECT status, COUNT(*)::int AS count\n FROM bridge_delivery\n WHERE COALESCE(delivered_at, attempted_at) > now() - make_interval(hours => ${windowHours})\n GROUP BY status\n `);\n\n const rows = extractRows<{ status: string; count: number }>(result);\n const hist: StatusHistogram = {};\n for (const r of rows) hist[r.status] = r.count;\n return hist;\n }\n\n async getRecentFailedJobs(limit: number): Promise<JobRunFailure[]> {\n const rows = await this.db\n .select({\n id: jobRuns.id,\n jobType: jobRuns.jobType,\n pool: jobRuns.pool,\n status: jobRuns.status,\n error: jobRuns.error,\n startedAt: jobRuns.startedAt,\n finishedAt: jobRuns.finishedAt,\n attempts: jobRuns.attempts,\n })\n .from(jobRuns)\n .where(eq(jobRuns.status, 'failed'))\n .orderBy(desc(jobRuns.finishedAt))\n .limit(limit);\n\n return rows.map((r) => ({\n id: r.id,\n jobType: r.jobType,\n pool: r.pool,\n status: r.status,\n error: r.error,\n startedAt: r.startedAt,\n finishedAt: r.finishedAt,\n attempts: r.attempts,\n }));\n }\n\n async getCursors(): Promise<CursorSnapshot[]> {\n const rows = await this.db\n .select({\n id: syncSubscriptions.id,\n integrationId: syncSubscriptions.integrationId,\n adapter: syncSubscriptions.adapter,\n domain: syncSubscriptions.domain,\n cursor: syncSubscriptions.cursor,\n lastSyncAt: syncSubscriptions.lastSyncAt,\n })\n .from(syncSubscriptions)\n .where(eq(syncSubscriptions.enabled, true))\n .orderBy(syncSubscriptions.integrationId, syncSubscriptions.domain);\n\n return rows.map((r) => ({\n subscriptionId: r.id,\n integrationId: r.integrationId,\n adapter: r.adapter,\n domain: r.domain,\n lastCursor: r.cursor,\n lastSyncAt: r.lastSyncAt,\n }));\n }\n}\n\n/**\n * Normalize `db.execute()` return shape. `node-postgres` returns `{ rows: [] }`\n * while some pg-compatible drivers return the row array directly.\n */\nfunction extractRows<T>(result: unknown): T[] {\n const maybe = result as { rows?: unknown };\n if (Array.isArray(maybe.rows)) return maybe.rows as T[];\n if (Array.isArray(result)) return result as T[];\n return [];\n}\n","/**\n * Drizzle schema for the sync subsystem audit/observability tables (SYNC-1).\n *\n * Three tables model end-to-end sync observability, keyed by the single port\n * every sync adapter implements (`IChangeSource<T>` from SYNC-2):\n *\n * - `sync_subscriptions` — owns the cursor per\n * `(integration_id, adapter, domain, external_ref)` tuple. Addressed\n * by id by `ICursorStore` (SYNC-3/SYNC-4).\n * - `sync_runs` — per-run audit log: start/complete, status,\n * cursor before/after, counts, direction (inbound|outbound),\n * action (poll|cdc|webhook|manual|writeback).\n * - `sync_run_items` — per-record change log with structured\n * `changed_fields` jsonb enforced by the Zod `FieldDiffSchema`\n * contract (ADR-0003; protocol lives in SYNC-2's\n * sync-field-diff.protocol.ts).\n *\n * Design calls (vs. issue #126 open questions):\n *\n * - `sync_subscriptions` ships in the subsystem (not consumer-owned).\n * Rationale: SYNC-4's `PostgresCursorStore` needs to read/write this\n * table directly; making it consumer-owned would require consumers to\n * hand-wire a shape the backend already knows. The row is addressable\n * by id and scoped by the uniqueness tuple; consumers can still\n * query/list it freely. Same stance as `job_run` being subsystem-\n * owned while remaining consumer-queryable.\n *\n * - `tenant_id` is always emitted on the three tables as nullable text.\n * The `SYNC_MULTI_TENANT` DI flag (SYNC-6) is what enforces the\n * non-null + cross-tenant-isolation contract at the service/orchestrator\n * boundary. This mirrors JOB-1/JOB-8's final shape — runtime guard, not\n * a scaffold-time conditional column. Keeps the schema file uniform\n * across single-tenant and multi-tenant deployments.\n *\n * - `changed_fields` on `sync_run_items` is typed via the Zod-inferred\n * `FieldDiff` shape from SYNC-2 (`{ [fieldName]: { from, to } }`). The\n * recorder service (SYNC-5) validates every write against\n * `FieldDiffSchema.parse` so consumers can rely on the shape.\n */\nimport {\n pgEnum,\n pgTable,\n uuid,\n text,\n jsonb,\n integer,\n boolean,\n timestamp,\n index,\n uniqueIndex,\n} from 'drizzle-orm/pg-core';\nimport type { InferSelectModel } from 'drizzle-orm';\n\nimport type { FieldDiff } from './sync-field-diff.protocol';\n\n// ─── Enums ──────────────────────────────────────────────────────────────────\n\n/**\n * Direction of a sync run relative to local state.\n *\n * - `inbound` — external → local (the common case: SFDC poll → local DB).\n * - `outbound` — local → external (writeback; deferred per epic but the\n * column shape is reserved so future writeback runs share the audit log).\n */\nexport const syncRunDirectionEnum = pgEnum('sync_run_direction', [\n 'inbound',\n 'outbound',\n]);\n\n/**\n * How the run detected upstream changes. Maps 1:1 to the `Change.source`\n * provenance on inbound runs; `manual` captures operator-triggered re-syncs\n * and `writeback` captures outbound runs.\n */\nexport const syncRunActionEnum = pgEnum('sync_run_action', [\n 'poll',\n 'cdc',\n 'webhook',\n 'manual',\n 'writeback',\n]);\n\n/**\n * Lifecycle status of a sync run.\n *\n * - `running` — in-flight; recorder has started but not completed.\n * - `success` — completed with at least one change processed.\n * - `no_changes` — completed cleanly, no upstream changes found.\n * - `failed` — errored before completion; `error` column carries the\n * message. `records_processed` may be non-zero (partial progress).\n */\nexport const syncRunStatusEnum = pgEnum('sync_run_status', [\n 'running',\n 'success',\n 'no_changes',\n 'failed',\n]);\n\n/**\n * Operation applied per record. Mirrors `Change<T>.operation` from SYNC-2,\n * plus the recorder's own `'noop'` for changes that matched existing state.\n */\nexport const syncRunItemOperationEnum = pgEnum('sync_run_item_operation', [\n 'created',\n 'updated',\n 'deleted',\n 'noop',\n]);\n\n/**\n * Per-record status within a run. `skipped` captures loopback-detected echoes\n * of the local system's own writes (see `ILoopbackFingerprintStore` in the\n * epic), which record the external_id but intentionally do not touch local\n * state.\n */\nexport const syncRunItemStatusEnum = pgEnum('sync_run_item_status', [\n 'success',\n 'failed',\n 'skipped',\n]);\n\n// ─── sync_subscriptions ─────────────────────────────────────────────────────\n\n/**\n * One cursor owner per (integration, adapter, domain, external_ref).\n *\n * - `integration_id` — opaque id of the connected account/instance. E.g.\n * the SFDC org id for polling strategies, the GitHub installation id\n * for webhook strategies.\n * - `adapter` — short adapter label, e.g. `'salesforce'`, `'hubspot'`.\n * - `domain` — canonical entity domain this subscription tracks,\n * e.g. `'opportunity'`, `'contact'`.\n * - `external_ref` — optional upstream scope (e.g. a filter id, a\n * webhook subscription id). NULL when the subscription covers the\n * entire domain.\n *\n * The cursor shape is opaque jsonb — strategies type it internally (poll:\n * `{ systemModstamp }`, cdc: `{ replayId }`, webhook: `{ ts }`). Overwritten\n * by `ICursorStore.put(id, cursor)`.\n */\nexport const syncSubscriptions = pgTable(\n 'sync_subscriptions',\n {\n id: uuid('id').primaryKey().defaultRandom(),\n integrationId: text('integration_id').notNull(),\n adapter: text('adapter').notNull(),\n domain: text('domain').notNull(),\n externalRef: text('external_ref'),\n enabled: boolean('enabled').notNull().default(true),\n /**\n * Per-subscription configuration bag. Strategies type it internally;\n * e.g. polling strategies stash `{ batchSize, highWatermark }` here.\n */\n config: jsonb('config').notNull().default({}).$type<Record<string, unknown>>(),\n /**\n * Opaque cursor persisted by `ICursorStore.put()`. NULL until the first\n * successful run advances it.\n */\n cursor: jsonb('cursor').$type<unknown>(),\n lastSyncAt: timestamp('last_sync_at', { withTimezone: true }),\n /** Runtime-enforced when `SYNC_MULTI_TENANT` is true; see SYNC-6. */\n tenantId: text('tenant_id'),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),\n },\n (t) => ({\n /**\n * Composite uniqueness per the epic shape. `external_ref` is nullable;\n * Postgres treats NULLs as distinct in a UNIQUE constraint, which means\n * two rows with the same `(integration_id, adapter, domain)` and NULL\n * external_ref are allowed. That's intentional — a subscription with\n * NULL external_ref covers the full domain, and duplicates there would\n * be a consumer-layer modeling issue, not a schema concern.\n */\n uqSyncSubscriptionTuple: uniqueIndex('uq_sync_subscriptions_tuple').on(\n t.integrationId,\n t.adapter,\n t.domain,\n t.externalRef,\n ),\n /** Scheduling query: list enabled subscriptions ordered by staleness. */\n idxSyncSubscriptionsEnabledLastSync: index(\n 'idx_sync_subscriptions_enabled_last_sync',\n ).on(t.enabled, t.lastSyncAt),\n }),\n);\n\nexport type SyncSubscriptionRow = InferSelectModel<typeof syncSubscriptions>;\n\n// ─── sync_runs ──────────────────────────────────────────────────────────────\n\n/**\n * One row per invocation of `ExecuteSyncUseCase`. `started_at` is set when\n * the recorder opens the run; `completed_at`, `status`, `records_*`,\n * `cursor_after`, and `duration_ms` are filled on completion.\n *\n * `cursor_before` / `cursor_after` carry the opaque cursor snapshots so the\n * run log is fully self-describing — given a run id, an operator can reason\n * about exactly what window was scanned without cross-referencing another\n * table.\n */\nexport const syncRuns = pgTable(\n 'sync_runs',\n {\n id: uuid('id').primaryKey().defaultRandom(),\n subscriptionId: uuid('subscription_id')\n .notNull()\n .references(() => syncSubscriptions.id, { onDelete: 'cascade' }),\n direction: syncRunDirectionEnum('direction').notNull(),\n action: syncRunActionEnum('action').notNull(),\n status: syncRunStatusEnum('status').notNull().default('running'),\n recordsFound: integer('records_found').notNull().default(0),\n recordsProcessed: integer('records_processed').notNull().default(0),\n cursorBefore: jsonb('cursor_before').$type<unknown>(),\n cursorAfter: jsonb('cursor_after').$type<unknown>(),\n durationMs: integer('duration_ms'),\n error: text('error'),\n startedAt: timestamp('started_at', { withTimezone: true })\n .notNull()\n .defaultNow(),\n completedAt: timestamp('completed_at', { withTimezone: true }),\n /** Runtime-enforced when `SYNC_MULTI_TENANT` is true; see SYNC-6. */\n tenantId: text('tenant_id'),\n },\n (t) => ({\n /** Timeline read: \"most recent runs for this subscription\". */\n idxSyncRunsSubscriptionStartedAt: index(\n 'idx_sync_runs_subscription_started_at',\n ).on(t.subscriptionId, t.startedAt),\n /** Stale-run sweeper: \"runs that started > N minutes ago and are still running\". */\n idxSyncRunsStatusStartedAt: index('idx_sync_runs_status_started_at').on(\n t.status,\n t.startedAt,\n ),\n }),\n);\n\nexport type SyncRunRow = InferSelectModel<typeof syncRuns>;\n\n// ─── sync_run_items ─────────────────────────────────────────────────────────\n\n/**\n * One row per upstream change processed within a run. Captures the canonical\n * decision the orchestrator made (`operation` + `status`), the structured\n * per-field diff (`changed_fields`, ADR-0003), and the local row id\n * (`local_id`) for drill-down joins.\n *\n * `changed_fields` is validated at the recorder layer via `FieldDiffSchema`\n * (SYNC-2) — the $type<FieldDiff> annotation here only documents the shape\n * for Drizzle consumers. The runtime enforcement is non-negotiable: downstream\n * drift-detection queries rely on the `{from, to}` shape per field.\n *\n * `title` is an optional human-readable label captured at write time (e.g.\n * `\"Pinnacle opportunity\"`) so run-log UIs don't need to re-hydrate the\n * canonical record.\n */\nexport const syncRunItems = pgTable(\n 'sync_run_items',\n {\n id: uuid('id').primaryKey().defaultRandom(),\n syncRunId: uuid('sync_run_id')\n .notNull()\n .references(() => syncRuns.id, { onDelete: 'cascade' }),\n entityType: text('entity_type').notNull(),\n externalId: text('external_id').notNull(),\n localId: text('local_id'),\n operation: syncRunItemOperationEnum('operation').notNull(),\n status: syncRunItemStatusEnum('status').notNull(),\n /**\n * Structured per-field diff — ADR-0003 shape enforced by\n * `FieldDiffSchema.parse` at the recorder service layer.\n *\n * Shape: `{ [fieldName]: { from: unknown, to: unknown } }`.\n * Empty `{}` for `noop` items; `{ [field]: { from: null, to: <value> } }`\n * for created items; `{ [field]: { from: <value>, to: null } }` for\n * deleted items.\n */\n changedFields: jsonb('changed_fields').notNull().default({}).$type<FieldDiff>(),\n title: text('title'),\n error: text('error'),\n createdAt: timestamp('created_at', { withTimezone: true })\n .notNull()\n .defaultNow(),\n /** Runtime-enforced when `SYNC_MULTI_TENANT` is true; see SYNC-6. */\n tenantId: text('tenant_id'),\n },\n (t) => ({\n /** Ordered timeline within a run. */\n idxSyncRunItemsRunCreatedAt: index('idx_sync_run_items_run_created_at').on(\n t.syncRunId,\n t.createdAt,\n ),\n /** Per-record history: \"every sync that touched opportunity/$extId\". */\n idxSyncRunItemsEntityExternal: index(\n 'idx_sync_run_items_entity_external',\n ).on(t.entityType, t.externalId),\n }),\n);\n\nexport type SyncRunItemRow = InferSelectModel<typeof syncRunItems>;\n","/**\n * MemoryObservabilityService — in-memory test backend for\n * IObservabilityService.\n *\n * Stores snapshot data set by the test harness and returns it verbatim.\n * This is deliberately NOT a replay-from-events simulator — the point of\n * the memory backend is to let tests assert \"when the subsystem returns\n * X, does the caller render Y correctly?\" without standing up Postgres.\n *\n * Tests populate the backend via the `seed*` methods, then exercise the\n * protocol reads. Each seed method replaces (not merges) its slice, which\n * keeps the mental model simple: the backend is a fixture holder.\n *\n * No lifecycle hooks (no background work to manage).\n */\nimport { Injectable } from '@nestjs/common';\nimport type {\n CursorSnapshot,\n IObservabilityService,\n JobRunFailure,\n PoolDepth,\n StatusHistogram,\n SyncRunSummary,\n} from './observability.protocol';\n\n@Injectable()\nexport class MemoryObservabilityService implements IObservabilityService {\n private pools: PoolDepth[] = [];\n private syncRuns: SyncRunSummary[] = [];\n private bridgeHistogram: StatusHistogram = {};\n private failedJobs: JobRunFailure[] = [];\n private cursors: CursorSnapshot[] = [];\n\n // ─── Core contract ─────────────────────────────────────────────────────\n\n async getPoolDepths(): Promise<PoolDepth[]> {\n return [...this.pools];\n }\n\n async getRecentSyncRuns(\n limit: number,\n integrationId?: string,\n ): Promise<SyncRunSummary[]> {\n const filtered =\n integrationId !== undefined\n ? this.syncRuns.filter((r) => r.integrationId === integrationId)\n : this.syncRuns;\n return filtered\n .slice()\n .sort((a, b) => b.startedAt.getTime() - a.startedAt.getTime())\n .slice(0, limit);\n }\n\n async getBridgeDeliveryHistogram(\n _windowHours: number,\n ): Promise<StatusHistogram> {\n // Memory backend ignores the window — tests that care about windowing\n // should seed the histogram for the window they're simulating.\n return { ...this.bridgeHistogram };\n }\n\n async getRecentFailedJobs(limit: number): Promise<JobRunFailure[]> {\n return this.failedJobs\n .slice()\n .sort(\n (a, b) =>\n (b.finishedAt?.getTime() ?? 0) - (a.finishedAt?.getTime() ?? 0),\n )\n .slice(0, limit);\n }\n\n async getCursors(): Promise<CursorSnapshot[]> {\n return [...this.cursors];\n }\n\n // ─── Test seams ────────────────────────────────────────────────────────\n\n /** Replace the pool-depth slice. */\n seedPools(pools: PoolDepth[]): void {\n this.pools = [...pools];\n }\n\n /** Replace the sync-run slice. */\n seedSyncRuns(runs: SyncRunSummary[]): void {\n this.syncRuns = [...runs];\n }\n\n /** Replace the bridge-delivery histogram. */\n seedBridgeHistogram(hist: StatusHistogram): void {\n this.bridgeHistogram = { ...hist };\n }\n\n /** Replace the failed-jobs slice. */\n seedFailedJobs(jobs: JobRunFailure[]): void {\n this.failedJobs = [...jobs];\n }\n\n /** Replace the cursor slice. */\n seedCursors(cursors: CursorSnapshot[]): void {\n this.cursors = [...cursors];\n }\n\n /** Reset every slice — for afterEach hooks. */\n reset(): void {\n this.pools = [];\n this.syncRuns = [];\n this.bridgeHistogram = {};\n this.failedJobs = [];\n this.cursors = [];\n }\n}\n","/**\n * Observability subsystem public API (ADR-008, 5th subsystem).\n *\n * Import token + protocol in services/controllers:\n * ```typescript\n * import { OBSERVABILITY, type IObservabilityService } from '@pattern-stack/codegen/runtime/subsystems/observability';\n * ```\n *\n * Import the module in AppModule:\n * ```typescript\n * import { ObservabilityModule } from '@pattern-stack/codegen/runtime/subsystems/observability';\n * ObservabilityModule.forRoot({ backend: 'drizzle', reporters: { bridgeMetrics: true } })\n * ```\n */\nexport { OBSERVABILITY, OBSERVABILITY_REPORTERS } from './observability.tokens';\nexport type {\n CursorSnapshot,\n IObservabilityService,\n JobRunFailure,\n PoolDepth,\n StatusHistogram,\n SyncRunSummary,\n} from './observability.protocol';\nexport {\n ObservabilityModule,\n type ObservabilityModuleOptions,\n type ObservabilityReporterOptions,\n} from './observability.module';\nexport { DrizzleObservabilityService } from './observability.drizzle-backend';\nexport { MemoryObservabilityService } from './observability.memory-backend';\nexport {\n BridgeMetricsReporter,\n type BridgeMetricsRow,\n type BridgeMetricsTick,\n} from './reporters/bridge-metrics.reporter';\n"],"mappings":"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","names":["index","jsonb","pgTable","text","timestamp","uuid","index","jsonb","pgEnum","pgTable","text","timestamp","uuid","sql","Inject","Injectable","eq","sql","sql","pgEnum","pgTable","uuid","text","jsonb","integer","timestamp","index","uniqueIndex","sql","Injectable","jobs","Injectable","BridgeMetricsReporter"]}
@@ -354,7 +354,8 @@ __export(bridge_metrics_reporter_exports, {
354
354
  import {
355
355
  Inject as Inject2,
356
356
  Injectable as Injectable3,
357
- Logger
357
+ Logger,
358
+ Optional
358
359
  } from "@nestjs/common";
359
360
  import { and, eq as eq2, gt, sql as sql4 } from "drizzle-orm";
360
361
  var INTERVAL_NAME, DEFAULT_INTERVAL_MS, MIN_INTERVAL_MS, BridgeMetricsReporter;
@@ -368,7 +369,7 @@ var init_bridge_metrics_reporter = __esm({
368
369
  DEFAULT_INTERVAL_MS = 6e4;
369
370
  MIN_INTERVAL_MS = 1e3;
370
371
  BridgeMetricsReporter = class {
371
- constructor(db, scheduler) {
372
+ constructor(db, scheduler = null) {
372
373
  this.db = db;
373
374
  this.scheduler = scheduler;
374
375
  this.intervalMs = this.resolveIntervalMs();
@@ -379,6 +380,14 @@ var init_bridge_metrics_reporter = __esm({
379
380
  logger = new Logger(BridgeMetricsReporter.name);
380
381
  intervalMs;
381
382
  lastTickAt;
383
+ /**
384
+ * Timer handle retained as a field when `SchedulerRegistry` isn't
385
+ * available (optional dep). `SchedulerRegistry` is the Nest-idiomatic
386
+ * home for interval cleanup, but global-module wiring across consumer
387
+ * topologies doesn't always make it injectable here — the fallback
388
+ * keeps the reporter self-sufficient.
389
+ */
390
+ ownedTimer = null;
382
391
  onModuleInit() {
383
392
  this.logger.log(
384
393
  `BridgeMetricsReporter starting (intervalMs=${this.intervalMs}).`
@@ -387,12 +396,20 @@ var init_bridge_metrics_reporter = __esm({
387
396
  void this.tick();
388
397
  }, this.intervalMs);
389
398
  timer.unref?.();
390
- this.scheduler.addInterval(INTERVAL_NAME, timer);
399
+ if (this.scheduler) {
400
+ this.scheduler.addInterval(INTERVAL_NAME, timer);
401
+ } else {
402
+ this.ownedTimer = timer;
403
+ }
391
404
  }
392
405
  onModuleDestroy() {
393
- if (this.scheduler.getIntervals().includes(INTERVAL_NAME)) {
406
+ if (this.scheduler && this.scheduler.getIntervals().includes(INTERVAL_NAME)) {
394
407
  this.scheduler.deleteInterval(INTERVAL_NAME);
395
408
  }
409
+ if (this.ownedTimer !== null) {
410
+ clearInterval(this.ownedTimer);
411
+ this.ownedTimer = null;
412
+ }
396
413
  }
397
414
  /**
398
415
  * Run one sampling tick. Public so tests can drive it deterministically
@@ -474,7 +491,8 @@ var init_bridge_metrics_reporter = __esm({
474
491
  };
475
492
  BridgeMetricsReporter = __decorateClass([
476
493
  Injectable3(),
477
- __decorateParam(0, Inject2(DRIZZLE))
494
+ __decorateParam(0, Inject2(DRIZZLE)),
495
+ __decorateParam(1, Optional())
478
496
  ], BridgeMetricsReporter);
479
497
  }
480
498
  });