deepline 0.3.286 → 0.3.287

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/bundling-sources/sdk/src/client.ts +47 -24
  2. package/dist/bundling-sources/sdk/src/index.ts +4 -0
  3. package/dist/bundling-sources/sdk/src/release.ts +7 -1
  4. package/dist/bundling-sources/sdk/src/types.ts +36 -0
  5. package/dist/bundling-sources/shared_libs/play-runtime/cell-provenance.ts +16 -0
  6. package/dist/bundling-sources/shared_libs/play-runtime/live-state-contract.ts +2 -0
  7. package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +38 -3
  8. package/dist/bundling-sources/shared_libs/play-runtime/run-observation.ts +12 -0
  9. package/dist/bundling-sources/shared_libs/play-runtime/run-snapshot-stream.ts +3 -0
  10. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +43 -1
  11. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +19 -0
  12. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +88 -1
  13. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +3 -1
  14. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-event-search.ts +53 -25
  15. package/dist/bundling-sources/shared_libs/play-runtime/step-progress.ts +83 -1
  16. package/dist/bundling-sources/shared_libs/play-runtime/worker-api-types.ts +2 -0
  17. package/dist/cli/index.js +579 -628
  18. package/dist/cli/index.mjs +456 -506
  19. package/dist/{client-B7ic-kY5.d.ts → client-FR_u0dxT.d.ts} +38 -1
  20. package/dist/{client-Dzf_KovW.d.mts → client-phK1Ixcl.d.mts} +38 -1
  21. package/dist/index.d.mts +2 -2
  22. package/dist/index.d.ts +2 -2
  23. package/dist/index.js +48 -25
  24. package/dist/index.mjs +48 -25
  25. package/dist/install-integrity.json +2 -2
  26. package/dist/plays/run-request.d.mts +1 -1
  27. package/dist/plays/run-request.d.ts +1 -1
  28. package/dist/release.d.mts +5 -1
  29. package/dist/release.d.ts +5 -1
  30. package/dist/release.js +6 -1
  31. package/dist/release.mjs +6 -1
  32. package/package.json +1 -1
@@ -22,6 +22,11 @@ const EVENTS_TABLE = 'runtime_observability.events';
22
22
  const RUNTIME_EVENT_LOG_DEDUPE_INDEX = 'runtime_events_log_attempt_digest_idx';
23
23
  const RUNTIME_EVENT_LOG_POSITION_INDEX =
24
24
  'runtime_events_log_attempt_position_idx';
25
+ const RUNTIME_EVENT_LOG_POSITION_CHANNELS = [
26
+ 'worker',
27
+ 'replay',
28
+ 'envelope',
29
+ ] as const;
25
30
  const RUNTIME_EVENT_LOG_LEGACY_INDEX =
26
31
  'runtime_events_legacy_log_normalize_idx';
27
32
  const RUNTIME_EVENT_LOG_BODY_PAGE_INDEX = 'runtime_events_run_log_body_seq_idx';
@@ -51,7 +56,7 @@ const RUNTIME_EVENT_LOG_POSITION_EXPRESSION = `jsonb_set(
51
56
  // Migration readiness tracks this projection body independently of scheduler
52
57
  // wire and schema versions. Bump when a changed trigger must be reinstalled.
53
58
  export const RUNTIME_EVENT_SEARCH_OUTBOX_PROJECTION_MARKER =
54
- 'runtime-event-search-log-context-v13-index-first-position';
59
+ 'runtime-event-search-log-context-v14-btree-position';
55
60
  export const RUNTIME_EVENT_CANONICAL_RETENTION_INDEX_READY_MARKER =
56
61
  'runtime-event-canonical-retention-v1';
57
62
  export const RUNTIME_EVENTS_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
@@ -1263,10 +1268,7 @@ export async function attachRuntimeEventSearchTriggers(
1263
1268
  'logPositions', jsonb_build_object(log_channel, log_position));
1264
1269
  position_alias := jsonb_build_object(
1265
1270
  'producerAttempt', (item->>'producerAttempt')::integer,
1266
- '_runtimeScope:' || ${quoteLiteral(scope)}, true,
1267
- '_orgId:' || run_org, true,
1268
- '_runId:' || observation.run_id, true)
1269
- || jsonb_build_object(log_channel, log_position);
1271
+ log_channel, log_position);
1270
1272
  matched_event_id := NULL;
1271
1273
  position_event_ids := ARRAY[
1272
1274
  legacy_position_prefix || log_position::text,
@@ -1293,24 +1295,26 @@ export async function attachRuntimeEventSearchTriggers(
1293
1295
  ORDER BY event.event_id
1294
1296
  LIMIT 1;
1295
1297
  IF matched_event_id IS NULL THEN
1296
- -- A transport may already be aliased onto an event whose
1297
- -- primary key belongs to another channel. Find that alias
1298
- -- independently of content so changed bytes still fail the
1299
- -- immutable-position check below. Materialize the scoped
1300
- -- position lookup before applying run filters: otherwise
1301
- -- the planner can choose the run-history index and inspect
1302
- -- every old log for each incoming position, even with a
1303
- -- custom plan. The indexed document already binds scope,
1304
- -- org, run, attempt and channel position; retain the outer
1305
- -- identity checks as defense in depth.
1298
+ -- Pack scope, organization and run into one indexed key.
1299
+ -- The materialization keeps generic plans from choosing a
1300
+ -- run-history scan based on the outer identity checks.
1306
1301
  WITH position_matches AS MATERIALIZED (
1302
+ ${RUNTIME_EVENT_LOG_POSITION_CHANNELS.map(
1303
+ (channel) => `
1307
1304
  SELECT event.event_id, event.source_digest, event.search_doc,
1308
- event.runtime_scope, event.org_id, event.run_id, event.log_seq
1305
+ event.log_seq, event.runtime_scope, event.org_id, event.run_id
1309
1306
  FROM runtime_observability.events AS event
1310
- WHERE event.kind = ${RUN_EVENT_KIND_SQL_LITERAL.logAppended}
1307
+ WHERE log_channel = '${channel}'
1308
+ AND ARRAY[event.runtime_scope, event.org_id, event.run_id]
1309
+ = ARRAY[${quoteLiteral(scope)}, run_org, observation.run_id]
1310
+ AND event.kind = ${RUN_EVENT_KIND_SQL_LITERAL.logAppended}
1311
1311
  AND event.search_doc ? 'producerAttempt'
1312
1312
  AND event.search_doc ? 'logPositions'
1313
- AND (${RUNTIME_EVENT_LOG_POSITION_EXPRESSION.replaceAll('search_doc', 'event.search_doc')}) @> position_alias
1313
+ AND (event.search_doc->'logPositions') ? '${channel}'
1314
+ AND event.search_doc->'producerAttempt' = position_alias->'producerAttempt'
1315
+ AND event.search_doc->'logPositions'->'${channel}' = position_alias->'${channel}'
1316
+ `,
1317
+ ).join(' UNION ALL ')}
1314
1318
  )
1315
1319
  SELECT event.event_id, event.source_digest, event.search_doc
1316
1320
  INTO matched_event_id, existing_digest, matched_doc
@@ -2034,8 +2038,27 @@ const RUNTIME_EVENT_LOG_POSITION_INDEX_SPEC: RuntimeEventIndexExpectation = {
2034
2038
  WHERE ${RUNTIME_EVENT_LOG_POSITION_PREDICATE}`,
2035
2039
  };
2036
2040
 
2041
+ const RUNTIME_EVENT_LOG_CHANNEL_POSITION_INDEXES: RuntimeEventIndexExpectation[] =
2042
+ RUNTIME_EVENT_LOG_POSITION_CHANNELS.map((channel) => ({
2043
+ name: `runtime_events_log_${channel}_position_idx`,
2044
+ keys: [
2045
+ 'ARRAY[runtime_scope, org_id, run_id]',
2046
+ "(search_doc -> 'producerAttempt'::text)",
2047
+ `(search_doc -> 'logPositions'::text) -> '${channel}'::text`,
2048
+ ],
2049
+ predicate: `${RUNTIME_EVENT_LOG_POSITION_PREDICATE}
2050
+ AND (search_doc->'logPositions') ? '${channel}'`,
2051
+ createSql: `CREATE INDEX CONCURRENTLY IF NOT EXISTS runtime_events_log_${channel}_position_idx
2052
+ ON runtime_observability.events
2053
+ ((ARRAY[runtime_scope, org_id, run_id]), (search_doc->'producerAttempt'),
2054
+ (search_doc->'logPositions'->'${channel}'))
2055
+ WHERE ${RUNTIME_EVENT_LOG_POSITION_PREDICATE}
2056
+ AND (search_doc->'logPositions') ? '${channel}'`,
2057
+ }));
2058
+
2037
2059
  const RUNTIME_EVENT_QUERY_INDEXES: RuntimeEventIndexExpectation[] = [
2038
2060
  RUNTIME_EVENT_LOG_POSITION_INDEX_SPEC,
2061
+ ...RUNTIME_EVENT_LOG_CHANNEL_POSITION_INDEXES,
2039
2062
  {
2040
2063
  name: RUNTIME_EVENT_LOG_DEDUPE_INDEX,
2041
2064
  keys: [
@@ -2167,20 +2190,25 @@ async function prepareRuntimeEventQueryIndex(
2167
2190
  export async function hasRuntimeEventLogPositionIndex(
2168
2191
  client: PostgresSchedulerQueryClient,
2169
2192
  ): Promise<boolean> {
2170
- return hasRuntimeEventQueryIndex(
2171
- client,
2193
+ for (const expected of [
2172
2194
  RUNTIME_EVENT_LOG_POSITION_INDEX_SPEC,
2173
- );
2195
+ ...RUNTIME_EVENT_LOG_CHANNEL_POSITION_INDEXES,
2196
+ ]) {
2197
+ if (!(await hasRuntimeEventQueryIndex(client, expected))) return false;
2198
+ }
2199
+ return true;
2174
2200
  }
2175
2201
 
2176
- /** Prepare the existing position index before replacing the projection body. */
2202
+ /** Prepare old and new position indexes before replacing the projection body. */
2177
2203
  export async function prepareRuntimeEventLogPositionIndex(
2178
2204
  client: PostgresSchedulerQueryClient,
2179
2205
  ): Promise<void> {
2180
- await prepareRuntimeEventQueryIndex(
2181
- client,
2206
+ for (const expected of [
2182
2207
  RUNTIME_EVENT_LOG_POSITION_INDEX_SPEC,
2183
- );
2208
+ ...RUNTIME_EVENT_LOG_CHANNEL_POSITION_INDEXES,
2209
+ ]) {
2210
+ await prepareRuntimeEventQueryIndex(client, expected);
2211
+ }
2184
2212
  }
2185
2213
 
2186
2214
  /** Build only operator-owned indexes, concurrently and outside a transaction. */
@@ -1,4 +1,17 @@
1
- import type { PlayStaticSubstep } from '../plays/static-pipeline';
1
+ import {
2
+ getTopLevelPipelineSubsteps,
3
+ type PlayStaticPipeline,
4
+ type PlayStaticSubstep,
5
+ } from '../plays/static-pipeline';
6
+ import {
7
+ docflowOutputRoots,
8
+ flattenSubsteps,
9
+ resolveDocflowOwnerDataset,
10
+ } from '../plays/docflow-binding-owner';
11
+ import {
12
+ normalizePlayConditionCounts,
13
+ type PlayConditionCounts,
14
+ } from './cell-provenance';
2
15
  import {
3
16
  normalizePlayDocflowNodeIoState,
4
17
  type PlayDocflowNodeIoState,
@@ -32,6 +45,7 @@ export type PlayVisualNodeProgress = {
32
45
  completedAt?: number | null;
33
46
  artifactTableNamespace?: string | null;
34
47
  nodeIo?: PlayDocflowNodeIoState;
48
+ conditionCounts?: PlayConditionCounts;
35
49
  };
36
50
 
37
51
  export type PlayVisualNodeState = {
@@ -162,6 +176,9 @@ export function normalizePlayVisualNodeProgressMap(
162
176
  ? null
163
177
  : undefined;
164
178
  const nodeIo = normalizePlayDocflowNodeIoState(rawProgress.nodeIo);
179
+ const conditionCounts = normalizePlayConditionCounts(
180
+ rawProgress.conditionCounts,
181
+ );
165
182
 
166
183
  normalized[nodeId] = {
167
184
  ...(completed !== undefined ? { completed } : {}),
@@ -180,11 +197,76 @@ export function normalizePlayVisualNodeProgressMap(
180
197
  ? { artifactTableNamespace }
181
198
  : {}),
182
199
  ...(nodeIo ? { nodeIo } : {}),
200
+ ...(conditionCounts ? { conditionCounts } : {}),
183
201
  };
184
202
  }
185
203
  return normalized;
186
204
  }
187
205
 
206
+ /** Join observed predicates to authored decision nodes by their owning column. */
207
+ export function projectDocflowConditionCounts(
208
+ pipeline: PlayStaticPipeline | null | undefined,
209
+ sheets: readonly {
210
+ tableNamespace: string;
211
+ conditions?: Record<string, PlayConditionCounts>;
212
+ }[],
213
+ ): Record<string, PlayConditionCounts> {
214
+ if (!pipeline?.docflow) return {};
215
+ const substeps = getTopLevelPipelineSubsteps(pipeline);
216
+ const namespaces = flattenSubsteps(substeps)
217
+ .filter((step) => step.type === 'dataset' || step.type === 'csv')
218
+ .map((step) =>
219
+ step.type === 'dataset'
220
+ ? (step.tableNamespace ?? step.field)
221
+ : step.field,
222
+ )
223
+ .sort((a, b) => b.length - a.length);
224
+ const decisions = new Set(
225
+ pipeline.docflow.nodes
226
+ .filter((node) => node.kind === 'decision' && !node.sketch)
227
+ .map((node) => node.id),
228
+ );
229
+ const result: Record<string, PlayConditionCounts> = {};
230
+ for (const binding of pipeline.docflow.bindings) {
231
+ if (!decisions.has(binding.nodeId)) continue;
232
+ const owner = resolveDocflowOwnerDataset(binding, substeps).substep;
233
+ if (!owner) continue;
234
+ const namespace = owner.tableNamespace ?? owner.field;
235
+ const roots = docflowOutputRoots(binding);
236
+ // Several output columns can carry different predicates. Do not combine them.
237
+ if (roots.length !== 1) continue;
238
+ const root = roots[0]!;
239
+ const column = owner.sheetContract?.columns.find(
240
+ (candidate) =>
241
+ candidate.field === root ||
242
+ candidate.id === root ||
243
+ candidate.sqlName === root,
244
+ );
245
+ const key = column?.sqlName ?? root;
246
+ for (const sheet of sheets) {
247
+ const sheetOwner = namespaces.find(
248
+ (candidate) =>
249
+ sheet.tableNamespace === candidate ||
250
+ sheet.tableNamespace.startsWith(`${candidate}_`),
251
+ );
252
+ if (sheetOwner !== namespace) continue;
253
+ const counts = normalizePlayConditionCounts(
254
+ sheet.conditions?.[key] ?? sheet.conditions?.[root],
255
+ );
256
+ if (!counts) continue;
257
+ const current = result[`docflow:${binding.nodeId}`] ?? {
258
+ true: 0,
259
+ false: 0,
260
+ };
261
+ result[`docflow:${binding.nodeId}`] = {
262
+ true: current.true + counts.true,
263
+ false: current.false + counts.false,
264
+ };
265
+ }
266
+ }
267
+ return result;
268
+ }
269
+
188
270
  export function applyVisualNodeProgress(input: {
189
271
  nodeStates: readonly PlayVisualNodeState[];
190
272
  progressByNode: PlayVisualNodeProgressMap;
@@ -1,5 +1,6 @@
1
1
  import type { PlayDocflowNodeIoState } from './docflow-node-io';
2
2
  import type { PlayMapRowActivitySource } from './run-ledger';
3
+ import type { PlayConditionCounts } from './cell-provenance';
3
4
 
4
5
  export type PlayStagedFileRef = {
5
6
  storageKind: 'r2';
@@ -33,6 +34,7 @@ export type PlayVisualNodeProgress = {
33
34
  completedAt?: number;
34
35
  artifactTableNamespace?: string | null;
35
36
  nodeIo?: PlayDocflowNodeIoState;
37
+ conditionCounts?: PlayConditionCounts;
36
38
  };
37
39
 
38
40
  export type PlayVisualNodeProgressMap = Record<string, PlayVisualNodeProgress>;