tubeless 0.2.2 → 0.2.4

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 (199) hide show
  1. package/README.md +52 -52
  2. package/dist/cli/cli-types.d.ts +1 -1
  3. package/dist/cli/cli-types.js.map +1 -1
  4. package/dist/cli/cli.d.ts +2 -4
  5. package/dist/cli/cli.js +1 -3
  6. package/dist/cli/cli.js.map +1 -1
  7. package/dist/core/child-execution.js +9 -4
  8. package/dist/core/child-execution.js.map +1 -1
  9. package/dist/core/lifecycle.d.ts +1 -1
  10. package/dist/core/lifecycle.js +2 -0
  11. package/dist/core/lifecycle.js.map +1 -1
  12. package/dist/core/pipeline-compiler.d.ts +17 -0
  13. package/dist/core/pipeline-compiler.js +44 -0
  14. package/dist/core/pipeline-compiler.js.map +1 -0
  15. package/dist/core/pipeline-definition-validation.d.ts +4 -0
  16. package/dist/core/pipeline-definition-validation.js +101 -0
  17. package/dist/core/pipeline-definition-validation.js.map +1 -0
  18. package/dist/core/pipeline-definition.d.ts +28 -0
  19. package/dist/core/pipeline-definition.js +2 -0
  20. package/dist/core/pipeline-definition.js.map +1 -0
  21. package/dist/core/pipeline-disposition.d.ts +26 -0
  22. package/dist/core/pipeline-disposition.js +44 -0
  23. package/dist/core/pipeline-disposition.js.map +1 -0
  24. package/dist/core/pipeline-errors.d.ts +8 -0
  25. package/dist/core/pipeline-errors.js +20 -0
  26. package/dist/core/pipeline-errors.js.map +1 -0
  27. package/dist/core/pipeline-execute.d.ts +3 -1
  28. package/dist/core/pipeline-execute.js +136 -396
  29. package/dist/core/pipeline-execute.js.map +1 -1
  30. package/dist/core/pipeline-finalizer.d.ts +18 -0
  31. package/dist/core/pipeline-finalizer.js +37 -0
  32. package/dist/core/pipeline-finalizer.js.map +1 -0
  33. package/dist/core/pipeline-graph.d.ts +23 -0
  34. package/dist/core/pipeline-graph.js +135 -0
  35. package/dist/core/pipeline-graph.js.map +1 -0
  36. package/dist/core/pipeline-identity.d.ts +5 -0
  37. package/dist/core/pipeline-identity.js +11 -0
  38. package/dist/core/pipeline-identity.js.map +1 -0
  39. package/dist/core/pipeline-mermaid.d.ts +4 -0
  40. package/dist/core/pipeline-mermaid.js +56 -0
  41. package/dist/core/pipeline-mermaid.js.map +1 -0
  42. package/dist/core/pipeline-plan.d.ts +5 -94
  43. package/dist/core/pipeline-plan.js +5 -392
  44. package/dist/core/pipeline-plan.js.map +1 -1
  45. package/dist/core/pipeline-run-state.d.ts +49 -0
  46. package/dist/core/pipeline-run-state.js +284 -0
  47. package/dist/core/pipeline-run-state.js.map +1 -0
  48. package/dist/core/pipeline-step-executor.d.ts +20 -0
  49. package/dist/core/pipeline-step-executor.js +52 -0
  50. package/dist/core/pipeline-step-executor.js.map +1 -0
  51. package/dist/core/pipeline-step-metadata.d.ts +4 -0
  52. package/dist/core/pipeline-step-metadata.js +5 -0
  53. package/dist/core/pipeline-step-metadata.js.map +1 -0
  54. package/dist/core/pipeline-steps.d.ts +1 -1
  55. package/dist/core/pipeline-steps.js +1 -1
  56. package/dist/core/pipeline-steps.js.map +1 -1
  57. package/dist/core/pipeline-types.d.ts +17 -8
  58. package/dist/core/pipeline-types.js +8 -0
  59. package/dist/core/pipeline-types.js.map +1 -1
  60. package/dist/core/pipeline-validation.d.ts +6 -0
  61. package/dist/core/pipeline-validation.js +41 -0
  62. package/dist/core/pipeline-validation.js.map +1 -0
  63. package/dist/core/pipeline.d.ts +8 -9
  64. package/dist/core/pipeline.js +9 -6
  65. package/dist/core/pipeline.js.map +1 -1
  66. package/dist/node/node.d.ts +0 -3
  67. package/dist/node/node.js +0 -3
  68. package/dist/node/node.js.map +1 -1
  69. package/dist/reporter/reporter-entry.d.ts +1 -2
  70. package/dist/reporter/reporter-entry.js +1 -2
  71. package/dist/reporter/reporter-entry.js.map +1 -1
  72. package/dist/run-store/run-store-codec.d.ts +4 -0
  73. package/dist/run-store/run-store-codec.js +31 -0
  74. package/dist/run-store/run-store-codec.js.map +1 -0
  75. package/dist/run-store/run-store-ndjson.js +2 -295
  76. package/dist/run-store/run-store-ndjson.js.map +1 -1
  77. package/dist/run-store/run-store-sqlite.js +41 -38
  78. package/dist/run-store/run-store-sqlite.js.map +1 -1
  79. package/dist/run-store/run-store.d.ts +4 -3
  80. package/dist/run-store/run-store.js +38 -195
  81. package/dist/run-store/run-store.js.map +1 -1
  82. package/dist/studio/run-store-ui-client-app.d.ts +12 -0
  83. package/dist/studio/run-store-ui-client-app.js +224 -0
  84. package/dist/studio/run-store-ui-client-app.js.map +1 -0
  85. package/dist/studio/run-store-ui-client-browser.d.ts +1 -0
  86. package/dist/studio/run-store-ui-client-browser.js +2 -0
  87. package/dist/studio/run-store-ui-client-commands.d.ts +18 -0
  88. package/dist/studio/run-store-ui-client-commands.js +98 -0
  89. package/dist/studio/run-store-ui-client-commands.js.map +1 -0
  90. package/dist/studio/run-store-ui-client-form.d.ts +11 -0
  91. package/dist/studio/run-store-ui-client-form.js +54 -0
  92. package/dist/studio/run-store-ui-client-form.js.map +1 -0
  93. package/dist/studio/run-store-ui-client-modals.d.ts +20 -0
  94. package/dist/studio/run-store-ui-client-modals.js +154 -0
  95. package/dist/studio/run-store-ui-client-modals.js.map +1 -0
  96. package/dist/studio/run-store-ui-client-model.d.ts +19 -0
  97. package/dist/studio/run-store-ui-client-model.js +189 -0
  98. package/dist/studio/run-store-ui-client-model.js.map +1 -0
  99. package/dist/studio/run-store-ui-client-runs.d.ts +24 -0
  100. package/dist/studio/run-store-ui-client-runs.js +78 -0
  101. package/dist/studio/run-store-ui-client-runs.js.map +1 -0
  102. package/dist/studio/run-store-ui-client-shared.d.ts +22 -0
  103. package/dist/studio/run-store-ui-client-shared.js +63 -0
  104. package/dist/studio/run-store-ui-client-shared.js.map +1 -0
  105. package/dist/studio/run-store-ui-client-transport.d.ts +25 -0
  106. package/dist/studio/run-store-ui-client-transport.js +317 -0
  107. package/dist/studio/run-store-ui-client-transport.js.map +1 -0
  108. package/dist/studio/run-store-ui-client.d.ts +2 -14
  109. package/dist/studio/run-store-ui-client.js +9 -1360
  110. package/dist/studio/run-store-ui-client.js.map +1 -1
  111. package/dist/studio/run-store-ui-page.d.ts +2 -2
  112. package/dist/studio/run-store-ui-page.js +7 -71
  113. package/dist/studio/run-store-ui-page.js.map +1 -1
  114. package/dist/studio/run-store-ui.d.ts +1 -1
  115. package/dist/studio/run-store-ui.js +43 -25
  116. package/dist/studio/run-store-ui.js.map +1 -1
  117. package/dist/tracing/tracing-codec.d.ts +5 -0
  118. package/dist/tracing/tracing-codec.js +13 -0
  119. package/dist/tracing/tracing-codec.js.map +1 -0
  120. package/dist/tracing/tracing-constants.d.ts +4 -0
  121. package/dist/tracing/tracing-constants.js +5 -0
  122. package/dist/tracing/tracing-constants.js.map +1 -0
  123. package/dist/tracing/tracing-contracts.d.ts +31 -0
  124. package/dist/tracing/tracing-contracts.js +2 -0
  125. package/dist/tracing/tracing-contracts.js.map +1 -0
  126. package/dist/tracing/tracing-internal.d.ts +1 -1
  127. package/dist/tracing/tracing-internal.js +116 -81
  128. package/dist/tracing/tracing-internal.js.map +1 -1
  129. package/dist/tracing/tracing-schema.d.ts +809 -0
  130. package/dist/tracing/tracing-schema.js +429 -0
  131. package/dist/tracing/tracing-schema.js.map +1 -0
  132. package/dist/tracing/tracing.d.ts +2 -54
  133. package/dist/tracing/tracing.js.map +1 -1
  134. package/dist/tracing/wire-schema.d.ts +50 -0
  135. package/dist/tracing/wire-schema.js +193 -0
  136. package/dist/tracing/wire-schema.js.map +1 -0
  137. package/dist/workbench/pipeline-module.js +4 -4
  138. package/dist/workbench/pipeline-module.js.map +1 -1
  139. package/dist/workbench/workbench-entry.d.ts +2 -0
  140. package/dist/workbench/workbench-entry.js +3 -0
  141. package/dist/workbench/workbench-entry.js.map +1 -0
  142. package/dist/workbench/workbench-history.js +8 -1
  143. package/dist/workbench/workbench-history.js.map +1 -1
  144. package/dist/workbench/workbench-ui.js +39 -43
  145. package/dist/workbench/workbench-ui.js.map +1 -1
  146. package/docs/README.md +22 -15
  147. package/docs/agent-guide.md +62 -68
  148. package/docs/agent-skills.md +59 -0
  149. package/docs/api-reference.md +14 -178
  150. package/docs/api-report.json +13 -215
  151. package/docs/child-pipeline-composition.md +139 -193
  152. package/docs/cli.md +86 -64
  153. package/docs/comparison.md +64 -62
  154. package/docs/concepts.md +210 -281
  155. package/docs/getting-started.md +61 -49
  156. package/docs/llms.txt +8 -8
  157. package/docs/recipes.md +42 -45
  158. package/docs/remote-step-composition.md +69 -74
  159. package/docs/studio.md +110 -73
  160. package/examples/catalog/pipelines/enrich.ts +2 -2
  161. package/examples/catalog/scripts/enrich.ts +1 -1
  162. package/examples/catalog/scripts/import.ts +1 -1
  163. package/examples/catalog/scripts/publish.ts +1 -1
  164. package/examples/catalog/tubeless.project.ts +6 -3
  165. package/examples/cli-job.ts +1 -1
  166. package/examples/host-embedding.ts +4 -5
  167. package/examples/live-tui.ts +1 -1
  168. package/examples/peloton.ts +1 -1
  169. package/examples/remote-steps.ts +8 -5
  170. package/examples/tracing.ts +96 -5
  171. package/package.json +17 -64
  172. package/skills/tubeless/SKILL.md +85 -0
  173. package/skills/tubeless-make-pipeline/SKILL.md +126 -0
  174. package/dist/node/env.d.ts +0 -1
  175. package/dist/node/env.js +0 -8
  176. package/dist/node/env.js.map +0 -1
  177. package/dist/node/file-utils.d.ts +0 -3
  178. package/dist/node/file-utils.js +0 -17
  179. package/dist/node/file-utils.js.map +0 -1
  180. package/dist/node/paths.d.ts +0 -8
  181. package/dist/node/paths.js +0 -19
  182. package/dist/node/paths.js.map +0 -1
  183. package/dist/reporter/prompt-select.d.ts +0 -80
  184. package/dist/reporter/prompt-select.js +0 -197
  185. package/dist/reporter/prompt-select.js.map +0 -1
  186. package/dist/studio/run-store-ui-client-source.d.ts +0 -1
  187. package/dist/studio/run-store-ui-client-source.js +0 -1
  188. package/dist/tracing/tracing-json.d.ts +0 -11
  189. package/dist/tracing/tracing-json.js +0 -8
  190. package/dist/tracing/tracing-json.js.map +0 -1
  191. package/dist/tracing/tracing-otel.d.ts +0 -35
  192. package/dist/tracing/tracing-otel.js +0 -60
  193. package/dist/tracing/tracing-otel.js.map +0 -1
  194. package/dist/workbench/workbench-studio.d.ts +0 -29
  195. package/dist/workbench/workbench-studio.js +0 -61
  196. package/dist/workbench/workbench-studio.js.map +0 -1
  197. package/examples/catalog/tubeless.studio.ts +0 -20
  198. package/examples/local-observability.ts +0 -105
  199. package/examples/rendering.ts +0 -34
@@ -1,23 +1,26 @@
1
1
  import { lstat, mkdir, realpath, stat } from "node:fs/promises";
2
2
  import * as path from "node:path";
3
3
  import { pathToFileURL } from "node:url";
4
- const RUN_EVENT_STORE_VERSION = 1;
4
+ import { decodePipelineTraceEvent } from "../tracing/tracing-codec.js";
5
+ const RUN_EVENT_STORE_VERSION = 3;
5
6
  /** Larger batches mean fewer commits but more tail loss on crash. */
6
7
  const EXPORT_BATCH_SIZE = 64;
7
8
  function eventRow(event) {
9
+ const canonical = decodePipelineTraceEvent(event);
8
10
  return [
9
- event.version,
10
- event.runId,
11
- event.parentRunId ?? null,
12
- event.pipelineId,
13
- event.stepId ?? null,
14
- event.attemptId ?? null,
15
- event.itemKey ?? null,
16
- event.name,
17
- event.timestampMs,
18
- event.durationMs ?? null,
19
- JSON.stringify(event.attributes),
20
- event.error ? JSON.stringify(event.error) : null,
11
+ canonical.version,
12
+ canonical.runId,
13
+ canonical.correlationId ?? null,
14
+ canonical.parentRunId ?? null,
15
+ canonical.pipelineId,
16
+ "stepId" in canonical ? (canonical.stepId ?? null) : null,
17
+ "attemptId" in canonical ? (canonical.attemptId ?? null) : null,
18
+ canonical.itemKey ?? null,
19
+ canonical.name,
20
+ canonical.timestampMs,
21
+ "durationMs" in canonical ? (canonical.durationMs ?? null) : null,
22
+ JSON.stringify(canonical.payload),
23
+ "error" in canonical && canonical.error ? JSON.stringify(canonical.error) : null,
21
24
  ];
22
25
  }
23
26
  const RUN_EVENT_STORE_APPEND_ONLY_TRIGGERS = `
@@ -42,6 +45,7 @@ const RUN_EVENT_STORE_SCHEMA = `
42
45
  id INTEGER PRIMARY KEY AUTOINCREMENT,
43
46
  version INTEGER NOT NULL,
44
47
  run_id TEXT NOT NULL,
48
+ correlation_id TEXT,
45
49
  parent_run_id TEXT,
46
50
  pipeline_id TEXT NOT NULL,
47
51
  step_id TEXT,
@@ -50,7 +54,7 @@ const RUN_EVENT_STORE_SCHEMA = `
50
54
  event_name TEXT NOT NULL,
51
55
  timestamp_ms INTEGER NOT NULL,
52
56
  duration_ms REAL,
53
- attributes_json TEXT NOT NULL,
57
+ payload_json TEXT NOT NULL,
54
58
  error_json TEXT
55
59
  );
56
60
 
@@ -69,6 +73,12 @@ function statement(database, sql) {
69
73
  throw new Error("The current SQLite runtime does not support prepared queries.");
70
74
  return prepared;
71
75
  }
76
+ function readStoreVersion(database) {
77
+ // SAFETY: `PRAGMA user_version` always returns a single row with a
78
+ // `user_version` column, so the first result row matches this shape.
79
+ const versionRow = statement(database, "PRAGMA user_version").all()[0];
80
+ return Number(versionRow?.user_version ?? 0);
81
+ }
72
82
  async function loadSqliteModule() {
73
83
  const specifier = "Bun" in globalThis ? "bun:sqlite" : "node:sqlite";
74
84
  // SAFETY: The dynamic import resolves to the built-in sqlite module whose
@@ -190,33 +200,29 @@ function openReadableDatabase(Database, filename, options) {
190
200
  }
191
201
  }
192
202
  function mapRow(row) {
193
- const event = {
194
- // SAFETY: attributes_json was written by this store via JSON.stringify of a
195
- // PipelineTraceEvent["attributes"] value, so parsing yields the same shape.
196
- attributes: JSON.parse(row.attributes_json),
197
- id: Number(row.id),
203
+ const encoded = {
198
204
  name: row.event_name,
199
205
  pipelineId: row.pipeline_id,
200
206
  runId: row.run_id,
201
207
  timestampMs: Number(row.timestamp_ms),
202
208
  version: row.version,
203
209
  };
210
+ encoded.payload = JSON.parse(row.payload_json);
204
211
  if (row.attempt_id)
205
- event.attemptId = row.attempt_id;
212
+ encoded.attemptId = row.attempt_id;
213
+ if (row.correlation_id != null)
214
+ encoded.correlationId = row.correlation_id;
206
215
  if (row.duration_ms !== null)
207
- event.durationMs = Number(row.duration_ms);
208
- if (row.error_json) {
209
- // SAFETY: error_json was written by this store via JSON.stringify of a
210
- // NonNullable<PipelineTraceEvent["error"]> value, so parsing yields the same shape.
211
- event.error = JSON.parse(row.error_json);
212
- }
216
+ encoded.durationMs = Number(row.duration_ms);
217
+ if (row.error_json)
218
+ encoded.error = JSON.parse(row.error_json);
213
219
  if (row.item_key)
214
- event.itemKey = row.item_key;
220
+ encoded.itemKey = row.item_key;
215
221
  if (row.parent_run_id)
216
- event.parentRunId = row.parent_run_id;
222
+ encoded.parentRunId = row.parent_run_id;
217
223
  if (row.step_id)
218
- event.stepId = row.step_id;
219
- return event;
224
+ encoded.stepId = row.step_id;
225
+ return { ...decodePipelineTraceEvent(encoded), id: Number(row.id) };
220
226
  }
221
227
  /**
222
228
  * Open the optional local SQLite trace store used by `tubeless run --store`,
@@ -243,14 +249,11 @@ export async function openSqlitePipelineRunStore(filename, options = {}) {
243
249
  }
244
250
  const database = await openDatabase(resolvedFilename, { create: initialize, readOnly });
245
251
  try {
246
- // SAFETY: `PRAGMA user_version` always returns a single row with a
247
- // `user_version` column, so the first result row matches this shape.
248
- const versionRow = statement(database, "PRAGMA user_version").all()[0];
249
- const version = Number(versionRow?.user_version ?? 0);
252
+ const version = readStoreVersion(database);
250
253
  if (version !== 0 && version !== RUN_EVENT_STORE_VERSION) {
251
254
  throw new Error(`Unsupported pipeline run store schema version ${version}; expected ${RUN_EVENT_STORE_VERSION}.`);
252
255
  }
253
- if (!initialize && version !== RUN_EVENT_STORE_VERSION) {
256
+ if (!initialize && version === 0) {
254
257
  throw new Error(`${resolvedFilename} is not a pipeline run store.`);
255
258
  }
256
259
  if (initialize) {
@@ -263,9 +266,9 @@ export async function openSqlitePipelineRunStore(filename, options = {}) {
263
266
  throw error;
264
267
  }
265
268
  const insert = statement(database, `INSERT INTO pipeline_run_events (
266
- version, run_id, parent_run_id, pipeline_id, step_id, attempt_id, item_key,
267
- event_name, timestamp_ms, duration_ms, attributes_json, error_json
268
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
269
+ version, run_id, correlation_id, parent_run_id, pipeline_id, step_id, attempt_id,
270
+ item_key, event_name, timestamp_ms, duration_ms, payload_json, error_json
271
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
269
272
  let closed = false;
270
273
  let exportError;
271
274
  const pending = [];
@@ -1 +1 @@
1
- {"version":3,"file":"run-store-sqlite.js","sourceRoot":"","sources":["../../src/run-store/run-store-sqlite.ts"],"names":[],"mappings":"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","sourcesContent":["import { lstat, mkdir, realpath, stat } from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport type { PipelineTraceEvent } from \"../tracing/tracing.js\";\nimport type {\n PipelineRunEventQuery,\n PipelineRunEventStore,\n StoredPipelineEvent,\n} from \"./run-store.js\";\n\ninterface SqliteStatement {\n all(...params: unknown[]): unknown[];\n run(...params: unknown[]): void;\n}\n\ninterface SqliteDatabase {\n close(): void;\n exec(sql: string): void;\n prepare?(sql: string): SqliteStatement;\n query?(sql: string): SqliteStatement;\n}\n\ninterface SqliteOpenOptions {\n create?: boolean;\n readOnly?: boolean;\n readonly?: boolean;\n readwrite?: boolean;\n}\n\ninterface SqliteModule {\n Database?: new (filename: string, options?: SqliteOpenOptions) => SqliteDatabase;\n DatabaseSync?: new (filename: string, options?: SqliteOpenOptions) => SqliteDatabase;\n}\n\ninterface StoredEventRow {\n attempt_id: string | null;\n attributes_json: string;\n duration_ms: number | null;\n error_json: string | null;\n event_name: PipelineTraceEvent[\"name\"];\n id: number | bigint;\n item_key: string | null;\n parent_run_id: string | null;\n pipeline_id: string;\n run_id: string;\n step_id: string | null;\n timestamp_ms: number | bigint;\n version: 1;\n}\n\nconst RUN_EVENT_STORE_VERSION = 1;\n/** Larger batches mean fewer commits but more tail loss on crash. */\nconst EXPORT_BATCH_SIZE = 64;\n\ntype EventRow = readonly [\n version: PipelineTraceEvent[\"version\"],\n runId: string,\n parentRunId: string | null,\n pipelineId: string,\n stepId: string | null,\n attemptId: string | null,\n itemKey: string | null,\n name: PipelineTraceEvent[\"name\"],\n timestampMs: number,\n durationMs: number | null,\n attributesJson: string,\n errorJson: string | null,\n];\n\nfunction eventRow(event: PipelineTraceEvent): EventRow {\n return [\n event.version,\n event.runId,\n event.parentRunId ?? null,\n event.pipelineId,\n event.stepId ?? null,\n event.attemptId ?? null,\n event.itemKey ?? null,\n event.name,\n event.timestampMs,\n event.durationMs ?? null,\n JSON.stringify(event.attributes),\n event.error ? JSON.stringify(event.error) : null,\n ];\n}\n\nconst RUN_EVENT_STORE_APPEND_ONLY_TRIGGERS = `\n CREATE TRIGGER IF NOT EXISTS pipeline_run_events_no_update\n BEFORE UPDATE ON pipeline_run_events\n BEGIN\n SELECT RAISE(ABORT, 'pipeline_run_events is append-only');\n END;\n\n CREATE TRIGGER IF NOT EXISTS pipeline_run_events_no_delete\n BEFORE DELETE ON pipeline_run_events\n BEGIN\n SELECT RAISE(ABORT, 'pipeline_run_events is append-only');\n END;\n`;\n\nconst RUN_EVENT_STORE_SCHEMA = `\n PRAGMA journal_mode = WAL;\n PRAGMA synchronous = NORMAL;\n PRAGMA foreign_keys = ON;\n\n CREATE TABLE IF NOT EXISTS pipeline_run_events (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n version INTEGER NOT NULL,\n run_id TEXT NOT NULL,\n parent_run_id TEXT,\n pipeline_id TEXT NOT NULL,\n step_id TEXT,\n attempt_id TEXT,\n item_key TEXT,\n event_name TEXT NOT NULL,\n timestamp_ms INTEGER NOT NULL,\n duration_ms REAL,\n attributes_json TEXT NOT NULL,\n error_json TEXT\n );\n\n CREATE INDEX IF NOT EXISTS pipeline_run_events_run_id_idx\n ON pipeline_run_events(run_id, id);\n CREATE INDEX IF NOT EXISTS pipeline_run_events_pipeline_id_idx\n ON pipeline_run_events(pipeline_id, id DESC);\n CREATE INDEX IF NOT EXISTS pipeline_run_events_timestamp_idx\n ON pipeline_run_events(timestamp_ms DESC);\n\n ${RUN_EVENT_STORE_APPEND_ONLY_TRIGGERS}\n`;\n\n/** SQLite event store plus the explicit all-history maintenance operation. */\nexport interface SqlitePipelineRunStore extends PipelineRunEventStore {\n /** Delete every recorded event and compact the database; individual mutation stays forbidden. */\n clearHistory(): void | Promise<void>;\n}\n\nfunction statement(database: SqliteDatabase, sql: string): SqliteStatement {\n const prepared = database.query?.(sql) ?? database.prepare?.(sql);\n if (!prepared) throw new Error(\"The current SQLite runtime does not support prepared queries.\");\n return prepared;\n}\n\nasync function loadSqliteModule(): Promise<SqliteModule> {\n const specifier = \"Bun\" in globalThis ? \"bun:sqlite\" : \"node:sqlite\";\n // SAFETY: The dynamic import resolves to the built-in sqlite module whose\n // runtime shape is the `SqliteModule` interface we depend on.\n return (await import(specifier)) as SqliteModule;\n}\n\nasync function openDatabase(\n filename: string,\n options: { create?: boolean; readOnly?: boolean } = {}\n): Promise<SqliteDatabase> {\n const module = await loadSqliteModule();\n const Database = module.Database ?? module.DatabaseSync;\n if (!Database) throw new Error(\"No synchronous SQLite database implementation is available.\");\n const bunSqlite = \"Bun\" in globalThis;\n const create = options.create !== false;\n const readOnly = options.readOnly === true;\n if (readOnly) {\n await sqliteAssertImmutableInspect(filename);\n const database = openReadableDatabase(\n Database,\n `${pathToFileURL(filename).href}?mode=ro&immutable=1`\n );\n try {\n await sqliteAssertImmutableInspect(filename);\n return database;\n } catch (error) {\n database.close();\n throw error;\n }\n }\n if (!create) {\n if (bunSqlite) {\n return openReadableDatabase(Database, filename, { create: false, readwrite: true });\n }\n // Node's DatabaseSync ignores `{ create: false }` and creates an empty\n // file. `mode=rw` fails closed when the path is missing.\n return openReadableDatabase(Database, `${pathToFileURL(filename).href}?mode=rw`);\n }\n return new Database(filename);\n}\n\nfunction isMissingDirectoryEntry(error: NodeJS.ErrnoException): boolean {\n return error.code === \"ENOENT\";\n}\n\nasync function sqliteSidecarExists(filename: string): Promise<boolean> {\n try {\n await stat(filename);\n return true;\n } catch (error) {\n // SAFETY: Node fs rejects with ErrnoException. Only ENOENT is a missing\n // directory entry; EACCES/EPERM and other inspect failures stay present.\n if (isMissingDirectoryEntry(error as NodeJS.ErrnoException) === false) return true;\n }\n try {\n await lstat(filename);\n return true;\n } catch (error) {\n // SAFETY: Same Node fs ErrnoException contract as the stat() branch.\n return isMissingDirectoryEntry(error as NodeJS.ErrnoException) === false;\n }\n}\n\nasync function sqliteSidecarTargets(filename: string): Promise<readonly string[]> {\n const targets: string[] = [];\n try {\n targets.push(await realpath(filename));\n } catch {\n // Missing or unresolvable; still check the given name.\n }\n if (targets.includes(filename) === false) targets.push(filename);\n return targets;\n}\n\nasync function sqliteDatabaseHasMultipleLinks(filename: string): Promise<boolean> {\n for (const target of await sqliteSidecarTargets(filename)) {\n try {\n if ((await stat(target)).nlink > 1) return true;\n } catch {\n return true;\n }\n }\n return false;\n}\n\nasync function sqliteAssertImmutableInspect(filename: string): Promise<void> {\n const pending = await sqlitePendingTransactionalSidecar(filename);\n if (pending !== undefined) {\n throw new Error(\n `${filename} has a write-ahead log or rollback journal that a read-only open cannot apply without writing a sidecar.`\n );\n }\n if (await sqliteDatabaseHasMultipleLinks(filename)) {\n throw new Error(\n `${filename} has multiple hard links; a read-only open cannot locate a writer's write-ahead log beside another name.`\n );\n }\n}\n\nasync function sqlitePendingTransactionalSidecar(filename: string): Promise<string | undefined> {\n for (const target of await sqliteSidecarTargets(filename)) {\n for (const suffix of [\"-journal\", \"-wal\"] as const) {\n const sidecar = `${target}${suffix}`;\n if ((await sqliteSidecarExists(sidecar)) === false) continue;\n try {\n if ((await stat(sidecar)).size > 0) return sidecar;\n } catch {\n return sidecar;\n }\n }\n }\n return undefined;\n}\n\nfunction openReadableDatabase(\n Database: new (filename: string, options?: SqliteOpenOptions) => SqliteDatabase,\n filename: string,\n options?: SqliteOpenOptions\n): SqliteDatabase {\n const database = options === undefined ? new Database(filename) : new Database(filename, options);\n try {\n statement(database, \"PRAGMA user_version\").all();\n return database;\n } catch (error) {\n database.close();\n throw error;\n }\n}\n\nfunction mapRow(row: StoredEventRow): StoredPipelineEvent {\n const event: StoredPipelineEvent = {\n // SAFETY: attributes_json was written by this store via JSON.stringify of a\n // PipelineTraceEvent[\"attributes\"] value, so parsing yields the same shape.\n attributes: JSON.parse(row.attributes_json) as PipelineTraceEvent[\"attributes\"],\n id: Number(row.id),\n name: row.event_name,\n pipelineId: row.pipeline_id,\n runId: row.run_id,\n timestampMs: Number(row.timestamp_ms),\n version: row.version,\n };\n if (row.attempt_id) event.attemptId = row.attempt_id;\n if (row.duration_ms !== null) event.durationMs = Number(row.duration_ms);\n if (row.error_json) {\n // SAFETY: error_json was written by this store via JSON.stringify of a\n // NonNullable<PipelineTraceEvent[\"error\"]> value, so parsing yields the same shape.\n event.error = JSON.parse(row.error_json) as NonNullable<PipelineTraceEvent[\"error\"]>;\n }\n if (row.item_key) event.itemKey = row.item_key;\n if (row.parent_run_id) event.parentRunId = row.parent_run_id;\n if (row.step_id) event.stepId = row.step_id;\n return event;\n}\n\n/** Options for `openSqlitePipelineRunStore`. */\nexport interface OpenSqlitePipelineRunStoreOptions {\n /**\n * When `false`, reject a path that is not already a versioned run store\n * and do not create parent directories or a database file.\n * Defaults to `true` so `run --store` and `ui` can create the database.\n */\n readonly initialize?: boolean;\n /**\n * Open the file without creating it or writing WAL/sidecars.\n * A leftover empty `-wal` is ignored and the main file is opened\n * immutable. A non-empty `-wal` or `-journal`, a multiply linked\n * database file, or a WAL that appears during the open or a later\n * `listEvents` is refused so pending events are not dropped.\n * `history` uses this so a supplied finished artifact can be inspected\n * without rewriting `-shm`.\n */\n readonly readOnly?: boolean;\n}\n\n/**\n * Open the optional local SQLite trace store used by `tubeless run --store`,\n * `tubeless history`, and `tubeless ui`. The main executor never imports this\n * module. `export()` buffers up to 64 events and commits them in one\n * transaction, plus on `flush()`, same-instance `listEvents`/`clearHistory`,\n * and `close()`. Other connections cannot see the unflushed tail; a crash can\n * lose it. Finished `tubeless run --store` invocations flush at completion.\n */\nexport async function openSqlitePipelineRunStore(\n filename: string,\n options: OpenSqlitePipelineRunStoreOptions = {}\n): Promise<SqlitePipelineRunStore> {\n const readOnly = options.readOnly === true;\n const initialize = options.initialize !== false && !readOnly;\n const resolvedFilename = filename === \":memory:\" ? filename : path.resolve(filename);\n if (resolvedFilename !== \":memory:\" && initialize) {\n await mkdir(path.dirname(resolvedFilename), { recursive: true });\n }\n if (resolvedFilename !== \":memory:\" && !initialize) {\n try {\n await stat(resolvedFilename);\n } catch {\n throw new Error(`${resolvedFilename} is not a pipeline run store.`);\n }\n }\n const database = await openDatabase(resolvedFilename, { create: initialize, readOnly });\n try {\n // SAFETY: `PRAGMA user_version` always returns a single row with a\n // `user_version` column, so the first result row matches this shape.\n const versionRow = statement(database, \"PRAGMA user_version\").all()[0] as\n | { user_version?: number | bigint }\n | undefined;\n const version = Number(versionRow?.user_version ?? 0);\n if (version !== 0 && version !== RUN_EVENT_STORE_VERSION) {\n throw new Error(\n `Unsupported pipeline run store schema version ${version}; expected ${RUN_EVENT_STORE_VERSION}.`\n );\n }\n if (!initialize && version !== RUN_EVENT_STORE_VERSION) {\n throw new Error(`${resolvedFilename} is not a pipeline run store.`);\n }\n if (initialize) {\n database.exec(RUN_EVENT_STORE_SCHEMA);\n database.exec(`PRAGMA user_version = ${RUN_EVENT_STORE_VERSION}`);\n }\n } catch (error) {\n database.close();\n throw error;\n }\n const insert = statement(\n database,\n `INSERT INTO pipeline_run_events (\n version, run_id, parent_run_id, pipeline_id, step_id, attempt_id, item_key,\n event_name, timestamp_ms, duration_ms, attributes_json, error_json\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`\n );\n let closed = false;\n let exportError: Error | undefined;\n const pending: EventRow[] = [];\n\n function drain(): void {\n if (pending.length === 0) return;\n try {\n database.exec(\"BEGIN IMMEDIATE\");\n for (const row of pending) insert.run(...row);\n database.exec(\"COMMIT\");\n pending.length = 0;\n } catch (error) {\n try {\n database.exec(\"ROLLBACK\");\n } catch {}\n exportError = error instanceof Error ? error : new Error(String(error));\n throw exportError;\n }\n }\n\n return {\n export(event) {\n if (closed) throw new Error(\"Cannot append to a closed pipeline run store.\");\n if (exportError) throw exportError;\n try {\n pending.push(eventRow(event));\n if (pending.length >= EXPORT_BATCH_SIZE) drain();\n } catch (error) {\n exportError = error instanceof Error ? error : new Error(String(error));\n throw exportError;\n }\n },\n flush() {\n if (exportError) throw exportError;\n drain();\n },\n async listEvents(query: PipelineRunEventQuery = {}) {\n if (closed) throw new Error(\"Cannot query a closed pipeline run store.\");\n drain();\n if (readOnly && resolvedFilename !== \":memory:\") {\n await sqliteAssertImmutableInspect(resolvedFilename);\n }\n const predicates: string[] = [];\n const params: unknown[] = [];\n if (query.afterId !== undefined) {\n predicates.push(\"id > ?\");\n params.push(query.afterId);\n }\n if (query.pipelineId !== undefined) {\n predicates.push(\"pipeline_id = ?\");\n params.push(query.pipelineId);\n }\n if (query.runId !== undefined) {\n predicates.push(\"run_id = ?\");\n params.push(query.runId);\n }\n const limit = Math.max(1, Math.min(100_000, Math.floor(query.limit ?? 20_000)));\n params.push(limit);\n const where = predicates.length > 0 ? `WHERE ${predicates.join(\" AND \")}` : \"\";\n // SAFETY: The SELECT * columns match the StoredEventRow interface exactly\n // (the table schema is owned by this module), so each result row is one.\n const rows = statement(\n database,\n `SELECT * FROM pipeline_run_events ${where} ORDER BY id ASC LIMIT ?`\n ).all(...params) as StoredEventRow[];\n if (readOnly && resolvedFilename !== \":memory:\") {\n await sqliteAssertImmutableInspect(resolvedFilename);\n }\n return rows.map(mapRow);\n },\n clearHistory() {\n if (closed) throw new Error(\"Cannot clear a closed pipeline run store.\");\n drain();\n try {\n database.exec(`\n BEGIN IMMEDIATE;\n DROP TRIGGER IF EXISTS pipeline_run_events_no_update;\n DROP TRIGGER IF EXISTS pipeline_run_events_no_delete;\n DELETE FROM pipeline_run_events;\n ${RUN_EVENT_STORE_APPEND_ONLY_TRIGGERS}\n COMMIT;\n `);\n } catch (error) {\n try {\n database.exec(\"ROLLBACK\");\n } catch {}\n throw error;\n }\n database.exec(\"VACUUM; PRAGMA wal_checkpoint(TRUNCATE);\");\n },\n close() {\n if (closed) return;\n closed = true;\n try {\n if (!exportError) drain();\n } catch {\n // exportError is recorded by drain; close still releases the handle.\n }\n database.close();\n if (exportError) throw exportError;\n },\n };\n}\n"]}
1
+ {"version":3,"file":"run-store-sqlite.js","sourceRoot":"","sources":["../../src/run-store/run-store-sqlite.ts"],"names":[],"mappings":"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","sourcesContent":["import { lstat, mkdir, realpath, stat } from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { decodePipelineTraceEvent } from \"../tracing/tracing-codec.js\";\nimport type { PipelineTraceEvent } from \"../tracing/tracing.js\";\nimport type {\n PipelineRunEventQuery,\n PipelineRunEventStore,\n StoredPipelineEvent,\n} from \"./run-store.js\";\n\ninterface SqliteStatement {\n all(...params: unknown[]): unknown[];\n run(...params: unknown[]): void;\n}\n\ninterface SqliteDatabase {\n close(): void;\n exec(sql: string): void;\n prepare?(sql: string): SqliteStatement;\n query?(sql: string): SqliteStatement;\n}\n\ninterface SqliteOpenOptions {\n create?: boolean;\n readOnly?: boolean;\n readonly?: boolean;\n readwrite?: boolean;\n}\n\ninterface SqliteModule {\n Database?: new (filename: string, options?: SqliteOpenOptions) => SqliteDatabase;\n DatabaseSync?: new (filename: string, options?: SqliteOpenOptions) => SqliteDatabase;\n}\n\ninterface StoredEventRow {\n attempt_id: string | null;\n correlation_id: string | null;\n duration_ms: number | null;\n error_json: string | null;\n event_name: string;\n id: number | bigint;\n item_key: string | null;\n parent_run_id: string | null;\n payload_json: string;\n pipeline_id: string;\n run_id: string;\n step_id: string | null;\n timestamp_ms: number | bigint;\n version: number;\n}\n\nconst RUN_EVENT_STORE_VERSION = 3;\n/** Larger batches mean fewer commits but more tail loss on crash. */\nconst EXPORT_BATCH_SIZE = 64;\n\ntype EventRow = readonly [\n version: number,\n runId: string,\n correlationId: string | null,\n parentRunId: string | null,\n pipelineId: string,\n stepId: string | null,\n attemptId: string | null,\n itemKey: string | null,\n name: string,\n timestampMs: number,\n durationMs: number | null,\n payloadJson: string,\n errorJson: string | null,\n];\n\nfunction eventRow(event: PipelineTraceEvent): EventRow {\n const canonical = decodePipelineTraceEvent(event);\n return [\n canonical.version,\n canonical.runId,\n canonical.correlationId ?? null,\n canonical.parentRunId ?? null,\n canonical.pipelineId,\n \"stepId\" in canonical ? (canonical.stepId ?? null) : null,\n \"attemptId\" in canonical ? (canonical.attemptId ?? null) : null,\n canonical.itemKey ?? null,\n canonical.name,\n canonical.timestampMs,\n \"durationMs\" in canonical ? (canonical.durationMs ?? null) : null,\n JSON.stringify(canonical.payload),\n \"error\" in canonical && canonical.error ? JSON.stringify(canonical.error) : null,\n ];\n}\n\nconst RUN_EVENT_STORE_APPEND_ONLY_TRIGGERS = `\n CREATE TRIGGER IF NOT EXISTS pipeline_run_events_no_update\n BEFORE UPDATE ON pipeline_run_events\n BEGIN\n SELECT RAISE(ABORT, 'pipeline_run_events is append-only');\n END;\n\n CREATE TRIGGER IF NOT EXISTS pipeline_run_events_no_delete\n BEFORE DELETE ON pipeline_run_events\n BEGIN\n SELECT RAISE(ABORT, 'pipeline_run_events is append-only');\n END;\n`;\n\nconst RUN_EVENT_STORE_SCHEMA = `\n PRAGMA journal_mode = WAL;\n PRAGMA synchronous = NORMAL;\n PRAGMA foreign_keys = ON;\n\n CREATE TABLE IF NOT EXISTS pipeline_run_events (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n version INTEGER NOT NULL,\n run_id TEXT NOT NULL,\n correlation_id TEXT,\n parent_run_id TEXT,\n pipeline_id TEXT NOT NULL,\n step_id TEXT,\n attempt_id TEXT,\n item_key TEXT,\n event_name TEXT NOT NULL,\n timestamp_ms INTEGER NOT NULL,\n duration_ms REAL,\n payload_json TEXT NOT NULL,\n error_json TEXT\n );\n\n CREATE INDEX IF NOT EXISTS pipeline_run_events_run_id_idx\n ON pipeline_run_events(run_id, id);\n CREATE INDEX IF NOT EXISTS pipeline_run_events_pipeline_id_idx\n ON pipeline_run_events(pipeline_id, id DESC);\n CREATE INDEX IF NOT EXISTS pipeline_run_events_timestamp_idx\n ON pipeline_run_events(timestamp_ms DESC);\n\n ${RUN_EVENT_STORE_APPEND_ONLY_TRIGGERS}\n`;\n\n/** SQLite event store plus the explicit all-history maintenance operation. */\nexport interface SqlitePipelineRunStore extends PipelineRunEventStore {\n /** Delete every recorded event and compact the database; individual mutation stays forbidden. */\n clearHistory(): void | Promise<void>;\n}\n\nfunction statement(database: SqliteDatabase, sql: string): SqliteStatement {\n const prepared = database.query?.(sql) ?? database.prepare?.(sql);\n if (!prepared) throw new Error(\"The current SQLite runtime does not support prepared queries.\");\n return prepared;\n}\n\nfunction readStoreVersion(database: SqliteDatabase): number {\n // SAFETY: `PRAGMA user_version` always returns a single row with a\n // `user_version` column, so the first result row matches this shape.\n const versionRow = statement(database, \"PRAGMA user_version\").all()[0] as\n | { user_version?: number | bigint }\n | undefined;\n return Number(versionRow?.user_version ?? 0);\n}\n\nasync function loadSqliteModule(): Promise<SqliteModule> {\n const specifier = \"Bun\" in globalThis ? \"bun:sqlite\" : \"node:sqlite\";\n // SAFETY: The dynamic import resolves to the built-in sqlite module whose\n // runtime shape is the `SqliteModule` interface we depend on.\n return (await import(specifier)) as SqliteModule;\n}\n\nasync function openDatabase(\n filename: string,\n options: { create?: boolean; readOnly?: boolean } = {}\n): Promise<SqliteDatabase> {\n const module = await loadSqliteModule();\n const Database = module.Database ?? module.DatabaseSync;\n if (!Database) throw new Error(\"No synchronous SQLite database implementation is available.\");\n const bunSqlite = \"Bun\" in globalThis;\n const create = options.create !== false;\n const readOnly = options.readOnly === true;\n if (readOnly) {\n await sqliteAssertImmutableInspect(filename);\n const database = openReadableDatabase(\n Database,\n `${pathToFileURL(filename).href}?mode=ro&immutable=1`\n );\n try {\n await sqliteAssertImmutableInspect(filename);\n return database;\n } catch (error) {\n database.close();\n throw error;\n }\n }\n if (!create) {\n if (bunSqlite) {\n return openReadableDatabase(Database, filename, { create: false, readwrite: true });\n }\n // Node's DatabaseSync ignores `{ create: false }` and creates an empty\n // file. `mode=rw` fails closed when the path is missing.\n return openReadableDatabase(Database, `${pathToFileURL(filename).href}?mode=rw`);\n }\n return new Database(filename);\n}\n\nfunction isMissingDirectoryEntry(error: NodeJS.ErrnoException): boolean {\n return error.code === \"ENOENT\";\n}\n\nasync function sqliteSidecarExists(filename: string): Promise<boolean> {\n try {\n await stat(filename);\n return true;\n } catch (error) {\n // SAFETY: Node fs rejects with ErrnoException. Only ENOENT is a missing\n // directory entry; EACCES/EPERM and other inspect failures stay present.\n if (isMissingDirectoryEntry(error as NodeJS.ErrnoException) === false) return true;\n }\n try {\n await lstat(filename);\n return true;\n } catch (error) {\n // SAFETY: Same Node fs ErrnoException contract as the stat() branch.\n return isMissingDirectoryEntry(error as NodeJS.ErrnoException) === false;\n }\n}\n\nasync function sqliteSidecarTargets(filename: string): Promise<readonly string[]> {\n const targets: string[] = [];\n try {\n targets.push(await realpath(filename));\n } catch {\n // Missing or unresolvable; still check the given name.\n }\n if (targets.includes(filename) === false) targets.push(filename);\n return targets;\n}\n\nasync function sqliteDatabaseHasMultipleLinks(filename: string): Promise<boolean> {\n for (const target of await sqliteSidecarTargets(filename)) {\n try {\n if ((await stat(target)).nlink > 1) return true;\n } catch {\n return true;\n }\n }\n return false;\n}\n\nasync function sqliteAssertImmutableInspect(filename: string): Promise<void> {\n const pending = await sqlitePendingTransactionalSidecar(filename);\n if (pending !== undefined) {\n throw new Error(\n `${filename} has a write-ahead log or rollback journal that a read-only open cannot apply without writing a sidecar.`\n );\n }\n if (await sqliteDatabaseHasMultipleLinks(filename)) {\n throw new Error(\n `${filename} has multiple hard links; a read-only open cannot locate a writer's write-ahead log beside another name.`\n );\n }\n}\n\nasync function sqlitePendingTransactionalSidecar(filename: string): Promise<string | undefined> {\n for (const target of await sqliteSidecarTargets(filename)) {\n for (const suffix of [\"-journal\", \"-wal\"] as const) {\n const sidecar = `${target}${suffix}`;\n if ((await sqliteSidecarExists(sidecar)) === false) continue;\n try {\n if ((await stat(sidecar)).size > 0) return sidecar;\n } catch {\n return sidecar;\n }\n }\n }\n return undefined;\n}\n\nfunction openReadableDatabase(\n Database: new (filename: string, options?: SqliteOpenOptions) => SqliteDatabase,\n filename: string,\n options?: SqliteOpenOptions\n): SqliteDatabase {\n const database = options === undefined ? new Database(filename) : new Database(filename, options);\n try {\n statement(database, \"PRAGMA user_version\").all();\n return database;\n } catch (error) {\n database.close();\n throw error;\n }\n}\n\nfunction mapRow(row: StoredEventRow): StoredPipelineEvent {\n const encoded: Record<string, unknown> = {\n name: row.event_name,\n pipelineId: row.pipeline_id,\n runId: row.run_id,\n timestampMs: Number(row.timestamp_ms),\n version: row.version,\n };\n encoded.payload = JSON.parse(row.payload_json);\n if (row.attempt_id) encoded.attemptId = row.attempt_id;\n if (row.correlation_id != null) encoded.correlationId = row.correlation_id;\n if (row.duration_ms !== null) encoded.durationMs = Number(row.duration_ms);\n if (row.error_json) encoded.error = JSON.parse(row.error_json);\n if (row.item_key) encoded.itemKey = row.item_key;\n if (row.parent_run_id) encoded.parentRunId = row.parent_run_id;\n if (row.step_id) encoded.stepId = row.step_id;\n return { ...decodePipelineTraceEvent(encoded), id: Number(row.id) };\n}\n\n/** Options for `openSqlitePipelineRunStore`. */\nexport interface OpenSqlitePipelineRunStoreOptions {\n /**\n * When `false`, reject a path that is not already a versioned run store\n * and do not create parent directories or a database file.\n * Defaults to `true` so `run --store` and `ui` can create the database.\n */\n readonly initialize?: boolean;\n /**\n * Open the file without creating it or writing WAL/sidecars.\n * A leftover empty `-wal` is ignored and the main file is opened\n * immutable. A non-empty `-wal` or `-journal`, a multiply linked\n * database file, or a WAL that appears during the open or a later\n * `listEvents` is refused so pending events are not dropped.\n * `history` uses this so a supplied finished artifact can be inspected\n * without rewriting `-shm`.\n */\n readonly readOnly?: boolean;\n}\n\n/**\n * Open the optional local SQLite trace store used by `tubeless run --store`,\n * `tubeless history`, and `tubeless ui`. The main executor never imports this\n * module. `export()` buffers up to 64 events and commits them in one\n * transaction, plus on `flush()`, same-instance `listEvents`/`clearHistory`,\n * and `close()`. Other connections cannot see the unflushed tail; a crash can\n * lose it. Finished `tubeless run --store` invocations flush at completion.\n */\nexport async function openSqlitePipelineRunStore(\n filename: string,\n options: OpenSqlitePipelineRunStoreOptions = {}\n): Promise<SqlitePipelineRunStore> {\n const readOnly = options.readOnly === true;\n const initialize = options.initialize !== false && !readOnly;\n const resolvedFilename = filename === \":memory:\" ? filename : path.resolve(filename);\n if (resolvedFilename !== \":memory:\" && initialize) {\n await mkdir(path.dirname(resolvedFilename), { recursive: true });\n }\n if (resolvedFilename !== \":memory:\" && !initialize) {\n try {\n await stat(resolvedFilename);\n } catch {\n throw new Error(`${resolvedFilename} is not a pipeline run store.`);\n }\n }\n const database = await openDatabase(resolvedFilename, { create: initialize, readOnly });\n try {\n const version = readStoreVersion(database);\n if (version !== 0 && version !== RUN_EVENT_STORE_VERSION) {\n throw new Error(\n `Unsupported pipeline run store schema version ${version}; expected ${RUN_EVENT_STORE_VERSION}.`\n );\n }\n if (!initialize && version === 0) {\n throw new Error(`${resolvedFilename} is not a pipeline run store.`);\n }\n if (initialize) {\n database.exec(RUN_EVENT_STORE_SCHEMA);\n database.exec(`PRAGMA user_version = ${RUN_EVENT_STORE_VERSION}`);\n }\n } catch (error) {\n database.close();\n throw error;\n }\n const insert = statement(\n database,\n `INSERT INTO pipeline_run_events (\n version, run_id, correlation_id, parent_run_id, pipeline_id, step_id, attempt_id,\n item_key, event_name, timestamp_ms, duration_ms, payload_json, error_json\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`\n );\n let closed = false;\n let exportError: Error | undefined;\n const pending: EventRow[] = [];\n\n function drain(): void {\n if (pending.length === 0) return;\n try {\n database.exec(\"BEGIN IMMEDIATE\");\n for (const row of pending) insert.run(...row);\n database.exec(\"COMMIT\");\n pending.length = 0;\n } catch (error) {\n try {\n database.exec(\"ROLLBACK\");\n } catch {}\n exportError = error instanceof Error ? error : new Error(String(error));\n throw exportError;\n }\n }\n\n return {\n export(event) {\n if (closed) throw new Error(\"Cannot append to a closed pipeline run store.\");\n if (exportError) throw exportError;\n try {\n pending.push(eventRow(event));\n if (pending.length >= EXPORT_BATCH_SIZE) drain();\n } catch (error) {\n exportError = error instanceof Error ? error : new Error(String(error));\n throw exportError;\n }\n },\n flush() {\n if (exportError) throw exportError;\n drain();\n },\n async listEvents(query: PipelineRunEventQuery = {}) {\n if (closed) throw new Error(\"Cannot query a closed pipeline run store.\");\n drain();\n if (readOnly && resolvedFilename !== \":memory:\") {\n await sqliteAssertImmutableInspect(resolvedFilename);\n }\n const predicates: string[] = [];\n const params: unknown[] = [];\n if (query.afterId !== undefined) {\n predicates.push(\"id > ?\");\n params.push(query.afterId);\n }\n if (query.pipelineId !== undefined) {\n predicates.push(\"pipeline_id = ?\");\n params.push(query.pipelineId);\n }\n if (query.runId !== undefined) {\n predicates.push(\"run_id = ?\");\n params.push(query.runId);\n }\n const limit = Math.max(1, Math.min(100_000, Math.floor(query.limit ?? 20_000)));\n params.push(limit);\n const where = predicates.length > 0 ? `WHERE ${predicates.join(\" AND \")}` : \"\";\n // SAFETY: The SELECT * columns match the StoredEventRow interface exactly\n // (the table schema is owned by this module), so each result row is one.\n const rows = statement(\n database,\n `SELECT * FROM pipeline_run_events ${where} ORDER BY id ASC LIMIT ?`\n ).all(...params) as StoredEventRow[];\n if (readOnly && resolvedFilename !== \":memory:\") {\n await sqliteAssertImmutableInspect(resolvedFilename);\n }\n return rows.map(mapRow);\n },\n clearHistory() {\n if (closed) throw new Error(\"Cannot clear a closed pipeline run store.\");\n drain();\n try {\n database.exec(`\n BEGIN IMMEDIATE;\n DROP TRIGGER IF EXISTS pipeline_run_events_no_update;\n DROP TRIGGER IF EXISTS pipeline_run_events_no_delete;\n DELETE FROM pipeline_run_events;\n ${RUN_EVENT_STORE_APPEND_ONLY_TRIGGERS}\n COMMIT;\n `);\n } catch (error) {\n try {\n database.exec(\"ROLLBACK\");\n } catch {}\n throw error;\n }\n database.exec(\"VACUUM; PRAGMA wal_checkpoint(TRUNCATE);\");\n },\n close() {\n if (closed) return;\n closed = true;\n try {\n if (!exportError) drain();\n } catch {\n // exportError is recorded by drain; close still releases the handle.\n }\n database.close();\n if (exportError) throw exportError;\n },\n };\n}\n"]}
@@ -1,10 +1,10 @@
1
1
  import { RUN_MODEL_VERSION, type PipelineRunStatus, type PipelineStepLifecycleStatus, type PipelineStepProgressDetail } from "../core/pipeline.js";
2
- import type { PipelineTraceError, PipelineTraceEvent, PipelineTraceExporter } from "../tracing/tracing.js";
2
+ import type { PipelineTraceError, PipelineTraceEvent, PipelineTraceExporter } from "../tracing/tracing-contracts.js";
3
3
  /** One trace event after it has been appended to a durable local store. */
4
- export interface StoredPipelineEvent extends PipelineTraceEvent {
4
+ export type StoredPipelineEvent = PipelineTraceEvent & {
5
5
  /** Store-local, monotonically increasing sequence. `0` is a valid first id. */
6
6
  id: number;
7
- }
7
+ };
8
8
  export interface PipelineRunEventQuery {
9
9
  /** Return events strictly after this store-local sequence. */
10
10
  afterId?: number;
@@ -75,6 +75,7 @@ export interface StoredPipelineStep {
75
75
  status: PipelineStepLifecycleStatus;
76
76
  }
77
77
  export interface StoredPipelineRun {
78
+ correlationId?: string;
78
79
  dryRun: boolean;
79
80
  durationMs?: number;
80
81
  error?: PipelineTraceError;
@@ -1,153 +1,5 @@
1
1
  import { RUN_MODEL_VERSION, } from "../core/pipeline.js";
2
2
  import { hasVisibleStepProgress } from "../core/progress.js";
3
- function isStringValue(value) {
4
- return typeof value === "string";
5
- }
6
- function isNumberValue(value) {
7
- return typeof value === "number";
8
- }
9
- function isBooleanValue(value) {
10
- return typeof value === "boolean";
11
- }
12
- function stringAttribute(attributes, key) {
13
- const value = attributes[key];
14
- return isStringValue(value) ? value : undefined;
15
- }
16
- function numberAttribute(attributes, key) {
17
- const value = attributes[key];
18
- return isNumberValue(value) ? value : undefined;
19
- }
20
- function booleanAttribute(attributes, key) {
21
- const value = attributes[key];
22
- return isBooleanValue(value) ? value : undefined;
23
- }
24
- function stringArrayAttribute(attributes, key) {
25
- const value = stringAttribute(attributes, key);
26
- if (!value)
27
- return [];
28
- try {
29
- const parsed = JSON.parse(value);
30
- return Array.isArray(parsed)
31
- ? parsed.filter((item) => typeof item === "string")
32
- : [];
33
- }
34
- catch {
35
- return [];
36
- }
37
- }
38
- function parseProgressDetails(attributes) {
39
- const value = stringAttribute(attributes, "details");
40
- if (!value)
41
- return undefined;
42
- try {
43
- const parsed = JSON.parse(value);
44
- if (!Array.isArray(parsed))
45
- return undefined;
46
- const details = parsed.flatMap((item) => {
47
- if (item === null || Array.isArray(item) || !(item instanceof Object))
48
- return [];
49
- // SAFETY: JSON.parse leaves object fields untyped; isStringValue keeps strings only.
50
- const id = item.id;
51
- if (!("id" in item) || !isStringValue(id) || id.length === 0)
52
- return [];
53
- const detail = { id: id.slice(0, 4_096) };
54
- // SAFETY: parsed fields are checked by the same guards as other trace attributes.
55
- const fields = item;
56
- const name = fields.name;
57
- if (isStringValue(name) && name)
58
- detail.name = name.slice(0, 4_096);
59
- for (const key of ["depth", "completed", "total"]) {
60
- const number = fields[key];
61
- if (isNumberValue(number) && Number.isFinite(number))
62
- detail[key] = number;
63
- }
64
- // SAFETY: JSON.parse leaves object fields untyped; isStringValue keeps strings only.
65
- const label = ("label" in item ? item.label : undefined);
66
- if (isStringValue(label) && label) {
67
- detail.label = label.slice(0, 4_096);
68
- }
69
- // SAFETY: JSON.parse leaves object fields untyped; isStringValue keeps strings only.
70
- const status = ("status" in item ? item.status : undefined);
71
- if (isStringValue(status) &&
72
- (status === "cancelled" ||
73
- status === "completed" ||
74
- status === "failed" ||
75
- status === "pending" ||
76
- status === "running" ||
77
- status === "skipped")) {
78
- detail.status = status;
79
- }
80
- return [detail];
81
- });
82
- return details.length > 0 ? details.slice(0, 128) : undefined;
83
- }
84
- catch {
85
- return undefined;
86
- }
87
- }
88
- function parseNestedPipeline(attributes) {
89
- const value = stringAttribute(attributes, "nested_pipeline");
90
- if (!value)
91
- return undefined;
92
- try {
93
- const parsed = JSON.parse(value);
94
- if (parsed === null || Array.isArray(parsed) || !(parsed instanceof Object))
95
- return undefined;
96
- if (!("mode" in parsed) || !("pipelineId" in parsed) || !("stepIds" in parsed))
97
- return undefined;
98
- if (parsed.mode !== "for-each" && parsed.mode !== "single")
99
- return undefined;
100
- // SAFETY: JSON.parse leaves object fields untyped; isStringValue keeps strings only.
101
- const pipelineId = parsed.pipelineId;
102
- if (!isStringValue(pipelineId) || pipelineId.length === 0)
103
- return undefined;
104
- if (!Array.isArray(parsed.stepIds))
105
- return undefined;
106
- const stepIds = parsed.stepIds
107
- .filter(isStringValue)
108
- .slice(0, 128)
109
- .map((item) => item.slice(0, 4_096));
110
- // SAFETY: JSON.parse leaves object fields untyped; isNumberValue keeps numbers only.
111
- const recorded = ("step_count" in parsed ? parsed.step_count : undefined);
112
- const recordedCount = isNumberValue(recorded) && Number.isFinite(recorded) ? Math.floor(recorded) : stepIds.length;
113
- return {
114
- mode: parsed.mode,
115
- pipelineId: pipelineId.slice(0, 4_096),
116
- stepCount: Math.max(stepIds.length, recordedCount),
117
- stepIds,
118
- };
119
- }
120
- catch {
121
- return undefined;
122
- }
123
- }
124
- function parseRemote(attributes) {
125
- const value = stringAttribute(attributes, "remote");
126
- if (!value)
127
- return undefined;
128
- try {
129
- const parsed = JSON.parse(value);
130
- if (parsed === null || Array.isArray(parsed) || !(parsed instanceof Object))
131
- return undefined;
132
- if (!("engine" in parsed))
133
- return undefined;
134
- // SAFETY: JSON.parse returned a plain object and we confirmed an engine key exists.
135
- const engine = parsed.engine;
136
- if (!isStringValue(engine) || engine.length === 0)
137
- return undefined;
138
- const remote = { engine: engine.slice(0, 4_096) };
139
- if ("target" in parsed) {
140
- // SAFETY: target is optional metadata on the same parsed remote object.
141
- const target = parsed.target;
142
- if (isStringValue(target) && target.length > 0)
143
- remote.target = target.slice(0, 4_096);
144
- }
145
- return remote;
146
- }
147
- catch {
148
- return undefined;
149
- }
150
- }
151
3
  function terminalStepStatus(event) {
152
4
  switch (event.name) {
153
5
  case "step.cancelled":
@@ -177,11 +29,10 @@ function applyRunEvent(projection, event) {
177
29
  projection.logCount += 1;
178
30
  if (!projection.retainLogs)
179
31
  return;
180
- const level = stringAttribute(event.attributes, "level");
181
32
  const log = {
182
33
  id: event.id,
183
- level: level === "error" || level === "warn" ? level : "log",
184
- message: stringAttribute(event.attributes, "message") ?? "",
34
+ level: event.payload.level,
35
+ message: event.payload.message,
185
36
  timestampMs: event.timestampMs,
186
37
  };
187
38
  if (event.attemptId)
@@ -200,14 +51,16 @@ function applyRunEvent(projection, event) {
200
51
  projection.stepOrder.push(event.stepId);
201
52
  }
202
53
  if (event.name === "step.planned") {
203
- step.name = stringAttribute(event.attributes, "name");
204
- step.description = stringAttribute(event.attributes, "description");
205
- const nestedPipeline = parseNestedPipeline(event.attributes);
206
- if (nestedPipeline)
207
- step.nestedPipeline = nestedPipeline;
208
- const remote = parseRemote(event.attributes);
209
- if (remote)
210
- step.remote = remote;
54
+ step.name = event.payload.name;
55
+ step.description = event.payload.description;
56
+ if (event.payload.nestedPipeline) {
57
+ step.nestedPipeline = {
58
+ ...event.payload.nestedPipeline,
59
+ stepIds: [...event.payload.nestedPipeline.stepIds],
60
+ };
61
+ }
62
+ if (event.payload.remote)
63
+ step.remote = { ...event.payload.remote };
211
64
  return;
212
65
  }
213
66
  if (event.attemptId) {
@@ -222,9 +75,7 @@ function applyRunEvent(projection, event) {
222
75
  step.attempt = attempt;
223
76
  }
224
77
  if (event.name === "step.attempted") {
225
- const retry = numberAttribute(event.attributes, "attempt");
226
- if (retry !== undefined)
227
- attempt.retries.push(retry);
78
+ attempt.retries.push(event.payload.attempt);
228
79
  }
229
80
  const terminal = terminalStepStatus(event);
230
81
  if (terminal) {
@@ -236,23 +87,11 @@ function applyRunEvent(projection, event) {
236
87
  if (event.name === "step.running") {
237
88
  step.status = "running";
238
89
  step.startedAtMs ??= event.timestampMs;
239
- const completedUnits = numberAttribute(event.attributes, "completed");
240
- if (completedUnits !== undefined) {
241
- const progress = { completed: completedUnits };
242
- const message = stringAttribute(event.attributes, "message");
243
- if (message)
244
- progress.message = message;
245
- const total = numberAttribute(event.attributes, "total");
246
- if (total !== undefined)
247
- progress.total = total;
248
- const details = parseProgressDetails(event.attributes);
249
- if (details)
250
- progress.details = details;
251
- const detailCount = numberAttribute(event.attributes, "detail_count");
252
- if (detailCount !== undefined)
253
- progress.detailCount = detailCount;
254
- else if (details)
255
- progress.detailCount = details.length;
90
+ if (event.payload.progress) {
91
+ const progress = {
92
+ ...event.payload.progress,
93
+ details: event.payload.progress.details?.map((detail) => ({ ...detail })),
94
+ };
256
95
  if (!step.progress || hasVisibleStepProgress(progress))
257
96
  step.progress = progress;
258
97
  }
@@ -284,12 +123,12 @@ function createRunProjection(event, retainLogs = true) {
284
123
  }
285
124
  function materializeRun(projection) {
286
125
  const { completed, eventCount, first, logs, started, stepOrder, steps } = projection;
287
- const statusValue = completed && stringAttribute(completed.attributes, "status");
126
+ const statusValue = completed?.name === "pipeline.completed" ? completed.payload.status : undefined;
288
127
  const status = statusValue === "cancelled" || statusValue === "completed" || statusValue === "failed"
289
128
  ? statusValue
290
129
  : "running";
291
130
  const run = {
292
- dryRun: booleanAttribute(started.attributes, "dry_run") ?? false,
131
+ dryRun: started.name === "pipeline.started" ? started.payload.dryRun : false,
293
132
  eventCount,
294
133
  logCount: projection.logCount,
295
134
  logs: projection.retainLogs ? logs.map((log) => ({ ...log })) : [],
@@ -300,6 +139,8 @@ function materializeRun(projection) {
300
139
  steps: stepOrder.map((stepId) => structuredClone(steps.get(stepId))),
301
140
  version: RUN_MODEL_VERSION,
302
141
  };
142
+ if (first.correlationId !== undefined)
143
+ run.correlationId = first.correlationId;
303
144
  if (completed?.durationMs !== undefined)
304
145
  run.durationMs = completed.durationMs;
305
146
  if (completed?.error)
@@ -322,25 +163,27 @@ export function projectPipelineRun(events) {
322
163
  }
323
164
  function definitionStep(event) {
324
165
  const step = {
325
- dependencies: stringArrayAttribute(event.attributes, "dependencies"),
326
- dryRun: stringAttribute(event.attributes, "dry_run") ?? "run",
166
+ dependencies: [...event.payload.dependencies],
167
+ dryRun: event.payload.dryRun,
327
168
  id: event.stepId,
328
- optionalDependencies: stringArrayAttribute(event.attributes, "optional_dependencies"),
329
- runtimeSkipPossible: booleanAttribute(event.attributes, "runtime_skip_possible") ?? false,
330
- skipAfterFailureOf: stringArrayAttribute(event.attributes, "skip_after_failure_of"),
169
+ optionalDependencies: [...event.payload.optionalDependencies],
170
+ runtimeSkipPossible: event.payload.runtimeSkipPossible,
171
+ skipAfterFailureOf: [...event.payload.skipAfterFailureOf],
331
172
  };
332
- const description = stringAttribute(event.attributes, "description");
173
+ const description = event.payload.description;
333
174
  if (description)
334
175
  step.description = description;
335
- const name = stringAttribute(event.attributes, "name");
176
+ const name = event.payload.name;
336
177
  if (name)
337
178
  step.name = name;
338
- const nestedPipeline = parseNestedPipeline(event.attributes);
339
- if (nestedPipeline)
340
- step.nestedPipeline = nestedPipeline;
341
- const remote = parseRemote(event.attributes);
342
- if (remote)
343
- step.remote = remote;
179
+ if (event.payload.nestedPipeline) {
180
+ step.nestedPipeline = {
181
+ ...event.payload.nestedPipeline,
182
+ stepIds: [...event.payload.nestedPipeline.stepIds],
183
+ };
184
+ }
185
+ if (event.payload.remote)
186
+ step.remote = { ...event.payload.remote };
344
187
  return step;
345
188
  }
346
189
  function createPipelineProjection(event) {
@@ -370,7 +213,7 @@ function applyPipelineEvent(projection, event) {
370
213
  if (event.name === "pipeline.started") {
371
214
  projection.runStartedAtMs.set(event.runId, event.timestampMs);
372
215
  projection.runStartedEventIds.set(event.runId, event.id);
373
- projection.runTargetIds.set(event.runId, stringArrayAttribute(event.attributes, "target_ids"));
216
+ projection.runTargetIds.set(event.runId, [...event.payload.targetIds]);
374
217
  }
375
218
  if (event.name !== "step.planned" || !event.stepId)
376
219
  return;