deepline 0.3.198 → 0.3.199

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.
@@ -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.198',
203
+ version: '0.3.199',
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: {
@@ -13735,51 +13735,70 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
13735
13735
  const suspendAfterTimeoutMs = timeoutPolicy
13736
13736
  ? timeoutPolicy.suspendAfterTimeoutMs
13737
13737
  : handler.suspendAfterTimeoutMs;
13738
- try {
13739
- await handler.onTimeout?.({
13740
- payload: input,
13741
- context: eventContext,
13742
- boundary: {
13743
- boundaryId: preparedBoundary.boundaryId,
13744
- eventKey: preparedBoundary.eventKey,
13745
- timeoutMs: preparedBoundary.timeoutMs,
13746
- provider: existing.provider ?? handler.provider,
13747
- toolId: existing.toolId ?? handler.toolId,
13748
- ...(existing.messageRef
13749
- ? { messageRef: existing.messageRef }
13750
- : {}),
13751
- ...(existing.requestKey
13752
- ? { requestKey: existing.requestKey }
13753
- : {}),
13754
- ...(existing.interactionId
13755
- ? { interactionId: existing.interactionId }
13738
+ let timeoutFinalizedAt = existing.timeoutFinalizedAt;
13739
+ // This is a replay acknowledgement, not exactly-once execution: a
13740
+ // crash after the provider succeeds but before this checkpoint is
13741
+ // durable can still repeat the finalizer on a later attempt.
13742
+ if (timeoutFinalizedAt === undefined && handler.onTimeout) {
13743
+ try {
13744
+ await handler.onTimeout({
13745
+ payload: input,
13746
+ context: eventContext,
13747
+ boundary: {
13748
+ boundaryId: preparedBoundary.boundaryId,
13749
+ eventKey: preparedBoundary.eventKey,
13750
+ timeoutMs: preparedBoundary.timeoutMs,
13751
+ provider: existing.provider ?? handler.provider,
13752
+ toolId: existing.toolId ?? handler.toolId,
13753
+ ...(existing.messageRef
13754
+ ? { messageRef: existing.messageRef }
13755
+ : {}),
13756
+ ...(existing.requestKey
13757
+ ? { requestKey: existing.requestKey }
13758
+ : {}),
13759
+ ...(existing.interactionId
13760
+ ? { interactionId: existing.interactionId }
13761
+ : {}),
13762
+ ...(timeoutPolicy ? { timeoutPolicy } : {}),
13763
+ },
13764
+ });
13765
+ timeoutFinalizedAt = Date.now();
13766
+ } catch (error) {
13767
+ this.recordSlackHitlTimeoutFailure(error);
13768
+ throw error;
13769
+ }
13770
+ }
13771
+ const persistTimeoutFinalization = (completedAt?: number) => {
13772
+ if (
13773
+ timeoutFinalizedAt === existing.timeoutFinalizedAt &&
13774
+ completedAt === existing.completedAt
13775
+ ) {
13776
+ return;
13777
+ }
13778
+ this.checkpoint.resolvedBoundaries = {
13779
+ ...(this.checkpoint.resolvedBoundaries ?? {}),
13780
+ [preparedBoundary.boundaryId]: {
13781
+ ...existing,
13782
+ ...(completedAt !== undefined ? { completedAt } : {}),
13783
+ ...(timeoutFinalizedAt !== undefined
13784
+ ? { timeoutFinalizedAt }
13756
13785
  : {}),
13757
- ...(timeoutPolicy ? { timeoutPolicy } : {}),
13758
13786
  },
13759
- });
13760
- } catch (error) {
13761
- this.recordSlackHitlTimeoutFailure(error);
13762
- throw error;
13763
- }
13787
+ };
13788
+ this.#options.onBatchComplete?.(this.checkpoint);
13789
+ };
13764
13790
  if (timeoutPolicy?.timeoutOutcome === 'error') {
13765
13791
  // The provider finalizer above must complete first. That ordering
13766
13792
  // makes an expired Slack card and the terminal Play state agree,
13767
13793
  // while provider finalization failures stay retryable instead.
13794
+ persistTimeoutFinalization();
13768
13795
  const error = new SlackHitlTimeoutError(timeoutPolicy.timeoutError);
13769
13796
  this.recordSlackHitlTimeoutFailure(error);
13770
13797
  throw error;
13771
13798
  }
13772
13799
  if (suspendAfterTimeoutMs !== undefined) {
13773
13800
  const finalizedAtMs = existing.completedAt ?? Date.now();
13774
- if (existing.completedAt === undefined) {
13775
- this.checkpoint.resolvedBoundaries = {
13776
- ...(this.checkpoint.resolvedBoundaries ?? {}),
13777
- [preparedBoundary.boundaryId]: {
13778
- ...existing,
13779
- completedAt: finalizedAtMs,
13780
- },
13781
- };
13782
- }
13801
+ persistTimeoutFinalization(finalizedAtMs);
13783
13802
  this.runtimeLog(
13784
13803
  `Integration event ${preparedBoundary.boundaryId}: timeout finalized externally; keeping Play parked`,
13785
13804
  );
@@ -13792,6 +13811,8 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
13792
13811
  postTimeoutPark: true,
13793
13812
  ...(existing.messageRef ? { messageRef: existing.messageRef } : {}),
13794
13813
  });
13814
+ } else {
13815
+ persistTimeoutFinalization();
13795
13816
  }
13796
13817
  }
13797
13818
  this.runtimeLog(
@@ -1210,6 +1210,8 @@ export interface PlayCheckpoint {
1210
1210
  timeoutPolicy?: IntegrationEventWaitBoundary['timeoutPolicy'];
1211
1211
  output?: unknown;
1212
1212
  completedAt?: number;
1213
+ /** Timestamp of the successful provider timeout finalizer, if any. */
1214
+ timeoutFinalizedAt?: number;
1213
1215
  }
1214
1216
  | {
1215
1217
  kind: 'fetch';
@@ -226,4 +226,5 @@ const fullenrichBulkSelfBatchStrategy: BatchOperationStrategy<
226
226
 
227
227
  export const fullenrichBatchStrategies = defineBatchStrategyMap({
228
228
  fullenrich_bulk_enrich: fullenrichBulkSelfBatchStrategy,
229
+ fullenrich_enrich: fullenrichBulkSelfBatchStrategy,
229
230
  });
@@ -26,7 +26,5 @@ export function getPlayRuntimeBatchStrategy(
26
26
  if (!operation) {
27
27
  return null;
28
28
  }
29
- const canonicalOperation =
30
- operation === 'fullenrich_enrich' ? 'fullenrich_bulk_enrich' : operation;
31
- return PLAY_RUNTIME_BATCH_OPERATION_REGISTRY[canonicalOperation] ?? null;
29
+ return PLAY_RUNTIME_BATCH_OPERATION_REGISTRY[operation] ?? null;
32
30
  }
@@ -15714,7 +15714,7 @@ export async function renewPostgresRunLedgerDelivery(
15714
15714
  throw new RunLedgerProjectionLeaseLostError();
15715
15715
  }
15716
15716
 
15717
- /** Only a currently claimed outbox batch may use the projection write path. */
15717
+ /** Only the currently claimed causal prefix may use the projection write path. */
15718
15718
  export async function assertPostgresRunLedgerDelivery(
15719
15719
  client: PostgresSchedulerQueryClient,
15720
15720
  input: {
@@ -15728,64 +15728,106 @@ export async function assertPostgresRunLedgerDelivery(
15728
15728
  const result = await client.query<{
15729
15729
  delivery_key: string;
15730
15730
  event_type: string;
15731
- lease_owner: string;
15731
+ lease_owner: string | null;
15732
+ lease_live: boolean;
15733
+ due: boolean;
15734
+ blocked: boolean;
15732
15735
  }>(
15733
15736
  `
15734
- SELECT outbox.delivery_key, outbox.event_type, outbox.lease_owner
15737
+ SELECT outbox.delivery_key,
15738
+ outbox.event_type,
15739
+ outbox.lease_owner,
15740
+ outbox.lease_expires_at > now() AS lease_live,
15741
+ coalesce(outbox.next_attempt_at, outbox.created_at) <= now() AS due,
15742
+ outbox.failed_at IS NOT NULL AS blocked
15735
15743
  FROM ${tableName('outbox', options)} AS outbox
15736
15744
  JOIN ${tableName('runs', options)} AS runs ON runs.run_id = outbox.run_id
15737
15745
  WHERE outbox.run_id = $1 AND runs.org_id = $2
15738
- AND outbox.delivered_at IS NULL AND outbox.failed_at IS NULL
15739
- AND outbox.lease_owner IS NOT NULL AND outbox.lease_expires_at > now()
15740
- AND ($3::text IS NULL OR outbox.lease_owner = $3)
15741
- ORDER BY outbox.created_at, outbox.event_id
15746
+ AND outbox.delivered_at IS NULL
15747
+ ORDER BY ${runLedgerOrderSql('outbox')}
15742
15748
  `,
15743
- [input.runId, input.orgId, input.leaseOwner ?? null],
15749
+ [input.runId, input.orgId],
15744
15750
  );
15745
- const groups = new Map<string, typeof result.rows>();
15746
- for (const row of result.rows) {
15747
- const group = groups.get(row.lease_owner) ?? [];
15748
- group.push(row);
15749
- groups.set(row.lease_owner, group);
15751
+ const rows = result.rows;
15752
+ const leaseOwner = input.leaseOwner?.trim() || rows[0]?.lease_owner;
15753
+ if (!leaseOwner || rows[0]?.lease_owner !== leaseOwner) {
15754
+ throw new RunLedgerProjectionLeaseLostError();
15750
15755
  }
15751
- for (const rows of groups.values()) {
15752
- const digest = createHash('sha256')
15753
- .update(rows.map((row) => row.delivery_key).join('\n'))
15754
- .digest('hex');
15755
- const batchKey = buildRunLedgerEventsIdempotencyKey({
15756
- runId: input.runId,
15757
- deliveryDigest: digest,
15758
- });
15759
- const terminal = [...rows]
15760
- .reverse()
15761
- .find((row) =>
15762
- ['run.completed', 'run.failed', 'run.cancelled'].includes(
15763
- row.event_type,
15764
- ),
15765
- );
15766
- // The old one-row dispatcher uses run-terminal for nonterminal facts too.
15767
- const single = rows.length === 1 ? rows[0] : terminal;
15768
- const terminalKey = single
15769
- ? buildRunLedgerTerminalIdempotencyKey({
15756
+
15757
+ // A normal projector leases only the batch it is about to send. Cancellation
15758
+ // intentionally transfers the entire pending causal prefix to one stable
15759
+ // owner before synchronously draining it, while run.events are still claimed
15760
+ // one row at a time. Validate the submitted key against a *contiguous due
15761
+ // prefix*, not every row carrying that owner; otherwise a valid cancellation
15762
+ // head is rejected whenever later facts share the cancellation lease.
15763
+ const prefix: typeof rows = [];
15764
+ for (const row of rows) {
15765
+ if (
15766
+ row.lease_owner !== leaseOwner ||
15767
+ row.lease_live !== true ||
15768
+ row.due !== true ||
15769
+ row.blocked === true
15770
+ ) {
15771
+ break;
15772
+ }
15773
+ if (prefix.length > 0 && row.event_type === 'run.events') break;
15774
+ prefix.push(row);
15775
+
15776
+ const first = prefix[0]!;
15777
+ const terminal = prefix.find((candidate) =>
15778
+ ['run.completed', 'run.failed', 'run.cancelled'].includes(
15779
+ candidate.event_type,
15780
+ ),
15781
+ );
15782
+ let expectedKey: string;
15783
+ if (terminal) {
15784
+ expectedKey = buildRunLedgerTerminalIdempotencyKey({
15785
+ runId: input.runId,
15786
+ deliveryKey: terminal.delivery_key,
15787
+ });
15788
+ } else if (
15789
+ prefix.length === 1 &&
15790
+ ['run.created', 'run.started'].includes(first.event_type)
15791
+ ) {
15792
+ expectedKey = buildRunLedgerStartIdempotencyKey({
15793
+ runId: input.runId,
15794
+ deliveryKey: first.delivery_key,
15795
+ });
15796
+ } else {
15797
+ const digest = createHash('sha256')
15798
+ .update(prefix.map((candidate) => candidate.delivery_key).join('\n'))
15799
+ .digest('hex');
15800
+ expectedKey = buildRunLedgerEventsIdempotencyKey({
15801
+ runId: input.runId,
15802
+ deliveryDigest: digest,
15803
+ });
15804
+ }
15805
+ if (input.idempotencyKey === expectedKey) return;
15806
+
15807
+ // The pre-batch projector used run-terminal:<delivery-key> for a single
15808
+ // nonterminal fact. Retain that wire-compatibility key for the current
15809
+ // singleton head, but never let it authorize a later row in the prefix.
15810
+ if (
15811
+ prefix.length === 1 &&
15812
+ !['run.created', 'run.started'].includes(first.event_type) &&
15813
+ input.idempotencyKey ===
15814
+ buildRunLedgerTerminalIdempotencyKey({
15770
15815
  runId: input.runId,
15771
- deliveryKey: single.delivery_key,
15816
+ deliveryKey: first.delivery_key,
15772
15817
  })
15773
- : null;
15774
- const startKey =
15775
- rows.length === 1 &&
15776
- single &&
15777
- ['run.created', 'run.started'].includes(single.event_type)
15778
- ? buildRunLedgerStartIdempotencyKey({
15779
- runId: input.runId,
15780
- deliveryKey: single.delivery_key,
15781
- })
15782
- : null;
15783
- if (
15784
- input.idempotencyKey === batchKey ||
15785
- input.idempotencyKey === terminalKey ||
15786
- input.idempotencyKey === startKey
15787
- )
15818
+ ) {
15788
15819
  return;
15820
+ }
15821
+
15822
+ if (
15823
+ prefix.length === 1 &&
15824
+ ['run.created', 'run.started', 'run.events'].includes(first.event_type)
15825
+ ) {
15826
+ break;
15827
+ }
15828
+ if (terminal || prefix.length >= 8) {
15829
+ break;
15830
+ }
15789
15831
  }
15790
15832
  throw new RunLedgerProjectionLeaseLostError();
15791
15833
  }
@@ -16965,6 +17007,7 @@ export async function dispatchRunLedgerOutboxToLedgerThroughEventWithClientScope
16965
17007
  withClient,
16966
17008
  {
16967
17009
  ...input,
17010
+ waitForRunLock: true,
16968
17011
  limit:
16969
17012
  remaining === null ? batchSize : Math.min(batchSize, remaining),
16970
17013
  },
@@ -17039,7 +17082,7 @@ export async function dispatchRunLedgerOutboxToLedgerBatchOnceWithClientScope(
17039
17082
  withClient: <T>(
17040
17083
  operation: (client: PostgresSchedulerQueryClient) => Promise<T>,
17041
17084
  ) => Promise<T>,
17042
- input: RunLedgerOutboxDispatchInput,
17085
+ input: RunLedgerOutboxDispatchInput & { waitForRunLock?: boolean },
17043
17086
  options?: Pick<PostgresSchedulerOptions, 'schema'>,
17044
17087
  ): Promise<PostgresSchedulerOutboxProjectionResult> {
17045
17088
  const leaseOwner =
@@ -17052,6 +17095,26 @@ export async function dispatchRunLedgerOutboxToLedgerBatchOnceWithClientScope(
17052
17095
  await client.query('BEGIN');
17053
17096
  try {
17054
17097
  const targetRunId = input.runId?.trim() || null;
17098
+ if (input.waitForRunLock && targetRunId) {
17099
+ // A synchronous drain must not mistake a busy run for an empty
17100
+ // outbox. In particular, cancellation owns its pending prefix lease,
17101
+ // so returning early also prevents background recovery from helping.
17102
+ // Wait only for this run, under the existing cancellation SQL budget,
17103
+ // then select the prefix using a fresh READ COMMITTED snapshot. The
17104
+ // transaction still ends before HTTP; background claims keep SKIP LOCKED.
17105
+ await client.query(
17106
+ `SELECT set_config('lock_timeout', $1, true),
17107
+ set_config('statement_timeout', $2, true)`,
17108
+ [
17109
+ `${RUNTIME_SCHEDULER_CANCEL_LOCK_TIMEOUT_MS}ms`,
17110
+ `${RUNTIME_SCHEDULER_CANCEL_STATEMENT_TIMEOUT_MS}ms`,
17111
+ ],
17112
+ );
17113
+ await client.query(
17114
+ `SELECT run_id FROM ${tableName('runs', options)} WHERE run_id = $1 FOR UPDATE`,
17115
+ [targetRunId],
17116
+ );
17117
+ }
17055
17118
  // A worker fast path always names one run. Keep that lookup on the
17056
17119
  // run-scoped outbox prefix: combining it with global recovery through a
17057
17120
  // nullable predicate made PostgreSQL materialize every pending run head
package/dist/cli/index.js CHANGED
@@ -3051,7 +3051,7 @@ var SDK_RELEASE = {
3051
3051
  // getters keep their established compatibility behavior.
3052
3052
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
3053
3053
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
3054
- version: "0.3.198",
3054
+ version: "0.3.199",
3055
3055
  updateSummary: "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.",
3056
3056
  packageCapabilities: {
3057
3057
  updatePreferences: 1
@@ -3046,7 +3046,7 @@ var SDK_RELEASE = {
3046
3046
  // getters keep their established compatibility behavior.
3047
3047
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
3048
3048
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
3049
- version: "0.3.198",
3049
+ version: "0.3.199",
3050
3050
  updateSummary: "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.",
3051
3051
  packageCapabilities: {
3052
3052
  updatePreferences: 1
package/dist/index.js CHANGED
@@ -864,7 +864,7 @@ var SDK_RELEASE = {
864
864
  // getters keep their established compatibility behavior.
865
865
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
866
866
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
867
- version: "0.3.198",
867
+ version: "0.3.199",
868
868
  updateSummary: "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.",
869
869
  packageCapabilities: {
870
870
  updatePreferences: 1
package/dist/index.mjs CHANGED
@@ -768,7 +768,7 @@ var SDK_RELEASE = {
768
768
  // getters keep their established compatibility behavior.
769
769
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
770
770
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
771
- version: "0.3.198",
771
+ version: "0.3.199",
772
772
  updateSummary: "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.",
773
773
  packageCapabilities: {
774
774
  updatePreferences: 1
@@ -149,7 +149,7 @@ type SdkRelease = {
149
149
  supportPolicy: SdkSupportPolicy;
150
150
  };
151
151
  declare const SDK_RELEASE: {
152
- readonly version: "0.3.198";
152
+ readonly version: "0.3.199";
153
153
  readonly updateSummary: "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.";
154
154
  readonly packageCapabilities: {
155
155
  readonly updatePreferences: 1;
package/dist/release.d.ts CHANGED
@@ -149,7 +149,7 @@ type SdkRelease = {
149
149
  supportPolicy: SdkSupportPolicy;
150
150
  };
151
151
  declare const SDK_RELEASE: {
152
- readonly version: "0.3.198";
152
+ readonly version: "0.3.199";
153
153
  readonly updateSummary: "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.";
154
154
  readonly packageCapabilities: {
155
155
  readonly updatePreferences: 1;
package/dist/release.js CHANGED
@@ -74,7 +74,7 @@ var SDK_RELEASE = {
74
74
  // getters keep their established compatibility behavior.
75
75
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
76
76
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
77
- version: "0.3.198",
77
+ version: "0.3.199",
78
78
  updateSummary: "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.",
79
79
  packageCapabilities: {
80
80
  updatePreferences: 1
package/dist/release.mjs CHANGED
@@ -48,7 +48,7 @@ var SDK_RELEASE = {
48
48
  // getters keep their established compatibility behavior.
49
49
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
50
50
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
51
- version: "0.3.198",
51
+ version: "0.3.199",
52
52
  updateSummary: "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.",
53
53
  packageCapabilities: {
54
54
  updatePreferences: 1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepline",
3
- "version": "0.3.198",
3
+ "version": "0.3.199",
4
4
  "description": "GTM data CLI and TypeScript SDK for coding agents",
5
5
  "license": "MIT",
6
6
  "homepage": "https://code.deepline.com",