deepline 0.3.168 → 0.3.170

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -41,7 +41,7 @@ with project-local skills automatically.
41
41
  Paste this into Claude Code, Codex, or another coding agent:
42
42
 
43
43
  ```text
44
- Install the official Deepline CLI globally from npm (package: deepline) by following https://code.deepline.com/INSTALL.md.
44
+ Install and set up the official Deepline CLI globally from npm (package: deepline) by following https://code.deepline.com/INSTALL.md.
45
45
  ```
46
46
 
47
47
  Once setup is complete, tell your agent what you want to do:
@@ -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.168',
203
+ version: '0.3.170',
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: {
@@ -73,6 +73,8 @@
73
73
  "./provider-monitor-errors.js": "./provider-monitor-errors.ts",
74
74
  "./provider-monitor-identity-claims": "./provider-monitor-identity-claims.ts",
75
75
  "./provider-monitor-identity-claims.js": "./provider-monitor-identity-claims.ts",
76
+ "./provider-monitor-conflicts": "./provider-monitor-conflicts.ts",
77
+ "./provider-monitor-conflicts.js": "./provider-monitor-conflicts.ts",
76
78
  "./provider-monitor-legacy-aliases": "./provider-monitor-legacy-aliases.ts",
77
79
  "./provider-monitor-legacy-aliases.js": "./provider-monitor-legacy-aliases.ts",
78
80
  "./provider-monitor-priority-execution": "./provider-monitor-priority-execution.ts",
@@ -197,10 +197,17 @@ export type ProviderMonitorCapabilityDefinition = {
197
197
  */
198
198
  upstreamCollision?: {
199
199
  identityKeyFields: readonly string[];
200
+ /**
201
+ * Different effective payload configurations may hold separate Deepline
202
+ * claims inside one provider identity scope. This does not override an
203
+ * upstream 409 if the provider itself enforces a broader uniqueness rule.
204
+ */
205
+ allowDistinctConfigurations?: boolean;
200
206
  /**
201
207
  * This provider can create an updated resource for the same Deepline
202
208
  * monitor key before the prior upstream resource is deprovisioned. This
203
- * never permits a second key to occupy the same identity in one org.
209
+ * never permits a second key to occupy the same exact configuration in
210
+ * one org.
204
211
  */
205
212
  supportsSameIdentityReplacement?: boolean;
206
213
  };
@@ -444,7 +451,7 @@ export function defineProviderMonitorCapability(input: {
444
451
  },
445
452
  identity: {
446
453
  semantics: input.upstreamCollision
447
- ? `One monitor identity uses ${input.upstreamCollision.identityKeyFields.join(', ')}.`
454
+ ? `The provider resource scope uses ${input.upstreamCollision.identityKeyFields.join(', ')}.${input.upstreamCollision.allowDistinctConfigurations ? ' Different effective payload configurations in that scope may be separate monitor resources; exact duplicates still conflict.' : ''}`
448
455
  : 'This monitor uses the provider capability identity declared by Deepline.',
449
456
  update:
450
457
  'Use monitors update for a patch or deploy for a complete desired definition.',
@@ -0,0 +1,85 @@
1
+ import {
2
+ providerMonitorUpstreamIdentityClaimKey,
3
+ providerMonitorUpstreamIdentitySignature,
4
+ } from './provider-monitor-upstream-identity';
5
+
6
+ export type ProviderMonitorConflictIdentity = {
7
+ tool: string;
8
+ /** Exact effective configuration key stored in providerMonitorIdentityClaims. */
9
+ identityKey: string;
10
+ /** Broad provider-declared scope; this is also the legacy claim key. */
11
+ scopeKey: string;
12
+ /** Safe, customer-facing description of the provider-declared scope. */
13
+ identity: Record<string, string | null>;
14
+ };
15
+
16
+ /**
17
+ * Builds the one canonical conflict identity used by scalar Monitors, Fleets,
18
+ * and Batches. Same scope + same configuration conflicts; same scope with a
19
+ * distinct supported configuration gets its own indexed claim.
20
+ */
21
+ export function providerMonitorConflictIdentity(input: {
22
+ tool: string;
23
+ payload: Record<string, unknown> | null | undefined;
24
+ }): ProviderMonitorConflictIdentity | null {
25
+ const signature = providerMonitorUpstreamIdentitySignature(
26
+ input.tool,
27
+ input.payload,
28
+ );
29
+ if (!signature) return null;
30
+ const identity = Object.fromEntries(
31
+ Object.entries(signature.key).sort(([left], [right]) =>
32
+ left.localeCompare(right),
33
+ ),
34
+ );
35
+ const scopeKey = JSON.stringify(identity);
36
+ return {
37
+ tool: signature.tool,
38
+ identityKey:
39
+ providerMonitorUpstreamIdentityClaimKey(input.tool, input.payload) ??
40
+ scopeKey,
41
+ scopeKey,
42
+ identity,
43
+ };
44
+ }
45
+
46
+ /** Exact same effective configuration, regardless of monitor key or owner. */
47
+ export function sameProviderMonitorConflictIdentity(
48
+ left: ProviderMonitorConflictIdentity | null | undefined,
49
+ right: ProviderMonitorConflictIdentity | null | undefined,
50
+ ) {
51
+ return Boolean(
52
+ left &&
53
+ right &&
54
+ left.tool === right.tool &&
55
+ left.identityKey === right.identityKey,
56
+ );
57
+ }
58
+
59
+ /** Same provider resource scope, even when filter/configuration differs. */
60
+ export function sameProviderMonitorConflictScope(
61
+ left: ProviderMonitorConflictIdentity | null | undefined,
62
+ right: ProviderMonitorConflictIdentity | null | undefined,
63
+ ) {
64
+ return Boolean(
65
+ left &&
66
+ right &&
67
+ left.tool === right.tool &&
68
+ left.scopeKey === right.scopeKey,
69
+ );
70
+ }
71
+
72
+ /**
73
+ * Whether the definitions are valid separate variants under the capability's
74
+ * declared policy. Providers can still reject the second create; their 409 is
75
+ * handled as an upstream conflict, not mistaken for a Deepline duplicate.
76
+ */
77
+ export function providerMonitorDefinitionsAreDistinctConfigurations(
78
+ left: ProviderMonitorConflictIdentity | null | undefined,
79
+ right: ProviderMonitorConflictIdentity | null | undefined,
80
+ ) {
81
+ return Boolean(
82
+ sameProviderMonitorConflictScope(left, right) &&
83
+ !sameProviderMonitorConflictIdentity(left, right),
84
+ );
85
+ }
@@ -10,14 +10,14 @@ import {
10
10
  import { MAX_PRIORITY_MONITORS_PER_ORG } from '@deepline/monitors/provider-monitor-priority-execution';
11
11
  import { internal } from '@deepline/convex-client/function-references';
12
12
  import { getRequiredInternalConvexClient } from '@deepline/convex-client/internal-client';
13
- import {
14
- providerMonitorUpstreamIdentitySignature,
15
- type ProviderMonitorUpstreamIdentitySignature,
16
- } from '@deepline/monitors/provider-monitor-upstream-identity';
13
+ import { providerMonitorConflictIdentity } from '@deepline/monitors/provider-monitor-conflicts';
17
14
 
18
15
  export type ProviderMonitorIdentityClaim = {
19
16
  tool: string;
17
+ /** Exact effective configuration identity, used by the indexed claim. */
20
18
  identityKey: string;
19
+ /** Provider-declared broad identity scope; legacy claims used this key. */
20
+ scopeKey: string;
21
21
  identity: Record<string, string | null>;
22
22
  };
23
23
 
@@ -31,13 +31,19 @@ type OrgAuthContext = { orgId: string };
31
31
  */
32
32
  export type ProviderMonitorIdentityClaimReference = Pick<
33
33
  ProviderMonitorIdentityClaim,
34
- 'tool' | 'identityKey'
34
+ 'tool' | 'identityKey' | 'scopeKey'
35
35
  >;
36
36
 
37
37
  export function providerMonitorIdentityClaimReference(
38
38
  claim: ProviderMonitorIdentityClaim | null | undefined,
39
39
  ): ProviderMonitorIdentityClaimReference | null {
40
- return claim ? { tool: claim.tool, identityKey: claim.identityKey } : null;
40
+ return claim
41
+ ? {
42
+ tool: claim.tool,
43
+ identityKey: claim.identityKey,
44
+ scopeKey: claim.scopeKey,
45
+ }
46
+ : null;
41
47
  }
42
48
 
43
49
  export type ProviderMonitorIdentityReservation =
@@ -146,19 +152,6 @@ export function normalizeProviderMonitorLifecycleReservation(input: {
146
152
  };
147
153
  }
148
154
 
149
- function claimFromSignature(
150
- signature: ProviderMonitorUpstreamIdentitySignature,
151
- ): ProviderMonitorIdentityClaim {
152
- const identity = Object.fromEntries(
153
- Object.entries(signature.key).sort(([a], [b]) => a.localeCompare(b)),
154
- );
155
- return {
156
- tool: signature.tool,
157
- identityKey: JSON.stringify(identity),
158
- identity,
159
- };
160
- }
161
-
162
155
  export function providerMonitorIdentityClaim(input: {
163
156
  tool: string;
164
157
  payload: Record<string, unknown> | null | undefined;
@@ -166,11 +159,7 @@ export function providerMonitorIdentityClaim(input: {
166
159
  // Claims protect provider resources, including legacy rows whose public
167
160
  // capability id was later retired. The Convex-safe identity contract owns
168
161
  // those aliases; the live capability registry intentionally does not.
169
- const signature = providerMonitorUpstreamIdentitySignature(
170
- input.tool,
171
- input.payload,
172
- );
173
- return signature ? claimFromSignature(signature) : null;
162
+ return providerMonitorConflictIdentity(input);
174
163
  }
175
164
 
176
165
  /**
@@ -230,6 +219,7 @@ export async function getProviderMonitorIdentityOwner(input: {
230
219
  orgId: input.auth.orgId,
231
220
  tool: input.claim.tool,
232
221
  identityKey: input.claim.identityKey,
222
+ scopeKey: input.claim.scopeKey,
233
223
  },
234
224
  );
235
225
  }
@@ -455,6 +445,7 @@ export async function beginProviderMonitorIdentityReservation(input: {
455
445
  orgId: input.auth.orgId,
456
446
  tool: claim.tool,
457
447
  identityKey: claim.identityKey,
448
+ scopeKey: claim.scopeKey,
458
449
  monitorKey: input.monitorKey,
459
450
  operationId,
460
451
  ...(input.reclaimSelfProvisioning
@@ -491,6 +482,7 @@ async function mutateClaim(
491
482
  orgId: input.auth.orgId,
492
483
  tool: input.claim.tool,
493
484
  identityKey: input.claim.identityKey,
485
+ ...(action === 'release' ? { scopeKey: input.claim.scopeKey } : {}),
494
486
  monitorKey: input.monitorKey,
495
487
  ...(input.operationId ? { operationId: input.operationId } : {}),
496
488
  },
@@ -22,20 +22,34 @@ type ProviderMonitorUpstreamIdentityField = {
22
22
  runtimePayloadAliases?: readonly string[];
23
23
  };
24
24
 
25
+ type ProviderMonitorUpstreamIdentityDefinition = {
26
+ fields: readonly ProviderMonitorUpstreamIdentityField[];
27
+ /**
28
+ * Let distinct effective configurations reserve separate provider monitor
29
+ * claims inside one broad identity scope. The upstream provider can still
30
+ * reject a second resource with 409; this only prevents Deepline from
31
+ * treating different authored filters as an exact duplicate.
32
+ */
33
+ allowDistinctConfigurations?: boolean;
34
+ };
35
+
25
36
  const PROVIDER_MONITOR_UPSTREAM_IDENTITY_FIELDS = {
26
- 'deepline_native.company_radar': [{ name: 'radar_type' }, { name: 'domain' }],
27
- 'deepline_native.contact_radar': [
28
- { name: 'radar_type' },
29
- { name: 'profile_url' },
30
- ],
31
- 'deepline_native.industry_radar': [
32
- { name: 'radar_type' },
33
- { name: 'industry', runtimePayloadAliases: ['keyword'] },
34
- ],
35
- } as const satisfies Record<
36
- string,
37
- readonly ProviderMonitorUpstreamIdentityField[]
38
- >;
37
+ 'deepline_native.company_radar': {
38
+ fields: [{ name: 'radar_type' }, { name: 'domain' }],
39
+ allowDistinctConfigurations: true,
40
+ },
41
+ 'deepline_native.contact_radar': {
42
+ fields: [{ name: 'radar_type' }, { name: 'profile_url' }],
43
+ allowDistinctConfigurations: true,
44
+ },
45
+ 'deepline_native.industry_radar': {
46
+ fields: [
47
+ { name: 'radar_type' },
48
+ { name: 'industry', runtimePayloadAliases: ['keyword'] },
49
+ ],
50
+ allowDistinctConfigurations: true,
51
+ },
52
+ } as const satisfies Record<string, ProviderMonitorUpstreamIdentityDefinition>;
39
53
 
40
54
  function canonicalMonitorTool(tool: string) {
41
55
  // Public TamRadar ids existed before the deepline_native hard cutover. Keep
@@ -82,7 +96,18 @@ export function providerMonitorUpstreamIdentityFields(tool: string) {
82
96
  const canonical = canonicalMonitorTool(tool);
83
97
  return PROVIDER_MONITOR_UPSTREAM_IDENTITY_FIELDS[
84
98
  canonical as keyof typeof PROVIDER_MONITOR_UPSTREAM_IDENTITY_FIELDS
85
- ]?.map((field) => field.name);
99
+ ]?.fields.map((field) => field.name);
100
+ }
101
+
102
+ export function providerMonitorUpstreamIdentityAllowsDistinctConfigurations(
103
+ tool: string,
104
+ ) {
105
+ const canonical = canonicalMonitorTool(tool);
106
+ return (
107
+ PROVIDER_MONITOR_UPSTREAM_IDENTITY_FIELDS[
108
+ canonical as keyof typeof PROVIDER_MONITOR_UPSTREAM_IDENTITY_FIELDS
109
+ ]?.allowDistinctConfigurations ?? false
110
+ );
86
111
  }
87
112
 
88
113
  export function providerMonitorUpstreamIdentitySignature(
@@ -93,7 +118,7 @@ export function providerMonitorUpstreamIdentitySignature(
93
118
  const fields =
94
119
  PROVIDER_MONITOR_UPSTREAM_IDENTITY_FIELDS[
95
120
  canonical as keyof typeof PROVIDER_MONITOR_UPSTREAM_IDENTITY_FIELDS
96
- ];
121
+ ]?.fields;
97
122
  if (!fields?.length) return null;
98
123
  const key: Record<string, ProviderMonitorUpstreamIdentityValue> = {};
99
124
  for (const field of fields) {
@@ -110,3 +135,42 @@ export function providerMonitorUpstreamIdentitySignature(
110
135
  }
111
136
  return { tool: canonical, key };
112
137
  }
138
+
139
+ /**
140
+ * Exact claim identity used by the Convex conflict index. The provider's
141
+ * declared scope remains visible in `scopeKey`; providers that allow
142
+ * configuration variants add every non-scope payload field to the claim key.
143
+ * Keeping the old scope JSON as the prefix lets readers recognize claims
144
+ * created before this distinction was introduced.
145
+ */
146
+ export function providerMonitorUpstreamIdentityClaimKey(
147
+ tool: string,
148
+ payload: Record<string, unknown> | null | undefined,
149
+ ) {
150
+ const signature = providerMonitorUpstreamIdentitySignature(tool, payload);
151
+ if (!signature) return null;
152
+ const scopeKey = stableJson(signature.key);
153
+ if (!providerMonitorUpstreamIdentityAllowsDistinctConfigurations(tool)) {
154
+ return scopeKey;
155
+ }
156
+
157
+ const definition =
158
+ PROVIDER_MONITOR_UPSTREAM_IDENTITY_FIELDS[
159
+ signature.tool as keyof typeof PROVIDER_MONITOR_UPSTREAM_IDENTITY_FIELDS
160
+ ];
161
+ const scopeAndAliases = new Set<string>(
162
+ definition.fields.flatMap((field) => [
163
+ field.name,
164
+ ...('runtimePayloadAliases' in field
165
+ ? (field.runtimePayloadAliases ?? [])
166
+ : []),
167
+ ]),
168
+ );
169
+ const configuration = Object.fromEntries(
170
+ Object.entries(payload ?? {}).filter(
171
+ ([field]) => !scopeAndAliases.has(field),
172
+ ),
173
+ );
174
+ if (Object.keys(configuration).length === 0) return scopeKey;
175
+ return `${scopeKey}#configuration=${stableJson(configuration)}`;
176
+ }
@@ -389,7 +389,12 @@ export type QueueItemEvidence = {
389
389
  };
390
390
 
391
391
  export type QueueItemUnavailable = {
392
- readonly code: 'item_reader_unavailable' | 'state_filter_unavailable';
392
+ readonly code:
393
+ | 'item_reader_unavailable'
394
+ | 'state_filter_unavailable'
395
+ | 'scope_required'
396
+ | 'scope_unsupported'
397
+ | 'cursor_invalid';
393
398
  readonly reason: string;
394
399
  };
395
400
 
@@ -524,6 +529,8 @@ export type QueueItemConnectorContext = {
524
529
  readonly limit: number;
525
530
  readonly cursor?: string;
526
531
  readonly state?: QueueItemState;
532
+ /** Explicit tenant scope for owner sources stored in a Customer DB. */
533
+ readonly orgId?: string;
527
534
  readonly actorId?: string;
528
535
  readonly operationId?: string;
529
536
  };
@@ -538,6 +545,10 @@ export type QueueItemConnector = {
538
545
  readonly id: string;
539
546
  readonly queueId: string;
540
547
  readonly source: string;
548
+ /** Stable reason surfaced when this registration is an unavailable shell. */
549
+ readonly unavailableReason?: string;
550
+ /** True only when the owner explicitly declares that no item reader exists. */
551
+ readonly explicitlyUnavailable?: boolean;
541
552
  readonly mapNativeState: (nativeState: string) => QueueItemState;
542
553
  readonly list: (context: QueueItemConnectorContext) => Promise<QueueItemList>;
543
554
  /** Optional source-indexed failure-window listing for safe, bounded
@@ -1035,7 +1046,64 @@ const DLQ_PATH_METADATA: Readonly<Record<string, DlqPathMetadata>> = {
1035
1046
  enabled: false,
1036
1047
  requiresApproval: true,
1037
1048
  reason:
1038
- 'Recovery must prove upstream ownership, current monitor binding, and idempotent re-enqueue; no generic replay is allowed.',
1049
+ 'This legacy ID is the provider-monitor source-routing DLQ. Recovery must prove upstream ownership, current monitor binding, and idempotent re-enqueue; no generic replay is allowed.',
1050
+ },
1051
+ ],
1052
+ },
1053
+ 'signal-radar-inbound-receipts': {
1054
+ lookupIndex: {
1055
+ table: 'signalRadarInboundReceipts',
1056
+ index: 'by_state_created_at',
1057
+ key: 'state + createdAt',
1058
+ bounded: true,
1059
+ },
1060
+ transitions: [
1061
+ {
1062
+ action: 'retry',
1063
+ fromStates: ['dead_lettered'],
1064
+ toState: 'pending',
1065
+ enabled: false,
1066
+ requiresApproval: true,
1067
+ reason:
1068
+ 'The receipt owner does not expose a fenced transition from dead_lettered; scheduled recovery owns pending retries.',
1069
+ },
1070
+ ],
1071
+ },
1072
+ 'signal-radar-inbound-legacy-dlq': {
1073
+ lookupIndex: {
1074
+ table: 'dl_meta.signal_radar_inbound_dead_letter',
1075
+ index: 'idx_dl_meta_sr_dead_letter_status',
1076
+ key: 'status + created_at (one organization only)',
1077
+ bounded: true,
1078
+ },
1079
+ transitions: [
1080
+ {
1081
+ action: 'replay',
1082
+ fromStates: ['open'],
1083
+ toState: 'replayed',
1084
+ enabled: false,
1085
+ requiresApproval: true,
1086
+ reason:
1087
+ 'The legacy replay owner does not verify current charge authorization or expose the shared revision and idempotency contract; inspect with --org, but do not replay through the generic queue action.',
1088
+ },
1089
+ ],
1090
+ },
1091
+ 'signal-radar-inbound-typed-write-failures': {
1092
+ lookupIndex: {
1093
+ table: 'dl_meta.monitor_inbound_raw',
1094
+ index: 'idx_dl_meta_monitor_inbound_raw_status_received',
1095
+ key: 'status + received_at (one organization only)',
1096
+ bounded: true,
1097
+ },
1098
+ transitions: [
1099
+ {
1100
+ action: 'retry',
1101
+ fromStates: ['write_failed'],
1102
+ toState: 'written',
1103
+ enabled: false,
1104
+ requiresApproval: true,
1105
+ reason:
1106
+ 'Raw failure rows are immutable delivery evidence. Provider redelivery and the receipt owner control retries; no per-item operator retry exists.',
1039
1107
  },
1040
1108
  ],
1041
1109
  },
@@ -201,10 +201,46 @@ export const OPERATIONS_QUEUE_CATALOG = [
201
201
  ),
202
202
  queue(
203
203
  'signal-radar-inbound-dlq',
204
- 'Signal-radar inbound / DLQ',
204
+ 'Signal-radar reconciliation source-routing DLQ',
205
205
  'provider_queue',
206
206
  'monitors',
207
- 'Persist inbound provider events and isolate poison events',
207
+ 'Preserved legacy ID for provider-monitor source-routing failures; this is not the inbound delivery queue',
208
+ 300_000,
209
+ 900_000,
210
+ ),
211
+ queue(
212
+ 'signal-radar-inbound-receipts',
213
+ 'Signal-radar inbound receipts',
214
+ 'leased_workset',
215
+ 'monitors',
216
+ 'Process the global Convex inbox of authenticated provider callbacks',
217
+ 300_000,
218
+ 900_000,
219
+ ),
220
+ queue(
221
+ 'signal-radar-inbound-legacy-dlq',
222
+ 'Signal-radar legacy inbound dead-letters',
223
+ 'provider_queue',
224
+ 'monitors',
225
+ 'Tenant-scoped legacy poison-callback records; counts can overlap raw write failures',
226
+ 300_000,
227
+ 900_000,
228
+ ),
229
+ queue(
230
+ 'signal-radar-inbound-typed-write-failures',
231
+ 'Signal-radar typed-write failures',
232
+ 'provider_queue',
233
+ 'monitors',
234
+ 'Tenant-scoped raw callback receipts whose typed write failed; records can overlap legacy dead-letters',
235
+ 300_000,
236
+ 900_000,
237
+ ),
238
+ queue(
239
+ 'signal-radar-inbound-legacy-vercel-drain',
240
+ 'Signal-radar legacy Vercel Queue drain',
241
+ 'provider_queue',
242
+ 'monitors',
243
+ 'Deprecated drain-only Vercel Queue implementation with no owner item reader',
208
244
  300_000,
209
245
  900_000,
210
246
  ),
@@ -17,6 +17,7 @@ export type QueueConnectorBackend =
17
17
  | 'convex'
18
18
  | 'workpool'
19
19
  | 'postgres-outbox'
20
+ | 'customer-db'
20
21
  | 'absurd'
21
22
  | 'provider';
22
23
 
@@ -99,7 +100,9 @@ export class QueueItemConnectorRegistry {
99
100
  /** True only after the owner has replaced the safe unavailable placeholder. */
100
101
  hasOwnerConnector(queueId: string): boolean {
101
102
  const registration = this.registrations.get(queueId);
102
- return registration !== undefined && !isUnavailableRegistration(registration);
103
+ return (
104
+ registration !== undefined && !isUnavailableRegistration(registration)
105
+ );
103
106
  }
104
107
 
105
108
  list(): readonly QueueItemConnectorRegistration[] {
@@ -127,11 +130,16 @@ const unavailableReason = (options: UnavailableConnectorOptions): string =>
127
130
  export function createUnavailableQueueItemConnector(
128
131
  options: UnavailableConnectorOptions,
129
132
  ): QueueItemConnectorRegistration {
133
+ if (options.reason !== undefined && options.reason.trim().length === 0) {
134
+ throw new Error('An explicit unavailable queue connector needs a reason.');
135
+ }
130
136
  const reason = unavailableReason(options);
131
137
  const connector: QueueItemConnector = {
132
138
  id: options.id ?? `${options.backend}-${options.queueId}-unavailable`,
133
139
  queueId: options.queueId,
134
140
  source: options.source,
141
+ unavailableReason: reason,
142
+ ...(options.reason === undefined ? {} : { explicitlyUnavailable: true }),
135
143
  mapNativeState: (nativeState: string): QueueItemState =>
136
144
  normalizeQueueItemState(nativeState),
137
145
  async list(context: QueueItemConnectorContext): Promise<QueueItemList> {
@@ -221,6 +229,16 @@ export function createPostgresOutboxQueueItemConnector(
221
229
  });
222
230
  }
223
231
 
232
+ export function createCustomerDbQueueItemConnector(
233
+ options: BackendConnectorOptions,
234
+ ): QueueItemConnectorRegistration {
235
+ return createUnavailableQueueItemConnector({
236
+ ...options,
237
+ backend: 'customer-db',
238
+ source: options.source ?? 'customer-db',
239
+ });
240
+ }
241
+
224
242
  export function createAbsurdQueueItemConnector(
225
243
  options: BackendConnectorOptions,
226
244
  ): QueueItemConnectorRegistration {
@@ -257,7 +275,11 @@ export const QUEUE_CONNECTOR_BACKENDS: Readonly<
257
275
  'monitor-deployment-jobs': 'workpool',
258
276
  'monitor-batch-operations': 'workpool',
259
277
  'provider-monitor-reconciliation': 'provider',
260
- 'signal-radar-inbound-dlq': 'provider',
278
+ 'signal-radar-inbound-dlq': 'convex',
279
+ 'signal-radar-inbound-receipts': 'convex',
280
+ 'signal-radar-inbound-legacy-dlq': 'customer-db',
281
+ 'signal-radar-inbound-typed-write-failures': 'customer-db',
282
+ 'signal-radar-inbound-legacy-vercel-drain': 'provider',
261
283
  'charge-reconciliation-backlog': 'postgres-outbox',
262
284
  'workspace-fast-settlement': 'convex',
263
285
  'metronome-usage-delivery': 'postgres-outbox',
@@ -283,6 +305,8 @@ function defaultRegistration(
283
305
  if (backend === 'workpool') return createWorkpoolQueueItemConnector(options);
284
306
  if (backend === 'postgres-outbox')
285
307
  return createPostgresOutboxQueueItemConnector(options);
308
+ if (backend === 'customer-db')
309
+ return createCustomerDbQueueItemConnector(options);
286
310
  if (backend === 'absurd') return createAbsurdQueueItemConnector(options);
287
311
  return createProviderQueueItemConnector(options);
288
312
  }
@@ -149,7 +149,11 @@ export type PlayDeployTaskParams = {
149
149
  playProjectId: string;
150
150
  deployToken: string;
151
151
  appBaseUrl: string;
152
- /** Pinned by the first enqueue. Missing means the original Fly executor. */
152
+ /**
153
+ * Always `vercel` for new tasks, which is the only executor. `fly` and a
154
+ * missing value survive only in tasks queued before the Fly build path was
155
+ * removed; the dispatcher builds those on Vercel too.
156
+ */
153
157
  buildBackend?: 'fly' | 'vercel';
154
158
  };
155
159
 
@@ -15317,9 +15317,15 @@ function runLedgerPrecedesSql(
15317
15317
  successor: string,
15318
15318
  inclusive = false,
15319
15319
  ): string {
15320
- const position = (alias: string) =>
15321
- `(CASE ${alias}.event_type WHEN 'run.created' THEN 0 WHEN 'run.started' THEN 1 ELSE 2 END, ${alias}.created_at, ${alias}.event_id)`;
15322
- return `${position(predecessor)} ${inclusive ? '<=' : '<'} ${position(successor)}`;
15320
+ return `${runLedgerPositionSql(predecessor)} ${inclusive ? '<=' : '<'} ${runLedgerPositionSql(successor)}`;
15321
+ }
15322
+
15323
+ function runLedgerPositionSql(alias: string): string {
15324
+ return `(${runLedgerOrderSql(alias)})`;
15325
+ }
15326
+
15327
+ function runLedgerOrderSql(alias: string): string {
15328
+ return `CASE ${alias}.event_type WHEN 'run.created' THEN 0 WHEN 'run.started' THEN 1 ELSE 2 END, ${alias}.created_at, ${alias}.event_id`;
15323
15329
  }
15324
15330
 
15325
15331
  /**
@@ -16527,17 +16533,24 @@ export async function dispatchRunLedgerOutboxToLedgerBatchOnceWithClientScope(
16527
16533
  try {
16528
16534
  const selected = await client.query<{ run_id: string }>(
16529
16535
  `
16536
+ WITH heads AS MATERIALIZED (
16537
+ SELECT DISTINCT ON (pending.run_id)
16538
+ pending.run_id,
16539
+ pending.event_id,
16540
+ pending.event_type,
16541
+ pending.created_at,
16542
+ pending.next_attempt_at,
16543
+ pending.failed_at
16544
+ FROM ${tableName('outbox', options)} AS pending
16545
+ WHERE pending.delivered_at IS NULL
16546
+ AND ($1::text IS NULL OR pending.run_id = $1)
16547
+ ORDER BY pending.run_id, ${runLedgerOrderSql('pending')}
16548
+ )
16530
16549
  SELECT runs.run_id
16531
- FROM ${tableName('outbox', options)} AS head
16550
+ FROM heads AS head
16532
16551
  JOIN ${tableName('runs', options)} AS runs ON runs.run_id = head.run_id
16533
- WHERE ($1::text IS NULL OR runs.run_id = $1)
16534
- AND head.delivered_at IS NULL AND head.failed_at IS NULL
16552
+ WHERE head.failed_at IS NULL
16535
16553
  AND coalesce(head.next_attempt_at, head.created_at) <= now()
16536
- AND NOT EXISTS (
16537
- SELECT 1 FROM ${tableName('outbox', options)} AS predecessor
16538
- WHERE predecessor.run_id = head.run_id AND predecessor.delivered_at IS NULL
16539
- AND ${runLedgerPrecedesSql('predecessor', 'head')}
16540
- )
16541
16554
  AND NOT EXISTS (
16542
16555
  SELECT 1 FROM ${tableName('outbox', options)} AS active
16543
16556
  WHERE active.run_id = runs.run_id AND active.delivered_at IS NULL
@@ -185,6 +185,101 @@ const EXPLICIT_SECRET_VALUE_PATTERNS = [
185
185
  /\bhttps?:\/\/[^/\s'"]+:[^@\s'"]+@[^/\s'"]+/i,
186
186
  ];
187
187
 
188
+ const EMAIL_LOCAL_CHAR_RE = /[A-Za-z0-9.!#$%&'*+/=?^_`{|}~-]/;
189
+ const EMAIL_DOMAIN_RE =
190
+ /^[A-Za-z0-9](?:[A-Za-z0-9.-]{0,251}[A-Za-z0-9])?\.[A-Za-z]{2,63}\b/;
191
+
192
+ function isEmailLocalPart(value: string, start: number, end: number): boolean {
193
+ let localStart = start;
194
+ while (localStart > 0 && EMAIL_LOCAL_CHAR_RE.test(value[localStart - 1]!)) {
195
+ localStart -= 1;
196
+ }
197
+ let localEnd = end;
198
+ while (
199
+ localEnd < value.length &&
200
+ EMAIL_LOCAL_CHAR_RE.test(value[localEnd]!)
201
+ ) {
202
+ localEnd += 1;
203
+ }
204
+ return (
205
+ localStart < localEnd &&
206
+ localEnd - localStart <= 64 &&
207
+ value[localEnd] === '@' &&
208
+ EMAIL_DOMAIN_RE.test(value.slice(localEnd + 1))
209
+ );
210
+ }
211
+
212
+ function isPublicEmailIdentifier(
213
+ value: string,
214
+ start: number,
215
+ end: number,
216
+ patternIndex: number,
217
+ options: SecretRedactionOptions,
218
+ ): boolean {
219
+ // Only self-identifying token prefixes can also be valid email local parts.
220
+ // Keep strict telemetry redaction and URL credential detection unchanged.
221
+ return (
222
+ options.redactHighEntropy === false &&
223
+ patternIndex < 6 &&
224
+ isEmailLocalPart(value, start, end)
225
+ );
226
+ }
227
+
228
+ function hasExplicitSecretValue(
229
+ value: string,
230
+ options: SecretRedactionOptions,
231
+ ): boolean {
232
+ return EXPLICIT_SECRET_VALUE_PATTERNS.some((pattern, patternIndex) => {
233
+ if (!pattern.test(value)) return false;
234
+ if (options.redactHighEntropy !== false || patternIndex >= 6) return true;
235
+ const globalPattern = new RegExp(pattern.source, `${pattern.flags}g`);
236
+ for (const match of value.matchAll(globalPattern)) {
237
+ if (
238
+ !isPublicEmailIdentifier(
239
+ value,
240
+ match.index,
241
+ match.index + match[0].length,
242
+ patternIndex,
243
+ options,
244
+ )
245
+ ) {
246
+ return true;
247
+ }
248
+ }
249
+ return false;
250
+ });
251
+ }
252
+
253
+ function redactExplicitSecretsInText(
254
+ value: string,
255
+ options: SecretRedactionOptions,
256
+ ): string {
257
+ let redacted = value;
258
+ for (const [
259
+ patternIndex,
260
+ pattern,
261
+ ] of EXPLICIT_SECRET_VALUE_PATTERNS.entries()) {
262
+ const globalPattern = new RegExp(pattern.source, `${pattern.flags}g`);
263
+ let next = '';
264
+ let end = 0;
265
+ for (const match of redacted.matchAll(globalPattern)) {
266
+ next += redacted.slice(end, match.index);
267
+ next += isPublicEmailIdentifier(
268
+ redacted,
269
+ match.index,
270
+ match.index + match[0].length,
271
+ patternIndex,
272
+ options,
273
+ )
274
+ ? match[0]
275
+ : REDACTED_VALUE;
276
+ end = match.index + match[0].length;
277
+ }
278
+ redacted = next + redacted.slice(end);
279
+ }
280
+ return redacted;
281
+ }
282
+
188
283
  const HIGH_ENTROPY_VALUE_PATTERN = /\b[A-Za-z0-9+/=_-]{32,}\b/g;
189
284
  // A public hostname can legitimately contain a generated label longer than the
190
285
  // high-entropy threshold (for example, a per-run probe hostname). Preserve the
@@ -275,7 +370,7 @@ function isPublicResultSafeTokenKey(
275
370
  }
276
371
 
277
372
  function maybeRedactString(value: string, options: SecretRedactionOptions) {
278
- if (EXPLICIT_SECRET_VALUE_PATTERNS.some((pattern) => pattern.test(value))) {
373
+ if (hasExplicitSecretValue(value, options)) {
279
374
  return REDACTED_VALUE;
280
375
  }
281
376
  const publicHttpUrl = inspectStandalonePublicHttpUrl(value);
@@ -304,7 +399,7 @@ export function redactSecretsInValue<T>(
304
399
  options: SecretRedactionOptions = {},
305
400
  ): T {
306
401
  if (typeof value === 'string') {
307
- if (EXPLICIT_SECRET_VALUE_PATTERNS.some((pattern) => pattern.test(value))) {
402
+ if (hasExplicitSecretValue(value, options)) {
308
403
  return REDACTED_VALUE as T;
309
404
  }
310
405
  if (isPublicResultTransferUrl(value, parentKey, options)) return value as T;
@@ -348,7 +443,7 @@ export function redactSecretsInValue<T>(
348
443
  itemParentKey: string | undefined,
349
444
  ): unknown => {
350
445
  if (typeof item !== 'string') return item;
351
- if (EXPLICIT_SECRET_VALUE_PATTERNS.some((pattern) => pattern.test(item))) {
446
+ if (hasExplicitSecretValue(item, options)) {
352
447
  return REDACTED_VALUE;
353
448
  }
354
449
  if (isPublicResultTransferUrl(item, itemParentKey, options)) return item;
@@ -444,11 +539,10 @@ export function redactSecretsInJsonText(
444
539
  2,
445
540
  );
446
541
  } catch {
447
- let redacted = text;
448
- for (const pattern of EXPLICIT_SECRET_VALUE_PATTERNS) {
449
- redacted = redacted.replace(pattern, REDACTED_VALUE);
450
- }
451
- return maybeRedactString(redacted, options);
542
+ return maybeRedactString(
543
+ redactExplicitSecretsInText(text, options),
544
+ options,
545
+ );
452
546
  }
453
547
  }
454
548
 
package/dist/cli/index.js CHANGED
@@ -3051,7 +3051,7 @@ var SDK_RELEASE = {
3051
3051
  // getters keep their established compatibility behavior.
3052
3052
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
3053
3053
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
3054
- version: "0.3.168",
3054
+ version: "0.3.170",
3055
3055
  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.",
3056
3056
  packageCapabilities: {
3057
3057
  updatePreferences: 1
@@ -3046,7 +3046,7 @@ var SDK_RELEASE = {
3046
3046
  // getters keep their established compatibility behavior.
3047
3047
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
3048
3048
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
3049
- version: "0.3.168",
3049
+ version: "0.3.170",
3050
3050
  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.",
3051
3051
  packageCapabilities: {
3052
3052
  updatePreferences: 1
package/dist/index.js CHANGED
@@ -864,7 +864,7 @@ var SDK_RELEASE = {
864
864
  // getters keep their established compatibility behavior.
865
865
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
866
866
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
867
- version: "0.3.168",
867
+ version: "0.3.170",
868
868
  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.",
869
869
  packageCapabilities: {
870
870
  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.168",
771
+ version: "0.3.170",
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
@@ -64,6 +64,7 @@
64
64
  "dist/bundling-sources/shared_libs/monitors/provider-callback-verification.ts",
65
65
  "dist/bundling-sources/shared_libs/monitors/provider-monitor-batch-operation.ts",
66
66
  "dist/bundling-sources/shared_libs/monitors/provider-monitor-capability.ts",
67
+ "dist/bundling-sources/shared_libs/monitors/provider-monitor-conflicts.ts",
67
68
  "dist/bundling-sources/shared_libs/monitors/provider-monitor-consumer-health.ts",
68
69
  "dist/bundling-sources/shared_libs/monitors/provider-monitor-control-plane-capability.ts",
69
70
  "dist/bundling-sources/shared_libs/monitors/provider-monitor-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.168";
152
+ readonly version: "0.3.170";
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.168";
152
+ readonly version: "0.3.170";
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.168",
77
+ version: "0.3.170",
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.168",
51
+ version: "0.3.170",
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.168",
3
+ "version": "0.3.170",
4
4
  "description": "GTM data CLI and TypeScript SDK for coding agents",
5
5
  "license": "MIT",
6
6
  "homepage": "https://code.deepline.com",