@pattern-stack/codegen 0.4.4 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/runtime/subsystems/index.d.ts +7 -0
  2. package/dist/runtime/subsystems/index.js +905 -208
  3. package/dist/runtime/subsystems/index.js.map +1 -1
  4. package/dist/runtime/subsystems/observability/index.d.ts +10 -0
  5. package/dist/runtime/subsystems/observability/index.js +895 -0
  6. package/dist/runtime/subsystems/observability/index.js.map +1 -0
  7. package/dist/runtime/subsystems/observability/observability.drizzle-backend.d.ts +15 -0
  8. package/dist/runtime/subsystems/observability/observability.drizzle-backend.js +465 -0
  9. package/dist/runtime/subsystems/observability/observability.drizzle-backend.js.map +1 -0
  10. package/dist/runtime/subsystems/observability/observability.memory-backend.d.ts +28 -0
  11. package/dist/runtime/subsystems/observability/observability.memory-backend.js +75 -0
  12. package/dist/runtime/subsystems/observability/observability.memory-backend.js.map +1 -0
  13. package/dist/runtime/subsystems/observability/observability.module.d.ts +56 -0
  14. package/dist/runtime/subsystems/observability/observability.module.js +887 -0
  15. package/dist/runtime/subsystems/observability/observability.module.js.map +1 -0
  16. package/dist/runtime/subsystems/observability/observability.protocol.d.ts +155 -0
  17. package/dist/runtime/subsystems/observability/observability.protocol.js +1 -0
  18. package/dist/runtime/subsystems/observability/observability.protocol.js.map +1 -0
  19. package/dist/runtime/subsystems/observability/observability.tokens.d.ts +19 -0
  20. package/dist/runtime/subsystems/observability/observability.tokens.js +8 -0
  21. package/dist/runtime/subsystems/observability/observability.tokens.js.map +1 -0
  22. package/dist/runtime/subsystems/observability/reporters/bridge-metrics.reporter.d.ts +79 -0
  23. package/dist/runtime/subsystems/observability/reporters/bridge-metrics.reporter.js +425 -0
  24. package/dist/runtime/subsystems/observability/reporters/bridge-metrics.reporter.js.map +1 -0
  25. package/dist/runtime/subsystems/sync/sync-audit.schema.d.ts +4 -4
  26. package/package.json +6 -1
  27. package/runtime/subsystems/index.ts +23 -0
  28. package/runtime/subsystems/observability/index.ts +35 -0
  29. package/runtime/subsystems/observability/observability.drizzle-backend.ts +223 -0
  30. package/runtime/subsystems/observability/observability.memory-backend.ts +111 -0
  31. package/runtime/subsystems/observability/observability.module.ts +115 -0
  32. package/runtime/subsystems/observability/observability.protocol.ts +167 -0
  33. package/runtime/subsystems/observability/observability.tokens.ts +18 -0
  34. package/runtime/subsystems/observability/reporters/bridge-metrics.reporter.ts +222 -0
@@ -0,0 +1,465 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __decorateClass = (decorators, target, key, kind) => {
4
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
5
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
6
+ if (decorator = decorators[i])
7
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
8
+ if (kind && result) __defProp(target, key, result);
9
+ return result;
10
+ };
11
+ var __decorateParam = (index3, decorator) => (target, key) => decorator(target, key, index3);
12
+
13
+ // runtime/subsystems/observability/observability.drizzle-backend.ts
14
+ import { Inject, Injectable } from "@nestjs/common";
15
+ import { desc, eq, sql as sql2 } from "drizzle-orm";
16
+
17
+ // runtime/constants/tokens.ts
18
+ var DRIZZLE = "DRIZZLE";
19
+
20
+ // runtime/subsystems/jobs/job-orchestration.schema.ts
21
+ import {
22
+ pgEnum,
23
+ pgTable,
24
+ uuid,
25
+ text,
26
+ jsonb,
27
+ integer,
28
+ timestamp,
29
+ index,
30
+ uniqueIndex
31
+ } from "drizzle-orm/pg-core";
32
+ import { sql } from "drizzle-orm";
33
+ var jobRunStatusEnum = pgEnum("job_run_status", [
34
+ "pending",
35
+ "running",
36
+ "waiting",
37
+ "completed",
38
+ "failed",
39
+ "timed_out",
40
+ "canceled"
41
+ ]);
42
+ var jobStepKindEnum = pgEnum("job_step_kind", ["task"]);
43
+ var jobStepStatusEnum = pgEnum("job_step_status", [
44
+ "pending",
45
+ "running",
46
+ "completed",
47
+ "failed",
48
+ "skipped"
49
+ ]);
50
+ var collisionModeEnum = pgEnum("job_collision_mode", [
51
+ "queue",
52
+ "reject",
53
+ "replace"
54
+ ]);
55
+ var replayFromEnum = pgEnum("job_replay_from", [
56
+ "scratch",
57
+ "last_step",
58
+ "last_checkpoint"
59
+ ]);
60
+ var parentClosePolicyEnum = pgEnum("job_parent_close_policy", [
61
+ "terminate",
62
+ "cancel",
63
+ "abandon"
64
+ ]);
65
+ var waitKindEnum = pgEnum("job_wait_kind", ["signal"]);
66
+ var triggerSourceEnum = pgEnum("job_trigger_source", [
67
+ "manual",
68
+ "schedule",
69
+ "event",
70
+ "parent"
71
+ ]);
72
+ var jobs = pgTable("job", {
73
+ type: text("type").primaryKey(),
74
+ version: integer("version").notNull().default(1),
75
+ pool: text("pool").notNull(),
76
+ scopeEntityType: text("scope_entity_type"),
77
+ retryPolicy: jsonb("retry_policy").notNull().$type(),
78
+ timeoutMs: integer("timeout_ms"),
79
+ concurrencyKeyTemplate: text("concurrency_key_template"),
80
+ collisionMode: collisionModeEnum("collision_mode").notNull().default("queue"),
81
+ dedupeKeyTemplate: text("dedupe_key_template"),
82
+ dedupeWindowMs: integer("dedupe_window_ms"),
83
+ priorityDefault: integer("priority_default").notNull().default(0),
84
+ replayFrom: replayFromEnum("replay_from").notNull().default("last_checkpoint"),
85
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
86
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
87
+ });
88
+ var jobRuns = pgTable(
89
+ "job_run",
90
+ {
91
+ id: uuid("id").primaryKey().defaultRandom(),
92
+ jobType: text("job_type").notNull().references(() => jobs.type),
93
+ jobVersion: integer("job_version").notNull(),
94
+ parentRunId: uuid("parent_run_id").references(() => jobRuns.id),
95
+ /**
96
+ * Service generates `id` client-side via randomUUID() and sets
97
+ * root_run_id = id for root runs (single INSERT, no self-FK race).
98
+ */
99
+ rootRunId: uuid("root_run_id").notNull(),
100
+ parentClosePolicy: parentClosePolicyEnum("parent_close_policy").notNull().default("terminate"),
101
+ scopeEntityType: text("scope_entity_type"),
102
+ scopeEntityId: text("scope_entity_id"),
103
+ tenantId: text("tenant_id"),
104
+ tags: jsonb("tags").notNull().default({}).$type(),
105
+ pool: text("pool").notNull(),
106
+ priority: integer("priority").notNull().default(0),
107
+ concurrencyKey: text("concurrency_key"),
108
+ dedupeKey: text("dedupe_key"),
109
+ status: jobRunStatusEnum("status").notNull().default("pending"),
110
+ input: jsonb("input").notNull().$type(),
111
+ output: jsonb("output").$type(),
112
+ error: jsonb("error").$type(),
113
+ triggerSource: triggerSourceEnum("trigger_source").notNull(),
114
+ triggerRef: text("trigger_ref"),
115
+ runAt: timestamp("run_at", { withTimezone: true }).notNull().defaultNow(),
116
+ startedAt: timestamp("started_at", { withTimezone: true }),
117
+ finishedAt: timestamp("finished_at", { withTimezone: true }),
118
+ claimedAt: timestamp("claimed_at", { withTimezone: true }),
119
+ attempts: integer("attempts").notNull().default(0),
120
+ // Phase 3 placeholder — see ADR-025
121
+ waitKind: waitKindEnum("wait_kind"),
122
+ // Phase 3 placeholder — see ADR-025
123
+ resumeToken: text("resume_token"),
124
+ // Phase 3 placeholder — see ADR-025
125
+ waitDeadline: timestamp("wait_deadline", { withTimezone: true }),
126
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
127
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
128
+ },
129
+ (t) => ({
130
+ /** Claim query: ORDER BY priority DESC, run_at ASC. */
131
+ idxJobRunClaim: index("idx_job_run_claim").on(t.status, t.pool, t.runAt),
132
+ /** Tree traversal / cascade cancel. */
133
+ idxJobRunRoot: index("idx_job_run_root").on(t.rootRunId),
134
+ /** listForScope query. */
135
+ idxJobRunScope: index("idx_job_run_scope").on(t.scopeEntityType, t.scopeEntityId),
136
+ /** Idempotency collapse — partial index. */
137
+ idxJobRunDedupe: index("idx_job_run_dedupe").on(t.jobType, t.dedupeKey).where(sql`${t.dedupeKey} IS NOT NULL`),
138
+ /** Collision check — partial index. */
139
+ idxJobRunConcurrency: index("idx_job_run_concurrency").on(t.concurrencyKey).where(
140
+ sql`${t.concurrencyKey} IS NOT NULL AND ${t.status} IN ('pending','running')`
141
+ )
142
+ })
143
+ );
144
+ var jobSteps = pgTable(
145
+ "job_step",
146
+ {
147
+ id: uuid("id").primaryKey().defaultRandom(),
148
+ jobRunId: uuid("job_run_id").notNull().references(() => jobRuns.id),
149
+ stepId: text("step_id").notNull(),
150
+ kind: jobStepKindEnum("kind").notNull().default("task"),
151
+ /**
152
+ * Monotonic within run. integer (max ~2B per run) is sufficient —
153
+ * downgraded from ADR-022's bigint; revisit only if a single run
154
+ * ever exceeds 2 billion steps.
155
+ */
156
+ seq: integer("seq").notNull(),
157
+ status: jobStepStatusEnum("status").notNull().default("pending"),
158
+ input: jsonb("input").$type(),
159
+ /** Memoised on success for replay. */
160
+ output: jsonb("output").$type(),
161
+ error: jsonb("error").$type(),
162
+ attempts: integer("attempts").notNull().default(0),
163
+ startedAt: timestamp("started_at", { withTimezone: true }),
164
+ finishedAt: timestamp("finished_at", { withTimezone: true })
165
+ },
166
+ (t) => ({
167
+ /** No duplicate step IDs per run. */
168
+ idxJobStepRunStep: uniqueIndex("idx_job_step_run_step").on(t.jobRunId, t.stepId),
169
+ /** Ordered timeline reads. */
170
+ idxJobStepTimeline: index("idx_job_step_timeline").on(t.jobRunId, t.seq)
171
+ })
172
+ );
173
+
174
+ // runtime/subsystems/sync/sync-audit.schema.ts
175
+ import {
176
+ pgEnum as pgEnum2,
177
+ pgTable as pgTable2,
178
+ uuid as uuid2,
179
+ text as text2,
180
+ jsonb as jsonb2,
181
+ integer as integer2,
182
+ boolean,
183
+ timestamp as timestamp2,
184
+ index as index2,
185
+ uniqueIndex as uniqueIndex2
186
+ } from "drizzle-orm/pg-core";
187
+ var syncRunDirectionEnum = pgEnum2("sync_run_direction", [
188
+ "inbound",
189
+ "outbound"
190
+ ]);
191
+ var syncRunActionEnum = pgEnum2("sync_run_action", [
192
+ "poll",
193
+ "cdc",
194
+ "webhook",
195
+ "manual",
196
+ "writeback"
197
+ ]);
198
+ var syncRunStatusEnum = pgEnum2("sync_run_status", [
199
+ "running",
200
+ "success",
201
+ "no_changes",
202
+ "failed"
203
+ ]);
204
+ var syncRunItemOperationEnum = pgEnum2("sync_run_item_operation", [
205
+ "created",
206
+ "updated",
207
+ "deleted",
208
+ "noop"
209
+ ]);
210
+ var syncRunItemStatusEnum = pgEnum2("sync_run_item_status", [
211
+ "success",
212
+ "failed",
213
+ "skipped"
214
+ ]);
215
+ var syncSubscriptions = pgTable2(
216
+ "sync_subscriptions",
217
+ {
218
+ id: uuid2("id").primaryKey().defaultRandom(),
219
+ integrationId: text2("integration_id").notNull(),
220
+ adapter: text2("adapter").notNull(),
221
+ domain: text2("domain").notNull(),
222
+ externalRef: text2("external_ref"),
223
+ enabled: boolean("enabled").notNull().default(true),
224
+ /**
225
+ * Per-subscription configuration bag. Strategies type it internally;
226
+ * e.g. polling strategies stash `{ batchSize, highWatermark }` here.
227
+ */
228
+ config: jsonb2("config").notNull().default({}).$type(),
229
+ /**
230
+ * Opaque cursor persisted by `ICursorStore.put()`. NULL until the first
231
+ * successful run advances it.
232
+ */
233
+ cursor: jsonb2("cursor").$type(),
234
+ lastSyncAt: timestamp2("last_sync_at", { withTimezone: true }),
235
+ /** Runtime-enforced when `SYNC_MULTI_TENANT` is true; see SYNC-6. */
236
+ tenantId: text2("tenant_id"),
237
+ createdAt: timestamp2("created_at", { withTimezone: true }).notNull().defaultNow(),
238
+ updatedAt: timestamp2("updated_at", { withTimezone: true }).notNull().defaultNow()
239
+ },
240
+ (t) => ({
241
+ /**
242
+ * Composite uniqueness per the epic shape. `external_ref` is nullable;
243
+ * Postgres treats NULLs as distinct in a UNIQUE constraint, which means
244
+ * two rows with the same `(integration_id, adapter, domain)` and NULL
245
+ * external_ref are allowed. That's intentional — a subscription with
246
+ * NULL external_ref covers the full domain, and duplicates there would
247
+ * be a consumer-layer modeling issue, not a schema concern.
248
+ */
249
+ uqSyncSubscriptionTuple: uniqueIndex2("uq_sync_subscriptions_tuple").on(
250
+ t.integrationId,
251
+ t.adapter,
252
+ t.domain,
253
+ t.externalRef
254
+ ),
255
+ /** Scheduling query: list enabled subscriptions ordered by staleness. */
256
+ idxSyncSubscriptionsEnabledLastSync: index2(
257
+ "idx_sync_subscriptions_enabled_last_sync"
258
+ ).on(t.enabled, t.lastSyncAt)
259
+ })
260
+ );
261
+ var syncRuns = pgTable2(
262
+ "sync_runs",
263
+ {
264
+ id: uuid2("id").primaryKey().defaultRandom(),
265
+ subscriptionId: uuid2("subscription_id").notNull().references(() => syncSubscriptions.id, { onDelete: "cascade" }),
266
+ direction: syncRunDirectionEnum("direction").notNull(),
267
+ action: syncRunActionEnum("action").notNull(),
268
+ status: syncRunStatusEnum("status").notNull().default("running"),
269
+ recordsFound: integer2("records_found").notNull().default(0),
270
+ recordsProcessed: integer2("records_processed").notNull().default(0),
271
+ cursorBefore: jsonb2("cursor_before").$type(),
272
+ cursorAfter: jsonb2("cursor_after").$type(),
273
+ durationMs: integer2("duration_ms"),
274
+ error: text2("error"),
275
+ startedAt: timestamp2("started_at", { withTimezone: true }).notNull().defaultNow(),
276
+ completedAt: timestamp2("completed_at", { withTimezone: true }),
277
+ /** Runtime-enforced when `SYNC_MULTI_TENANT` is true; see SYNC-6. */
278
+ tenantId: text2("tenant_id")
279
+ },
280
+ (t) => ({
281
+ /** Timeline read: "most recent runs for this subscription". */
282
+ idxSyncRunsSubscriptionStartedAt: index2(
283
+ "idx_sync_runs_subscription_started_at"
284
+ ).on(t.subscriptionId, t.startedAt),
285
+ /** Stale-run sweeper: "runs that started > N minutes ago and are still running". */
286
+ idxSyncRunsStatusStartedAt: index2("idx_sync_runs_status_started_at").on(
287
+ t.status,
288
+ t.startedAt
289
+ )
290
+ })
291
+ );
292
+ var syncRunItems = pgTable2(
293
+ "sync_run_items",
294
+ {
295
+ id: uuid2("id").primaryKey().defaultRandom(),
296
+ syncRunId: uuid2("sync_run_id").notNull().references(() => syncRuns.id, { onDelete: "cascade" }),
297
+ entityType: text2("entity_type").notNull(),
298
+ externalId: text2("external_id").notNull(),
299
+ localId: text2("local_id"),
300
+ operation: syncRunItemOperationEnum("operation").notNull(),
301
+ status: syncRunItemStatusEnum("status").notNull(),
302
+ /**
303
+ * Structured per-field diff — ADR-0003 shape enforced by
304
+ * `FieldDiffSchema.parse` at the recorder service layer.
305
+ *
306
+ * Shape: `{ [fieldName]: { from: unknown, to: unknown } }`.
307
+ * Empty `{}` for `noop` items; `{ [field]: { from: null, to: <value> } }`
308
+ * for created items; `{ [field]: { from: <value>, to: null } }` for
309
+ * deleted items.
310
+ */
311
+ changedFields: jsonb2("changed_fields").notNull().default({}).$type(),
312
+ title: text2("title"),
313
+ error: text2("error"),
314
+ createdAt: timestamp2("created_at", { withTimezone: true }).notNull().defaultNow(),
315
+ /** Runtime-enforced when `SYNC_MULTI_TENANT` is true; see SYNC-6. */
316
+ tenantId: text2("tenant_id")
317
+ },
318
+ (t) => ({
319
+ /** Ordered timeline within a run. */
320
+ idxSyncRunItemsRunCreatedAt: index2("idx_sync_run_items_run_created_at").on(
321
+ t.syncRunId,
322
+ t.createdAt
323
+ ),
324
+ /** Per-record history: "every sync that touched opportunity/$extId". */
325
+ idxSyncRunItemsEntityExternal: index2(
326
+ "idx_sync_run_items_entity_external"
327
+ ).on(t.entityType, t.externalId)
328
+ })
329
+ );
330
+
331
+ // runtime/subsystems/observability/observability.drizzle-backend.ts
332
+ var DrizzleObservabilityService = class {
333
+ constructor(db) {
334
+ this.db = db;
335
+ }
336
+ db;
337
+ async getPoolDepths() {
338
+ const result = await this.db.execute(sql2`
339
+ SELECT
340
+ pool AS name,
341
+ COUNT(*) FILTER (WHERE status = 'pending')::int AS pending,
342
+ COUNT(*) FILTER (WHERE status = 'running')::int AS running,
343
+ (percentile_cont(0.95) WITHIN GROUP (
344
+ ORDER BY EXTRACT(EPOCH FROM (now() - claimed_at)) * 1000
345
+ ) FILTER (WHERE status = 'running' AND claimed_at IS NOT NULL))::int
346
+ AS claimed_age_p95_ms
347
+ FROM job_run
348
+ WHERE status IN ('pending','running')
349
+ GROUP BY pool
350
+ ORDER BY pool
351
+ `);
352
+ const rows = extractRows(result);
353
+ return rows.map((r) => ({
354
+ name: r.name,
355
+ pending: r.pending,
356
+ running: r.running,
357
+ claimedAgeP95Ms: r.claimed_age_p95_ms
358
+ }));
359
+ }
360
+ async getRecentSyncRuns(limit, integrationId) {
361
+ const base = this.db.select({
362
+ id: syncRuns.id,
363
+ subscriptionId: syncRuns.subscriptionId,
364
+ integrationId: syncSubscriptions.integrationId,
365
+ adapter: syncSubscriptions.adapter,
366
+ domain: syncSubscriptions.domain,
367
+ direction: syncRuns.direction,
368
+ action: syncRuns.action,
369
+ status: syncRuns.status,
370
+ recordsFound: syncRuns.recordsFound,
371
+ recordsProcessed: syncRuns.recordsProcessed,
372
+ durationMs: syncRuns.durationMs,
373
+ error: syncRuns.error,
374
+ startedAt: syncRuns.startedAt,
375
+ completedAt: syncRuns.completedAt
376
+ }).from(syncRuns).innerJoin(
377
+ syncSubscriptions,
378
+ eq(syncRuns.subscriptionId, syncSubscriptions.id)
379
+ );
380
+ const filtered = integrationId !== void 0 ? base.where(eq(syncSubscriptions.integrationId, integrationId)) : base;
381
+ const rows = await filtered.orderBy(desc(syncRuns.startedAt)).limit(limit);
382
+ return rows.map((r) => ({
383
+ id: r.id,
384
+ subscriptionId: r.subscriptionId,
385
+ integrationId: r.integrationId,
386
+ adapter: r.adapter,
387
+ domain: r.domain,
388
+ direction: r.direction,
389
+ action: r.action,
390
+ status: r.status,
391
+ recordsFound: r.recordsFound,
392
+ recordsProcessed: r.recordsProcessed,
393
+ durationMs: r.durationMs,
394
+ error: r.error,
395
+ startedAt: r.startedAt,
396
+ completedAt: r.completedAt
397
+ }));
398
+ }
399
+ async getBridgeDeliveryHistogram(windowHours) {
400
+ const result = await this.db.execute(sql2`
401
+ SELECT status, COUNT(*)::int AS count
402
+ FROM bridge_delivery
403
+ WHERE COALESCE(delivered_at, attempted_at) > now() - make_interval(hours => ${windowHours})
404
+ GROUP BY status
405
+ `);
406
+ const rows = extractRows(result);
407
+ const hist = {};
408
+ for (const r of rows) hist[r.status] = r.count;
409
+ return hist;
410
+ }
411
+ async getRecentFailedJobs(limit) {
412
+ const rows = await this.db.select({
413
+ id: jobRuns.id,
414
+ jobType: jobRuns.jobType,
415
+ pool: jobRuns.pool,
416
+ status: jobRuns.status,
417
+ error: jobRuns.error,
418
+ startedAt: jobRuns.startedAt,
419
+ finishedAt: jobRuns.finishedAt,
420
+ attempts: jobRuns.attempts
421
+ }).from(jobRuns).where(eq(jobRuns.status, "failed")).orderBy(desc(jobRuns.finishedAt)).limit(limit);
422
+ return rows.map((r) => ({
423
+ id: r.id,
424
+ jobType: r.jobType,
425
+ pool: r.pool,
426
+ status: r.status,
427
+ error: r.error,
428
+ startedAt: r.startedAt,
429
+ finishedAt: r.finishedAt,
430
+ attempts: r.attempts
431
+ }));
432
+ }
433
+ async getCursors() {
434
+ const rows = await this.db.select({
435
+ id: syncSubscriptions.id,
436
+ integrationId: syncSubscriptions.integrationId,
437
+ adapter: syncSubscriptions.adapter,
438
+ domain: syncSubscriptions.domain,
439
+ cursor: syncSubscriptions.cursor,
440
+ lastSyncAt: syncSubscriptions.lastSyncAt
441
+ }).from(syncSubscriptions).where(eq(syncSubscriptions.enabled, true)).orderBy(syncSubscriptions.integrationId, syncSubscriptions.domain);
442
+ return rows.map((r) => ({
443
+ subscriptionId: r.id,
444
+ integrationId: r.integrationId,
445
+ adapter: r.adapter,
446
+ domain: r.domain,
447
+ lastCursor: r.cursor,
448
+ lastSyncAt: r.lastSyncAt
449
+ }));
450
+ }
451
+ };
452
+ DrizzleObservabilityService = __decorateClass([
453
+ Injectable(),
454
+ __decorateParam(0, Inject(DRIZZLE))
455
+ ], DrizzleObservabilityService);
456
+ function extractRows(result) {
457
+ const maybe = result;
458
+ if (Array.isArray(maybe.rows)) return maybe.rows;
459
+ if (Array.isArray(result)) return result;
460
+ return [];
461
+ }
462
+ export {
463
+ DrizzleObservabilityService
464
+ };
465
+ //# sourceMappingURL=observability.drizzle-backend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../runtime/subsystems/observability/observability.drizzle-backend.ts","../../../../runtime/constants/tokens.ts","../../../../runtime/subsystems/jobs/job-orchestration.schema.ts","../../../../runtime/subsystems/sync/sync-audit.schema.ts"],"sourcesContent":["/**\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 * 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 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"],"mappings":"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","names":["sql","pgEnum","pgTable","uuid","text","jsonb","integer","timestamp","index","uniqueIndex","sql"]}
@@ -0,0 +1,28 @@
1
+ import { IObservabilityService, PoolDepth, SyncRunSummary, StatusHistogram, JobRunFailure, CursorSnapshot } from './observability.protocol.js';
2
+
3
+ declare class MemoryObservabilityService implements IObservabilityService {
4
+ private pools;
5
+ private syncRuns;
6
+ private bridgeHistogram;
7
+ private failedJobs;
8
+ private cursors;
9
+ getPoolDepths(): Promise<PoolDepth[]>;
10
+ getRecentSyncRuns(limit: number, integrationId?: string): Promise<SyncRunSummary[]>;
11
+ getBridgeDeliveryHistogram(_windowHours: number): Promise<StatusHistogram>;
12
+ getRecentFailedJobs(limit: number): Promise<JobRunFailure[]>;
13
+ getCursors(): Promise<CursorSnapshot[]>;
14
+ /** Replace the pool-depth slice. */
15
+ seedPools(pools: PoolDepth[]): void;
16
+ /** Replace the sync-run slice. */
17
+ seedSyncRuns(runs: SyncRunSummary[]): void;
18
+ /** Replace the bridge-delivery histogram. */
19
+ seedBridgeHistogram(hist: StatusHistogram): void;
20
+ /** Replace the failed-jobs slice. */
21
+ seedFailedJobs(jobs: JobRunFailure[]): void;
22
+ /** Replace the cursor slice. */
23
+ seedCursors(cursors: CursorSnapshot[]): void;
24
+ /** Reset every slice — for afterEach hooks. */
25
+ reset(): void;
26
+ }
27
+
28
+ export { MemoryObservabilityService };
@@ -0,0 +1,75 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __decorateClass = (decorators, target, key, kind) => {
4
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
5
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
6
+ if (decorator = decorators[i])
7
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
8
+ if (kind && result) __defProp(target, key, result);
9
+ return result;
10
+ };
11
+
12
+ // runtime/subsystems/observability/observability.memory-backend.ts
13
+ import { Injectable } from "@nestjs/common";
14
+ var MemoryObservabilityService = class {
15
+ pools = [];
16
+ syncRuns = [];
17
+ bridgeHistogram = {};
18
+ failedJobs = [];
19
+ cursors = [];
20
+ // ─── Core contract ─────────────────────────────────────────────────────
21
+ async getPoolDepths() {
22
+ return [...this.pools];
23
+ }
24
+ async getRecentSyncRuns(limit, integrationId) {
25
+ const filtered = integrationId !== void 0 ? this.syncRuns.filter((r) => r.integrationId === integrationId) : this.syncRuns;
26
+ return filtered.slice().sort((a, b) => b.startedAt.getTime() - a.startedAt.getTime()).slice(0, limit);
27
+ }
28
+ async getBridgeDeliveryHistogram(_windowHours) {
29
+ return { ...this.bridgeHistogram };
30
+ }
31
+ async getRecentFailedJobs(limit) {
32
+ return this.failedJobs.slice().sort(
33
+ (a, b) => (b.finishedAt?.getTime() ?? 0) - (a.finishedAt?.getTime() ?? 0)
34
+ ).slice(0, limit);
35
+ }
36
+ async getCursors() {
37
+ return [...this.cursors];
38
+ }
39
+ // ─── Test seams ────────────────────────────────────────────────────────
40
+ /** Replace the pool-depth slice. */
41
+ seedPools(pools) {
42
+ this.pools = [...pools];
43
+ }
44
+ /** Replace the sync-run slice. */
45
+ seedSyncRuns(runs) {
46
+ this.syncRuns = [...runs];
47
+ }
48
+ /** Replace the bridge-delivery histogram. */
49
+ seedBridgeHistogram(hist) {
50
+ this.bridgeHistogram = { ...hist };
51
+ }
52
+ /** Replace the failed-jobs slice. */
53
+ seedFailedJobs(jobs) {
54
+ this.failedJobs = [...jobs];
55
+ }
56
+ /** Replace the cursor slice. */
57
+ seedCursors(cursors) {
58
+ this.cursors = [...cursors];
59
+ }
60
+ /** Reset every slice — for afterEach hooks. */
61
+ reset() {
62
+ this.pools = [];
63
+ this.syncRuns = [];
64
+ this.bridgeHistogram = {};
65
+ this.failedJobs = [];
66
+ this.cursors = [];
67
+ }
68
+ };
69
+ MemoryObservabilityService = __decorateClass([
70
+ Injectable()
71
+ ], MemoryObservabilityService);
72
+ export {
73
+ MemoryObservabilityService
74
+ };
75
+ //# sourceMappingURL=observability.memory-backend.js.map