deepline 0.3.290 → 0.3.292

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 (46) hide show
  1. package/dist/bundling-sources/sdk/src/client.ts +8 -6
  2. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  3. package/dist/bundling-sources/sdk/src/runs/overview.ts +16 -6
  4. package/dist/bundling-sources/sdk/src/types.ts +21 -2
  5. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +11 -1
  6. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +209 -51
  7. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +10 -2
  8. package/dist/bundling-sources/shared_libs/play-runtime/log-context.ts +36 -0
  9. package/dist/bundling-sources/shared_libs/play-runtime/log-provenance.ts +2 -1
  10. package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +5 -1
  11. package/dist/bundling-sources/shared_libs/play-runtime/run-ledger-event-transport.ts +54 -41
  12. package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +50 -3
  13. package/dist/bundling-sources/shared_libs/play-runtime/run-log-buffer.ts +9 -2
  14. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +35 -7
  15. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-terminal-push.ts +55 -9
  16. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +16 -4
  17. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +140 -4
  18. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +559 -313
  19. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-ledger-outbox-recovery.ts +1 -1
  20. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-event-search.ts +116 -24
  21. package/dist/bundling-sources/shared_libs/play-runtime/step-progress.ts +6 -1
  22. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/runtime-step-receipts-adapter.ts +22 -5
  23. package/dist/bundling-sources/shared_libs/play-runtime/tool-http-errors.ts +35 -0
  24. package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +68 -8
  25. package/dist/bundling-sources/shared_libs/play-runtime/worker-api-types.ts +2 -0
  26. package/dist/bundling-sources/shared_libs/plays/tool-result-types.ts +2 -0
  27. package/dist/cli/index.js +209 -588
  28. package/dist/cli/index.mjs +209 -588
  29. package/dist/{client-FR_u0dxT.d.ts → client-Ct4jmvAg.d.ts} +29 -8
  30. package/dist/{client-phK1Ixcl.d.mts → client-Ct9GKrwu.d.mts} +29 -8
  31. package/dist/{compiler-manifest-9SQtUuKb.d.mts → compiler-manifest-qtTNYvBl.d.mts} +2 -0
  32. package/dist/{compiler-manifest-9SQtUuKb.d.ts → compiler-manifest-qtTNYvBl.d.ts} +2 -0
  33. package/dist/index.d.mts +4 -4
  34. package/dist/index.d.ts +4 -4
  35. package/dist/index.js +12 -5
  36. package/dist/index.mjs +12 -5
  37. package/dist/install-integrity.json +4 -4
  38. package/dist/plays/bundle-play-file.d.mts +2 -2
  39. package/dist/plays/bundle-play-file.d.ts +2 -2
  40. package/dist/plays/run-request.d.mts +2 -2
  41. package/dist/plays/run-request.d.ts +2 -2
  42. package/dist/release.d.mts +1 -1
  43. package/dist/release.d.ts +1 -1
  44. package/dist/release.js +1 -1
  45. package/dist/release.mjs +1 -1
  46. package/package.json +1 -1
@@ -40,7 +40,7 @@ const MAX_TIMEOUT_MS = 2_147_483_647;
40
40
  /**
41
41
  * One projector can keep this many independent Run Ledger deliveries in
42
42
  * flight after it has found durable work. Each delivery owns only short
43
- * Postgres checkouts: claim, token-context read, and acknowledgement. It does
43
+ * Postgres checkouts: claim with routing, renewal, and acknowledgement. It does
44
44
  * not hold a scheduler connection across either gateway HTTP request. This is
45
45
  * therefore a network/projection concurrency budget, deliberately independent
46
46
  * of the projector's much smaller Postgres pool.
@@ -3,8 +3,12 @@
3
3
  *
4
4
  * After reader-first cutover, triggers persist raw input in the existing
5
5
  * outbox. The leased dispatcher projects and compacts it before delivery.
6
- * Receipt output remains exclusively in the authoritative receipt store.
7
- * The API may read only through fixed, tenant-scoped queries in this module.
6
+ * PlanetScale runtime_observability.events is the single log/event/receipt
7
+ * discovery surface. JSONPath runs natively against its indexed search_doc;
8
+ * exact identities use indexed columns on that same table. Full saved results
9
+ * resolve through the scoped fetch_receipt capability, never a second search
10
+ * store or a CLI-side scan. Receipt output remains authoritative in its store.
11
+ * The API uses only the fixed, tenant-scoped readers in this module.
8
12
  */
9
13
  import { normalizeRuntimeSchedulerSchema } from '../runtime-scheduler-topology';
10
14
  import type { PostgresSchedulerQueryClient } from './postgres';
@@ -57,7 +61,7 @@ const RUNTIME_EVENT_LOG_POSITION_EXPRESSION = `jsonb_set(
57
61
  // Migration readiness tracks this projection body independently of scheduler
58
62
  // wire and schema versions. Bump when a changed trigger must be reinstalled.
59
63
  export const RUNTIME_EVENT_SEARCH_OUTBOX_PROJECTION_MARKER =
60
- 'runtime-event-search-origin-batches-independent-progress';
64
+ 'runtime-event-search-origin-batches-log-metadata';
61
65
  /** Trusted trigger-authored state; absent on already-projected legacy rows. */
62
66
  export const RUNTIME_EVENT_DEFERRED_PROJECTION_FIELD =
63
67
  '_runtimeSearchProjectionPending';
@@ -177,7 +181,7 @@ function parseRuntimeEventJsonpath(value: string): RuntimeEventPredicate[] {
177
181
  );
178
182
  }
179
183
  const clause =
180
- /\$\.(orgId|runId|playName|runtimeScope|producerAttempt|kind|status|source|stepId|stepKind|datasetId|phase|provider|operation|context\.[A-Za-z_][A-Za-z0-9_]{0,63})\s*==\s*("(?:\\.|[^"\\])*"|true|false|-?\d+(?:\.\d+)?)/y;
184
+ /\$\.(eventId|orgId|runId|playName|runtimeScope|producerAttempt|kind|status|source|level|stepId|stepKind|datasetId|phase|provider|operation|context\.[A-Za-z_][A-Za-z0-9_]{0,63})\s*==\s*("(?:\\.|[^"\\])*"|true|false|-?\d+(?:\.\d+)?)/y;
181
185
  let position = 0;
182
186
  let clauses = 0;
183
187
  const predicates: RuntimeEventPredicate[] = [];
@@ -214,7 +218,9 @@ function parseRuntimeEventJsonpath(value: string): RuntimeEventPredicate[] {
214
218
  );
215
219
  }
216
220
  if (
217
- ['orgId', 'runId', 'playName', 'runtimeScope'].includes(match[1]) &&
221
+ ['eventId', 'orgId', 'runId', 'playName', 'runtimeScope'].includes(
222
+ match[1],
223
+ ) &&
218
224
  typeof parsed !== 'string'
219
225
  ) {
220
226
  throw new RuntimeEventInputError(
@@ -231,6 +237,14 @@ function parseRuntimeEventJsonpath(value: string): RuntimeEventPredicate[] {
231
237
  'Runtime event producerAttempt requires a positive integer.',
232
238
  );
233
239
  }
240
+ if (
241
+ match[1] === 'level' &&
242
+ !['debug', 'info', 'warn', 'error'].includes(String(parsed))
243
+ ) {
244
+ throw new RuntimeEventInputError(
245
+ 'Runtime event level requires debug, info, warn or error.',
246
+ );
247
+ }
234
248
  predicates.push({ path: match[1], literal: match[2], value: parsed });
235
249
  if (
236
250
  match[1].startsWith('context.') &&
@@ -348,6 +362,7 @@ export async function searchRuntimeEvents(
348
362
  addExact('kind', input.kind);
349
363
  if (input.jsonpath !== undefined) {
350
364
  const identityColumns = {
365
+ eventId: 'event_id',
351
366
  orgId: 'org_id',
352
367
  runId: 'run_id',
353
368
  playName: 'play_name',
@@ -1170,7 +1185,19 @@ export async function attachRuntimeEventSearchTriggers(
1170
1185
  NEW.updated_at, NEW.run_id, play, 'receipt.' || NEW.status,
1171
1186
  ref, NEW.k, jsonb_strip_nulls(jsonb_build_object(
1172
1187
  'kind', 'receipt.' || NEW.status, 'status', NEW.status,
1173
- 'source', 'receipt', 'producerAttempt', NEW.lease_owner_attempt
1188
+ 'source', 'receipt', 'producerAttempt', NEW.lease_owner_attempt,
1189
+ -- Only the reserved runtime diagnostic record supplies this flat
1190
+ -- context. Ordinary authored receipt outputs are never indexed.
1191
+ 'context', CASE WHEN NEW.k LIKE 'tool-evidence:%'
1192
+ AND NEW.output_json->>'kind' = 'tool_call_evidence' THEN jsonb_strip_nulls(jsonb_build_object(
1193
+ 'callKey', NEW.output_json#>'{context,callKey}',
1194
+ 'toolId', NEW.output_json#>'{context,toolId}',
1195
+ 'callStatus', NEW.output_json#>'{context,callStatus}',
1196
+ 'stepId', NEW.output_json#>'{context,stepId}',
1197
+ 'dataset', NEW.output_json#>'{context,dataset}',
1198
+ 'column', NEW.output_json#>'{context,column}',
1199
+ 'rowKey', NEW.output_json#>'{context,rowKey}'
1200
+ )) END
1174
1201
  ))
1175
1202
  );
1176
1203
  RETURN NEW;
@@ -1189,6 +1216,41 @@ export async function attachRuntimeEventSearchTriggers(
1189
1216
  FOR EACH ROW EXECUTE FUNCTION ${schema}.search_receipt_event();
1190
1217
  END IF;
1191
1218
  END $create$`);
1219
+ await client.query(`
1220
+ CREATE OR REPLACE FUNCTION ${schema}.run_log_metadata(line text, metadata jsonb, fallback_at timestamptz) RETURNS jsonb
1221
+ LANGUAGE plpgsql IMMUTABLE SET search_path = pg_catalog AS $function$
1222
+ DECLARE clean text; message text; level text; at_time timestamptz := fallback_at;
1223
+ BEGIN
1224
+ clean := regexp_replace(line, '^' || chr(1) || 'prov:[^' || chr(1) || ']*' || chr(1), '');
1225
+ IF metadata IS NOT NULL AND metadata <> 'null'::jsonb THEN
1226
+ IF jsonb_typeof(metadata) IS DISTINCT FROM 'object'
1227
+ OR NOT coalesce(metadata->>'level' IN ('debug','info','warn','error'), false)
1228
+ OR jsonb_typeof(metadata->'occurredAt') IS DISTINCT FROM 'number'
1229
+ OR NOT coalesce(metadata->>'occurredAt' ~ '^[1-9][0-9]{0,15}$', false)
1230
+ OR (metadata ? 'stepId' AND (jsonb_typeof(metadata->'stepId') IS DISTINCT FROM 'string'
1231
+ OR length(metadata->>'stepId') NOT BETWEEN 1 AND 256)) THEN
1232
+ RAISE EXCEPTION 'Run log metadata is invalid';
1233
+ END IF;
1234
+ RETURN jsonb_strip_nulls(jsonb_build_object('level', metadata->'level',
1235
+ 'occurredAt', metadata->'occurredAt', 'stepId', metadata->'stepId'));
1236
+ END IF;
1237
+ -- Historical lines already carry the original ISO time and severity.
1238
+ IF clean ~ '^\\[[-0-9]{10}T[0-9:.]+Z\\]' THEN
1239
+ BEGIN at_time := substring(clean from '^\\[([^]]+)\\]')::timestamptz;
1240
+ EXCEPTION WHEN invalid_datetime_format OR datetime_field_overflow THEN at_time := fallback_at; END;
1241
+ END IF;
1242
+ message := regexp_replace(clean, '^\\[[-0-9]{10}T[0-9:.]+Z\\][ ]*', '');
1243
+ level := CASE
1244
+ WHEN message ~* '^\\[(error|console.error)\\]' THEN 'error'
1245
+ WHEN message ~* '^\\[(warn|console.warn)\\]' THEN 'warn'
1246
+ WHEN message ~* '^\\[info\\]' THEN 'info'
1247
+ WHEN message ~* '^\\[(debug|console.debug|perf|runtime.(phase_summary|logs|log_transport))\\]'
1248
+ OR line ~ ('^' || chr(1) || 'prov:(infra|receipt|replay)' || chr(1)) THEN 'debug'
1249
+ ELSE 'info' END;
1250
+ RETURN jsonb_build_object('level', level, 'occurredAt', floor(extract(epoch FROM at_time) * 1000)::bigint);
1251
+ END
1252
+ $function$
1253
+ `);
1192
1254
  await client.query(`
1193
1255
  CREATE OR REPLACE FUNCTION ${schema}.project_origin_log_batch(observation ${schema}.outbox, item jsonb, run_org text, play text, compact_logs boolean, canonical_events boolean, at_time timestamptz, doc jsonb, closed boolean)
1194
1256
  RETURNS text[] LANGUAGE plpgsql SECURITY DEFINER SET search_path = pg_catalog AS $function$
@@ -1221,7 +1283,8 @@ export async function attachRuntimeEventSearchTriggers(
1221
1283
  WITH entries AS MATERIALIZED (
1222
1284
  SELECT value #>> '{}' AS body, ordinality AS ordinal,
1223
1285
  'log-origin:' || observation.run_id || ':' || (item->'lineIds'->>(ordinality-1)::int) AS eid,
1224
- NULLIF(item->'lineContexts'->(ordinality-1)::int, 'null'::jsonb) AS ctx
1286
+ NULLIF(item->'lineContexts'->(ordinality-1)::int, 'null'::jsonb) AS ctx,
1287
+ ${schema}.run_log_metadata(value #>> '{}', item->'lineMetadata'->(ordinality-1)::int, at_time) AS metadata
1225
1288
  FROM jsonb_array_elements(item->'lines') WITH ORDINALITY
1226
1289
  ), inserted AS (
1227
1290
  INSERT INTO runtime_observability.events (
@@ -1229,16 +1292,16 @@ export async function attachRuntimeEventSearchTriggers(
1229
1292
  run_id, play_name, kind, level, message, log_body, source_digest, log_text_digest, search_doc
1230
1293
  ) SELECT ${quoteLiteral(scope)}, run_org, eid,
1231
1294
  CASE WHEN canonical_events THEN nextval('runtime_observability.runtime_event_order_seq') END,
1232
- CASE WHEN canonical_events THEN jsonb_strip_nulls((item - 'lines' - 'lineContexts' - 'lineIds') || jsonb_build_object(
1295
+ CASE WHEN canonical_events THEN jsonb_strip_nulls((item - 'lines' - 'lineContexts' - 'lineIds' - 'lineMetadata') || jsonb_build_object(
1233
1296
  'type', ${RUN_EVENT_KIND_SQL_LITERAL.logAppended}, 'runId', observation.run_id,
1234
- 'occurredAt', floor(extract(epoch FROM at_time)*1000)::bigint, 'lineOrdinal', ordinal,
1297
+ 'occurredAt', metadata->'occurredAt', 'level', metadata->'level', 'stepId', metadata->'stepId', 'lineOrdinal', ordinal,
1235
1298
  'entryId', item->'lineIds'->(ordinal-1)::int, 'context', ctx)) END,
1236
- at_time, observation.run_id, play, ${RUN_EVENT_KIND_SQL_LITERAL.logAppended}, NULL,
1299
+ to_timestamp((metadata->>'occurredAt')::double precision / 1000), observation.run_id, play, ${RUN_EVENT_KIND_SQL_LITERAL.logAppended}, metadata->>'level',
1237
1300
  CASE WHEN compact_logs THEN NULL ELSE left(body, 2048) END, body,
1238
1301
  CASE WHEN ctx IS NULL THEN encode(sha256(convert_to(body,'UTF8')),'hex')
1239
1302
  ELSE encode(sha256(convert_to(jsonb_build_array(body,ctx)::text,'UTF8')),'hex') END,
1240
1303
  encode(sha256(convert_to(btrim(body),'UTF8')),'hex'),
1241
- doc || jsonb_strip_nulls(jsonb_build_object('entryId', item->'lineIds'->(ordinal-1)::int, 'context', ctx))
1304
+ doc || (metadata - 'occurredAt') || jsonb_strip_nulls(jsonb_build_object('entryId', item->'lineIds'->(ordinal-1)::int, 'context', ctx))
1242
1305
  FROM entries WHERE NOT closed ORDER BY ordinal
1243
1306
  ON CONFLICT (runtime_scope, org_id, event_id) DO NOTHING RETURNING event_id
1244
1307
  ) SELECT coalesce(array_agg(inserted.event_id ORDER BY entries.ordinal), ARRAY[]::text[])
@@ -1264,7 +1327,7 @@ export async function attachRuntimeEventSearchTriggers(
1264
1327
  LANGUAGE plpgsql SECURITY DEFINER SET search_path = pg_catalog AS $function$
1265
1328
  DECLARE run_org text; play text; compact_logs boolean; canonical_events boolean; run_status text; item jsonb; line jsonb; event_batch jsonb;
1266
1329
  ordinal bigint; line_ordinal bigint; kind text; at_time timestamptz;
1267
- doc jsonb; line_doc jsonb; line_context jsonb;
1330
+ doc jsonb; line_doc jsonb; line_context jsonb; line_metadata jsonb; line_at timestamptz;
1268
1331
  eid text; existing_digest text; digest text; assigned_seq bigint; terminal_progress_identity boolean;
1269
1332
  log_channel text; log_position bigint; matched_event_id text; matched_doc jsonb;
1270
1333
  legacy_position_prefix text; position_event_ids text[];
@@ -1349,6 +1412,7 @@ export async function attachRuntimeEventSearchTriggers(
1349
1412
  'producerAttempt', envelope_attempt, 'channelOffset', 0,
1350
1413
  'lines', observation.payload_json->'terminalLogLines',
1351
1414
  'lineContexts', observation.payload_json->'terminalLogContexts',
1415
+ 'lineMetadata', observation.payload_json->'terminalLogMetadata',
1352
1416
  'lineIds', NULLIF(observation.payload_json->'terminalLogIds', 'null'::jsonb)
1353
1417
  ))
1354
1418
  );
@@ -1453,6 +1517,11 @@ export async function attachRuntimeEventSearchTriggers(
1453
1517
  OR jsonb_array_length(item->'lineContexts') <> jsonb_array_length(item->'lines')) THEN
1454
1518
  RAISE EXCEPTION 'Search projection requires lineContexts aligned to log lines';
1455
1519
  END IF;
1520
+ IF kind = ${RUN_EVENT_KIND_SQL_LITERAL.logAppended} AND item ? 'lineMetadata'
1521
+ AND (jsonb_typeof(item->'lineMetadata') IS DISTINCT FROM 'array'
1522
+ OR jsonb_array_length(item->'lineMetadata') <> jsonb_array_length(item->'lines')) THEN
1523
+ RAISE EXCEPTION 'Run log metadata must align with lines';
1524
+ END IF;
1456
1525
  IF kind = ${RUN_EVENT_KIND_SQL_LITERAL.logAppended} AND item ? 'lineIds' THEN
1457
1526
  IF jsonb_typeof(item->'lineIds') IS DISTINCT FROM 'array' OR jsonb_array_length(item->'lineIds') <> jsonb_array_length(item->'lines') THEN
1458
1527
  RAISE EXCEPTION 'Origin log identities must align with lines';
@@ -1524,8 +1593,10 @@ export async function attachRuntimeEventSearchTriggers(
1524
1593
  RAISE EXCEPTION 'Search projection log context has an unsupported key or value';
1525
1594
  END IF;
1526
1595
  END IF;
1527
- line_doc := CASE WHEN line_context IS NULL THEN doc
1528
- ELSE doc || jsonb_build_object('context', line_context) END;
1596
+ line_metadata := ${schema}.run_log_metadata(line #>> '{}', item->'lineMetadata'->((line_ordinal - 1)::int), at_time);
1597
+ line_at := to_timestamp((line_metadata->>'occurredAt')::double precision / 1000);
1598
+ line_doc := (CASE WHEN line_context IS NULL THEN doc
1599
+ ELSE doc || jsonb_build_object('context', line_context) END) || (line_metadata - 'occurredAt');
1529
1600
  eid := CASE WHEN (item->>'channelOffset') ~ '^[0-9]{1,12}$'
1530
1601
  AND (item->>'producerAttempt') ~ '^[0-9]{1,6}$'
1531
1602
  THEN 'log:' || observation.run_id || ':' || (item->>'producerAttempt') || ':' ||
@@ -1673,14 +1744,14 @@ export async function attachRuntimeEventSearchTriggers(
1673
1744
  ${quoteLiteral(scope)}, run_org, eid,
1674
1745
  CASE WHEN canonical_events THEN nextval('runtime_observability.runtime_event_order_seq') END,
1675
1746
  CASE WHEN canonical_events THEN jsonb_strip_nulls(
1676
- (item - 'lines' - 'lineContexts' - 'lineIds') || jsonb_build_object(
1747
+ (item - 'lines' - 'lineContexts' - 'lineMetadata' - 'lineIds') || jsonb_build_object(
1677
1748
  'type', ${RUN_EVENT_KIND_SQL_LITERAL.logAppended}, 'runId', observation.run_id,
1678
- 'occurredAt', floor(extract(epoch FROM at_time) * 1000)::bigint,
1749
+ 'occurredAt', line_metadata->'occurredAt', 'level', line_metadata->'level', 'stepId', line_metadata->'stepId',
1679
1750
  'lineOrdinal', line_ordinal,
1680
1751
  'context', line_context
1681
1752
  )) END,
1682
- at_time, observation.run_id, play,
1683
- ${RUN_EVENT_KIND_SQL_LITERAL.logAppended}, NULL, CASE WHEN compact_logs THEN NULL ELSE left(line #>> '{}', 2048) END,
1753
+ line_at, observation.run_id, play,
1754
+ ${RUN_EVENT_KIND_SQL_LITERAL.logAppended}, line_metadata->>'level', CASE WHEN compact_logs THEN NULL ELSE left(line #>> '{}', 2048) END,
1684
1755
  line #>> '{}', digest,
1685
1756
  encode(sha256(convert_to(btrim(line #>> '{}'), 'UTF8')), 'hex'), line_doc
1686
1757
  ) ON CONFLICT DO NOTHING;
@@ -1760,6 +1831,15 @@ export async function attachRuntimeEventSearchTriggers(
1760
1831
  AND NOT (observation.payload_json ? 'terminalLogLines') THEN
1761
1832
  RAISE EXCEPTION 'Search projection requires terminalLogLines for terminalLogContexts';
1762
1833
  END IF;
1834
+ IF observation.payload_json ? 'terminalLogMetadata'
1835
+ AND NOT (observation.payload_json ? 'terminalLogLines') THEN
1836
+ RAISE EXCEPTION 'Search projection requires terminalLogLines for terminalLogMetadata';
1837
+ END IF;
1838
+ IF observation.payload_json ? 'terminalLogMetadata'
1839
+ AND (jsonb_typeof(observation.payload_json->'terminalLogMetadata') IS DISTINCT FROM 'array'
1840
+ OR jsonb_array_length(observation.payload_json->'terminalLogMetadata') <> jsonb_array_length(observation.payload_json->'terminalLogLines')) THEN
1841
+ RAISE EXCEPTION 'Search projection requires terminalLogMetadata aligned to terminalLogLines';
1842
+ END IF;
1763
1843
  IF observation.event_type IN (${RUN_EVENT_TERMINAL_LOG_CARRIER_SQL_LIST})
1764
1844
  AND observation.payload_json ? 'terminalLogLines'
1765
1845
  AND NOT coalesce((envelope_attempt #>> '{}') ~ '^[1-9][0-9]{0,5}$', false) THEN
@@ -1852,7 +1932,9 @@ export async function attachRuntimeEventSearchTriggers(
1852
1932
  RAISE EXCEPTION 'Search projection terminal log context has an unsupported key or value';
1853
1933
  END IF;
1854
1934
  END IF;
1855
- line_doc := jsonb_strip_nulls(jsonb_build_object(
1935
+ line_metadata := ${schema}.run_log_metadata(line #>> '{}', observation.payload_json->'terminalLogMetadata'->((line_ordinal - 1)::int), observation.created_at);
1936
+ line_at := to_timestamp((line_metadata->>'occurredAt')::double precision / 1000);
1937
+ line_doc := (line_metadata - 'occurredAt') || jsonb_strip_nulls(jsonb_build_object(
1856
1938
  'kind', ${RUN_EVENT_KIND_SQL_LITERAL.logAppended}, 'source', 'terminal',
1857
1939
  'producerAttempt', observation.payload_json->'producerAttempt'
1858
1940
  ));
@@ -1868,11 +1950,11 @@ export async function attachRuntimeEventSearchTriggers(
1868
1950
  CASE WHEN canonical_events THEN nextval('runtime_observability.runtime_event_order_seq') END,
1869
1951
  CASE WHEN canonical_events THEN jsonb_strip_nulls(jsonb_build_object(
1870
1952
  'type', ${RUN_EVENT_KIND_SQL_LITERAL.logAppended}, 'runId', observation.run_id,
1871
- 'occurredAt', floor(extract(epoch FROM observation.created_at) * 1000)::bigint,
1953
+ 'occurredAt', line_metadata->'occurredAt', 'level', line_metadata->'level', 'stepId', line_metadata->'stepId',
1872
1954
  'source', 'terminal', 'producerAttempt', observation.payload_json->'producerAttempt',
1873
1955
  'lineOrdinal', line_ordinal, 'context', line_context
1874
1956
  )) END,
1875
- observation.created_at, observation.run_id, play, ${RUN_EVENT_KIND_SQL_LITERAL.logAppended}, NULL,
1957
+ line_at, observation.run_id, play, ${RUN_EVENT_KIND_SQL_LITERAL.logAppended}, line_metadata->>'level',
1876
1958
  CASE WHEN compact_logs THEN NULL ELSE left(regexp_replace(line #>> '{}',
1877
1959
  '^' || chr(1) || 'prov:[^' || chr(1) || ']*' || chr(1), ''), 2048) END,
1878
1960
  line #>> '{}',
@@ -2019,7 +2101,7 @@ export async function attachRuntimeEventSearchTriggers(
2019
2101
  LANGUAGE sql IMMUTABLE SET search_path = pg_catalog AS $function$
2020
2102
  WITH envelope AS (
2021
2103
  SELECT CASE WHEN jsonb_typeof(value) = 'object' THEN
2022
- (value - 'logContexts' - 'logIds') || CASE WHEN value ? 'logs'
2104
+ (value - 'logContexts' - 'logMetadata' - 'logIds') || CASE WHEN value ? 'logs'
2023
2105
  THEN jsonb_build_object('logs', '[]'::jsonb) ELSE '{}'::jsonb END
2024
2106
  ELSE value END AS result
2025
2107
  )
@@ -2037,7 +2119,7 @@ export async function attachRuntimeEventSearchTriggers(
2037
2119
  INTO compact_logs, canonical_events
2038
2120
  FROM ${schema}.runs WHERE run_id = observation.run_id;
2039
2121
  IF compact_logs OR canonical_events THEN
2040
- observation.payload_json := observation.payload_json - 'terminalLogLines' - 'terminalLogContexts' - 'terminalLogIds';
2122
+ observation.payload_json := observation.payload_json - 'terminalLogLines' - 'terminalLogContexts' - 'terminalLogIds' - 'terminalLogMetadata';
2041
2123
  IF observation.event_type = ${RUN_SCHEDULER_OUTBOX_EVENT_TYPE_SQL_LITERAL.runEvents} THEN
2042
2124
  observation.payload_json := jsonb_set(observation.payload_json, '{events}',
2043
2125
  ${schema}.without_log_events(observation.payload_json->'events'))
@@ -2110,6 +2192,16 @@ export async function attachRuntimeEventSearchTriggers(
2110
2192
  NEW.payload_json := NEW.payload_json - '${RUNTIME_EVENT_DEFERRED_PROJECTION_FIELD}' - '_runtimeSearchRunClosed';
2111
2193
  PERFORM ${schema}.project_outbox_event(NEW);
2112
2194
  NEW.payload_json := ${schema}.compact_runtime_outbox_payload(NEW);
2195
+ -- The search rows above are committed atomically with this outbox row.
2196
+ -- When compaction leaves no ledger event to project, settle the no-op
2197
+ -- receipt here so a delayed log-search fact cannot hold up run terminal
2198
+ -- projection behind the scheduler outbox.
2199
+ IF NEW.event_type = ${RUN_SCHEDULER_OUTBOX_EVENT_TYPE_SQL_LITERAL.runEvents}
2200
+ AND NEW.payload_json->>'logBodyStore' = 'planet_scale'
2201
+ AND jsonb_typeof(NEW.payload_json->'events') = 'array'
2202
+ AND jsonb_array_length(NEW.payload_json->'events') = 0 THEN
2203
+ NEW.delivered_at := coalesce(NEW.delivered_at, clock_timestamp());
2204
+ END IF;
2113
2205
  RETURN NEW;
2114
2206
  END
2115
2207
  $function$
@@ -2295,7 +2387,7 @@ export async function attachRuntimeEventSearchTriggers(
2295
2387
  ${schema}.accept_runner_log_payload(), ${schema}.search_outbox_event(),
2296
2388
  ${schema}.compact_run_log_result(), ${schema}.without_log_events(jsonb),
2297
2389
  ${schema}.convex_projection_event(jsonb), ${schema}.convex_projection_events(jsonb),
2298
- ${schema}.compact_runner_log_envelope(jsonb), ${schema}.without_runtime_log_tail(jsonb)
2390
+ ${schema}.run_log_metadata(text, jsonb, timestamptz), ${schema}.compact_runner_log_envelope(jsonb), ${schema}.without_runtime_log_tail(jsonb)
2299
2391
  FROM PUBLIC`);
2300
2392
  // Publish readiness last, after all acceptance/compaction functions exist.
2301
2393
  await client.query(
@@ -45,6 +45,8 @@ export type PlayVisualNodeProgress = {
45
45
  completedAt?: number | null;
46
46
  artifactTableNamespace?: string | null;
47
47
  nodeIo?: PlayDocflowNodeIoState;
48
+ /** Error reported by this step, distinct from a run stopping other work. */
49
+ error?: string;
48
50
  conditionCounts?: PlayConditionCounts;
49
51
  };
50
52
 
@@ -193,6 +195,9 @@ export function normalizePlayVisualNodeProgressMap(
193
195
  ...(startedAt !== undefined ? { startedAt } : {}),
194
196
  ...(completedAt !== undefined ? { completedAt } : {}),
195
197
  ...(message !== undefined ? { message } : {}),
198
+ ...(typeof rawProgress.error === 'string' && rawProgress.error.trim()
199
+ ? { error: rawProgress.error.trim() }
200
+ : {}),
196
201
  ...(artifactTableNamespace !== undefined
197
202
  ? { artifactTableNamespace }
198
203
  : {}),
@@ -305,7 +310,7 @@ export function applyVisualNodeProgress(input: {
305
310
  const completed = Number(progress.completed ?? 0);
306
311
  const total = Number(progress.total ?? 0);
307
312
  const progressStatus =
308
- nodeState.status === 'failed'
313
+ nodeState.status === 'failed' || progress.error || progress.nodeIo?.error
309
314
  ? 'failed'
310
315
  : Number.isFinite(total) && total > 0
311
316
  ? Number.isFinite(completed) && completed >= total
@@ -349,10 +349,14 @@ export function createRuntimeStepToolResultReceipts(
349
349
  // exists to cap how many stored payloads expand at once; holding one
350
350
  // of its slots across this gateway round trip only queued unrelated
351
351
  // rows behind this row's write, so the mutation runs outside it.
352
+ const storedResult = stampResultReceipt(
353
+ candidate.result,
354
+ record.resultReceiptKey,
355
+ );
352
356
  const persisted = await input.store.complete(
353
357
  record.resultReceiptKey,
354
358
  ownerRunId,
355
- serializeToolExecuteResult(candidate.result),
359
+ serializeToolExecuteResult(storedResult),
356
360
  COMPLETED_RECEIPT_CACHE_INSERT_LEASE_ID,
357
361
  );
358
362
  // Publication is immutable, but an independent miss keeps the
@@ -369,10 +373,10 @@ export function createRuntimeStepToolResultReceipts(
369
373
  }
370
374
  }
371
375
  await input.onStored?.({
372
- result: candidate.result,
376
+ result: storedResult,
373
377
  resultReceiptKey: record.resultReceiptKey,
374
378
  });
375
- return { kind: 'stored', result: candidate.result };
379
+ return { kind: 'stored', result: storedResult };
376
380
  } finally {
377
381
  await stopHeartbeat(record);
378
382
  await releaseExecutionLock(record);
@@ -394,6 +398,16 @@ export function createRuntimeStepToolResultReceipts(
394
398
  };
395
399
  }
396
400
 
401
+ function stampResultReceipt(
402
+ result: ToolExecuteResult,
403
+ resultReceiptKey: string,
404
+ ): ToolExecuteResult {
405
+ return deserializeToolExecuteResult({
406
+ ...serializeToolExecuteResult(result),
407
+ execution: { ...result._metadata.execution, resultReceiptKey },
408
+ });
409
+ }
410
+
397
411
  function completedResult(
398
412
  receipt: RuntimeStepReceipt | null,
399
413
  resultReceiptKey: string,
@@ -402,10 +416,13 @@ function completedResult(
402
416
  return null;
403
417
  }
404
418
  if (isSerializedToolExecuteResult(receipt.output)) {
405
- return deserializeToolExecuteResult(receipt.output);
419
+ return stampResultReceipt(
420
+ deserializeToolExecuteResult(receipt.output),
421
+ resultReceiptKey,
422
+ );
406
423
  }
407
424
  if (isToolExecuteResult(receipt.output)) {
408
- return receipt.output;
425
+ return stampResultReceipt(receipt.output, resultReceiptKey);
409
426
  }
410
427
  throw new Error(
411
428
  `Completed Tool Result Receipt ${resultReceiptKey} has no valid serialized ToolExecuteResult.`,
@@ -12,6 +12,41 @@ import {
12
12
  } from '../plays/tool-execution-error';
13
13
 
14
14
  const TOOL_HTTP_ERROR_BRAND = Symbol.for('deepline.tool-http-error.v1');
15
+ const callEvidenceByError = new WeakMap<object, unknown>();
16
+
17
+ /** Payload evidence stays out of the scalar portable error/status contract. */
18
+ export function attachToolCallFailureEvidence(
19
+ error: object,
20
+ evidence: unknown,
21
+ ): void {
22
+ callEvidenceByError.set(error, evidence);
23
+ }
24
+
25
+ /** Read the already consumed error body without changing failure classification. */
26
+ export function attachToolCallFailureEvidenceFromResponse(
27
+ error: object,
28
+ body: string,
29
+ ): void {
30
+ let response: unknown;
31
+ try {
32
+ response = JSON.parse(body);
33
+ } catch {
34
+ return;
35
+ }
36
+ if (!response || typeof response !== 'object') return;
37
+ const meta = (response as Record<string, unknown>).meta;
38
+ if (!meta || typeof meta !== 'object') return;
39
+ attachToolCallFailureEvidence(
40
+ error,
41
+ (meta as Record<string, unknown>).callEvidence,
42
+ );
43
+ }
44
+
45
+ export function readToolCallFailureEvidence(error: unknown): unknown {
46
+ return error && typeof error === 'object'
47
+ ? callEvidenceByError.get(error)
48
+ : undefined;
49
+ }
15
50
 
16
51
  type ToolHttpErrorConstructorInput = {
17
52
  message: string;
@@ -1,3 +1,7 @@
1
+ import {
2
+ jsonByteLengthUpTo,
3
+ RUNTIME_RECEIPT_OUTPUT_MAX_BYTES,
4
+ } from './output-size-limits';
1
5
  export type {
2
6
  SerializedToolExecuteResult,
3
7
  ToolExecuteResult,
@@ -83,6 +87,11 @@ const relayPublishedResultReferences = new WeakMap<
83
87
  RuntimeReceiptValueReference
84
88
  >();
85
89
 
90
+ // Call evidence belongs to the saved per-call payload. Keep it out of the
91
+ // enumerable Play result so returning a tool result or storing it in a row
92
+ // does not enlarge customer output with diagnostic request bodies.
93
+ const savedToolCallEvidence = new WeakMap<object, unknown>();
94
+
86
95
  export function attachRelayPublishedToolResultReference(
87
96
  result: ToolExecuteResult,
88
97
  reference: RuntimeReceiptValueReference,
@@ -1271,6 +1280,7 @@ export function createToolExecuteResult<TResult = unknown>(input: {
1271
1280
  meta?: Record<string, unknown>;
1272
1281
  };
1273
1282
  }): ToolExecuteResult<TResult> {
1283
+ const { callEvidence, ...publicMeta } = input.meta ?? {};
1274
1284
  const result = toResultEnvelope(input.result);
1275
1285
  const resultRoot = {
1276
1286
  toolResponse: {
@@ -1320,7 +1330,7 @@ export function createToolExecuteResult<TResult = unknown>(input: {
1320
1330
  const wrapper = {
1321
1331
  status: input.status,
1322
1332
  ...(input.jobId ? { job_id: input.jobId } : {}),
1323
- ...(input.meta ? { meta: input.meta } : {}),
1333
+ ...(input.meta ? { meta: publicMeta } : {}),
1324
1334
  ...(input.billing ? { billing: input.billing } : {}),
1325
1335
  toolResponse,
1326
1336
  extractedValues,
@@ -1336,6 +1346,9 @@ export function createToolExecuteResult<TResult = unknown>(input: {
1336
1346
  value: metadata,
1337
1347
  enumerable: false,
1338
1348
  });
1349
+ if (callEvidence !== undefined) {
1350
+ savedToolCallEvidence.set(wrapper, callEvidence);
1351
+ }
1339
1352
  return wrapper;
1340
1353
  }
1341
1354
 
@@ -1976,9 +1989,50 @@ function rawV2WithSerializedListRows(input: {
1976
1989
  });
1977
1990
  }
1978
1991
 
1992
+ /** Optional call evidence cannot turn a valid provider result into a retry. */
1993
+ function fitSavedCallEvidence(
1994
+ value: SerializedToolExecuteResult,
1995
+ ): SerializedToolExecuteResult {
1996
+ if (
1997
+ !value.meta?.callEvidence ||
1998
+ !jsonByteLengthUpTo(value, RUNTIME_RECEIPT_OUTPUT_MAX_BYTES).exceeded
1999
+ )
2000
+ return value;
2001
+ const { callEvidence: _evidence, ...meta } = value.meta;
2002
+ const withoutEvidence = { ...value, meta };
2003
+ // Preserve the original oversized-output failure if the result itself is
2004
+ // invalid. Only newly added diagnostic evidence may be omitted.
2005
+ if (
2006
+ jsonByteLengthUpTo(withoutEvidence, RUNTIME_RECEIPT_OUTPUT_MAX_BYTES)
2007
+ .exceeded
2008
+ )
2009
+ return value;
2010
+ const reason =
2011
+ 'Call evidence exceeds the saved-result size limit; the complete tool output is retained.';
2012
+ const unavailable = {
2013
+ ...value,
2014
+ meta: { ...meta, callEvidence: { unavailable: reason } },
2015
+ };
2016
+ if (
2017
+ !jsonByteLengthUpTo(unavailable, RUNTIME_RECEIPT_OUTPUT_MAX_BYTES).exceeded
2018
+ )
2019
+ return unavailable;
2020
+ // An output exactly at the historical limit has no room for the marker.
2021
+ // Its original bytes remain legal; the runner retains the warning instead.
2022
+ console.warn(reason);
2023
+ return withoutEvidence;
2024
+ }
2025
+
1979
2026
  export function serializeToolExecuteResult(
1980
2027
  value: ToolExecuteResult,
2028
+ options?: { includeCallEvidence?: boolean },
1981
2029
  ): SerializedToolExecuteResult {
2030
+ const callEvidence =
2031
+ options?.includeCallEvidence === false
2032
+ ? undefined
2033
+ : savedToolCallEvidence.get(value);
2034
+ const meta =
2035
+ callEvidence !== undefined ? { ...value.meta, callEvidence } : value.meta;
1982
2036
  const metadata = metadataInputFromToolExecuteResult(value);
1983
2037
  const listDatasets = serializedListDatasetsFromResult(value);
1984
2038
  const listRows = serializedListRowsFromResult({
@@ -2007,11 +2061,11 @@ export function serializeToolExecuteResult(
2007
2061
  view,
2008
2062
  meta: value.toolResponse.meta,
2009
2063
  });
2010
- return {
2064
+ return fitSavedCallEvidence({
2011
2065
  __kind: SERIALIZED_TOOL_EXECUTE_RESULT_V2_KIND,
2012
2066
  status: value.status,
2013
2067
  ...(typeof value.job_id === 'string' ? { job_id: value.job_id } : {}),
2014
- ...(isRecord(value.meta) ? { meta: value.meta } : {}),
2068
+ ...(isRecord(meta) ? { meta } : {}),
2015
2069
  ...(isRecord(value.billing) ? { billing: value.billing } : {}),
2016
2070
  toolResponse: {
2017
2071
  // rawV2 is the canonical complete provider response. Only the legacy
@@ -2035,13 +2089,13 @@ export function serializeToolExecuteResult(
2035
2089
  ...(targetValues ? { targetValues } : {}),
2036
2090
  metadata,
2037
2091
  execution: value._metadata.execution,
2038
- };
2092
+ });
2039
2093
  }
2040
- return {
2094
+ return fitSavedCallEvidence({
2041
2095
  __kind: SERIALIZED_TOOL_EXECUTE_RESULT_V1_KIND,
2042
2096
  status: value.status,
2043
2097
  ...(typeof value.job_id === 'string' ? { job_id: value.job_id } : {}),
2044
- ...(isRecord(value.meta) ? { meta: value.meta } : {}),
2098
+ ...(isRecord(meta) ? { meta } : {}),
2045
2099
  ...(isRecord(value.billing) ? { billing: value.billing } : {}),
2046
2100
  toolResponse: {
2047
2101
  raw: serializedRaw,
@@ -2052,7 +2106,7 @@ export function serializeToolExecuteResult(
2052
2106
  ...(targetValues ? { targetValues } : {}),
2053
2107
  metadata,
2054
2108
  execution: value._metadata.execution,
2055
- };
2109
+ });
2056
2110
  }
2057
2111
 
2058
2112
  export function isSerializedToolExecuteResult(
@@ -2156,7 +2210,13 @@ export function cloneToolExecuteResultWithExecution<TResult>(
2156
2210
  ): ToolExecuteResult<TResult> {
2157
2211
  return deserializeToolExecuteResult({
2158
2212
  ...serializeToolExecuteResult(value),
2159
- execution,
2213
+ execution: {
2214
+ ...execution,
2215
+ ...(!execution.resultReceiptKey &&
2216
+ value._metadata.execution.resultReceiptKey
2217
+ ? { resultReceiptKey: value._metadata.execution.resultReceiptKey }
2218
+ : {}),
2219
+ },
2160
2220
  }) as ToolExecuteResult<TResult>;
2161
2221
  }
2162
2222
 
@@ -34,6 +34,8 @@ export type PlayVisualNodeProgress = {
34
34
  completedAt?: number;
35
35
  artifactTableNamespace?: string | null;
36
36
  nodeIo?: PlayDocflowNodeIoState;
37
+ /** Error reported by this step, distinct from a run stopping other work. */
38
+ error?: string;
37
39
  conditionCounts?: PlayConditionCounts;
38
40
  };
39
41
 
@@ -37,6 +37,8 @@ export type ToolResultExecutionMetadata = {
37
37
  cacheKey?: string;
38
38
  receiptRole?: 'owner' | 'follower';
39
39
  receiptKey?: string;
40
+ /** Canonical saved result identity; receiptKey may name a transport alias. */
41
+ resultReceiptKey?: string;
40
42
  attachedToReceiptKey?: string;
41
43
  };
42
44