deepline 0.3.120 → 0.3.122

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.
@@ -200,7 +200,7 @@ export const SDK_RELEASE = {
200
200
  // getters keep their established compatibility behavior.
201
201
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
202
202
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
203
- version: '0.3.120',
203
+ version: '0.3.122',
204
204
  updateSummary:
205
205
  'Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.',
206
206
  packageCapabilities: {
@@ -0,0 +1,77 @@
1
+ import { parseStoredMonitorBillingState } from './state';
2
+
3
+ /**
4
+ * Only these event-time billing denials are healed by a restored Deepline
5
+ * balance. A monthly spending cap and an opaque billing failure deliberately
6
+ * remain explicit-recovery states.
7
+ */
8
+ export function isCreditExhaustionSuspensionReason(
9
+ reason: string | undefined,
10
+ ): boolean {
11
+ return (
12
+ reason === 'insufficient_credits' ||
13
+ reason === 'insufficient_credits_after_provider_event'
14
+ );
15
+ }
16
+
17
+ /**
18
+ * A billing suspension is not itself proof that TamRadar accepted the delete.
19
+ * Keep this test beside the recovery selector so every automatic path rejects
20
+ * an unfinished cleanup, including a retired binding that still needs work.
21
+ */
22
+ export function hasConfirmedProviderMonitorUpstreamCleanup(
23
+ providerBinding: unknown,
24
+ ): boolean {
25
+ if (!providerBinding || typeof providerBinding !== 'object') return false;
26
+ const binding = providerBinding as Record<string, unknown>;
27
+ const suspension = binding.billing_suspension;
28
+ if (!suspension || typeof suspension !== 'object') return false;
29
+ const upstreamDisabledAt = (suspension as Record<string, unknown>)
30
+ .upstream_disabled_at;
31
+ if (typeof upstreamDisabledAt !== 'string' || !upstreamDisabledAt.length) {
32
+ return false;
33
+ }
34
+ const retiredBindings = binding.retired_bindings;
35
+ if (!Array.isArray(retiredBindings) || retiredBindings.length === 0) {
36
+ return true;
37
+ }
38
+ const cleanup = binding.cleanup;
39
+ const cleanupStatus =
40
+ cleanup && typeof cleanup === 'object'
41
+ ? (cleanup as Record<string, unknown>).status
42
+ : null;
43
+ return cleanupStatus === 'old_cleanup_deleted';
44
+ }
45
+
46
+ /**
47
+ * Narrow selector for direct event-time suspensions that credit restoration
48
+ * may re-provision. It intentionally does not include policy-only pauses,
49
+ * manual intent, spending-cap denials, or cleanup still in flight.
50
+ */
51
+ export function isCreditRestorableDirectMonitorSuspension(input: {
52
+ fleetId?: string;
53
+ status: string;
54
+ desired?: 'active' | 'inactive';
55
+ deactivationReason?: string;
56
+ billingState: unknown;
57
+ providerBinding: unknown;
58
+ }): boolean {
59
+ if (input.fleetId !== undefined || input.status !== 'disabled') return false;
60
+ const billingState = parseStoredMonitorBillingState(input.billingState);
61
+ if (
62
+ billingState.state !== 'billing_suspended' ||
63
+ !billingState.reactivation_required ||
64
+ !isCreditExhaustionSuspensionReason(billingState.suspension_reason) ||
65
+ !hasConfirmedProviderMonitorUpstreamCleanup(input.providerBinding)
66
+ ) {
67
+ return false;
68
+ }
69
+ // An inactive desired state with no policy provenance is the customer's
70
+ // current choice. Historical policy rows can be identified exactly by the
71
+ // closed control-plane reason; an already-active desired state is a prior
72
+ // restoration intent that still requires the normal reactivation lifecycle.
73
+ return (
74
+ input.desired !== 'inactive' ||
75
+ input.deactivationReason === 'insufficient_balance'
76
+ );
77
+ }
@@ -11,6 +11,8 @@
11
11
  "./analytics-public.js": "./analytics-public.ts",
12
12
  "./billing/charges": "./billing/charges.ts",
13
13
  "./billing/charges.js": "./billing/charges.ts",
14
+ "./billing/credit-recovery": "./billing/credit-recovery.ts",
15
+ "./billing/credit-recovery.js": "./billing/credit-recovery.ts",
14
16
  "./billing/policy-builders": "./billing/policy-builders.ts",
15
17
  "./billing/policy-builders.js": "./billing/policy-builders.ts",
16
18
  "./billing/pricing": "./billing/pricing.ts",
@@ -65,6 +65,11 @@ export type ProviderMonitorBatchGlobalAuditScope = {
65
65
  monitorKey?: string;
66
66
  };
67
67
 
68
+ /** Internal-only scope for credit-restoration reactivation. */
69
+ export type ProviderMonitorBatchCreditRecoveryScope = {
70
+ kind: 'credit_recovery';
71
+ };
72
+
68
73
  export type ProviderMonitorBatchScope =
69
74
  | { kind: 'keys'; monitorKeys: readonly string[] }
70
75
  | { kind: 'target'; target: ProviderMonitorBatchTarget }
@@ -76,7 +81,8 @@ export type ProviderMonitorBatchScope =
76
81
  generation: number;
77
82
  intent: 'sync' | 'start' | 'stop';
78
83
  }
79
- | ProviderMonitorBatchGlobalAuditScope;
84
+ | ProviderMonitorBatchGlobalAuditScope
85
+ | ProviderMonitorBatchCreditRecoveryScope;
80
86
 
81
87
  export type ProviderMonitorBatchOperationRequest = {
82
88
  kind: ProviderMonitorBatchOperationKind;
@@ -389,6 +395,18 @@ export function validateProviderMonitorBatchOperationRequest(
389
395
  ...(callbackBaseUrl === undefined ? {} : { callbackBaseUrl }),
390
396
  };
391
397
  }
398
+ if (scope.kind === 'credit_recovery') {
399
+ if (request.kind !== 'reactivate') {
400
+ throw new Error(
401
+ 'A credit-recovery scope is only valid for reactivation.',
402
+ );
403
+ }
404
+ return {
405
+ ...request,
406
+ scope: { kind: 'credit_recovery' },
407
+ ...(callbackBaseUrl === undefined ? {} : { callbackBaseUrl }),
408
+ };
409
+ }
392
410
  throw new Error(
393
411
  'A monitor batch operation requires exactly one supported scope.',
394
412
  );
@@ -177,6 +177,345 @@ export type DlqApplyResult = {
177
177
  readonly verificationRequired: boolean;
178
178
  };
179
179
 
180
+ /**
181
+ * Queue-item control is deliberately a separate port from an inventory. A
182
+ * count and a bounded index description are not permission to manufacture a
183
+ * row scan: owners register this reader only when they can return exact item
184
+ * evidence without widening their data-access boundary.
185
+ */
186
+ export const QUEUE_ITEM_CONTROL_CONTRACT_VERSION = 1 as const;
187
+
188
+ /** The common state vocabulary exposed by every queue connector. */
189
+ export const QUEUE_ITEM_STATES = [
190
+ 'ready',
191
+ 'delayed',
192
+ 'running',
193
+ 'held',
194
+ 'quarantined',
195
+ 'archived',
196
+ 'completed',
197
+ 'cancelled',
198
+ 'unknown',
199
+ ] as const;
200
+
201
+ export type QueueItemState = (typeof QUEUE_ITEM_STATES)[number];
202
+
203
+ export type QueueItemAction =
204
+ | 'archive'
205
+ | 'quarantine'
206
+ | 'retry'
207
+ | 'hold'
208
+ | 'resume'
209
+ | 'cancel'
210
+ | 'update';
211
+
212
+ export const QUEUE_ITEM_ACTIONS = [
213
+ 'archive',
214
+ 'quarantine',
215
+ 'retry',
216
+ 'hold',
217
+ 'resume',
218
+ 'cancel',
219
+ 'update',
220
+ ] as const satisfies readonly QueueItemAction[];
221
+
222
+ export type QueueItemActionAvailability = {
223
+ readonly action: QueueItemAction;
224
+ readonly allowed: boolean;
225
+ readonly reason?: string;
226
+ };
227
+
228
+ export type QueueItemFieldType =
229
+ | 'string'
230
+ | 'number'
231
+ | 'boolean'
232
+ | 'datetime'
233
+ | 'json';
234
+
235
+ export type QueueItemFieldDefinition = {
236
+ readonly name: string;
237
+ readonly type: QueueItemFieldType;
238
+ readonly required: boolean;
239
+ readonly mutable: boolean;
240
+ readonly description?: string;
241
+ };
242
+
243
+ export type QueueItemLegacyAction = Exclude<DlqPathAction, 'inspect'>;
244
+ export type QueueItemActionInput = QueueItemAction | QueueItemLegacyAction;
245
+
246
+ export type QueueItemLastError = {
247
+ readonly code: string;
248
+ readonly message: string;
249
+ };
250
+
251
+ /** Names used in the API documentation and in connector implementations. */
252
+ export type ActionAvailability = QueueItemActionAvailability;
253
+ export type FieldDefinition = QueueItemFieldDefinition;
254
+
255
+ /** A normalized item returned by an owner connector. `nativeState` is always
256
+ * retained so an operator can distinguish a connector mapping from the
257
+ * backend's actual state. */
258
+ export type QueueItem = {
259
+ readonly queueId: string;
260
+ readonly id: string;
261
+ /** Opaque source revision used for compare-and-set mutation. */
262
+ readonly revision: string;
263
+ readonly observedAt: string;
264
+ readonly state: QueueItemState;
265
+ readonly nativeState: string;
266
+ readonly lastError?: QueueItemLastError;
267
+ readonly actions: readonly ActionAvailability[];
268
+ readonly editableFields: readonly FieldDefinition[];
269
+ readonly createdAt?: string;
270
+ readonly updatedAt?: string;
271
+ readonly summary?: string;
272
+ };
273
+
274
+ /** Keep old DLQ verbs working at the boundary while exposing a smaller,
275
+ * operator-readable control vocabulary. */
276
+ export function normalizeQueueItemAction(
277
+ action: QueueItemActionInput,
278
+ ): QueueItemAction {
279
+ const aliases: Readonly<Record<QueueItemLegacyAction, QueueItemAction>> = {
280
+ redrive: 'retry',
281
+ discard: 'archive',
282
+ mark_done: 'archive',
283
+ handoff: 'resume',
284
+ recover: 'retry',
285
+ reconcile: 'update',
286
+ replay: 'retry',
287
+ retry: 'retry',
288
+ quarantine: 'quarantine',
289
+ release: 'cancel',
290
+ settle: 'resume',
291
+ };
292
+ return action in aliases
293
+ ? aliases[action as QueueItemLegacyAction]
294
+ : (action as QueueItemAction);
295
+ }
296
+
297
+ /**
298
+ * Legacy bounded evidence kept for callers that only consume the original
299
+ * `id/state/revision` projection. New connectors must return `QueueItem`.
300
+ */
301
+ export type QueueItemEvidence = {
302
+ readonly id: string;
303
+ readonly state: string;
304
+ readonly revision: string;
305
+ /** Additive fields understood by newer readers; old producers may omit them. */
306
+ readonly nativeState?: string;
307
+ readonly lastError?: QueueItemLastError;
308
+ readonly actions?: readonly QueueItemActionAvailability[];
309
+ readonly editableFields?: readonly QueueItemFieldDefinition[];
310
+ readonly createdAt?: number;
311
+ readonly updatedAt?: number;
312
+ readonly summary?: string;
313
+ };
314
+
315
+ export type QueueItemUnavailable = {
316
+ readonly code: 'item_reader_unavailable';
317
+ readonly reason: string;
318
+ };
319
+
320
+ export type QueueItemList = {
321
+ readonly contractVersion: typeof QUEUE_ITEM_CONTROL_CONTRACT_VERSION;
322
+ readonly queueId: string;
323
+ readonly environment: string;
324
+ readonly observedAt: number;
325
+ readonly items: readonly QueueItem[];
326
+ readonly nextCursor: string | null;
327
+ readonly limit: number;
328
+ readonly cursor: string | null;
329
+ readonly unavailable?: QueueItemUnavailable;
330
+ };
331
+
332
+ export type QueueItemLookup =
333
+ | { readonly kind: 'found'; readonly item: QueueItem }
334
+ | { readonly kind: 'not_found' }
335
+ | {
336
+ readonly kind: 'unavailable';
337
+ readonly unavailable: QueueItemUnavailable;
338
+ };
339
+
340
+ export type QueueItemActionRequest = {
341
+ readonly action: QueueItemAction;
342
+ readonly sourceAction?: QueueItemActionInput;
343
+ readonly idempotencyKey: string;
344
+ readonly expectedRevision: string;
345
+ readonly reason: string;
346
+ /** Connector-declared mutable fields for the `update` action only. */
347
+ readonly fields?: Readonly<Record<string, unknown>>;
348
+ /** Authenticated platform-admin identity forwarded to the owner connector. */
349
+ readonly actorId?: string;
350
+ };
351
+
352
+ export type QueueItemActionOperation = {
353
+ readonly contractVersion: typeof QUEUE_ITEM_CONTROL_CONTRACT_VERSION;
354
+ readonly operationId: string;
355
+ readonly queueId: string;
356
+ readonly itemId: string;
357
+ readonly environment: string;
358
+ readonly action: QueueItemAction;
359
+ readonly idempotencyKey: string;
360
+ readonly expectedRevision: string;
361
+ /** `pending` means the owner accepted the request but has not yet verified
362
+ * the native transition. `confirmed` is the only successful terminal
363
+ * status. The rejected/stale statuses preserve the existing safe boundary. */
364
+ readonly status: 'pending' | 'confirmed' | 'rejected' | 'stale' | 'failed';
365
+ readonly requestedAt?: string;
366
+ readonly confirmedAt?: string;
367
+ readonly replacementItemId?: string;
368
+ readonly item?: QueueItem;
369
+ readonly error?: QueueItemLastError;
370
+ readonly audit: {
371
+ readonly recorded: boolean;
372
+ readonly reason: string;
373
+ };
374
+ /** Existing owner command, when the catalog has one. This is guidance only;
375
+ * callers must still invoke its own authenticated, audited surface. */
376
+ readonly ownerCommand?: string;
377
+ };
378
+
379
+ export type QueueItemActionStatus =
380
+ | 'pending'
381
+ | 'confirmed'
382
+ | 'accepted'
383
+ | 'rejected'
384
+ | 'stale'
385
+ | 'failed'
386
+ | 'not_found'
387
+ | 'unavailable';
388
+
389
+ /**
390
+ * Connector result for a requested transition. `confirmed` means the owner
391
+ * verified the resulting native state; `pending` means the owner durably
392
+ * accepted it but has not yet confirmed that state. `accepted` remains as a
393
+ * compatibility spelling for older connectors and must not be treated as
394
+ * confirmed by the shared control service.
395
+ */
396
+ export type QueueItemActionResult = {
397
+ readonly contractVersion: typeof QUEUE_ITEM_CONTROL_CONTRACT_VERSION;
398
+ readonly operationId: string;
399
+ readonly queueId: string;
400
+ readonly itemId: string;
401
+ readonly environment: string;
402
+ readonly action: QueueItemAction;
403
+ readonly idempotencyKey: string;
404
+ readonly expectedRevision: string;
405
+ readonly status: QueueItemActionStatus;
406
+ readonly item?: QueueItem;
407
+ readonly replacementItemId?: string;
408
+ readonly reason?: string;
409
+ readonly audit: {
410
+ readonly recorded: boolean;
411
+ readonly auditId?: string;
412
+ };
413
+ };
414
+
415
+ export type QueueItemConnectorContext = {
416
+ readonly queueId: string;
417
+ readonly environment: string;
418
+ readonly now: number;
419
+ readonly limit: number;
420
+ readonly cursor?: string;
421
+ readonly actorId?: string;
422
+ readonly operationId?: string;
423
+ };
424
+
425
+ /**
426
+ * Owner implementation port. The connector owns native reads, state mapping,
427
+ * lease/CAS checks, and outcome verification. The shared control service only
428
+ * authenticates, validates, journals idempotency, and returns this result.
429
+ */
430
+ export type QueueItemConnector = {
431
+ readonly id: string;
432
+ readonly queueId: string;
433
+ readonly source: string;
434
+ readonly mapNativeState: (nativeState: string) => QueueItemState;
435
+ readonly list: (context: QueueItemConnectorContext) => Promise<QueueItemList>;
436
+ readonly get: (
437
+ context: QueueItemConnectorContext,
438
+ itemId: string,
439
+ ) => Promise<QueueItemLookup>;
440
+ readonly requestAction: (
441
+ context: QueueItemConnectorContext,
442
+ itemId: string,
443
+ request: QueueItemActionRequest,
444
+ ) => Promise<QueueItemActionOperation>;
445
+ };
446
+
447
+ /** Conservative shared fallback for an unrecognised native state. */
448
+ export function normalizeQueueItemState(nativeState: string): QueueItemState {
449
+ const state = nativeState.trim().toLowerCase();
450
+ if ((QUEUE_ITEM_STATES as readonly string[]).includes(state)) {
451
+ return state as QueueItemState;
452
+ }
453
+ if (
454
+ state === 'in_flight' ||
455
+ state === 'in-flight' ||
456
+ state === 'processing'
457
+ ) {
458
+ return 'running';
459
+ }
460
+ if (
461
+ state === 'dead_lettered' ||
462
+ state === 'dead-lettered' ||
463
+ state === 'dlq'
464
+ ) {
465
+ return 'quarantined';
466
+ }
467
+ if (state === 'done' || state === 'succeeded' || state === 'success') {
468
+ return 'completed';
469
+ }
470
+ if (state === 'canceled' || state === 'aborted') return 'cancelled';
471
+ return 'unknown';
472
+ }
473
+
474
+ export function unavailableQueueItemList(input: {
475
+ queueId: string;
476
+ environment: string;
477
+ observedAt: number;
478
+ reason: string;
479
+ limit?: number;
480
+ cursor?: string | null;
481
+ }): QueueItemList {
482
+ return {
483
+ contractVersion: QUEUE_ITEM_CONTROL_CONTRACT_VERSION,
484
+ queueId: input.queueId,
485
+ environment: input.environment,
486
+ observedAt: input.observedAt,
487
+ items: [],
488
+ nextCursor: null,
489
+ limit: input.limit ?? 50,
490
+ cursor: input.cursor ?? null,
491
+ unavailable: { code: 'item_reader_unavailable', reason: input.reason },
492
+ };
493
+ }
494
+
495
+ /** A deterministic request identity is safe to show before an owner has a
496
+ * durable operation store. It is explicitly not represented as an audit. */
497
+ export function queueItemOperationId(input: {
498
+ queueId: string;
499
+ itemId: string;
500
+ action: QueueItemAction;
501
+ idempotencyKey: string;
502
+ }): string {
503
+ const value = [
504
+ input.queueId,
505
+ input.itemId,
506
+ input.action,
507
+ input.idempotencyKey,
508
+ ]
509
+ .map((part) => part.trim())
510
+ .join(':');
511
+ let hash = 2166136261;
512
+ for (let index = 0; index < value.length; index += 1) {
513
+ hash ^= value.charCodeAt(index);
514
+ hash = Math.imul(hash, 16777619);
515
+ }
516
+ return `queue-item-${(hash >>> 0).toString(36)}`;
517
+ }
518
+
180
519
  /** Every backend—including Absurd—must implement this port at its boundary. */
181
520
  export type DlqAdapter = {
182
521
  readonly id: string;
@@ -0,0 +1,314 @@
1
+ import {
2
+ normalizeQueueItemState,
3
+ queueItemOperationId,
4
+ unavailableQueueItemList,
5
+ type QueueItemActionOperation,
6
+ type QueueItemActionRequest,
7
+ type QueueItemConnector,
8
+ type QueueItemConnectorContext,
9
+ type QueueItemList,
10
+ type QueueItemLookup,
11
+ type QueueItemState,
12
+ } from './dlq';
13
+ import { OPERATIONS_QUEUE_CATALOG } from './queue-health';
14
+
15
+ /** Native source families supported by the shared control-plane port. */
16
+ export type QueueConnectorBackend =
17
+ | 'convex'
18
+ | 'workpool'
19
+ | 'postgres-outbox'
20
+ | 'absurd'
21
+ | 'provider';
22
+
23
+ export type QueueItemConnectorRegistration = {
24
+ readonly backend: QueueConnectorBackend;
25
+ readonly connector: QueueItemConnector;
26
+ };
27
+
28
+ export type QueueItemConnectorRegistryOptions = {
29
+ /** Registration is intentionally injectable for tests and owner wiring. */
30
+ readonly registrations?: readonly QueueItemConnectorRegistration[];
31
+ };
32
+
33
+ export type QueueItemConnectorRegisterOptions = {
34
+ /** Only an unavailable placeholder may be replaced at startup. */
35
+ readonly replaceUnavailable?: boolean;
36
+ };
37
+
38
+ /**
39
+ * The registry is the routing boundary: the control service looks up a queue
40
+ * by its stable public id and never switches on a native table or provider.
41
+ * Duplicate registrations are rejected so a new owner cannot silently shadow
42
+ * an existing mutation implementation.
43
+ */
44
+ export class QueueItemConnectorRegistry {
45
+ private readonly registrations = new Map<
46
+ string,
47
+ QueueItemConnectorRegistration
48
+ >();
49
+
50
+ constructor(options: QueueItemConnectorRegistryOptions = {}) {
51
+ for (const registration of options.registrations ?? []) {
52
+ this.register(registration);
53
+ }
54
+ }
55
+
56
+ register(
57
+ registration: QueueItemConnectorRegistration,
58
+ options: QueueItemConnectorRegisterOptions = {},
59
+ ): this {
60
+ const { backend, connector } = registration;
61
+ if (!backend) throw new Error('Queue connector backend is required.');
62
+ if (!connector.queueId.trim())
63
+ throw new Error('Queue connector queueId is required.');
64
+ if (!connector.id.trim())
65
+ throw new Error('Queue connector id is required.');
66
+ const existing = this.registrations.get(connector.queueId);
67
+ if (existing && !options.replaceUnavailable) {
68
+ throw new Error(
69
+ `A queue connector is already registered for ${connector.queueId}.`,
70
+ );
71
+ }
72
+ if (
73
+ existing &&
74
+ options.replaceUnavailable &&
75
+ !existing.connector.id.endsWith('-unavailable')
76
+ ) {
77
+ throw new Error(
78
+ `Queue connector ${connector.queueId} is already owned by ${existing.connector.id}; replacement is not allowed.`,
79
+ );
80
+ }
81
+ this.registrations.set(connector.queueId, registration);
82
+ return this;
83
+ }
84
+
85
+ get(queueId: string): QueueItemConnectorRegistration | undefined {
86
+ return this.registrations.get(queueId);
87
+ }
88
+
89
+ has(queueId: string): boolean {
90
+ return this.registrations.has(queueId);
91
+ }
92
+
93
+ list(): readonly QueueItemConnectorRegistration[] {
94
+ return [...this.registrations.values()];
95
+ }
96
+ }
97
+
98
+ type UnavailableConnectorOptions = {
99
+ readonly queueId: string;
100
+ readonly backend: QueueConnectorBackend;
101
+ readonly source: string;
102
+ readonly id?: string;
103
+ readonly reason?: string;
104
+ };
105
+
106
+ const unavailableReason = (options: UnavailableConnectorOptions): string =>
107
+ options.reason ??
108
+ `The ${options.backend} owner has not registered a bounded item reader and fenced mutation adapter for ${options.queueId}.`;
109
+
110
+ /**
111
+ * Safe connector shell used until an owner supplies source-owned reads and
112
+ * transactions. It is deliberately observable and routable, but cannot
113
+ * claim an item exists or that a transition was applied.
114
+ */
115
+ export function createUnavailableQueueItemConnector(
116
+ options: UnavailableConnectorOptions,
117
+ ): QueueItemConnectorRegistration {
118
+ const reason = unavailableReason(options);
119
+ const connector: QueueItemConnector = {
120
+ id: options.id ?? `${options.backend}-${options.queueId}-unavailable`,
121
+ queueId: options.queueId,
122
+ source: options.source,
123
+ mapNativeState: (nativeState: string): QueueItemState =>
124
+ normalizeQueueItemState(nativeState),
125
+ async list(context: QueueItemConnectorContext): Promise<QueueItemList> {
126
+ return unavailableQueueItemList({
127
+ queueId: options.queueId,
128
+ environment: context.environment,
129
+ observedAt: context.now,
130
+ reason,
131
+ limit: context.limit,
132
+ cursor: context.cursor,
133
+ });
134
+ },
135
+ async get(
136
+ _context: QueueItemConnectorContext,
137
+ _itemId: string,
138
+ ): Promise<QueueItemLookup> {
139
+ return {
140
+ kind: 'unavailable',
141
+ unavailable: { code: 'item_reader_unavailable', reason },
142
+ };
143
+ },
144
+ async requestAction(
145
+ context: QueueItemConnectorContext,
146
+ itemId: string,
147
+ request: QueueItemActionRequest,
148
+ ): Promise<QueueItemActionOperation> {
149
+ const operationId =
150
+ context.operationId ??
151
+ queueItemOperationId({
152
+ queueId: options.queueId,
153
+ itemId,
154
+ action: request.action,
155
+ idempotencyKey: request.idempotencyKey,
156
+ });
157
+ return {
158
+ contractVersion: 1,
159
+ operationId,
160
+ queueId: options.queueId,
161
+ itemId,
162
+ environment: context.environment,
163
+ action: request.action,
164
+ idempotencyKey: request.idempotencyKey,
165
+ expectedRevision: request.expectedRevision,
166
+ status: 'rejected',
167
+ audit: { recorded: false, reason },
168
+ };
169
+ },
170
+ };
171
+ return { backend: options.backend, connector };
172
+ }
173
+
174
+ type BackendConnectorOptions = Omit<
175
+ UnavailableConnectorOptions,
176
+ 'backend' | 'source'
177
+ > & {
178
+ readonly source?: string;
179
+ readonly reason?: string;
180
+ };
181
+
182
+ export function createConvexQueueItemConnector(
183
+ options: BackendConnectorOptions,
184
+ ): QueueItemConnectorRegistration {
185
+ return createUnavailableQueueItemConnector({
186
+ ...options,
187
+ backend: 'convex',
188
+ source: options.source ?? 'convex',
189
+ });
190
+ }
191
+
192
+ export function createWorkpoolQueueItemConnector(
193
+ options: BackendConnectorOptions,
194
+ ): QueueItemConnectorRegistration {
195
+ return createUnavailableQueueItemConnector({
196
+ ...options,
197
+ backend: 'workpool',
198
+ source: options.source ?? 'convex-workpool',
199
+ });
200
+ }
201
+
202
+ export function createPostgresOutboxQueueItemConnector(
203
+ options: BackendConnectorOptions,
204
+ ): QueueItemConnectorRegistration {
205
+ return createUnavailableQueueItemConnector({
206
+ ...options,
207
+ backend: 'postgres-outbox',
208
+ source: options.source ?? 'postgres-outbox',
209
+ });
210
+ }
211
+
212
+ export function createAbsurdQueueItemConnector(
213
+ options: BackendConnectorOptions,
214
+ ): QueueItemConnectorRegistration {
215
+ return createUnavailableQueueItemConnector({
216
+ ...options,
217
+ backend: 'absurd',
218
+ source: options.source ?? 'absurd',
219
+ });
220
+ }
221
+
222
+ export function createProviderQueueItemConnector(
223
+ options: BackendConnectorOptions,
224
+ ): QueueItemConnectorRegistration {
225
+ return createUnavailableQueueItemConnector({
226
+ ...options,
227
+ backend: 'provider',
228
+ source: options.source ?? 'provider',
229
+ });
230
+ }
231
+
232
+ /** Explicit owner mapping. Unknown queues must fail registration instead of
233
+ * silently falling back to a connector with potentially wrong semantics. */
234
+ export const QUEUE_CONNECTOR_BACKENDS: Readonly<
235
+ Record<string, QueueConnectorBackend>
236
+ > = {
237
+ 'play-runtime-tasks': 'absurd',
238
+ 'run-ledger-projector-outbox': 'postgres-outbox',
239
+ 'compute-billing-jobs': 'postgres-outbox',
240
+ 'play-webhook-receipts': 'convex',
241
+ 'workflow-cron-bindings': 'convex',
242
+ 'play-cron-bindings': 'convex',
243
+ 'ingestion-jobs': 'provider',
244
+ 'monitor-fleet-convergence': 'convex',
245
+ 'monitor-deployment-jobs': 'workpool',
246
+ 'monitor-batch-operations': 'workpool',
247
+ 'provider-monitor-reconciliation': 'provider',
248
+ 'signal-radar-inbound-dlq': 'provider',
249
+ 'charge-reconciliation-backlog': 'postgres-outbox',
250
+ 'workspace-fast-settlement': 'convex',
251
+ 'metronome-usage-delivery': 'postgres-outbox',
252
+ 'billing-operations': 'postgres-outbox',
253
+ 'stripe-financial-inbox': 'provider',
254
+ 'async-usage-recovery': 'postgres-outbox',
255
+ 'notification-deliveries': 'workpool',
256
+ 'usage-rollup-fold': 'postgres-outbox',
257
+ 'play-deployment-jobs': 'workpool',
258
+ 'github-runner-wait': 'provider',
259
+ 'sandbox-cleanup-jobs': 'postgres-outbox',
260
+ };
261
+
262
+ function defaultRegistration(
263
+ queueId: string,
264
+ backend: QueueConnectorBackend,
265
+ ): QueueItemConnectorRegistration {
266
+ const options = {
267
+ queueId,
268
+ source: `${backend}:unavailable`,
269
+ };
270
+ if (backend === 'convex') return createConvexQueueItemConnector(options);
271
+ if (backend === 'workpool') return createWorkpoolQueueItemConnector(options);
272
+ if (backend === 'postgres-outbox')
273
+ return createPostgresOutboxQueueItemConnector(options);
274
+ if (backend === 'absurd') return createAbsurdQueueItemConnector(options);
275
+ return createProviderQueueItemConnector(options);
276
+ }
277
+
278
+ export function createDefaultQueueItemConnectorRegistry(): QueueItemConnectorRegistry {
279
+ const catalogIds = new Set(OPERATIONS_QUEUE_CATALOG.map((entry) => entry.id));
280
+ const mappedIds = Object.keys(QUEUE_CONNECTOR_BACKENDS);
281
+ const missing = [...catalogIds].filter(
282
+ (queueId) => QUEUE_CONNECTOR_BACKENDS[queueId] === undefined,
283
+ );
284
+ const extra = mappedIds.filter((queueId) => !catalogIds.has(queueId));
285
+ if (missing.length > 0 || extra.length > 0) {
286
+ throw new Error(
287
+ `Queue connector mapping drift: missing=${missing.join(',') || 'none'} extra=${extra.join(',') || 'none'}`,
288
+ );
289
+ }
290
+ return new QueueItemConnectorRegistry({
291
+ registrations: OPERATIONS_QUEUE_CATALOG.map((entry) =>
292
+ defaultRegistration(entry.id, QUEUE_CONNECTOR_BACKENDS[entry.id]!),
293
+ ),
294
+ });
295
+ }
296
+
297
+ /** Process-local default used by the admin control service. Owner modules can
298
+ * replace an unavailable registration during startup with
299
+ * `register(ownerRegistration, { replaceUnavailable: true })`; there is no
300
+ * fallback that mutates source data. */
301
+ export const defaultQueueItemConnectorRegistry =
302
+ createDefaultQueueItemConnectorRegistry();
303
+
304
+ /**
305
+ * Owner startup hook. A source module should register its connector before
306
+ * serving admin requests, replacing only the matching unavailable placeholder.
307
+ */
308
+ export function registerQueueItemConnector(
309
+ registration: QueueItemConnectorRegistration,
310
+ ): void {
311
+ defaultQueueItemConnectorRegistry.register(registration, {
312
+ replaceUnavailable: true,
313
+ });
314
+ }
@@ -655,7 +655,7 @@
655
655
  "id": "convex.provider-monitor-credit-recovery",
656
656
  "name": "Provider monitor credit-balance recovery",
657
657
  "scheduler": "convex_scheduled_action",
658
- "cadence": "runAfter(0) after a non-admitting balance becomes positive",
658
+ "cadence": "runAfter(0) after a non-admitting balance becomes positive (legacy queued delivery)",
659
659
  "timezone": "UTC",
660
660
  "source": "packages/convex/scheduledJobsNode.ts",
661
661
  "locator": "recoverProviderMonitorsAfterCreditBalanceChange",
@@ -669,6 +669,24 @@
669
669
  "failureAlert": "convex_action_wrapper",
670
670
  "domainReportRequired": true
671
671
  },
672
+ {
673
+ "id": "convex.credit-blocked-automation-recovery",
674
+ "name": "Credit-blocked automation recovery",
675
+ "scheduler": "convex_scheduled_action",
676
+ "cadence": "runAfter(0) after any positive balance movement; Monitor recovery only at the non-admitting-to-positive crossing",
677
+ "timezone": "UTC",
678
+ "source": "packages/convex/scheduledJobsNode.ts",
679
+ "locator": "recoverCreditBlockedAutomationsAfterCreditBalanceChange",
680
+ "owner": "billing",
681
+ "criticality": "critical",
682
+ "purpose": "Recover scheduled Plays and provider Monitors blocked by insufficient credits after a positive balance movement.",
683
+ "successDefinition": "Eligible credit-blocked automations are resumed or a valid no-op is recorded; ineligible resources remain unchanged.",
684
+ "failureDefinition": "Recovery intent cannot be durably recorded or an eligible automation cannot be rescheduled or reactivated.",
685
+ "startGraceMs": 120000,
686
+ "runTimeoutMs": 1800000,
687
+ "failureAlert": "convex_action_wrapper",
688
+ "domainReportRequired": true
689
+ },
672
690
  {
673
691
  "id": "inngest.workflow-run-watchdog",
674
692
  "name": "Workflow run watchdog",
@@ -31,6 +31,9 @@ export type TelemetryContext = {
31
31
  workflowOrigin?: string | null;
32
32
  playName?: string | null;
33
33
  machineId?: string | null;
34
+ gatewayRole?: string | null;
35
+ /** Stable identity for one in-process gateway handler lifecycle. */
36
+ gatewayLifecycleId?: string | null;
34
37
  };
35
38
 
36
39
  export type TelemetryLevel = 'debug' | 'info' | 'warn' | 'error';
@@ -182,6 +185,8 @@ const RESERVED_FIELDS = new Set([
182
185
  'workflowOrigin',
183
186
  'playName',
184
187
  'machineId',
188
+ 'gatewayRole',
189
+ 'gatewayLifecycleId',
185
190
  ]);
186
191
 
187
192
  function errorTriage(fields: TelemetryFields | undefined): TelemetryTriage {
@@ -110,6 +110,7 @@ import {
110
110
  type ToolResponseView,
111
111
  } from '../plays/tool-response-contract';
112
112
  import {
113
+ AllAdmittedRowsFailedError,
113
114
  isProviderExhaustedFailure,
114
115
  ProviderExhaustedError,
115
116
  } from './run-failure';
@@ -1529,10 +1530,13 @@ type FieldMapRunResult = {
1529
1530
  failedRows: PersistableMapRow[];
1530
1531
  completedRowCount: number;
1531
1532
  failedRowCount: number;
1533
+ failureSamples: Array<{ rowLabel: string; error: string }>;
1532
1534
  previewRows: Record<string, unknown>[];
1533
1535
  retainedRowsComplete: boolean;
1534
1536
  };
1535
1537
 
1538
+ const MAP_FAILURE_SAMPLE_LIMIT = 3;
1539
+
1536
1540
  type RuntimeMapRowPersistence = {
1537
1541
  persistRows: (rows: PersistableMapRow[]) => {
1538
1542
  admitted: Promise<void>;
@@ -6804,6 +6808,8 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
6804
6808
  let failedCount = 0;
6805
6809
  let duplicateReuseCount = 0;
6806
6810
  let executedCount = 0;
6811
+ const failureSamples: Array<{ rowLabel: string; error: string }> = [];
6812
+ const failureSampleErrors = new Set<string>();
6807
6813
  const previewRows: Record<string, unknown>[] = [];
6808
6814
  // A bounded dataset may be materialized by authored code immediately
6809
6815
  // after run(). Keep the rows produced by this exact awaited execution so
@@ -7179,6 +7185,16 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
7179
7185
  executedCount += rowsToExecute.length;
7180
7186
  successfulCount += mapResult.completedRowCount;
7181
7187
  failedCount += mapResult.failedRowCount;
7188
+ for (const sample of mapResult.failureSamples) {
7189
+ if (
7190
+ failureSamples.length >= MAP_FAILURE_SAMPLE_LIMIT ||
7191
+ failureSampleErrors.has(sample.error)
7192
+ ) {
7193
+ continue;
7194
+ }
7195
+ failureSampleErrors.add(sample.error);
7196
+ failureSamples.push(sample);
7197
+ }
7182
7198
  processedCount += rowsToExecute.length;
7183
7199
  page.rows.length = 0;
7184
7200
  }
@@ -7216,10 +7232,12 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
7216
7232
  totalRows: totalInputCount,
7217
7233
  at: Date.now(),
7218
7234
  });
7219
- throw new Error(
7220
- `ctx.dataset("${normalizedMapNamespace}") failed for every admitted row (${failedCount}/${totalInputCount}). ` +
7221
- 'Failed rows were persisted and will be retried on the next run.',
7222
- );
7235
+ throw new AllAdmittedRowsFailedError({
7236
+ mapName: normalizedMapNamespace,
7237
+ failedCount,
7238
+ totalInputCount,
7239
+ samples: failureSamples,
7240
+ });
7223
7241
  }
7224
7242
 
7225
7243
  this.setMapFrame({
@@ -8105,6 +8123,8 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
8105
8123
  const failFastOnRowError = runtimeOptions?.onRowError === 'fail';
8106
8124
  const completedRowsToPersist: PersistableMapRow[] = [];
8107
8125
  const failedRowsToPersist: PersistableMapRow[] = [];
8126
+ const failureSamples: Array<{ rowLabel: string; error: string }> = [];
8127
+ const failureSampleErrors = new Set<string>();
8108
8128
  const previewRows: Record<string, unknown>[] = [];
8109
8129
  let completedRowCount = 0;
8110
8130
  let failedRowCount = 0;
@@ -8368,6 +8388,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
8368
8388
  failedRows: [],
8369
8389
  completedRowCount: results.length,
8370
8390
  failedRowCount: 0,
8391
+ failureSamples: [],
8371
8392
  previewRows: results.slice(0, 5),
8372
8393
  retainedRowsComplete: true,
8373
8394
  };
@@ -8573,6 +8594,16 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
8573
8594
  error: formattedError,
8574
8595
  });
8575
8596
  failedRowCount += 1;
8597
+ if (
8598
+ failureSamples.length < MAP_FAILURE_SAMPLE_LIMIT &&
8599
+ !failureSampleErrors.has(formattedError)
8600
+ ) {
8601
+ failureSampleErrors.add(formattedError);
8602
+ failureSamples.push({
8603
+ rowLabel: failedMapRowLogLabel(failedRow),
8604
+ error: formattedError,
8605
+ });
8606
+ }
8576
8607
  retainPersistedRow(failedRow, failedRowsToPersist);
8577
8608
  this.emitScopedRowUpdate(rowKey, normalizedTableNamespace, {
8578
8609
  rowId: idx,
@@ -8985,6 +9016,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
8985
9016
  failedRows: failedRowsToPersist,
8986
9017
  completedRowCount,
8987
9018
  failedRowCount,
9019
+ failureSamples,
8988
9020
  previewRows,
8989
9021
  retainedRowsComplete,
8990
9022
  };
@@ -58,6 +58,93 @@ export const MODAL_PAYLOAD_UPLOAD_TIMEOUT_MESSAGE =
58
58
 
59
59
  export const WORKSPACE_STORAGE_NOT_READY_CODE = 'WORKSPACE_STORAGE_NOT_READY';
60
60
 
61
+ export const ALL_ADMITTED_ROWS_FAILED_CODE = 'ALL_ADMITTED_ROWS_FAILED';
62
+
63
+ export type AllAdmittedRowsFailedSample = {
64
+ rowLabel: string;
65
+ error: string;
66
+ };
67
+
68
+ const ALL_ADMITTED_ROWS_FAILED_SAMPLE_LIMIT = 3;
69
+ const ALL_ADMITTED_ROWS_FAILED_SAMPLE_TEXT_LIMIT = 500;
70
+
71
+ function boundedAllAdmittedRowsFailedSample(value: string): string {
72
+ const normalized = value.trim();
73
+ if (normalized.length <= ALL_ADMITTED_ROWS_FAILED_SAMPLE_TEXT_LIMIT) {
74
+ return normalized;
75
+ }
76
+ return `${normalized.slice(0, ALL_ADMITTED_ROWS_FAILED_SAMPLE_TEXT_LIMIT - 1)}…`;
77
+ }
78
+
79
+ function allAdmittedRowsFailedMessage(input: {
80
+ mapName: string;
81
+ failedCount: number;
82
+ totalInputCount: number;
83
+ samples: readonly AllAdmittedRowsFailedSample[];
84
+ }): string {
85
+ const representativeFailures = input.samples
86
+ .slice(0, ALL_ADMITTED_ROWS_FAILED_SAMPLE_LIMIT)
87
+ .map(
88
+ (sample) =>
89
+ `${boundedAllAdmittedRowsFailedSample(sample.rowLabel)}: ${boundedAllAdmittedRowsFailedSample(sample.error)}`,
90
+ )
91
+ .join('; ');
92
+ const omittedCount = Math.max(
93
+ 0,
94
+ input.failedCount -
95
+ Math.min(input.samples.length, ALL_ADMITTED_ROWS_FAILED_SAMPLE_LIMIT),
96
+ );
97
+ const omittedSuffix =
98
+ omittedCount > 0 ? `; ${omittedCount} additional failure(s) omitted` : '';
99
+ return (
100
+ `ctx.dataset("${input.mapName}") produced no successful rows ` +
101
+ `(${input.failedCount}/${input.totalInputCount} admitted rows failed). ` +
102
+ (representativeFailures
103
+ ? `Representative failures: ${representativeFailures}${omittedSuffix}. `
104
+ : '') +
105
+ 'Failed rows were persisted for review and may be retried on a later run.'
106
+ );
107
+ }
108
+
109
+ export class AllAdmittedRowsFailedError extends Error {
110
+ readonly code = ALL_ADMITTED_ROWS_FAILED_CODE;
111
+
112
+ constructor(input: {
113
+ mapName: string;
114
+ failedCount: number;
115
+ totalInputCount: number;
116
+ samples?: readonly AllAdmittedRowsFailedSample[];
117
+ }) {
118
+ super(
119
+ `${ALL_ADMITTED_ROWS_FAILED_CODE}: ${allAdmittedRowsFailedMessage({
120
+ ...input,
121
+ samples: input.samples ?? [],
122
+ })}`,
123
+ );
124
+ this.name = 'AllAdmittedRowsFailedError';
125
+ }
126
+ }
127
+
128
+ export function isAllAdmittedRowsFailedFailure(error: unknown): boolean {
129
+ if (error instanceof AllAdmittedRowsFailedError) return true;
130
+ const code =
131
+ error && typeof error === 'object'
132
+ ? (error as { code?: unknown }).code
133
+ : null;
134
+ const message = error instanceof Error ? error.message : String(error ?? '');
135
+ return (
136
+ code === ALL_ADMITTED_ROWS_FAILED_CODE ||
137
+ new RegExp(`^${ALL_ADMITTED_ROWS_FAILED_CODE}:\\s*`).test(message)
138
+ );
139
+ }
140
+
141
+ function allAdmittedRowsFailedPublicMessage(cause: string): string {
142
+ return cause.replace(
143
+ new RegExp(`^${ALL_ADMITTED_ROWS_FAILED_CODE}:\\s*`),
144
+ '',
145
+ );
146
+ }
147
+
61
148
  // User-facing message. Contains NO SQL, role names, database names, or hosts by
62
149
  // construction, so it is safe past redactRuntimeInternalError (which only strips
63
150
  // postgres-URI passwords). The raw pg error goes in the Error `cause` for server
@@ -323,6 +410,15 @@ function toErrorText(error: unknown): string {
323
410
  export function normalizePlayRunFailure(error: unknown): PlayRunFailureDetails {
324
411
  const rawCause = toErrorText(error);
325
412
  const cause = boundedFailureText(rawCause);
413
+ if (isAllAdmittedRowsFailedFailure(error)) {
414
+ return {
415
+ code: ALL_ADMITTED_ROWS_FAILED_CODE,
416
+ phase: 'runtime',
417
+ message: allAdmittedRowsFailedPublicMessage(cause),
418
+ retryable: false,
419
+ cause,
420
+ };
421
+ }
326
422
  if (
327
423
  error instanceof ToolExecutionError &&
328
424
  error.origin === 'deepline' &&
@@ -2043,6 +2043,16 @@ async function prepareAbsurdAttempt(input: {
2043
2043
  exists: projection.exists,
2044
2044
  blocked: projection.blocked,
2045
2045
  });
2046
+ console.info('[runtime.scheduler.projection_deferred]', {
2047
+ runId: input.runId,
2048
+ eventId,
2049
+ phase: 'run_created_projection_gate',
2050
+ status: 'deferred',
2051
+ exists: projection.exists,
2052
+ blocked: projection.blocked,
2053
+ delaySeconds: projection.blocked ? 5 : 1,
2054
+ attemptAllocated: false,
2055
+ });
2046
2056
  throw new DeferTask(
2047
2057
  `Run-created projection is not yet delivered for ${input.runId}.`,
2048
2058
  projection.blocked ? 5 : 1,
package/dist/cli/index.js CHANGED
@@ -3068,7 +3068,7 @@ var SDK_RELEASE = {
3068
3068
  // getters keep their established compatibility behavior.
3069
3069
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
3070
3070
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
3071
- version: "0.3.120",
3071
+ version: "0.3.122",
3072
3072
  updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.",
3073
3073
  packageCapabilities: {
3074
3074
  updatePreferences: 1
@@ -3063,7 +3063,7 @@ var SDK_RELEASE = {
3063
3063
  // getters keep their established compatibility behavior.
3064
3064
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
3065
3065
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
3066
- version: "0.3.120",
3066
+ version: "0.3.122",
3067
3067
  updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.",
3068
3068
  packageCapabilities: {
3069
3069
  updatePreferences: 1
package/dist/index.js CHANGED
@@ -863,7 +863,7 @@ var SDK_RELEASE = {
863
863
  // getters keep their established compatibility behavior.
864
864
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
865
865
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
866
- version: "0.3.120",
866
+ version: "0.3.122",
867
867
  updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.",
868
868
  packageCapabilities: {
869
869
  updatePreferences: 1
package/dist/index.mjs CHANGED
@@ -768,7 +768,7 @@ var SDK_RELEASE = {
768
768
  // getters keep their established compatibility behavior.
769
769
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
770
770
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
771
- version: "0.3.120",
771
+ version: "0.3.122",
772
772
  updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.",
773
773
  packageCapabilities: {
774
774
  updatePreferences: 1
@@ -35,6 +35,7 @@
35
35
  "dist/bundling-sources/shared_libs/monitors/analytics-install-guides.ts",
36
36
  "dist/bundling-sources/shared_libs/monitors/analytics-public.ts",
37
37
  "dist/bundling-sources/shared_libs/monitors/billing/charges.ts",
38
+ "dist/bundling-sources/shared_libs/monitors/billing/credit-recovery.ts",
38
39
  "dist/bundling-sources/shared_libs/monitors/billing/policy-builders.ts",
39
40
  "dist/bundling-sources/shared_libs/monitors/billing/pricing.ts",
40
41
  "dist/bundling-sources/shared_libs/monitors/billing/public-pricing.ts",
@@ -93,6 +94,7 @@
93
94
  "dist/bundling-sources/shared_libs/observability/log-metadata.ts",
94
95
  "dist/bundling-sources/shared_libs/observability/node-tracing.ts",
95
96
  "dist/bundling-sources/shared_libs/observability/queue-health.ts",
97
+ "dist/bundling-sources/shared_libs/observability/queue-item-connectors.ts",
96
98
  "dist/bundling-sources/shared_libs/observability/redaction.ts",
97
99
  "dist/bundling-sources/shared_libs/observability/request-context.ts",
98
100
  "dist/bundling-sources/shared_libs/observability/scheduled-job-errors.ts",
@@ -149,7 +149,7 @@ type SdkRelease = {
149
149
  supportPolicy: SdkSupportPolicy;
150
150
  };
151
151
  declare const SDK_RELEASE: {
152
- readonly version: "0.3.120";
152
+ readonly version: "0.3.122";
153
153
  readonly updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.";
154
154
  readonly packageCapabilities: {
155
155
  readonly updatePreferences: 1;
package/dist/release.d.ts CHANGED
@@ -149,7 +149,7 @@ type SdkRelease = {
149
149
  supportPolicy: SdkSupportPolicy;
150
150
  };
151
151
  declare const SDK_RELEASE: {
152
- readonly version: "0.3.120";
152
+ readonly version: "0.3.122";
153
153
  readonly updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.";
154
154
  readonly packageCapabilities: {
155
155
  readonly updatePreferences: 1;
package/dist/release.js CHANGED
@@ -74,7 +74,7 @@ var SDK_RELEASE = {
74
74
  // getters keep their established compatibility behavior.
75
75
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
76
76
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
77
- version: "0.3.120",
77
+ version: "0.3.122",
78
78
  updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.",
79
79
  packageCapabilities: {
80
80
  updatePreferences: 1
package/dist/release.mjs CHANGED
@@ -48,7 +48,7 @@ var SDK_RELEASE = {
48
48
  // getters keep their established compatibility behavior.
49
49
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
50
50
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
51
- version: "0.3.120",
51
+ version: "0.3.122",
52
52
  updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.",
53
53
  packageCapabilities: {
54
54
  updatePreferences: 1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepline",
3
- "version": "0.3.120",
3
+ "version": "0.3.122",
4
4
  "description": "GTM data CLI and TypeScript SDK for coding agents",
5
5
  "license": "MIT",
6
6
  "homepage": "https://code.deepline.com",