deepline 0.3.301 → 0.3.303

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 (106) hide show
  1. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  2. package/dist/bundling-sources/sdk/src/runs/watch-polling.ts +1 -1
  3. package/dist/bundling-sources/shared_libs/play-runtime/live-state-contract.ts +2 -0
  4. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/gateway-adapter.ts +2 -4
  5. package/dist/bundling-sources/shared_libs/play-runtime/run-snapshot-stream.ts +13 -0
  6. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +16 -5
  7. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-start-admission.ts +7 -1
  8. package/dist/bundling-sources/shared_libs/play-runtime/runtime-capacity-policy.ts +6 -4
  9. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +2 -2
  10. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +229 -98
  11. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-ledger-outbox-recovery.ts +2 -4
  12. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-event-search.ts +82 -0
  13. package/dist/bundling-sources/shared_libs/plays/tool-response-contract.ts +25 -0
  14. package/dist/cli/{auth-DCN4DA2T.mjs → auth-337SVWQD.mjs} +4 -4
  15. package/dist/cli/auth-4ME3NANJ.js +31 -0
  16. package/dist/cli/{billing-OGWOCZOZ.js → billing-CVQURTWQ.js} +71 -71
  17. package/dist/cli/{billing-VLZNKJ4N.mjs → billing-SVGYQSJI.mjs} +5 -5
  18. package/dist/cli/{chunk-V7J7U63X.js → chunk-4D5YQETI.js} +6 -6
  19. package/dist/cli/{chunk-OILZKWKC.mjs → chunk-4LRG6AJZ.mjs} +2 -2
  20. package/dist/cli/{chunk-JCS5TPNY.mjs → chunk-4NIQA6S7.mjs} +1 -1
  21. package/dist/cli/{chunk-AKWMFS7H.js → chunk-5NEXN4MZ.js} +2 -2
  22. package/dist/cli/{chunk-PXJAS6X2.mjs → chunk-BVJSAJKV.mjs} +1 -1
  23. package/dist/cli/{chunk-SBUIMF2B.mjs → chunk-DH6LEJWD.mjs} +3 -3
  24. package/dist/cli/{chunk-YFRWD6LM.js → chunk-EAIGGEV5.js} +6 -6
  25. package/dist/cli/{chunk-6JIXIMC5.js → chunk-GQUI3NYN.js} +57 -57
  26. package/dist/cli/{chunk-FAQGAPKK.js → chunk-HIXYNASX.js} +4 -4
  27. package/dist/cli/{chunk-GILGPI67.mjs → chunk-IVT2CZPM.mjs} +10 -3
  28. package/dist/cli/{chunk-QDFEF4PI.js → chunk-JGEFX6FQ.js} +6 -6
  29. package/dist/cli/{chunk-AXHRD6ME.mjs → chunk-JWUOPLAR.mjs} +1 -1
  30. package/dist/cli/{chunk-57J4GHJG.mjs → chunk-LGGR7CGZ.mjs} +4 -4
  31. package/dist/cli/{chunk-YBYPHZ4I.mjs → chunk-MTICVDNY.mjs} +1 -1
  32. package/dist/cli/{chunk-M6KBDWGI.js → chunk-NFWFUCGM.js} +19 -12
  33. package/dist/cli/{chunk-TAPI3QLV.js → chunk-OOQ4IRP4.js} +1 -1
  34. package/dist/cli/{chunk-7JA3CLD2.js → chunk-P2KELOST.js} +48 -48
  35. package/dist/cli/{chunk-SHQNCP2G.mjs → chunk-PUKK7SZX.mjs} +1 -1
  36. package/dist/cli/{chunk-ZNC3KU5O.js → chunk-RY6WID7C.js} +26 -26
  37. package/dist/cli/{chunk-GONRLVV3.js → chunk-W7INSYJS.js} +9 -9
  38. package/dist/cli/{chunk-7VBN7TU5.js → chunk-WJGRIDWQ.js} +15 -15
  39. package/dist/cli/{chunk-RQKAKIVL.mjs → chunk-XKW6HTWO.mjs} +1 -1
  40. package/dist/cli/{chunk-D7WKHLLF.mjs → chunk-XXQYOQWO.mjs} +2 -2
  41. package/dist/cli/{chunk-6S2UJUVD.mjs → chunk-YZLPXH7G.mjs} +2 -2
  42. package/dist/cli/{client-F7LPXBQS.mjs → client-AMSHGQFH.mjs} +4 -4
  43. package/dist/cli/client-WN33QCMV.js +20 -0
  44. package/dist/cli/{csv-DTGOY553.js → csv-B2OKPMNX.js} +12 -12
  45. package/dist/cli/{csv-PFAL3PLE.mjs → csv-F4RPWI4E.mjs} +3 -3
  46. package/dist/cli/{db-B7JTWZQB.js → db-AJYYK5HH.js} +21 -21
  47. package/dist/cli/{db-24PTJ6J5.mjs → db-QP4WNVSS.mjs} +5 -5
  48. package/dist/cli/{executions-3CPX35CQ.js → executions-4LHEJCCY.js} +7 -7
  49. package/dist/cli/{executions-53BGMOYB.mjs → executions-NY7HYAEH.mjs} +5 -5
  50. package/dist/cli/{feedback-4JPCFN6F.js → feedback-CXXJJARR.js} +6 -6
  51. package/dist/cli/{feedback-GA6FF4UP.mjs → feedback-SJYHLVYY.mjs} +3 -3
  52. package/dist/cli/{git-VXB6ZLJ3.mjs → git-DUODO3NL.mjs} +4 -4
  53. package/dist/cli/git-K7YPSXLC.js +23 -0
  54. package/dist/cli/index.d.mts +1 -6
  55. package/dist/cli/index.d.ts +1 -6
  56. package/dist/cli/index.js +100 -87
  57. package/dist/cli/index.mjs +54 -41
  58. package/dist/cli/{legacy-noops-ANYHW7ER.js → legacy-noops-3UYJVVNW.js} +7 -7
  59. package/dist/cli/{legacy-noops-7J5WN4M6.mjs → legacy-noops-IUDXZFYO.mjs} +4 -4
  60. package/dist/cli/{monitors-EAV6KHOK.js → monitors-IEEVACOD.js} +86 -86
  61. package/dist/cli/{monitors-DXETJ5NX.mjs → monitors-PDWE6BGA.mjs} +5 -5
  62. package/dist/cli/{org-3IVIYYVM.js → org-NUFEZRY7.js} +61 -61
  63. package/dist/cli/{org-B57DDLLC.mjs → org-VSPO2YF3.mjs} +5 -5
  64. package/dist/cli/{play-FXABI2NH.mjs → play-KMHPJVWH.mjs} +21 -19
  65. package/dist/cli/{play-LNYRYGOT.js → play-MXYK2BRE.js} +229 -227
  66. package/dist/cli/{providers-LMZBXPLP.mjs → providers-7ENB4EYV.mjs} +5 -5
  67. package/dist/cli/{providers-SWHCMHUN.js → providers-YE5YRIML.js} +8 -8
  68. package/dist/cli/{proxy-env-M4ZHXHRL.mjs → proxy-env-NWUIGWS3.mjs} +34 -17
  69. package/dist/cli/{proxy-env-EBANZHPV.js → proxy-env-ZV2WZFAT.js} +37 -20
  70. package/dist/cli/{quickstart-PHGLWVGA.mjs → quickstart-GFOOHX2U.mjs} +3 -3
  71. package/dist/cli/{quickstart-YYKEMF5A.js → quickstart-KXCU2MZ2.js} +5 -5
  72. package/dist/cli/{search-PHSXMXSV.mjs → search-KESXCJDL.mjs} +6 -6
  73. package/dist/cli/search-R426JJQW.js +18 -0
  74. package/dist/cli/{secrets-CWWMAPBB.js → secrets-BRB5SIXG.js} +14 -14
  75. package/dist/cli/{secrets-ZRVJ6GWI.mjs → secrets-HDR522GC.mjs} +5 -5
  76. package/dist/cli/{sessions-3HSV7W2M.js → sessions-IK3OSQC6.js} +5 -5
  77. package/dist/cli/{sessions-IXMB2B5R.mjs → sessions-VOOSWVLS.mjs} +4 -4
  78. package/dist/cli/{settings-XEM64DRR.mjs → settings-BZKDIHTX.mjs} +8 -8
  79. package/dist/cli/{settings-PGYWTWJ7.js → settings-OPGEFGXL.js} +75 -75
  80. package/dist/cli/{setup-MCXGMDIV.mjs → setup-RVPS7AJJ.mjs} +6 -6
  81. package/dist/cli/{setup-EOTIXFLU.js → setup-TP2VWSCU.js} +41 -41
  82. package/dist/cli/{skills-4HXO76M7.mjs → skills-7BDD2F4V.mjs} +4 -4
  83. package/dist/cli/skills-TK2CIWVS.js +28 -0
  84. package/dist/cli/{tools-FMOJUYVW.js → tools-46ILS57M.js} +51 -51
  85. package/dist/cli/{tools-P3OULQFZ.mjs → tools-XLGGDBM3.mjs} +6 -6
  86. package/dist/cli/{update-2QXPDAIV.mjs → update-2JEM7WUF.mjs} +6 -6
  87. package/dist/cli/update-3JOCQWNF.js +28 -0
  88. package/dist/cli/{workflow-VAJ3GV2W.mjs → workflow-SSTGMXOQ.mjs} +6 -6
  89. package/dist/cli/{workflow-P2YQPB7Y.js → workflow-ZKGPY7T4.js} +16 -16
  90. package/dist/index.js +7 -7
  91. package/dist/index.mjs +4 -4
  92. package/dist/install-integrity.json +74 -74
  93. package/dist/plays/bundle-play-file.mjs +2 -2
  94. package/dist/release.d.mts +1 -1
  95. package/dist/release.d.ts +1 -1
  96. package/dist/release.js +2 -2
  97. package/dist/release.mjs +1 -1
  98. package/package.json +1 -1
  99. package/dist/cli/auth-G2CKLPSX.js +0 -31
  100. package/dist/cli/client-WTE3IK53.js +0 -20
  101. package/dist/cli/git-YTJCZVEL.js +0 -23
  102. package/dist/cli/search-CEBKSGXX.js +0 -18
  103. package/dist/cli/skills-3IXQN3KZ.js +0 -28
  104. package/dist/cli/update-H6I7437Q.js +0 -28
  105. /package/dist/cli/{chunk-GVUJJJ5G.mjs → chunk-6IKARAYD.mjs} +0 -0
  106. /package/dist/cli/{chunk-6A3TJUWC.js → chunk-S6R3OX7B.js} +0 -0
@@ -200,7 +200,7 @@ export const SDK_RELEASE = {
200
200
  // getters keep their established compatibility behavior.
201
201
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
202
202
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
203
- version: '0.3.301',
203
+ version: '0.3.303',
204
204
  updateSummary:
205
205
  'Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.',
206
206
  packageCapabilities: {
@@ -95,7 +95,7 @@ async function backfillRotatedLogTail(input: {
95
95
  }): Promise<PlayRunLiveSnapshot> {
96
96
  // A late first attach intentionally shows the retained tail and its gap
97
97
  // marker. Backfill only lines that rotated out after this watcher attached.
98
- if (!input.previous.runSignature) {
98
+ if (!input.previous.runSignature || input.snapshot.logsUnavailable) {
99
99
  return input.snapshot;
100
100
  }
101
101
  const gap = resolvePlayRunLogGap(input.snapshot, input.previous.lastLogSeq);
@@ -84,6 +84,8 @@ export type PlayRunLiveSnapshot = {
84
84
  status: PlayRunLiveStatus;
85
85
  updatedAt: number | null;
86
86
  logs: string[];
87
+ /** Present when an optional canonical log read failed for this snapshot. */
88
+ logsUnavailable?: true;
87
89
  activeArtifactTableNamespace: string | null;
88
90
  resultTableNamespace: string | null;
89
91
  nodeStates: PlayVisualNodeStateSnapshot[];
@@ -5,7 +5,7 @@ import {
5
5
  dispatchRunLedgerOutboxToLedgerThroughEventWithClientScope,
6
6
  type PostgresSchedulerOptions,
7
7
  type PostgresSchedulerOutboxDrainResult,
8
- type PostgresSchedulerQueryClient,
8
+ type RunLedgerOutboxWithClient,
9
9
  } from '../../scheduler-backends/postgres';
10
10
 
11
11
  import type { RunLedgerProjectionPorts } from '../../play-runs/projection/ports';
@@ -17,9 +17,7 @@ export type GatewayRunLedgerProjectionAdapterInput = {
17
17
  * against the active outbox lease while retaining supported transport hooks.
18
18
  */
19
19
  runtimeApi: WorkerRuntimeApiContext;
20
- withClient: <T>(
21
- operation: (client: PostgresSchedulerQueryClient) => Promise<T>,
22
- ) => Promise<T>;
20
+ withClient: RunLedgerOutboxWithClient;
23
21
  schedulerOptions?: Pick<PostgresSchedulerOptions, 'schema'>;
24
22
  };
25
23
 
@@ -99,6 +99,12 @@ export type PlayRunLiveSnapshot = {
99
99
  * crossed the retention cap (ADR-0009). Additive/optional on the wire.
100
100
  */
101
101
  logsTruncated?: boolean;
102
+ /**
103
+ * The canonical log store could not be read for this snapshot. The run
104
+ * status remains authoritative; callers should retry logs on the next poll
105
+ * or use the paginated logs endpoint. The log cursor does not advance.
106
+ */
107
+ logsUnavailable?: true;
102
108
  activeArtifactTableNamespace: string | null;
103
109
  resultTableNamespace: string | null;
104
110
  datasets?: PlayRunLedgerDatasetSnapshot[];
@@ -573,6 +579,13 @@ function diffLogLines(input: {
573
579
  lastLogSeq: number;
574
580
  }): { lines: string[]; lastLogSeq: number; firstSeq: number | null } {
575
581
  const { logs, totalLogCount } = input.snapshot;
582
+ if (input.snapshot.logsUnavailable) {
583
+ return {
584
+ lines: [],
585
+ lastLogSeq: input.lastLogSeq,
586
+ firstSeq: null,
587
+ };
588
+ }
576
589
  if (totalLogCount <= input.lastLogSeq) {
577
590
  return { lines: [], lastLogSeq: input.lastLogSeq, firstSeq: null };
578
591
  }
@@ -694,12 +694,22 @@ async function acquireOneShotDaytonaSandbox(input: {
694
694
  },
695
695
  );
696
696
  input.emitStage('create:start');
697
+ const admissionRequestedAt = Date.now();
698
+ let admissionWaitMs = 0;
699
+ let admissionQueued = false;
700
+ const create = () => {
701
+ admissionWaitMs = Date.now() - admissionRequestedAt;
702
+ if (admissionQueued) {
703
+ input.emitStage('create:admission_ready', { admissionWaitMs });
704
+ }
705
+ return createRetriedOneShotDaytonaSandbox(input);
706
+ };
697
707
  const result = input.startAdmission
698
- ? await input.startAdmission.run(
699
- () => createRetriedOneShotDaytonaSandbox(input),
700
- input.cancellationSignal,
701
- )
702
- : await createRetriedOneShotDaytonaSandbox(input);
708
+ ? await input.startAdmission.run(create, input.cancellationSignal, () => {
709
+ admissionQueued = true;
710
+ input.emitStage('create:admission_wait');
711
+ })
712
+ : await create();
703
713
  // The runner's egress probe measures the sandbox-to-estate round trip; the
704
714
  // sandbox's target names the region that round trip crosses.
705
715
  console.info('[play-runner.daytona.sandbox_acquired]', {
@@ -779,6 +789,7 @@ async function acquireOneShotDaytonaSandbox(input: {
779
789
  sandboxId: sandbox.id,
780
790
  attempt: result.attempt,
781
791
  elapsedMs: billingStartedAt - input.startedAt,
792
+ admissionWaitMs,
782
793
  attemptElapsedMs: result.attemptElapsedMs,
783
794
  // Daytona's default snapshot is intentionally provider-managed. Record the
784
795
  // resolved immutable snapshot identifier so a slow acquisition can be
@@ -1,7 +1,11 @@
1
1
  import { RUNTIME_CAPACITY_POLICY } from '../../runtime-capacity-policy';
2
2
 
3
3
  export type DaytonaSandboxStartAdmission = {
4
- run<T>(operation: () => Promise<T>, signal?: AbortSignal): Promise<T>;
4
+ run<T>(
5
+ operation: () => Promise<T>,
6
+ signal?: AbortSignal,
7
+ onQueued?: () => void,
8
+ ): Promise<T>;
5
9
  };
6
10
 
7
11
  /**
@@ -45,6 +49,7 @@ export function createDaytonaSandboxStartAdmission(
45
49
  async run<T>(
46
50
  operation: () => Promise<T>,
47
51
  signal?: AbortSignal,
52
+ onQueued?: () => void,
48
53
  ): Promise<T> {
49
54
  signal?.throwIfAborted();
50
55
  // Preserve eager prepare semantics when capacity is free: the provider
@@ -57,6 +62,7 @@ export function createDaytonaSandboxStartAdmission(
57
62
  return createRelease();
58
63
  })()
59
64
  : await new Promise<() => void>((resolve, reject) => {
65
+ onQueued?.();
60
66
  const accept = (release: () => void) => {
61
67
  signal?.removeEventListener('abort', abort);
62
68
  resolve(release);
@@ -66,11 +66,13 @@ export const RUNTIME_CAPACITY_POLICY = {
66
66
  sandboxAcquisition: {
67
67
  /**
68
68
  * Starting a Daytona sandbox is a provider control-plane transition, not
69
- * an Absurd execution slot. Serialize starts within each worker process;
70
- * once started, all eight execution slots may run concurrently.
69
+ * an Absurd execution slot. Allow up to five concurrent starts within one
70
+ * worker process; the fleet-wide limiter paces lifecycle admissions, and
71
+ * this semaphore bounds concurrent create/retry ladders. A worker has
72
+ * eight execution slots.
71
73
  */
72
- daytonaConcurrentStartsPerProcess: 1,
73
- /** Fleet-wide AIMD start pacing, separate from execution-slot scaling. */
74
+ daytonaConcurrentStartsPerProcess: 5,
75
+ /** Fleet-wide AIMD lifecycle-admission pacing. */
74
76
  daytonaInitialStartsPerMinute: 120,
75
77
  daytonaMaximumStartsPerMinute: 600,
76
78
  },
@@ -870,8 +870,8 @@ function buildDefaultDeps(absurd: Absurd): AbsurdRuntimeWorkerDeps {
870
870
  ...absurdRuntimeApiFromLaunch(launch),
871
871
  ...(signal ? { signal } : {}),
872
872
  },
873
- withClient: (operation) =>
874
- withRuntimeSchedulerClient(getPool(), operation),
873
+ withClient: (operation, trace) =>
874
+ withRuntimeSchedulerClient(getPool(), operation, trace),
875
875
  schedulerOptions: options,
876
876
  }),
877
877
  }).deliver(targetEventId ? { targetEventId } : {}),
@@ -642,6 +642,8 @@ export type PostgresSchedulerRunStatusSnapshot = {
642
642
  error: string | null;
643
643
  suspension: unknown | null;
644
644
  staticPipeline: PlayStaticPipeline | null;
645
+ /** Included only when requested by the admission-projection read path. */
646
+ createdProjectionPending?: boolean;
645
647
  };
646
648
 
647
649
  type RunStatusRow = RunRow & {
@@ -651,6 +653,7 @@ type RunStatusRow = RunRow & {
651
653
  runtime_profile: string | null;
652
654
  launch_json: unknown;
653
655
  definition_scope?: string | null;
656
+ created_projection_pending?: boolean;
654
657
  suspension_json: unknown | null;
655
658
  updated_at: Date | null;
656
659
  };
@@ -17380,11 +17383,23 @@ export async function readPostgresSchedulerRunStatus(
17380
17383
  options?: Pick<PostgresSchedulerOptions, 'schema'> & {
17381
17384
  includeDetails?: boolean;
17382
17385
  includeAdmissionScope?: boolean;
17386
+ includeCreatedProjectionPending?: boolean;
17383
17387
  },
17384
17388
  ): Promise<PostgresSchedulerRunStatusSnapshot | null> {
17385
17389
  const admissionScopeSelection = options?.includeAdmissionScope
17386
- ? ", launch_json ->> 'definitionScope' AS definition_scope"
17390
+ ? ", scheduler_run.launch_json ->> 'definitionScope' AS definition_scope"
17387
17391
  : '';
17392
+ const createdProjectionJoin = options?.includeCreatedProjectionPending
17393
+ ? `LEFT JOIN ${tableName('outbox', options)} AS created_event
17394
+ ON created_event.run_id = scheduler_run.run_id
17395
+ AND created_event.event_id = $2`
17396
+ : '';
17397
+ const createdProjectionSelection = options?.includeCreatedProjectionPending
17398
+ ? ', (created_event.event_id IS NOT NULL AND created_event.delivered_at IS NULL) AS created_projection_pending'
17399
+ : '';
17400
+ const values = options?.includeCreatedProjectionPending
17401
+ ? [runId, `run-created:${runId}`]
17402
+ : [runId];
17388
17403
  if (options?.includeDetails === false) {
17389
17404
  // An admission projection gap needs lifecycle truth only. Launch, result,
17390
17405
  // suspension and error JSON may contain row/call-sized detail: never even
@@ -17403,15 +17418,20 @@ export async function readPostgresSchedulerRunStatus(
17403
17418
  | 'started_at'
17404
17419
  | 'finished_at'
17405
17420
  | 'definition_scope'
17421
+ | 'created_projection_pending'
17406
17422
  >
17407
17423
  >(
17408
17424
  `
17409
- SELECT run_id, org_id, status, play_name, graph_hash, runtime_profile,
17410
- created_at, updated_at, started_at, finished_at${admissionScopeSelection}
17411
- FROM ${tableName('runs', options)}
17412
- WHERE run_id = $1
17425
+ SELECT scheduler_run.run_id, scheduler_run.org_id, scheduler_run.status,
17426
+ scheduler_run.play_name, scheduler_run.graph_hash,
17427
+ scheduler_run.runtime_profile, scheduler_run.created_at,
17428
+ scheduler_run.updated_at, scheduler_run.started_at,
17429
+ scheduler_run.finished_at${admissionScopeSelection}${createdProjectionSelection}
17430
+ FROM ${tableName('runs', options)} AS scheduler_run
17431
+ ${createdProjectionJoin}
17432
+ WHERE scheduler_run.run_id = $1
17413
17433
  `,
17414
- [runId],
17434
+ values,
17415
17435
  );
17416
17436
  const row = result.rows[0];
17417
17437
  if (!row) return null;
@@ -17441,17 +17461,25 @@ export async function readPostgresSchedulerRunStatus(
17441
17461
  error: null,
17442
17462
  suspension: null,
17443
17463
  staticPipeline: null,
17464
+ ...(options.includeCreatedProjectionPending
17465
+ ? { createdProjectionPending: row.created_projection_pending === true }
17466
+ : {}),
17444
17467
  };
17445
17468
  }
17446
17469
  const result = await client.query<RunStatusRow>(
17447
17470
  `
17448
- SELECT run_id, org_id, status, play_name, graph_hash, runtime_profile,
17449
- launch_json, suspension_json, terminal_result_json, terminal_error_json,
17450
- runtime_timing_json, created_at, updated_at, started_at, finished_at${admissionScopeSelection}
17451
- FROM ${tableName('runs', options)}
17452
- WHERE run_id = $1
17471
+ SELECT scheduler_run.run_id, scheduler_run.org_id, scheduler_run.status,
17472
+ scheduler_run.play_name, scheduler_run.graph_hash,
17473
+ scheduler_run.runtime_profile, scheduler_run.launch_json,
17474
+ scheduler_run.suspension_json, scheduler_run.terminal_result_json,
17475
+ scheduler_run.terminal_error_json, scheduler_run.runtime_timing_json,
17476
+ scheduler_run.created_at, scheduler_run.updated_at,
17477
+ scheduler_run.started_at, scheduler_run.finished_at${admissionScopeSelection}${createdProjectionSelection}
17478
+ FROM ${tableName('runs', options)} AS scheduler_run
17479
+ ${createdProjectionJoin}
17480
+ WHERE scheduler_run.run_id = $1
17453
17481
  `,
17454
- [runId],
17482
+ values,
17455
17483
  );
17456
17484
  const row = result.rows[0] ?? null;
17457
17485
  if (!row) return null;
@@ -17487,6 +17515,9 @@ export async function readPostgresSchedulerRunStatus(
17487
17515
  : null,
17488
17516
  suspension: row.suspension_json ?? null,
17489
17517
  staticPipeline: staticPipelineFromContractSnapshot(launch.contractSnapshot),
17518
+ ...(options?.includeCreatedProjectionPending
17519
+ ? { createdProjectionPending: row.created_projection_pending === true }
17520
+ : {}),
17490
17521
  };
17491
17522
  }
17492
17523
 
@@ -19526,6 +19557,40 @@ export type RunLedgerOutboxDeliveredRow = {
19526
19557
  lagMs: number;
19527
19558
  };
19528
19559
 
19560
+ export type RunLedgerOutboxClientTrace = {
19561
+ runId: string;
19562
+ phase: string;
19563
+ priority?: 'ordinary' | 'critical';
19564
+ };
19565
+
19566
+ export type RunLedgerOutboxWithClient = <T>(
19567
+ operation: (client: PostgresSchedulerQueryClient) => Promise<T>,
19568
+ trace?: RunLedgerOutboxClientTrace,
19569
+ ) => Promise<T>;
19570
+
19571
+ function logRunLedgerOutboxSettlementFailure(input: {
19572
+ runId: string;
19573
+ eventIds: readonly string[];
19574
+ stage: 'continuation_release' | 'aborted_release' | 'failure_settlement';
19575
+ settlementError: unknown;
19576
+ deliveryError?: unknown;
19577
+ }): void {
19578
+ const errorMessage = (error: unknown) =>
19579
+ (error instanceof Error ? error.message : String(error)).slice(0, 500);
19580
+ console.error('[run-ledger-outbox.settlement_failed]', {
19581
+ runId: input.runId,
19582
+ stage: input.stage,
19583
+ eventCount: input.eventIds.length,
19584
+ eventIds: input.eventIds.slice(0, 8),
19585
+ settlementError: errorMessage(input.settlementError),
19586
+ deliveryError:
19587
+ input.deliveryError === undefined
19588
+ ? null
19589
+ : errorMessage(input.deliveryError),
19590
+ recovery: 'lease_expiry',
19591
+ });
19592
+ }
19593
+
19529
19594
  export async function dispatchRunLedgerOutboxToLedgerOnce(
19530
19595
  client: PostgresSchedulerQueryClient,
19531
19596
  input: RunLedgerOutboxDispatchInput,
@@ -19570,9 +19635,7 @@ export async function dispatchRunLedgerOutboxToLedgerThroughEvent(
19570
19635
  * network calls. Each callback owns only the claim or settlement query phase.
19571
19636
  */
19572
19637
  export async function dispatchRunLedgerOutboxToLedgerThroughEventWithClientScope(
19573
- withClient: <T>(
19574
- operation: (client: PostgresSchedulerQueryClient) => Promise<T>,
19575
- ) => Promise<T>,
19638
+ withClient: RunLedgerOutboxWithClient,
19576
19639
  input: RunLedgerOutboxDispatchInput & {
19577
19640
  targetEventId: string;
19578
19641
  maxEvents?: number;
@@ -19606,9 +19669,7 @@ type RunLedgerOutboxAuthorityScope = {
19606
19669
  };
19607
19670
 
19608
19671
  async function dispatchRunLedgerOutboxThroughEventWithScope(
19609
- withClient: <T>(
19610
- operation: (client: PostgresSchedulerQueryClient) => Promise<T>,
19611
- ) => Promise<T>,
19672
+ withClient: RunLedgerOutboxWithClient,
19612
19673
  input: RunLedgerOutboxDispatchInput & {
19613
19674
  targetEventId: string;
19614
19675
  maxEvents?: number;
@@ -19745,9 +19806,7 @@ async function dispatchRunLedgerOutboxThroughEventWithScope(
19745
19806
  * background projector use this exact implementation.
19746
19807
  */
19747
19808
  export async function dispatchRunLedgerOutboxToLedgerBatchOnceWithClientScope(
19748
- withClient: <T>(
19749
- operation: (client: PostgresSchedulerQueryClient) => Promise<T>,
19750
- ) => Promise<T>,
19809
+ withClient: RunLedgerOutboxWithClient,
19751
19810
  input: RunLedgerOutboxDispatchInput & { waitForRunLock?: boolean },
19752
19811
  options?: Pick<PostgresSchedulerOptions, 'schema'>,
19753
19812
  ): Promise<PostgresSchedulerOutboxProjectionResult> {
@@ -19761,9 +19820,7 @@ export async function dispatchRunLedgerOutboxToLedgerBatchOnceWithClientScope(
19761
19820
  }
19762
19821
 
19763
19822
  async function dispatchRunLedgerOutboxBatchWithScope(
19764
- withClient: <T>(
19765
- operation: (client: PostgresSchedulerQueryClient) => Promise<T>,
19766
- ) => Promise<T>,
19823
+ withClient: RunLedgerOutboxWithClient,
19767
19824
  input: RunLedgerOutboxDispatchInput & { waitForRunLock?: boolean },
19768
19825
  options?: Pick<PostgresSchedulerOptions, 'schema'>,
19769
19826
  scope?: RunLedgerOutboxDrainScope,
@@ -19788,14 +19845,31 @@ async function dispatchRunLedgerOutboxBatchWithScope(
19788
19845
  const releasePendingContinuation = async () => {
19789
19846
  const nextClaimed = continuation.current?.nextClaimed ?? [];
19790
19847
  if (nextClaimed.length === 0) return;
19791
- await withClient((client) =>
19792
- releasePostgresSchedulerOutboxLease(
19793
- client,
19794
- nextClaimed.map((event) => event.eventId),
19795
- { ...options, leaseOwner },
19796
- ),
19797
- );
19798
- nextClaimed.length = 0;
19848
+ const eventIds = nextClaimed.map((event) => event.eventId);
19849
+ try {
19850
+ await withClient(
19851
+ (client) =>
19852
+ releasePostgresSchedulerOutboxLease(client, eventIds, {
19853
+ ...options,
19854
+ leaseOwner,
19855
+ }),
19856
+ {
19857
+ runId: continuation.current?.runId ?? nextClaimed[0]!.runId,
19858
+ phase: 'run_ledger_outbox_lease_release',
19859
+ priority: 'critical',
19860
+ },
19861
+ );
19862
+ nextClaimed.length = 0;
19863
+ } catch (settlementError) {
19864
+ // Keep the in-memory claim visible to this drain and let its durable
19865
+ // lease expire if even critical pool admission cannot settle it.
19866
+ logRunLedgerOutboxSettlementFailure({
19867
+ runId: continuation.current?.runId ?? nextClaimed[0]!.runId,
19868
+ eventIds,
19869
+ stage: 'continuation_release',
19870
+ settlementError,
19871
+ });
19872
+ }
19799
19873
  };
19800
19874
  const claimEvents = async (client: PostgresSchedulerQueryClient) => {
19801
19875
  // The checkout is exclusive. Hold the existing scheduler run's row lock
@@ -20497,13 +20571,31 @@ async function dispatchRunLedgerOutboxBatchWithScope(
20497
20571
  // projection attempt or scheduling backoff, and wake the durable
20498
20572
  // projector. If the append already reached the ledger, the projector's
20499
20573
  // replay carries the same idempotency key and is a no-op there.
20500
- const released = await withClient((client) =>
20501
- releasePostgresSchedulerOutboxLease(
20502
- client,
20503
- events.map((event) => event.eventId),
20504
- { ...options, leaseOwner },
20505
- ),
20506
- );
20574
+ const eventIds = events.map((event) => event.eventId);
20575
+ let released: string[];
20576
+ try {
20577
+ released = await withClient(
20578
+ (client) =>
20579
+ releasePostgresSchedulerOutboxLease(client, eventIds, {
20580
+ ...options,
20581
+ leaseOwner,
20582
+ }),
20583
+ {
20584
+ runId: events[0]!.runId,
20585
+ phase: 'run_ledger_outbox_lease_release',
20586
+ priority: 'critical',
20587
+ },
20588
+ );
20589
+ } catch (settlementError) {
20590
+ logRunLedgerOutboxSettlementFailure({
20591
+ runId: events[0]!.runId,
20592
+ eventIds,
20593
+ stage: 'aborted_release',
20594
+ settlementError,
20595
+ deliveryError: error,
20596
+ });
20597
+ released = eventIds;
20598
+ }
20507
20599
  return {
20508
20600
  attempted: events.length,
20509
20601
  projected: 0,
@@ -20515,22 +20607,34 @@ async function dispatchRunLedgerOutboxBatchWithScope(
20515
20607
  };
20516
20608
  }
20517
20609
  if (error instanceof DeferredRuntimeProjectionInputError) {
20518
- const outcome = await withClient((client) =>
20519
- settlePostgresSchedulerOutboxFailure(client, error.eventId, error, {
20520
- ...options,
20521
- leaseOwner,
20522
- }),
20610
+ const outcome = await withClient(
20611
+ (client) =>
20612
+ settlePostgresSchedulerOutboxFailure(client, error.eventId, error, {
20613
+ ...options,
20614
+ leaseOwner,
20615
+ }),
20616
+ {
20617
+ runId: events[0]!.runId,
20618
+ phase: 'run_ledger_outbox_failure_settlement',
20619
+ priority: 'critical',
20620
+ },
20523
20621
  );
20524
20622
  // Earlier facts may already be indexed. Only the invalid immutable row
20525
20623
  // is quarantined; valid predecessors remain deliverable in causal order.
20526
- const released = await withClient((client) =>
20527
- releasePostgresSchedulerOutboxLease(
20528
- client,
20529
- events
20530
- .filter((event) => event.eventId !== error.eventId)
20531
- .map((event) => event.eventId),
20532
- { ...options, leaseOwner },
20533
- ),
20624
+ const released = await withClient(
20625
+ (client) =>
20626
+ releasePostgresSchedulerOutboxLease(
20627
+ client,
20628
+ events
20629
+ .filter((event) => event.eventId !== error.eventId)
20630
+ .map((event) => event.eventId),
20631
+ { ...options, leaseOwner },
20632
+ ),
20633
+ {
20634
+ runId: events[0]!.runId,
20635
+ phase: 'run_ledger_outbox_lease_release',
20636
+ priority: 'critical',
20637
+ },
20534
20638
  );
20535
20639
  const failed = outcome === 'blocked' ? [error.eventId] : [];
20536
20640
  const retrying =
@@ -20550,52 +20654,79 @@ async function dispatchRunLedgerOutboxBatchWithScope(
20550
20654
  retryingEventIds: retrying,
20551
20655
  };
20552
20656
  }
20553
- const settlement = await withClient(async (client) => {
20554
- await client.query('BEGIN');
20555
- try {
20556
- const blocked: string[] = [];
20557
- const retrying: string[] = [];
20558
- const ownedRetrying: string[] = [];
20559
- for (const event of events) {
20560
- const outcome = await settlePostgresSchedulerOutboxFailure(
20561
- client,
20562
- event.eventId,
20563
- error,
20564
- { ...options, leaseOwner },
20565
- );
20566
- if (outcome === 'blocked') blocked.push(event.eventId);
20567
- else retrying.push(event.eventId);
20568
- if (outcome === 'retrying') ownedRetrying.push(event.eventId);
20569
- }
20570
- // All members of this failed append must become due together. Keep
20571
- // the existing per-row retry policy, taking its latest deadline only
20572
- // for rows this owner actually settled, before releasing their locks.
20573
- if (ownedRetrying.length > 1) {
20574
- await client.query(
20575
- `
20576
- WITH retry AS (
20577
- SELECT max(next_attempt_at) AS retry_at
20578
- FROM ${tableName('outbox', options)}
20579
- WHERE event_id = ANY($1::text[])
20580
- AND delivered_at IS NULL AND failed_at IS NULL
20581
- )
20582
- UPDATE ${tableName('outbox', options)} AS outbox
20583
- SET next_attempt_at = retry.retry_at
20584
- FROM retry
20585
- WHERE outbox.event_id = ANY($1::text[])
20586
- AND outbox.delivered_at IS NULL AND outbox.failed_at IS NULL
20587
- AND retry.retry_at IS NOT NULL
20588
- `,
20589
- [ownedRetrying],
20590
- );
20591
- }
20592
- await client.query('COMMIT');
20593
- return { blocked, retrying };
20594
- } catch (settlementError) {
20595
- await client.query('ROLLBACK');
20596
- throw settlementError;
20597
- }
20598
- });
20657
+ let settlement: { blocked: string[]; retrying: string[] };
20658
+ try {
20659
+ settlement = await withClient(
20660
+ async (client) => {
20661
+ await client.query('BEGIN');
20662
+ try {
20663
+ const blocked: string[] = [];
20664
+ const retrying: string[] = [];
20665
+ const ownedRetrying: string[] = [];
20666
+ for (const event of events) {
20667
+ const outcome = await settlePostgresSchedulerOutboxFailure(
20668
+ client,
20669
+ event.eventId,
20670
+ error,
20671
+ { ...options, leaseOwner },
20672
+ );
20673
+ if (outcome === 'blocked') blocked.push(event.eventId);
20674
+ else retrying.push(event.eventId);
20675
+ if (outcome === 'retrying') ownedRetrying.push(event.eventId);
20676
+ }
20677
+ // Keep every member of a failed append on the same retry deadline.
20678
+ if (ownedRetrying.length > 1) {
20679
+ await client.query(
20680
+ `
20681
+ WITH retry AS (
20682
+ SELECT max(next_attempt_at) AS retry_at
20683
+ FROM ${tableName('outbox', options)}
20684
+ WHERE event_id = ANY($1::text[])
20685
+ AND delivered_at IS NULL AND failed_at IS NULL
20686
+ )
20687
+ UPDATE ${tableName('outbox', options)} AS outbox
20688
+ SET next_attempt_at = retry.retry_at
20689
+ FROM retry
20690
+ WHERE outbox.event_id = ANY($1::text[])
20691
+ AND outbox.delivered_at IS NULL AND outbox.failed_at IS NULL
20692
+ AND retry.retry_at IS NOT NULL
20693
+ `,
20694
+ [ownedRetrying],
20695
+ );
20696
+ }
20697
+ await client.query('COMMIT');
20698
+ return { blocked, retrying };
20699
+ } catch (settlementError) {
20700
+ await client.query('ROLLBACK');
20701
+ throw settlementError;
20702
+ }
20703
+ },
20704
+ {
20705
+ runId: events[0]!.runId,
20706
+ phase: 'run_ledger_outbox_failure_settlement',
20707
+ priority: 'critical',
20708
+ },
20709
+ );
20710
+ } catch (settlementError) {
20711
+ // The delivery error is already known. If the critical settlement
20712
+ // retry also fails, durable lease expiry remains the recovery path.
20713
+ logRunLedgerOutboxSettlementFailure({
20714
+ runId: events[0]!.runId,
20715
+ eventIds: events.map((event) => event.eventId),
20716
+ stage: 'failure_settlement',
20717
+ settlementError,
20718
+ deliveryError: error,
20719
+ });
20720
+ return {
20721
+ attempted: events.length,
20722
+ projected: 0,
20723
+ eventIds: [],
20724
+ failed: 0,
20725
+ failedEventIds: [],
20726
+ retrying: events.length,
20727
+ retryingEventIds: events.map((event) => event.eventId),
20728
+ };
20729
+ }
20599
20730
  const failure = runLedgerDeliveryFailure({ error });
20600
20731
  console.warn('[run-ledger-outbox.batch_dispatch_failed]', {
20601
20732
  runId: input.runId ?? null,
@@ -15,10 +15,10 @@ import {
15
15
  readPostgresSchedulerOutboxNextDueAt,
16
16
  type PostgresSchedulerOptions,
17
17
  type PostgresSchedulerOutboxProjectionResult,
18
- type PostgresSchedulerQueryClient,
19
18
  type RunLedgerOutboxDeliveredRow,
20
19
  type RunLedgerOutboxHeadPriority,
21
20
  type RunLedgerOutboxTokenAuthority,
21
+ type RunLedgerOutboxWithClient,
22
22
  } from './postgres';
23
23
  import {
24
24
  acquirePgNotifyHub,
@@ -561,9 +561,7 @@ export type RunCreatedInlineDeliveryOutcome =
561
561
  */
562
562
  export async function deliverRunCreatedOutboxEventInline(input: {
563
563
  runId: string;
564
- withClient: <T>(
565
- operation: (client: PostgresSchedulerQueryClient) => Promise<T>,
566
- ) => Promise<T>;
564
+ withClient: RunLedgerOutboxWithClient;
567
565
  schedulerOptions?: Pick<PostgresSchedulerOptions, 'schema'>;
568
566
  tokenAuthority: RunLedgerOutboxTokenAuthority;
569
567
  deadlineMs?: number;