@sixb/sqlite 0.1.18 → 0.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -25,12 +25,8 @@ import {
25
25
  serializeActionRunFailure
26
26
  } from "@sixb/core/internal/action-run-storage";
27
27
  import { assertActionRunExecution } from "@sixb/core/internal/action-run-storage-provider";
28
- import {
29
- actionRunPhaseRecordsEqual,
30
- canRequeueActionRunAfterEnqueueFailure,
31
- finishActionRunPhase
32
- } from "@sixb/core/internal/storage";
33
- import { ActionRunError, isTerminalActionRun } from "@sixb/core/storage";
28
+ import { normalizeRecordActionRunInput, resolveActionRunEffects } from "@sixb/core/internal/storage";
29
+ import { ActionRunError } from "@sixb/core/storage";
34
30
 
35
31
  // src/migrations.ts
36
32
  import { Database } from "bun:sqlite";
@@ -4994,6 +4990,158 @@ CREATE INDEX idx_ontology_sources_drained
4994
4990
  WHERE status = 'superseded' AND root_count = 0;
4995
4991
  `;
4996
4992
 
4993
+ // src/migrations/060-terminal-action-runs.sql
4994
+ var _060_terminal_action_runs_default = `-- Actions run in the request that asks for them and record their run once, when it ends. No run
4995
+ -- is queued, running, or cancelled any more, and every run has started and finished. SQLite cannot
4996
+ -- alter a CHECK constraint, so the table is rebuilt.
4997
+ CREATE TABLE action_runs_v2 (
4998
+ project_id TEXT NOT NULL,
4999
+ id TEXT NOT NULL,
5000
+ execution_id TEXT NOT NULL,
5001
+ action_id TEXT NOT NULL,
5002
+ subject_kind TEXT NOT NULL CHECK (subject_kind IN ('none', 'object')),
5003
+ object_type_id TEXT,
5004
+ primary_id TEXT,
5005
+ status TEXT NOT NULL CHECK (status IN ('succeeded', 'failed')),
5006
+ phase TEXT NOT NULL CHECK (phase IN ('validation', 'writeback', 'edits', 'commit', 'effects')),
5007
+ started_at TEXT NOT NULL,
5008
+ finished_at TEXT NOT NULL,
5009
+ params TEXT NOT NULL,
5010
+ idempotency_key TEXT NOT NULL,
5011
+ writeback_status TEXT CHECK (writeback_status IS NULL OR writeback_status IN ('succeeded', 'failed')),
5012
+ writeback_completed_at TEXT,
5013
+ writeback_result TEXT,
5014
+ writeback_error TEXT CHECK (writeback_error IS NULL OR json_valid(writeback_error)),
5015
+ effects_status TEXT CHECK (effects_status IS NULL OR effects_status IN ('succeeded', 'failed')),
5016
+ effects_completed_at TEXT,
5017
+ effects_error TEXT CHECK (effects_error IS NULL OR json_valid(effects_error)),
5018
+ error TEXT CHECK (error IS NULL OR json_valid(error)),
5019
+ CHECK (
5020
+ (subject_kind = 'none' AND object_type_id IS NULL AND primary_id IS NULL)
5021
+ OR (subject_kind = 'object' AND object_type_id IS NOT NULL AND primary_id IS NOT NULL)
5022
+ ),
5023
+ CHECK ((status = 'failed') = (error IS NOT NULL)),
5024
+ PRIMARY KEY (project_id, id),
5025
+ UNIQUE (project_id, execution_id)
5026
+ );
5027
+
5028
+ INSERT INTO action_runs_v2 (
5029
+ project_id, id, execution_id, action_id, subject_kind, object_type_id, primary_id, status,
5030
+ phase, started_at, finished_at, params, idempotency_key, writeback_status,
5031
+ writeback_completed_at, writeback_result, writeback_error, effects_status,
5032
+ effects_completed_at, effects_error, error
5033
+ )
5034
+ SELECT
5035
+ project_id, id, execution_id, action_id, subject_kind, object_type_id, primary_id,
5036
+ migrated_status, migrated_phase,
5037
+ -- A run starts when it was queued at the latest.
5038
+ COALESCE(started_at, queued_at),
5039
+ migrated_finished_at,
5040
+ params, idempotency_key, writeback_status,
5041
+ CASE
5042
+ WHEN writeback_status IS NULL THEN NULL
5043
+ ELSE COALESCE(writeback_completed_at, migrated_finished_at)
5044
+ END,
5045
+ writeback_result, writeback_error,
5046
+ CASE WHEN effects_abandoned THEN 'failed' ELSE effects_status END,
5047
+ CASE
5048
+ WHEN effects_abandoned THEN migrated_at
5049
+ WHEN effects_status IS NULL THEN NULL
5050
+ ELSE COALESCE(effects_completed_at, migrated_finished_at)
5051
+ END,
5052
+ CASE
5053
+ WHEN effects_abandoned THEN json_object(
5054
+ 'code', 'internal.unexpected',
5055
+ 'message', 'Abandoned: Actions now run synchronously; these effects never finished.',
5056
+ 'retryable', json('false'),
5057
+ 'at', migrated_at,
5058
+ 'details', json_object('actionId', action_id, 'runId', id, 'phase', 'effects')
5059
+ )
5060
+ ELSE effects_error
5061
+ END,
5062
+ -- A failure names the phase its run ended in.
5063
+ CASE
5064
+ WHEN migrated_error IS NULL THEN NULL
5065
+ WHEN json_extract(migrated_error, '$.details.phase') IS migrated_phase THEN migrated_error
5066
+ ELSE json_set(migrated_error, '$.details.phase', migrated_phase)
5067
+ END
5068
+ FROM (
5069
+ SELECT
5070
+ *,
5071
+ CASE
5072
+ WHEN status = 'succeeded' OR edits_committed THEN 'succeeded'
5073
+ ELSE 'failed'
5074
+ END AS migrated_status,
5075
+ CASE
5076
+ WHEN edits_committed THEN CASE WHEN phase = 'effects' THEN 'effects' ELSE 'commit' END
5077
+ -- The phases \`request\`, \`enqueue\`, and \`cancelled\` are gone: a run that stopped in one of
5078
+ -- them never got past validation.
5079
+ WHEN phase IN ('validation', 'writeback', 'edits', 'commit', 'effects') THEN phase
5080
+ ELSE 'validation'
5081
+ END AS migrated_phase,
5082
+ CASE
5083
+ WHEN edits_committed THEN committed_at
5084
+ WHEN status IN ('queued', 'running') THEN migrated_at
5085
+ ELSE COALESCE(finished_at, migrated_at)
5086
+ END AS migrated_finished_at,
5087
+ edits_committed AND phase = 'effects' AND effects_status IS NULL AS effects_abandoned,
5088
+ CASE
5089
+ WHEN edits_committed THEN NULL
5090
+ WHEN status IN ('queued', 'running') THEN json_object(
5091
+ 'code', 'internal.unexpected',
5092
+ 'message', 'Abandoned: Actions now run synchronously; this run never finished.',
5093
+ 'retryable', json('false'),
5094
+ 'at', migrated_at,
5095
+ 'details', json_object('actionId', action_id, 'runId', id)
5096
+ )
5097
+ -- Every failed run carries a failure. One recorded before failures were kept explains
5098
+ -- itself.
5099
+ WHEN status IN ('failed', 'cancelled') AND error IS NULL THEN json_object(
5100
+ 'code', 'internal.unexpected',
5101
+ 'message', 'This run failed before Sixb recorded why.',
5102
+ 'retryable', json('false'),
5103
+ 'at', COALESCE(finished_at, migrated_at),
5104
+ 'details', json_object('actionId', action_id, 'runId', id)
5105
+ )
5106
+ -- An enqueue failure, which no run can meet any more, reads as the unexpected error it
5107
+ -- now is.
5108
+ WHEN json_extract(error, '$.code') = 'queue.enqueue_failed'
5109
+ THEN json_set(error, '$.code', 'internal.unexpected', '$.retryable', json('false'))
5110
+ ELSE error
5111
+ END AS migrated_error
5112
+ FROM (
5113
+ SELECT
5114
+ run.*,
5115
+ committed.committed_at,
5116
+ strftime('%Y-%m-%dT%H:%M:%fZ', 'now') AS migrated_at,
5117
+ -- A run left running after its edits committed, as a crash between the commit and the end
5118
+ -- of the run left it, succeeded: its edits and their outbox facts are in the commit. A run
5119
+ -- that stopped during its effects keeps that phase, and records effects that never ended.
5120
+ -- Any other run left queued or running never finished, and nothing will resume it. A
5121
+ -- cancelled run failed, and keeps the failure it recorded.
5122
+ run.status IN ('queued', 'running') AND committed.committed_at IS NOT NULL
5123
+ AS edits_committed
5124
+ FROM action_runs AS run
5125
+ LEFT JOIN ontology_commits AS committed
5126
+ ON committed.origin_kind = 'action'
5127
+ AND committed.project_id = run.project_id
5128
+ AND committed.origin_run_id = run.id
5129
+ )
5130
+ );
5131
+
5132
+ DROP TABLE action_runs;
5133
+ ALTER TABLE action_runs_v2 RENAME TO action_runs;
5134
+
5135
+ CREATE INDEX idx_action_runs_project_started
5136
+ ON action_runs(project_id, started_at DESC);
5137
+ CREATE INDEX idx_action_runs_project_action_started
5138
+ ON action_runs(project_id, action_id, started_at DESC);
5139
+ CREATE INDEX idx_action_runs_project_object_started
5140
+ ON action_runs(project_id, object_type_id, primary_id, started_at DESC);
5141
+ CREATE INDEX idx_action_runs_project_status_started
5142
+ ON action_runs(project_id, status, started_at DESC);
5143
+ `;
5144
+
4997
5145
  // src/migrations.ts
4998
5146
  var MIGRATIONS_TABLE_SQL = `
4999
5147
  CREATE TABLE IF NOT EXISTS sixb_migrations (
@@ -5100,7 +5248,8 @@ var sqliteStorageMigrations = defineMigrations({
5100
5248
  sqliteSql("056-audio-transcription", _056_audio_transcription_default),
5101
5249
  sqliteSql("057-reranking-model-kind", _057_reranking_model_kind_default),
5102
5250
  sqliteSql("058-file-download-grants", _058_file_download_grants_default),
5103
- sqliteSql("059-drained-source-versions", _059_drained_source_versions_default)
5251
+ sqliteSql("059-drained-source-versions", _059_drained_source_versions_default),
5252
+ sqliteSql("060-terminal-action-runs", _060_terminal_action_runs_default)
5104
5253
  ]
5105
5254
  });
5106
5255
  function sqliteStoragePath(basePath) {
@@ -5437,174 +5586,50 @@ class SqliteActionRunStorage {
5437
5586
  installFreshSqliteSchema(this.db);
5438
5587
  }
5439
5588
  }
5440
- async lockForMaterialization(input) {
5441
- const row = this.db.query(`SELECT action_id, status FROM action_runs WHERE project_id = ? AND id = ?`).get(input.projectId, input.runId);
5442
- if (!row) {
5443
- throw new ActionRunError(`[SixbSqlite] Action run '${input.runId}' not found for project '${input.projectId}'.`);
5444
- }
5445
- if (row.action_id !== input.actionId) {
5446
- throw new ActionRunError(`[SixbSqlite] Action run '${input.runId}' does not belong to action '${input.actionId}'.`);
5447
- }
5448
- if (row.status !== "running") {
5449
- throw new ActionRunError(`[SixbSqlite] Action run '${input.runId}' cannot materialize from status '${row.status}'.`);
5450
- }
5451
- const record = await this.getById({ projectId: input.projectId, id: input.runId });
5452
- if (!record) {
5453
- throw new ActionRunError(`[SixbSqlite] Action run '${input.runId}' disappeared while locking materialization.`);
5454
- }
5455
- return record;
5456
- }
5457
- async queue(input) {
5458
- const queuedAt = input.queuedAt ?? new Date;
5589
+ async record(input) {
5590
+ const record = normalizeRecordActionRunInput(input);
5459
5591
  await assertActionRunExecution({
5460
5592
  executions: this.executions,
5461
- projectId: input.projectId,
5462
- executionId: input.executionId,
5463
- runId: input.id,
5464
- actionId: input.actionId
5593
+ projectId: record.projectId,
5594
+ executionId: record.executionId,
5595
+ runId: record.id,
5596
+ actionId: record.actionId
5465
5597
  });
5466
- try {
5467
- this.db.query(`
5468
- INSERT INTO action_runs (
5469
- project_id,
5470
- id,
5471
- execution_id,
5472
- action_id,
5473
- subject_kind,
5474
- object_type_id,
5475
- primary_id,
5476
- status,
5477
- phase,
5478
- queued_at,
5479
- started_at,
5480
- finished_at,
5481
- params,
5482
- idempotency_key,
5483
- writeback_status,
5484
- writeback_completed_at,
5485
- writeback_result,
5486
- writeback_error,
5487
- effects_status,
5488
- effects_completed_at,
5489
- effects_error,
5490
- error
5491
- ) VALUES (
5492
- ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, NULL, ?, ?,
5493
- NULL, NULL, NULL, NULL,
5494
- NULL, NULL, NULL,
5495
- NULL
5496
- )
5497
- `).run(input.projectId, input.id, input.executionId, input.actionId, input.subject.kind, input.subject.kind === "object" ? input.subject.objectTypeId : null, input.subject.kind === "object" ? input.subject.primaryId : null, "queued", "request", queuedAt.toISOString(), JSON.stringify(input.params), input.idempotencyKey);
5498
- } catch (error) {
5499
- if (isUniqueConstraintError(error)) {
5500
- return this.requeueAfterEnqueueFailure(input, queuedAt);
5501
- }
5502
- throw error;
5503
- }
5504
- const record = await this.getById({ projectId: input.projectId, id: input.id });
5505
- if (!record) {
5506
- throw new ActionRunError(`[SixbSqlite] Failed to load action run '${input.id}' for project '${input.projectId}'.`);
5598
+ const inserted = this.db.query(`
5599
+ INSERT INTO action_runs (
5600
+ project_id,
5601
+ id,
5602
+ execution_id,
5603
+ action_id,
5604
+ subject_kind,
5605
+ object_type_id,
5606
+ primary_id,
5607
+ status,
5608
+ phase,
5609
+ started_at,
5610
+ finished_at,
5611
+ params,
5612
+ idempotency_key,
5613
+ writeback_status,
5614
+ writeback_completed_at,
5615
+ writeback_result,
5616
+ writeback_error,
5617
+ error
5618
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
5619
+ ON CONFLICT DO NOTHING
5620
+ `).run(record.projectId, record.id, record.executionId, record.actionId, record.subject.kind, record.subject.kind === "object" ? record.subject.objectTypeId : null, record.subject.kind === "object" ? record.subject.primaryId : null, record.status, record.phase, record.startedAt.toISOString(), record.finishedAt.toISOString(), JSON.stringify(record.params), record.idempotencyKey, record.writeback?.status ?? null, record.writeback?.completedAt.toISOString() ?? null, record.writeback?.status === "succeeded" ? JSON.stringify(record.writeback.result) : null, record.writeback?.status === "failed" ? serializeActionRunFailure(record.writeback.error, "writeback") : null, record.status === "failed" ? serializeActionRunFailure(record.error) : null);
5621
+ if (inserted.changes === 0) {
5622
+ const conflict = this.selectRow(record.projectId, record.id);
5623
+ throw new ActionRunError(conflict ? `[SixbSqlite] Action run '${record.id}' is already recorded for project '${record.projectId}'.` : `[SixbSqlite] Execution '${record.executionId}' already belongs to another Action run.`);
5507
5624
  }
5508
- return record;
5509
- }
5510
- async requeueAfterEnqueueFailure(input, queuedAt) {
5511
- return this.db.transaction(() => {
5512
- const existing = this.db.query("SELECT * FROM action_runs WHERE project_id = ? AND id = ?").get(input.projectId, input.id);
5513
- if (!existing || !canRequeueActionRunAfterEnqueueFailure(rowToActionRunRecord(existing), input)) {
5514
- throw new ActionRunError(`[SixbSqlite] Action run '${input.id}' already exists for project '${input.projectId}'.`);
5515
- }
5516
- this.db.query(`
5517
- UPDATE action_runs
5518
- SET
5519
- status = ?,
5520
- phase = ?,
5521
- queued_at = ?,
5522
- started_at = NULL,
5523
- finished_at = NULL,
5524
- writeback_status = NULL,
5525
- writeback_completed_at = NULL,
5526
- writeback_result = NULL,
5527
- writeback_error = NULL,
5528
- effects_status = NULL,
5529
- effects_completed_at = NULL,
5530
- effects_error = NULL,
5531
- error = NULL
5532
- WHERE project_id = ? AND id = ?
5533
- `).run("queued", "request", queuedAt.toISOString(), input.projectId, input.id);
5534
- const updated = this.db.query("SELECT * FROM action_runs WHERE project_id = ? AND id = ?").get(input.projectId, input.id);
5535
- return rowToActionRunRecord(updated);
5536
- })();
5537
- }
5538
- async start(input) {
5539
- return this.db.transaction(() => {
5540
- const existing = this.db.query("SELECT * FROM action_runs WHERE project_id = ? AND id = ?").get(input.projectId, input.id);
5541
- if (!existing) {
5542
- throw new ActionRunError(`[SixbSqlite] Action run '${input.id}' not found for project '${input.projectId}'.`);
5543
- }
5544
- if (existing.status !== "queued") {
5545
- throw new ActionRunError(`[SixbSqlite] Action run '${input.id}' cannot start from status '${existing.status}'.`);
5546
- }
5547
- this.db.query(`
5548
- UPDATE action_runs
5549
- SET
5550
- status = ?,
5551
- phase = ?,
5552
- started_at = ?,
5553
- error = NULL
5554
- WHERE project_id = ? AND id = ?
5555
- `).run("running", input.phase ?? "validation", (input.startedAt ?? new Date).toISOString(), input.projectId, input.id);
5556
- const updated = this.db.query("SELECT * FROM action_runs WHERE project_id = ? AND id = ?").get(input.projectId, input.id);
5557
- return rowToActionRunRecord(updated);
5558
- })();
5559
- }
5560
- async enterPhase(input) {
5561
- return this.db.transaction(() => {
5562
- this.requireRunningRun(input.projectId, input.id, "transition phase");
5563
- this.db.query(`
5564
- UPDATE action_runs
5565
- SET phase = ?
5566
- WHERE project_id = ? AND id = ?
5567
- `).run(input.phase, input.projectId, input.id);
5568
- const updated = this.db.query("SELECT * FROM action_runs WHERE project_id = ? AND id = ?").get(input.projectId, input.id);
5569
- return rowToActionRunRecord(updated);
5570
- })();
5571
- }
5572
- async recordWriteback(input) {
5573
- return this.db.transaction(() => {
5574
- const existing = this.requireRunningRun(input.projectId, input.id, "record writeback");
5575
- const nextWriteback = toWritebackRecord(input, new Date(input.completedAt ?? new Date));
5576
- const currentWriteback = toActionRunWritebackRecord(existing);
5577
- if (currentWriteback) {
5578
- if (actionRunPhaseRecordsEqual(currentWriteback, nextWriteback)) {
5579
- return rowToActionRunRecord(existing);
5580
- }
5581
- throw new ActionRunError(`[SixbSqlite] Action run '${input.id}' already has a different writeback record.`);
5582
- }
5583
- this.db.query(`
5584
- UPDATE action_runs
5585
- SET
5586
- phase = ?,
5587
- writeback_status = ?,
5588
- writeback_completed_at = ?,
5589
- writeback_result = ?,
5590
- writeback_error = ?
5591
- WHERE project_id = ? AND id = ?
5592
- `).run("writeback", input.status, nextWriteback.completedAt.toISOString(), input.status === "succeeded" ? serializeJsonValue(input.result) : null, input.status === "failed" ? serializeActionRunFailure(input.error, "writeback") : null, input.projectId, input.id);
5593
- const updated = this.db.query("SELECT * FROM action_runs WHERE project_id = ? AND id = ?").get(input.projectId, input.id);
5594
- return rowToActionRunRecord(updated);
5595
- })();
5625
+ return rowToActionRunRecord(this.requireRow(record.projectId, record.id));
5596
5626
  }
5597
5627
  async recordEffects(input) {
5598
5628
  return this.db.transaction(() => {
5599
- const existing = this.requireRunningRun(input.projectId, input.id, "record effects");
5600
- const nextEffects = toEffectsRecord(input, new Date(input.completedAt ?? new Date));
5601
- const currentEffects = toActionRunEffectsRecord(existing);
5602
- if (currentEffects) {
5603
- if (actionRunPhaseRecordsEqual(currentEffects, nextEffects)) {
5604
- return rowToActionRunRecord(existing);
5605
- }
5606
- throw new ActionRunError(`[SixbSqlite] Action run '${input.id}' already has a different effects record.`);
5607
- }
5629
+ const existing = this.selectRow(input.projectId, input.id);
5630
+ const { run, effects } = resolveActionRunEffects(existing ? rowToActionRunRecord(existing) : null, input);
5631
+ if (!effects)
5632
+ return run;
5608
5633
  this.db.query(`
5609
5634
  UPDATE action_runs
5610
5635
  SET
@@ -5613,32 +5638,8 @@ class SqliteActionRunStorage {
5613
5638
  effects_completed_at = ?,
5614
5639
  effects_error = ?
5615
5640
  WHERE project_id = ? AND id = ?
5616
- `).run("effects", input.status, nextEffects.completedAt.toISOString(), input.status === "failed" ? serializeActionRunFailure(input.error, "effects") : null, input.projectId, input.id);
5617
- const updated = this.db.query("SELECT * FROM action_runs WHERE project_id = ? AND id = ?").get(input.projectId, input.id);
5618
- return rowToActionRunRecord(updated);
5619
- })();
5620
- }
5621
- async finish(input) {
5622
- return this.db.transaction(() => {
5623
- const existing = this.db.query("SELECT * FROM action_runs WHERE project_id = ? AND id = ?").get(input.projectId, input.id);
5624
- if (!existing) {
5625
- throw new ActionRunError(`[SixbSqlite] Action run '${input.id}' not found for project '${input.projectId}'.`);
5626
- }
5627
- if (isTerminalActionRun({ status: existing.status })) {
5628
- throw new ActionRunError(`[SixbSqlite] Action run '${input.id}' cannot finish from terminal status '${existing.status}'.`);
5629
- }
5630
- const phase = finishActionRunPhase(input, existing.phase);
5631
- this.db.query(`
5632
- UPDATE action_runs
5633
- SET
5634
- status = ?,
5635
- phase = ?,
5636
- finished_at = ?,
5637
- error = ?
5638
- WHERE project_id = ? AND id = ?
5639
- `).run(input.status, phase, (input.finishedAt ?? new Date).toISOString(), input.status === "succeeded" ? null : serializeActionRunFailure(input.error), input.projectId, input.id);
5640
- const updated = this.db.query("SELECT * FROM action_runs WHERE project_id = ? AND id = ?").get(input.projectId, input.id);
5641
- return rowToActionRunRecord(updated);
5641
+ `).run("effects", effects.status, effects.completedAt.toISOString(), effects.status === "failed" ? serializeActionRunFailure(effects.error, "effects") : null, input.projectId, input.id);
5642
+ return rowToActionRunRecord(this.requireRow(input.projectId, input.id));
5642
5643
  })();
5643
5644
  }
5644
5645
  async getById(params) {
@@ -5699,11 +5700,11 @@ class SqliteActionRunStorage {
5699
5700
  args.push(...input.statuses);
5700
5701
  }
5701
5702
  if (input.startedAfter) {
5702
- whereClauses.push("COALESCE(started_at, queued_at) >= ?");
5703
+ whereClauses.push("started_at >= ?");
5703
5704
  args.push(input.startedAfter.toISOString());
5704
5705
  }
5705
5706
  if (input.startedBefore) {
5706
- whereClauses.push("COALESCE(started_at, queued_at) <= ?");
5707
+ whereClauses.push("started_at <= ?");
5707
5708
  args.push(input.startedBefore.toISOString());
5708
5709
  }
5709
5710
  const where = `WHERE ${whereClauses.join(" AND ")}`;
@@ -5714,7 +5715,7 @@ class SqliteActionRunStorage {
5714
5715
  let query = `
5715
5716
  SELECT * FROM action_runs
5716
5717
  ${where}
5717
- ORDER BY COALESCE(started_at, queued_at) ${order}, id ${order}
5718
+ ORDER BY started_at ${order}, id ${order}
5718
5719
  `;
5719
5720
  const queryArgs = [...args];
5720
5721
  if (limit !== undefined) {
@@ -5735,31 +5736,22 @@ class SqliteActionRunStorage {
5735
5736
  close() {
5736
5737
  closeSqliteStoreConnection(this.connection);
5737
5738
  }
5738
- requireRunningRun(projectId, id, operation) {
5739
- const existing = this.db.query("SELECT * FROM action_runs WHERE project_id = ? AND id = ?").get(projectId, id);
5740
- if (!existing) {
5741
- throw new ActionRunError(`[SixbSqlite] Action run '${id}' not found for project '${projectId}'.`);
5742
- }
5743
- if (existing.status !== "running") {
5744
- throw new ActionRunError(`[SixbSqlite] Action run '${id}' cannot ${operation} from status '${existing.status}'.`);
5739
+ selectRow(projectId, id) {
5740
+ return this.db.query("SELECT * FROM action_runs WHERE project_id = ? AND id = ?").get(projectId, id);
5741
+ }
5742
+ requireRow(projectId, id) {
5743
+ const row = this.selectRow(projectId, id);
5744
+ if (!row) {
5745
+ throw new ActionRunError(`[SixbSqlite] Action run '${id}' disappeared from project '${projectId}' while recording it.`);
5745
5746
  }
5746
- return existing;
5747
+ return row;
5747
5748
  }
5748
5749
  }
5749
- function serializeJsonValue(value) {
5750
- return JSON.stringify(value);
5751
- }
5752
- function toActionRunFailure(row) {
5753
- return row.error === null ? undefined : parseActionRunFailure(row.error);
5754
- }
5755
- function parsePhaseFailure(value, expectedPhase) {
5756
- return parseActionRunFailure(value, expectedPhase);
5757
- }
5758
5750
  function toActionRunWritebackRecord(row) {
5759
5751
  if (!row.writeback_status) {
5760
5752
  return;
5761
5753
  }
5762
- const completedAt = row.writeback_completed_at ? new Date(row.writeback_completed_at) : new Date(row.queued_at);
5754
+ const completedAt = new Date(row.writeback_completed_at ?? row.finished_at);
5763
5755
  if (row.writeback_status === "succeeded") {
5764
5756
  return {
5765
5757
  status: "succeeded",
@@ -5770,14 +5762,14 @@ function toActionRunWritebackRecord(row) {
5770
5762
  return {
5771
5763
  status: "failed",
5772
5764
  completedAt,
5773
- error: parsePhaseFailure(row.writeback_error, "writeback")
5765
+ error: parseActionRunFailure(row.writeback_error, "writeback")
5774
5766
  };
5775
5767
  }
5776
5768
  function toActionRunEffectsRecord(row) {
5777
5769
  if (!row.effects_status) {
5778
5770
  return;
5779
5771
  }
5780
- const completedAt = row.effects_completed_at ? new Date(row.effects_completed_at) : new Date(row.queued_at);
5772
+ const completedAt = new Date(row.effects_completed_at ?? row.finished_at);
5781
5773
  if (row.effects_status === "succeeded") {
5782
5774
  return {
5783
5775
  status: "succeeded",
@@ -5787,54 +5779,29 @@ function toActionRunEffectsRecord(row) {
5787
5779
  return {
5788
5780
  status: "failed",
5789
5781
  completedAt,
5790
- error: parsePhaseFailure(row.effects_error, "effects")
5791
- };
5792
- }
5793
- function toWritebackRecord(input, completedAt) {
5794
- if (input.status === "succeeded") {
5795
- return {
5796
- status: "succeeded",
5797
- completedAt,
5798
- result: input.result
5799
- };
5800
- }
5801
- return {
5802
- status: "failed",
5803
- completedAt,
5804
- error: input.error
5805
- };
5806
- }
5807
- function toEffectsRecord(input, completedAt) {
5808
- if (input.status === "succeeded") {
5809
- return {
5810
- status: "succeeded",
5811
- completedAt
5812
- };
5813
- }
5814
- return {
5815
- status: "failed",
5816
- completedAt,
5817
- error: input.error
5782
+ error: parseActionRunFailure(row.effects_error, "effects")
5818
5783
  };
5819
5784
  }
5820
5785
  function rowToActionRunRecord(row) {
5821
- return {
5786
+ const writeback = toActionRunWritebackRecord(row);
5787
+ const effects = toActionRunEffectsRecord(row);
5788
+ const fields = {
5822
5789
  id: row.id,
5823
5790
  projectId: row.project_id,
5824
5791
  executionId: row.execution_id,
5825
5792
  actionId: row.action_id,
5826
5793
  subject: rowToActionSubject(row),
5827
- status: row.status,
5828
- phase: row.phase ?? undefined,
5829
- queuedAt: new Date(row.queued_at),
5830
- startedAt: row.started_at ? new Date(row.started_at) : undefined,
5831
- finishedAt: row.finished_at ? new Date(row.finished_at) : undefined,
5794
+ phase: row.phase,
5795
+ startedAt: new Date(row.started_at),
5796
+ finishedAt: new Date(row.finished_at),
5832
5797
  params: JSON.parse(row.params),
5833
5798
  idempotencyKey: row.idempotency_key,
5834
- writeback: toActionRunWritebackRecord(row),
5835
- effects: toActionRunEffectsRecord(row),
5836
- error: toActionRunFailure(row)
5799
+ ...writeback === undefined ? {} : { writeback },
5800
+ ...effects === undefined ? {} : { effects }
5837
5801
  };
5802
+ if (row.status === "succeeded")
5803
+ return { ...fields, status: "succeeded" };
5804
+ return { ...fields, status: "failed", error: parseActionRunFailure(row.error) };
5838
5805
  }
5839
5806
  function rowToActionSubject(row) {
5840
5807
  if (row.subject_kind === "none") {
@@ -5849,9 +5816,6 @@ function rowToActionSubject(row) {
5849
5816
  primaryId: row.primary_id
5850
5817
  };
5851
5818
  }
5852
- function isUniqueConstraintError(error) {
5853
- return error instanceof Error && error.message.includes("UNIQUE constraint failed");
5854
- }
5855
5819
 
5856
5820
  // src/agents/checkpoints.ts
5857
5821
  import {
@@ -6105,7 +6069,7 @@ import {
6105
6069
  } from "@sixb/core/storage";
6106
6070
 
6107
6071
  // src/storage-errors.ts
6108
- function isUniqueConstraintError2(error) {
6072
+ function isUniqueConstraintError(error) {
6109
6073
  return error instanceof Error && error.message.includes("UNIQUE constraint failed");
6110
6074
  }
6111
6075
  function isForeignKeyConstraintError(error) {
@@ -6151,7 +6115,7 @@ class SqliteAgentMessageStore {
6151
6115
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
6152
6116
  `).run(input.projectId, input.id, input.threadId, input.runId, input.role, input.authorPrincipal?.type ?? null, input.authorPrincipal?.id ?? null, seqRow.next, JSON.stringify(input.parts), input.metadata === undefined ? null : JSON.stringify(input.metadata), AGENT_MESSAGE_CONTENT_VERSION, createdAt.toISOString(), input.completedAt ? input.completedAt.toISOString() : null);
6153
6117
  } catch (error) {
6154
- if (isUniqueConstraintError2(error)) {
6118
+ if (isUniqueConstraintError(error)) {
6155
6119
  throw new AgentStorageError2("duplicate_id", `[SixbSqlite] Agent message '${input.id}' already exists for project '${input.projectId}'.`);
6156
6120
  }
6157
6121
  throw error;
@@ -6340,7 +6304,7 @@ class SqliteAgentRunStore {
6340
6304
  ) VALUES (?, ?, ?, 'conversation', ?, ?, ?, 'queued', 0, ?)
6341
6305
  `).run(input.projectId, input.id, input.executionId, input.threadId, input.triggerMessageId, JSON.stringify(input.spec), createdAt.toISOString());
6342
6306
  } catch (error) {
6343
- if (isUniqueConstraintError2(error)) {
6307
+ if (isUniqueConstraintError(error)) {
6344
6308
  throw new AgentStorageError3("duplicate_id", `[SixbSqlite] Agent run '${input.id}' already exists for project '${input.projectId}'.`);
6345
6309
  }
6346
6310
  throw error;
@@ -6395,7 +6359,7 @@ class SqliteAgentRunStore {
6395
6359
  ) VALUES (?, ?, ?, 'subagent', ?, ?, ?, 'queued', 0, ?)
6396
6360
  `).run(input.projectId, input.id, input.executionId, input.parentRunId, input.spawnKey, JSON.stringify(input.spec), (input.createdAt ?? new Date).toISOString());
6397
6361
  } catch (error) {
6398
- if (isUniqueConstraintError2(error)) {
6362
+ if (isUniqueConstraintError(error)) {
6399
6363
  throw new AgentStorageError3("duplicate_id", `[SixbSqlite] Subagent run '${input.id}' conflicts with an existing durable spawn.`);
6400
6364
  }
6401
6365
  throw error;
@@ -6624,7 +6588,7 @@ class SqliteAgentThreadStore {
6624
6588
  ) VALUES (?, ?, ?, ?, ?, ?, ?, NULL, NULL, 0, ?, ?)
6625
6589
  `).run(input.projectId, input.id, input.ownerPrincipal.type, input.ownerPrincipal.id, input.title ?? null, sandboxParams, input.status ?? "active", createdAt.toISOString(), updatedAt.toISOString());
6626
6590
  } catch (error) {
6627
- if (isUniqueConstraintError2(error)) {
6591
+ if (isUniqueConstraintError(error)) {
6628
6592
  throw new AgentStorageError4("duplicate_id", `[SixbSqlite] Agent thread '${input.id}' already exists for project '${input.projectId}'.`);
6629
6593
  }
6630
6594
  throw error;
@@ -6827,7 +6791,7 @@ class SqliteAiUsageStorage {
6827
6791
  try {
6828
6792
  this.insertRecord(record);
6829
6793
  } catch (error) {
6830
- if (!isUniqueConstraintError2(error))
6794
+ if (!isUniqueConstraintError(error))
6831
6795
  throw error;
6832
6796
  const concurrent = this.findByIdempotencyKey(record);
6833
6797
  if (concurrent)
@@ -7035,7 +6999,7 @@ class SqliteAiCostStorage {
7035
6999
  try {
7036
7000
  this.insertCost(record);
7037
7001
  } catch (error) {
7038
- if (!isUniqueConstraintError2(error))
7002
+ if (!isUniqueConstraintError(error))
7039
7003
  throw error;
7040
7004
  if (!this.findCost(record.projectId, record.usageRecordId))
7041
7005
  throw error;
@@ -8613,7 +8577,7 @@ function revokeActiveMagicLinksForEmail(db, params) {
8613
8577
  }));
8614
8578
  }
8615
8579
  function mapUniqueConstraintError(error, code, message) {
8616
- if (isUniqueConstraintError2(error)) {
8580
+ if (isUniqueConstraintError(error)) {
8617
8581
  throw new AuthStorageError(code, message);
8618
8582
  }
8619
8583
  throw error;
@@ -10233,7 +10197,7 @@ class SqliteConnectorConnectionPersistence {
10233
10197
  `).run(record.projectId, record.connectorId, record.id, record.slot, record.initiatedByExecutionId, record.stateHash, record.codeVerifier === undefined ? null : json(record.codeVerifier), record.redirectUri, record.connectionRunId ?? null, record.returnTo ?? null, record.callbackBindingHash ?? null, record.reauthorizationId ?? null, record.reauthorizationRevision ?? null, record.reauthorizationConnectionIds ? json(record.reauthorizationConnectionIds) : null, iso(record.createdAt), iso(record.expiresAt));
10234
10198
  return true;
10235
10199
  } catch (error) {
10236
- if (isUniqueConstraintError2(error))
10200
+ if (isUniqueConstraintError(error))
10237
10201
  return false;
10238
10202
  throw error;
10239
10203
  }
@@ -10259,7 +10223,7 @@ class SqliteConnectorConnectionPersistence {
10259
10223
  this.insertRun(record);
10260
10224
  return true;
10261
10225
  } catch (error) {
10262
- if (isUniqueConstraintError2(error))
10226
+ if (isUniqueConstraintError(error))
10263
10227
  return false;
10264
10228
  throw error;
10265
10229
  }
@@ -10293,7 +10257,7 @@ class SqliteConnectorConnectionPersistence {
10293
10257
  this.insertAuthorizationRow(record);
10294
10258
  return true;
10295
10259
  } catch (error) {
10296
- if (isUniqueConstraintError2(error))
10260
+ if (isUniqueConstraintError(error))
10297
10261
  return false;
10298
10262
  throw error;
10299
10263
  }
@@ -10324,7 +10288,7 @@ class SqliteConnectorConnectionPersistence {
10324
10288
  `).run(record.projectId, record.connectorId, record.id, record.authorizationId, record.slot, json(record.account), record.account.id, record.status, optionalIso(record.disconnectedAt), iso(record.createdAt), iso(record.updatedAt));
10325
10289
  return true;
10326
10290
  } catch (error) {
10327
- if (isUniqueConstraintError2(error))
10291
+ if (isUniqueConstraintError(error))
10328
10292
  return false;
10329
10293
  throw error;
10330
10294
  }
@@ -10837,7 +10801,7 @@ class SqliteFileUploadSessionPersistence {
10837
10801
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(session.id, session.projectId, session.principal.type, session.principal.id, session.strategy, session.status, session.fileName ?? null, session.mediaType ?? null, session.logicalPath ?? null, session.expectedSizeBytes ?? null, session.expectedDigest ?? null, ...mutableColumns(session));
10838
10802
  return true;
10839
10803
  } catch (error) {
10840
- if (isUniqueConstraintError2(error))
10804
+ if (isUniqueConstraintError(error))
10841
10805
  return false;
10842
10806
  throw error;
10843
10807
  }
@@ -16392,7 +16356,7 @@ class SqlitePipelineRunStorage {
16392
16356
  ) VALUES (?, ?, ?, ?, ?, ?, NULL)
16393
16357
  `).run(input.projectId, input.id, input.executionId, input.pipelineId, "queued", queuedAt.toISOString());
16394
16358
  } catch (error) {
16395
- if (isUniqueConstraintError2(error)) {
16359
+ if (isUniqueConstraintError(error)) {
16396
16360
  return this.requeueAfterEnqueueFailure(input, queuedAt);
16397
16361
  }
16398
16362
  throw error;
@@ -16489,7 +16453,7 @@ class SqlitePipelineRunStorage {
16489
16453
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
16490
16454
  `).run(input.projectId, input.id, input.pipelineRunId, input.pipelineId, input.stepId, input.datasetId, input.mode, "running", startedAt.toISOString(), serializeDatasetVersionRefs(input.inputs));
16491
16455
  } catch (error) {
16492
- if (isUniqueConstraintError2(error)) {
16456
+ if (isUniqueConstraintError(error)) {
16493
16457
  throw new PipelineRunError(`[SixbSqlite] Pipeline step run '${input.id}' already exists for project '${input.projectId}'.`);
16494
16458
  }
16495
16459
  throw error;
@@ -16757,7 +16721,7 @@ class SqliteProjectionRunStorage {
16757
16721
  try {
16758
16722
  this.insertQueued(record);
16759
16723
  } catch (error) {
16760
- if (isUniqueConstraintError3(error))
16724
+ if (isUniqueConstraintError2(error))
16761
16725
  throw duplicateProjectionRun(input);
16762
16726
  throw error;
16763
16727
  }
@@ -17146,7 +17110,7 @@ function databaseSafeInteger(value, fieldName) {
17146
17110
  }
17147
17111
  return value;
17148
17112
  }
17149
- function isUniqueConstraintError3(error) {
17113
+ function isUniqueConstraintError2(error) {
17150
17114
  return error instanceof Error && error.message.includes("UNIQUE constraint failed");
17151
17115
  }
17152
17116
  function duplicateProjectionRun(input) {
@@ -17422,7 +17386,7 @@ class SqliteShareGrantStorage {
17422
17386
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
17423
17387
  `).run(record.projectId, record.id, record.definitionId, record.target.objectTypeId, record.target.primaryId, record.issuedBy.type, record.issuedBy.id, record.authoritySnapshot.version, JSON.stringify(record.authoritySnapshot), record.authorityDigest, record.tokenHash, record.destinationPath, record.createdAt.toISOString(), record.expiresAt.toISOString());
17424
17388
  } catch (error) {
17425
- if (isUniqueConstraintError2(error)) {
17389
+ if (isUniqueConstraintError(error)) {
17426
17390
  throw new ShareGrantStorageError("duplicate", `[SixbSqlite] Share grant '${record.id}' conflicts with an existing record.`, { cause: error });
17427
17391
  }
17428
17392
  throw error;
@@ -17571,7 +17535,7 @@ class SqliteShareSessionStorage {
17571
17535
  throw invalidRecord2(`Share session '${record.id}' disappeared after create.`);
17572
17536
  return rowToRecord2(row);
17573
17537
  } catch (error) {
17574
- if (isUniqueConstraintError2(error)) {
17538
+ if (isUniqueConstraintError(error)) {
17575
17539
  throw new ShareSessionStorageError("duplicate", `[SixbSqlite] Share session '${record.id}' conflicts with an existing record.`, { cause: error });
17576
17540
  }
17577
17541
  if (isForeignKeyConstraintError(error)) {
@@ -17688,7 +17652,7 @@ class SqliteSyncRunStorage {
17688
17652
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?)
17689
17653
  `).run(input.projectId, input.id, input.executionId, input.syncId, input.datasetId, input.mode, "queued", queuedAt.toISOString(), input.expectedLatestVersionId ?? null, input.commitMessage ?? null);
17690
17654
  } catch (error) {
17691
- if (isUniqueConstraintError4(error)) {
17655
+ if (isUniqueConstraintError3(error)) {
17692
17656
  return this.requeueAfterEnqueueFailure(input, queuedAt);
17693
17657
  }
17694
17658
  throw error;
@@ -17853,7 +17817,7 @@ function rowToSyncRunRecord(row) {
17853
17817
  checkpoint: parseCheckpoint(row.checkpoint)
17854
17818
  };
17855
17819
  }
17856
- function isUniqueConstraintError4(error) {
17820
+ function isUniqueConstraintError3(error) {
17857
17821
  return error instanceof Error && error.message.includes("UNIQUE constraint failed");
17858
17822
  }
17859
17823
 
@@ -18019,7 +17983,7 @@ class SqliteWebhookRunStorage {
18019
17983
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
18020
17984
  `).run(input.projectId, input.id, input.executionId, input.connectorId, input.webhookId, "running", input.method, input.route, startedAt.toISOString(), input.requestBodyBytes, input.requestBodySha256, input.idempotencyKey ?? null);
18021
17985
  } catch (error) {
18022
- if (isUniqueConstraintError2(error)) {
17986
+ if (isUniqueConstraintError(error)) {
18023
17987
  throw new WebhookRunError(`[SixbSqlite] Webhook run '${input.id}' or delivery key already exists for project '${input.projectId}'.`, "duplicate_run");
18024
17988
  }
18025
17989
  throw error;
@@ -18172,7 +18136,7 @@ class SqliteWorkflowInterventionStorage {
18172
18136
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
18173
18137
  `).run(input.projectId, input.id, input.workflowId, input.workflowRunId, input.nodeRunId, input.nodeIndex, input.nodeId, input.nodeKey, input.interventionId, serializeRecord(input.input), serializeRecord(input.defaultResponse), "pending", (input.requestedAt ?? new Date).toISOString(), input.expiresAt?.toISOString() ?? null);
18174
18138
  } catch (error) {
18175
- if (isUniqueConstraintError2(error)) {
18139
+ if (isUniqueConstraintError(error)) {
18176
18140
  throw new WorkflowInterventionError(`[SixbSqlite] Workflow intervention '${input.id}' already exists for project '${input.projectId}'.`);
18177
18141
  }
18178
18142
  throw error;
@@ -18413,7 +18377,7 @@ class SqliteWorkflowRunStorage {
18413
18377
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
18414
18378
  `).run(input.projectId, input.id, input.executionId, input.workflowId, "queued", serializeRecord2(input.input), queuedAt.toISOString(), queuedAt.toISOString(), 0);
18415
18379
  } catch (error) {
18416
- if (isUniqueConstraintError2(error)) {
18380
+ if (isUniqueConstraintError(error)) {
18417
18381
  throw new WorkflowRunError(`[SixbSqlite] Workflow run '${input.id}' or execution '${input.executionId}' is already linked for project '${input.projectId}'.`);
18418
18382
  }
18419
18383
  throw error;
@@ -18639,7 +18603,7 @@ class SqliteWorkflowAgentNodeRunStorage {
18639
18603
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
18640
18604
  `).run(input.projectId, input.nodeRunId, input.executionId, input.actorId, "queued", input.prompt, 0, (input.createdAt ?? new Date).toISOString());
18641
18605
  } catch (error) {
18642
- if (isUniqueConstraintError2(error)) {
18606
+ if (isUniqueConstraintError(error)) {
18643
18607
  throw new WorkflowRunError(`[SixbSqlite] Agent execution already exists for workflow node run '${input.nodeRunId}'.`);
18644
18608
  }
18645
18609
  throw error;
@@ -18788,7 +18752,7 @@ class SqliteWorkflowNodeRunStorage {
18788
18752
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
18789
18753
  `).run(input.projectId, input.id, input.workflowRunId, input.workflowId, input.nodeIndex, input.nodeType, input.nodeId, input.nodeKey, "running", serializeRecord2(input.input), startedAt.toISOString());
18790
18754
  } catch (error) {
18791
- if (isUniqueConstraintError2(error)) {
18755
+ if (isUniqueConstraintError(error)) {
18792
18756
  throw new WorkflowRunError(`[SixbSqlite] Workflow node run '${input.id}' already exists for project '${input.projectId}'.`);
18793
18757
  }
18794
18758
  throw error;
@@ -19206,4 +19170,4 @@ export {
19206
19170
  migrateSqliteStorage
19207
19171
  };
19208
19172
 
19209
- //# debugId=0E043C316BC5C57264756E2164756E21
19173
+ //# debugId=682E7F57F0A7280864756E2164756E21