deepline 0.3.174 → 0.3.176

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.
@@ -107,6 +107,7 @@ import type { PlayCompilerManifest } from '../../shared_libs/plays/compiler-mani
107
107
  import type { EnrichCompiledConfig } from './cli/enrich-play-compiler.js';
108
108
  import { RUNTIME_ENVIRONMENT_TOKEN_HEADER } from '../../shared_libs/play-runtime/coordinator-headers.js';
109
109
  import { resolveTheirstackClientTimeoutMs } from '../../shared_libs/integration-contracts/theirstack/execution-policy.js';
110
+ import { resolveWaterfallClientTimeoutMs } from '../../shared_libs/integration-contracts/waterfall/execution-policy.js';
110
111
  import {
111
112
  BETTERCONTACT_LAUNCHER_CLIENT_TIMEOUT_MS,
112
113
  usesExtendedBetterContactLauncherBudget,
@@ -724,6 +725,8 @@ function resolveToolExecuteTimeoutMs(
724
725
  input,
725
726
  );
726
727
  if (theirstackTimeoutMs !== null) return theirstackTimeoutMs;
728
+ const waterfallTimeoutMs = resolveWaterfallClientTimeoutMs(normalized);
729
+ if (waterfallTimeoutMs !== null) return waterfallTimeoutMs;
727
730
  if (usesExtendedBetterContactLauncherBudget(normalized)) {
728
731
  return BETTERCONTACT_LAUNCHER_CLIENT_TIMEOUT_MS;
729
732
  }
@@ -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.174',
203
+ version: '0.3.176',
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: {
@@ -1,3 +1,4 @@
1
1
  export * from './bettercontact/execution-policy';
2
2
  export * from './theirstack/execution-policy';
3
+ export * from './waterfall/execution-policy';
3
4
  export * from './monitor-input-contract';
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Waterfall launcher responses include a bounded launch-and-poll window. The
3
+ * SDK default is only 60 seconds, which can abandon a valid prospector job
4
+ * while the server is still polling it. Keep this budget specific to the
5
+ * launcher instead of widening every integration request.
6
+ */
7
+ export const WATERFALL_PROSPECTOR_CLIENT_TIMEOUT_MS = 180_000;
8
+
9
+ const WATERFALL_PROSPECTOR_LAUNCHER_TOOL_IDS = new Set([
10
+ 'prospector',
11
+ 'deepline_native_prospector',
12
+ ]);
13
+
14
+ export function resolveWaterfallClientTimeoutMs(toolId: string): number | null {
15
+ return WATERFALL_PROSPECTOR_LAUNCHER_TOOL_IDS.has(toolId.trim().toLowerCase())
16
+ ? WATERFALL_PROSPECTOR_CLIENT_TIMEOUT_MS
17
+ : null;
18
+ }
@@ -2,3 +2,4 @@ export * from './column-names';
2
2
  export * from './contracts';
3
3
  export * from './sheet-contract';
4
4
  export * from './runtime-sheet-storage-contract';
5
+ export * from './runtime-sheet-orm';
@@ -0,0 +1,489 @@
1
+ /**
2
+ * Typed owner for the physical Runtime Sheet table contract.
3
+ *
4
+ * This is intentionally a small ORM seam, not a general SQL builder. It owns
5
+ * the facts that every Runtime Sheet writer needs to agree on: which columns
6
+ * belong to Deepline, what shape they have, how readiness is determined, and
7
+ * which additive/default repairs are safe. Application and worker code supply
8
+ * the query adapter and keep their transport-specific concerns outside this
9
+ * Module.
10
+ */
11
+
12
+ export const RUNTIME_SHEET_VERSION_SEQUENCE = '_deepline_sheet_version_seq';
13
+
14
+ export const RUNTIME_SHEET_CELL_META_COLUMN = {
15
+ name: '_cell_meta',
16
+ dataType: 'jsonb',
17
+ nullable: 'NO',
18
+ defaultSql: "'{}'::jsonb",
19
+ } as const;
20
+
21
+ type RuntimeSheetNullable = 'YES' | 'NO';
22
+
23
+ export type RuntimeSheetColumnContract = {
24
+ name: string;
25
+ sqlType: string;
26
+ dataType: string;
27
+ nullable: RuntimeSheetNullable;
28
+ defaultSql: string | null;
29
+ defaultKind?: 'version_sequence';
30
+ /** A missing column can be added without guessing at customer data. */
31
+ repairable: boolean;
32
+ /** The column must have a default because writers intentionally omit it. */
33
+ requiresDefault: boolean;
34
+ };
35
+
36
+ export type RuntimeSheetColumnState = {
37
+ column_name: string;
38
+ data_type: string;
39
+ is_nullable: string;
40
+ column_default: string | null;
41
+ };
42
+
43
+ export type RuntimeSheetSchemaSnapshot = {
44
+ sequenceExists: boolean;
45
+ columns: ReadonlyMap<string, RuntimeSheetColumnState>;
46
+ };
47
+
48
+ export type RuntimeSheetSchemaReadiness = {
49
+ ready: boolean;
50
+ missingColumns: string[];
51
+ incompatibleColumns: string[];
52
+ missingDefaults: string[];
53
+ sequenceReady: boolean;
54
+ };
55
+
56
+ export type RuntimeSheetOrmQuery = <Row = Record<string, unknown>>(
57
+ sql: string,
58
+ params?: readonly unknown[],
59
+ ) => Promise<{ rows: Row[] }>;
60
+
61
+ export type RuntimeSheetOrm = {
62
+ inspect(): Promise<RuntimeSheetSchemaSnapshot>;
63
+ readiness(
64
+ snapshot?: RuntimeSheetSchemaSnapshot,
65
+ ): Promise<RuntimeSheetSchemaReadiness>;
66
+ repairOwnerMetadata(): Promise<void>;
67
+ isOwnerMetadataConstraintError(error: unknown): boolean;
68
+ };
69
+
70
+ export function isRuntimeSheetOwnerMetadataConstraintError(
71
+ error: unknown,
72
+ input: { table: string },
73
+ ): boolean {
74
+ if (!error || typeof error !== 'object') return false;
75
+ const record = error as Record<string, unknown>;
76
+ if (String(record.code ?? '') !== '23502') return false;
77
+ const table = typeof record.table === 'string' ? record.table : null;
78
+ const message = typeof record.message === 'string' ? record.message : '';
79
+ const tableMatches =
80
+ table === input.table || message.includes(`relation "${input.table}"`);
81
+ const column = readColumnName(error);
82
+ return (
83
+ tableMatches &&
84
+ column !== null &&
85
+ RUNTIME_SHEET_REPAIRABLE_COLUMNS.some(
86
+ (contract) => contract.name === column,
87
+ )
88
+ );
89
+ }
90
+
91
+ const RUNTIME_SHEET_COLUMN_CONTRACTS: readonly RuntimeSheetColumnContract[] = [
92
+ {
93
+ name: '_key',
94
+ sqlType: 'text',
95
+ dataType: 'text',
96
+ nullable: 'NO',
97
+ defaultSql: null,
98
+ repairable: false,
99
+ requiresDefault: false,
100
+ },
101
+ {
102
+ name: '_status',
103
+ sqlType: 'text NOT NULL',
104
+ dataType: 'text',
105
+ nullable: 'NO',
106
+ defaultSql: null,
107
+ repairable: false,
108
+ requiresDefault: false,
109
+ },
110
+ {
111
+ name: '_run_id',
112
+ sqlType: 'text',
113
+ dataType: 'text',
114
+ nullable: 'YES',
115
+ defaultSql: null,
116
+ repairable: true,
117
+ requiresDefault: false,
118
+ },
119
+ {
120
+ name: '_error',
121
+ sqlType: 'text',
122
+ dataType: 'text',
123
+ nullable: 'YES',
124
+ defaultSql: null,
125
+ repairable: true,
126
+ requiresDefault: false,
127
+ },
128
+ {
129
+ name: '_stage',
130
+ sqlType: 'text',
131
+ dataType: 'text',
132
+ nullable: 'YES',
133
+ defaultSql: null,
134
+ repairable: true,
135
+ requiresDefault: false,
136
+ },
137
+ {
138
+ name: '_provider',
139
+ sqlType: 'text',
140
+ dataType: 'text',
141
+ nullable: 'YES',
142
+ defaultSql: null,
143
+ repairable: true,
144
+ requiresDefault: false,
145
+ },
146
+ {
147
+ name: '_input_index',
148
+ sqlType: 'bigint',
149
+ dataType: 'bigint',
150
+ nullable: 'YES',
151
+ defaultSql: null,
152
+ repairable: true,
153
+ requiresDefault: false,
154
+ },
155
+ {
156
+ name: '_attempt_id',
157
+ sqlType: 'text',
158
+ dataType: 'text',
159
+ nullable: 'YES',
160
+ defaultSql: null,
161
+ repairable: true,
162
+ requiresDefault: false,
163
+ },
164
+ {
165
+ name: '_attempt_owner_run_id',
166
+ sqlType: 'text',
167
+ dataType: 'text',
168
+ nullable: 'YES',
169
+ defaultSql: null,
170
+ repairable: true,
171
+ requiresDefault: false,
172
+ },
173
+ {
174
+ name: '_attempt_expires_at',
175
+ sqlType: 'timestamptz',
176
+ dataType: 'timestamp with time zone',
177
+ nullable: 'YES',
178
+ defaultSql: null,
179
+ repairable: true,
180
+ requiresDefault: false,
181
+ },
182
+ {
183
+ name: '_attempt_seq',
184
+ sqlType: 'integer',
185
+ dataType: 'integer',
186
+ nullable: 'YES',
187
+ defaultSql: null,
188
+ repairable: true,
189
+ requiresDefault: false,
190
+ },
191
+ {
192
+ name: '_write_version',
193
+ sqlType: 'bigint NOT NULL DEFAULT 0',
194
+ dataType: 'bigint',
195
+ nullable: 'NO',
196
+ defaultSql: '0',
197
+ repairable: true,
198
+ requiresDefault: true,
199
+ },
200
+ {
201
+ name: '_writer_run_id',
202
+ sqlType: 'text',
203
+ dataType: 'text',
204
+ nullable: 'YES',
205
+ defaultSql: null,
206
+ repairable: true,
207
+ requiresDefault: false,
208
+ },
209
+ {
210
+ name: '_created_at',
211
+ sqlType: 'timestamptz NOT NULL DEFAULT now()',
212
+ dataType: 'timestamp with time zone',
213
+ nullable: 'NO',
214
+ defaultSql: 'now()',
215
+ repairable: true,
216
+ requiresDefault: true,
217
+ },
218
+ {
219
+ name: '_updated_at',
220
+ sqlType: 'timestamptz NOT NULL DEFAULT now()',
221
+ dataType: 'timestamp with time zone',
222
+ nullable: 'NO',
223
+ defaultSql: 'now()',
224
+ repairable: true,
225
+ requiresDefault: true,
226
+ },
227
+ {
228
+ name: '_version',
229
+ sqlType: 'bigint NOT NULL',
230
+ dataType: 'bigint',
231
+ nullable: 'NO',
232
+ defaultSql: null,
233
+ defaultKind: 'version_sequence',
234
+ repairable: true,
235
+ requiresDefault: true,
236
+ },
237
+ {
238
+ ...RUNTIME_SHEET_CELL_META_COLUMN,
239
+ sqlType: `jsonb NOT NULL DEFAULT ${RUNTIME_SHEET_CELL_META_COLUMN.defaultSql}`,
240
+ repairable: true,
241
+ requiresDefault: true,
242
+ },
243
+ ];
244
+
245
+ export const RUNTIME_SHEET_ATTEMPT_COLUMNS =
246
+ RUNTIME_SHEET_COLUMN_CONTRACTS.filter((column) =>
247
+ column.name.startsWith('_attempt_'),
248
+ ).map((column) => column.name);
249
+
250
+ export const RUNTIME_SHEET_WRITE_VERSION_COLUMNS =
251
+ RUNTIME_SHEET_COLUMN_CONTRACTS.filter(
252
+ (column) =>
253
+ column.name === '_write_version' || column.name === '_writer_run_id',
254
+ ).map((column) => column.name);
255
+
256
+ export const RUNTIME_SHEET_TIMESTAMP_COLUMNS =
257
+ RUNTIME_SHEET_COLUMN_CONTRACTS.filter(
258
+ (column) => column.name === '_created_at' || column.name === '_updated_at',
259
+ ).map((column) => column.name);
260
+
261
+ export const RUNTIME_SHEET_SYSTEM_COLUMNS = new Set(
262
+ RUNTIME_SHEET_COLUMN_CONTRACTS.map((column) => column.name),
263
+ );
264
+
265
+ const RUNTIME_SHEET_REPAIRABLE_COLUMNS = RUNTIME_SHEET_COLUMN_CONTRACTS.filter(
266
+ (column) => column.repairable,
267
+ );
268
+
269
+ export function runtimeSheetColumnContracts(): readonly RuntimeSheetColumnContract[] {
270
+ return RUNTIME_SHEET_COLUMN_CONTRACTS;
271
+ }
272
+
273
+ export function runtimeSheetRepairableColumnContracts(): readonly RuntimeSheetColumnContract[] {
274
+ return RUNTIME_SHEET_REPAIRABLE_COLUMNS;
275
+ }
276
+
277
+ export function isRuntimeSheetSystemColumn(columnName: string): boolean {
278
+ return RUNTIME_SHEET_SYSTEM_COLUMNS.has(columnName);
279
+ }
280
+
281
+ /**
282
+ * SQL projection for adapters that already have a larger relation-readiness
283
+ * query. The expected rows are generated from the contract, so adding an
284
+ * owner column expands this projection automatically instead of requiring a
285
+ * new field-specific boolean in every adapter.
286
+ */
287
+ export function runtimeSheetOwnerColumnsReadinessSql(input: {
288
+ schemaParam: string;
289
+ tableParam: string;
290
+ }): string {
291
+ const expectedRows = RUNTIME_SHEET_COLUMN_CONTRACTS.map(
292
+ (column) =>
293
+ `(${quoteLiteral(column.name)}, ${quoteLiteral(column.dataType)}, ${quoteLiteral(column.nullable)}, ${column.requiresDefault ? 'TRUE' : 'FALSE'})`,
294
+ ).join(',\n ');
295
+ return `(
296
+ SELECT count(*) = ${RUNTIME_SHEET_COLUMN_CONTRACTS.length}
297
+ FROM information_schema.columns AS actual
298
+ JOIN (VALUES
299
+ ${expectedRows}
300
+ ) AS expected(column_name, data_type, is_nullable, requires_default)
301
+ ON expected.column_name = actual.column_name
302
+ WHERE actual.table_schema = ${input.schemaParam}
303
+ AND actual.table_name = ${input.tableParam}
304
+ AND actual.data_type = expected.data_type
305
+ AND actual.is_nullable = expected.is_nullable
306
+ AND (NOT expected.requires_default OR actual.column_default IS NOT NULL)
307
+ )`;
308
+ }
309
+
310
+ export function isRuntimeSheetColumnReady(
311
+ contract: RuntimeSheetColumnContract,
312
+ state: RuntimeSheetColumnState | undefined,
313
+ ): boolean {
314
+ return (
315
+ state?.data_type === contract.dataType &&
316
+ state.is_nullable === contract.nullable &&
317
+ (!contract.requiresDefault || state.column_default != null)
318
+ );
319
+ }
320
+
321
+ export function evaluateRuntimeSheetSchema(
322
+ snapshot: RuntimeSheetSchemaSnapshot,
323
+ ): RuntimeSheetSchemaReadiness {
324
+ const missingColumns: string[] = [];
325
+ const incompatibleColumns: string[] = [];
326
+ const missingDefaults: string[] = [];
327
+
328
+ for (const contract of RUNTIME_SHEET_COLUMN_CONTRACTS) {
329
+ const state = snapshot.columns.get(contract.name);
330
+ if (!state) {
331
+ missingColumns.push(contract.name);
332
+ continue;
333
+ }
334
+ if (
335
+ state.data_type !== contract.dataType ||
336
+ state.is_nullable !== contract.nullable
337
+ ) {
338
+ incompatibleColumns.push(contract.name);
339
+ continue;
340
+ }
341
+ if (contract.requiresDefault && state.column_default == null) {
342
+ missingDefaults.push(contract.name);
343
+ }
344
+ }
345
+
346
+ const sequenceReady = snapshot.sequenceExists;
347
+ return {
348
+ ready:
349
+ missingColumns.length === 0 &&
350
+ incompatibleColumns.length === 0 &&
351
+ missingDefaults.length === 0 &&
352
+ sequenceReady,
353
+ missingColumns,
354
+ incompatibleColumns,
355
+ missingDefaults,
356
+ sequenceReady,
357
+ };
358
+ }
359
+
360
+ function quoteIdentifier(identifier: string): string {
361
+ return `"${identifier.replace(/"/g, '""')}"`;
362
+ }
363
+
364
+ function quoteLiteral(value: string): string {
365
+ return `'${value.replace(/'/g, "''")}'`;
366
+ }
367
+
368
+ function relation(schema: string, table: string): string {
369
+ return `${quoteIdentifier(schema)}.${quoteIdentifier(table)}`;
370
+ }
371
+
372
+ function versionDefaultSql(schema: string): string {
373
+ return `nextval(${quoteLiteral(`${schema}.${RUNTIME_SHEET_VERSION_SEQUENCE}`)}::regclass)`;
374
+ }
375
+
376
+ function columnDefaultSql(
377
+ column: RuntimeSheetColumnContract,
378
+ schema: string,
379
+ ): string | null {
380
+ if (column.defaultKind === 'version_sequence') {
381
+ return versionDefaultSql(schema);
382
+ }
383
+ return column.defaultSql;
384
+ }
385
+
386
+ function readColumnName(error: unknown): string | null {
387
+ if (!error || typeof error !== 'object') return null;
388
+ const record = error as Record<string, unknown>;
389
+ if (typeof record.column === 'string') return record.column;
390
+ if (typeof record.message !== 'string') return null;
391
+ return /column ['"]([^'"]+)['"]/i.exec(record.message)?.[1] ?? null;
392
+ }
393
+
394
+ export function createRuntimeSheetOrm(input: {
395
+ schema: string;
396
+ table: string;
397
+ query: RuntimeSheetOrmQuery;
398
+ }): RuntimeSheetOrm {
399
+ const tableReference = relation(input.schema, input.table);
400
+ const versionSequenceReference = `${input.schema}.${RUNTIME_SHEET_VERSION_SEQUENCE}`;
401
+
402
+ async function inspect(): Promise<RuntimeSheetSchemaSnapshot> {
403
+ const [columns, sequence] = await Promise.all([
404
+ input.query<RuntimeSheetColumnState>(
405
+ `
406
+ SELECT column_name, data_type, is_nullable, column_default
407
+ FROM information_schema.columns
408
+ WHERE table_schema = $1
409
+ AND table_name = $2
410
+ AND column_name = ANY($3::text[])
411
+ `,
412
+ [input.schema, input.table, [...RUNTIME_SHEET_SYSTEM_COLUMNS]],
413
+ ),
414
+ input.query<{ exists: boolean }>(
415
+ 'SELECT to_regclass($1) IS NOT NULL AS exists',
416
+ [versionSequenceReference],
417
+ ),
418
+ ]);
419
+ return {
420
+ sequenceExists: sequence.rows[0]?.exists === true,
421
+ columns: new Map(columns.rows.map((row) => [row.column_name, row])),
422
+ };
423
+ }
424
+
425
+ async function readiness(
426
+ snapshot?: RuntimeSheetSchemaSnapshot,
427
+ ): Promise<RuntimeSheetSchemaReadiness> {
428
+ return evaluateRuntimeSheetSchema(snapshot ?? (await inspect()));
429
+ }
430
+
431
+ async function repairOwnerMetadata(): Promise<void> {
432
+ await input.query(`
433
+ ALTER TABLE ${tableReference}
434
+ ${RUNTIME_SHEET_REPAIRABLE_COLUMNS.map(
435
+ (column) =>
436
+ `ADD COLUMN IF NOT EXISTS ${quoteIdentifier(column.name)} ${column.dataType}`,
437
+ ).join(',\n ')}
438
+ `);
439
+
440
+ const defaultedColumns = RUNTIME_SHEET_REPAIRABLE_COLUMNS.filter(
441
+ (column) => column.requiresDefault,
442
+ );
443
+ await input.query(`
444
+ ALTER TABLE ${tableReference}
445
+ ${defaultedColumns
446
+ .map(
447
+ (column) =>
448
+ `ALTER COLUMN ${quoteIdentifier(column.name)} SET DEFAULT ${columnDefaultSql(column, input.schema)}`,
449
+ )
450
+ .join(',\n ')}
451
+ `);
452
+
453
+ for (const column of defaultedColumns) {
454
+ const defaultSql = columnDefaultSql(column, input.schema);
455
+ if (defaultSql === null) continue;
456
+ await input.query(`
457
+ UPDATE ${tableReference}
458
+ SET ${quoteIdentifier(column.name)} = ${defaultSql}
459
+ WHERE ${quoteIdentifier(column.name)} IS NULL
460
+ `);
461
+ }
462
+
463
+ const nonNullableColumns = RUNTIME_SHEET_REPAIRABLE_COLUMNS.filter(
464
+ (column) => column.nullable === 'NO',
465
+ );
466
+ await input.query(`
467
+ ALTER TABLE ${tableReference}
468
+ ${nonNullableColumns
469
+ .map(
470
+ (column) =>
471
+ `ALTER COLUMN ${quoteIdentifier(column.name)} SET NOT NULL`,
472
+ )
473
+ .join(',\n ')}
474
+ `);
475
+ }
476
+
477
+ function isOwnerMetadataConstraintError(error: unknown): boolean {
478
+ return isRuntimeSheetOwnerMetadataConstraintError(error, {
479
+ table: input.table,
480
+ });
481
+ }
482
+
483
+ return {
484
+ inspect,
485
+ readiness,
486
+ repairOwnerMetadata,
487
+ isOwnerMetadataConstraintError,
488
+ };
489
+ }
@@ -1,16 +1,7 @@
1
- /**
2
- * Canonical contract for the runtime sheet cell-provenance column.
3
- *
4
- * Runtime sheet writers intentionally omit this column and rely on the
5
- * database default. Keep the type, nullability, and default in one Module so
6
- * table creation, readiness probes, migrations, and diagnostics cannot drift.
7
- */
8
- export const RUNTIME_SHEET_CELL_META_COLUMN = {
9
- name: '_cell_meta',
10
- dataType: 'jsonb',
11
- nullable: 'NO',
12
- defaultSql: "'{}'::jsonb",
13
- } as const;
1
+ /** Compatibility seam for callers that used the cell-meta-only contract. */
2
+ import { RUNTIME_SHEET_CELL_META_COLUMN } from './runtime-sheet-orm';
3
+
4
+ export { RUNTIME_SHEET_CELL_META_COLUMN } from './runtime-sheet-orm';
14
5
 
15
6
  export type RuntimeSheetCellMetaColumnState = {
16
7
  data_type: string;