deepline 0.3.197 → 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.
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +54 -33
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/docflow-node-io.ts +33 -8
- package/dist/bundling-sources/shared_libs/play-runtime/fullenrich-batching.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-batching-registry.ts +1 -3
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +150 -70
- package/dist/cli/index.js +24 -4
- package/dist/cli/index.mjs +24 -4
- package/dist/index.js +24 -4
- package/dist/index.mjs +24 -4
- package/dist/release.d.mts +1 -1
- package/dist/release.d.ts +1 -1
- package/dist/release.js +1 -1
- package/dist/release.mjs +1 -1
- package/package.json +1 -1
|
@@ -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.
|
|
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
|
-
|
|
13739
|
-
|
|
13740
|
-
|
|
13741
|
-
|
|
13742
|
-
|
|
13743
|
-
|
|
13744
|
-
|
|
13745
|
-
|
|
13746
|
-
|
|
13747
|
-
|
|
13748
|
-
|
|
13749
|
-
|
|
13750
|
-
:
|
|
13751
|
-
|
|
13752
|
-
|
|
13753
|
-
|
|
13754
|
-
|
|
13755
|
-
|
|
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
|
-
|
|
13761
|
-
|
|
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
|
-
|
|
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';
|
|
@@ -78,19 +78,44 @@ type PreviewOptions = {
|
|
|
78
78
|
|
|
79
79
|
const utf8Encoder = new TextEncoder();
|
|
80
80
|
const PLAY_DATASET_BRAND = Symbol.for('deepline.play.dataset');
|
|
81
|
+
const MAX_CONVEX_PREVIEW_KEY_LENGTH = 1_024;
|
|
81
82
|
|
|
82
83
|
/**
|
|
83
84
|
* Preview-map keys must be persistable everywhere the ledger lands, and Convex
|
|
84
|
-
* reserves
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
85
|
+
* reserves leading '$', non-ASCII/control characters, and keys over 1,024
|
|
86
|
+
* bytes. A rejected preview key poisons the whole ordered terminal batch, even
|
|
87
|
+
* when the terminal result is replaced by a scheduler reference. Keep already
|
|
88
|
+
* valid keys unchanged and encode only invalid bytes, so repeated normalization
|
|
89
|
+
* of durable observations converges on the same key. `$output` remains `output`.
|
|
89
90
|
*/
|
|
90
91
|
export function ledgerSafeDocflowPreviewKey(key: string): string {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
const source = key.startsWith('$')
|
|
93
|
+
? key.replace(/^\$+/, '') || 'output'
|
|
94
|
+
: key || 'output';
|
|
95
|
+
let encoded = '';
|
|
96
|
+
for (const character of source) {
|
|
97
|
+
const code = character.charCodeAt(0);
|
|
98
|
+
if (code >= 32 && code < 127) {
|
|
99
|
+
encoded += character;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
for (const byte of utf8Encoder.encode(character)) {
|
|
103
|
+
encoded += `%${byte.toString(16).toUpperCase().padStart(2, '0')}`;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (encoded.length <= MAX_CONVEX_PREVIEW_KEY_LENGTH) return encoded;
|
|
107
|
+
|
|
108
|
+
// Retain a readable prefix while making long, otherwise identical paths
|
|
109
|
+
// distinguishable. The suffix is stable across runtime and app deployments.
|
|
110
|
+
let hashA = 0x811c9dc5;
|
|
111
|
+
let hashB = 0x9e3779b9;
|
|
112
|
+
for (let index = 0; index < encoded.length; index += 1) {
|
|
113
|
+
const code = encoded.charCodeAt(index);
|
|
114
|
+
hashA = Math.imul(hashA ^ code, 0x01000193);
|
|
115
|
+
hashB = Math.imul(hashB ^ code, 0x01000193);
|
|
116
|
+
}
|
|
117
|
+
const suffix = `~${(hashA >>> 0).toString(16).padStart(8, '0')}${(hashB >>> 0).toString(16).padStart(8, '0')}`;
|
|
118
|
+
return `${encoded.slice(0, MAX_CONVEX_PREVIEW_KEY_LENGTH - suffix.length)}${suffix}`;
|
|
94
119
|
}
|
|
95
120
|
|
|
96
121
|
function utf8Bytes(value: string): number {
|
|
@@ -26,7 +26,5 @@ export function getPlayRuntimeBatchStrategy(
|
|
|
26
26
|
if (!operation) {
|
|
27
27
|
return null;
|
|
28
28
|
}
|
|
29
|
-
|
|
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
|
}
|
|
@@ -12213,10 +12213,9 @@ export async function cancelPostgresSchedulerRun(
|
|
|
12213
12213
|
engineTaskTable: string | null;
|
|
12214
12214
|
};
|
|
12215
12215
|
/**
|
|
12216
|
-
* Optional control-plane ownership for the pending causal
|
|
12217
|
-
*
|
|
12218
|
-
*
|
|
12219
|
-
* was waiting on its retry schedule.
|
|
12216
|
+
* Optional control-plane ownership for the oldest pending causal fact.
|
|
12217
|
+
* Cancellation makes the remaining prefix due without sharing this lease
|
|
12218
|
+
* across distinct append batches, then the caller drains it after commit.
|
|
12220
12219
|
*/
|
|
12221
12220
|
outboxLease?: { leaseOwner: string; leaseSeconds: number };
|
|
12222
12221
|
},
|
|
@@ -12349,11 +12348,15 @@ export async function cancelPostgresSchedulerRun(
|
|
|
12349
12348
|
AND NULLIF(resource->>'sandboxId', '') IS NOT NULL
|
|
12350
12349
|
ON CONFLICT (provider, sandbox_id, routing_domain_key) DO NOTHING
|
|
12351
12350
|
),
|
|
12352
|
-
--
|
|
12353
|
-
--
|
|
12354
|
-
--
|
|
12351
|
+
-- Revoke any in-flight background lease and make the complete prefix
|
|
12352
|
+
-- due, but reserve only its head. The app-side delivery validator treats
|
|
12353
|
+
-- every row under one lease owner as one exact append batch. Leasing the
|
|
12354
|
+
-- whole prefix here makes the first run.events append fail that check.
|
|
12355
|
+
-- The head lease fences background claims until the direct projector
|
|
12356
|
+
-- acknowledges it, then the next causal head can be claimed normally.
|
|
12355
12357
|
cancellation_outbox_pending AS MATERIALIZED (
|
|
12356
|
-
SELECT pending.event_id
|
|
12358
|
+
SELECT pending.event_id,
|
|
12359
|
+
row_number() OVER (ORDER BY ${runLedgerOrderSql('pending')}) AS position
|
|
12357
12360
|
FROM ${tableName('outbox', options)} AS pending
|
|
12358
12361
|
JOIN cancelled_run ON cancelled_run.run_id = pending.run_id
|
|
12359
12362
|
WHERE pending.delivered_at IS NULL
|
|
@@ -12361,11 +12364,15 @@ export async function cancelPostgresSchedulerRun(
|
|
|
12361
12364
|
),
|
|
12362
12365
|
cancellation_outbox_lease AS (
|
|
12363
12366
|
UPDATE ${tableName('outbox', options)} AS pending
|
|
12364
|
-
SET lease_owner = $6::text,
|
|
12365
|
-
lease_expires_at =
|
|
12367
|
+
SET lease_owner = CASE WHEN prefix.position = 1 THEN $6::text ELSE NULL END,
|
|
12368
|
+
lease_expires_at = CASE
|
|
12369
|
+
WHEN prefix.position = 1 THEN now() + make_interval(secs => $7::integer)
|
|
12370
|
+
ELSE NULL
|
|
12371
|
+
END,
|
|
12366
12372
|
next_attempt_at = NULL
|
|
12373
|
+
FROM cancellation_outbox_pending AS prefix
|
|
12367
12374
|
WHERE $6::text IS NOT NULL
|
|
12368
|
-
AND pending.event_id
|
|
12375
|
+
AND pending.event_id = prefix.event_id
|
|
12369
12376
|
RETURNING pending.event_id
|
|
12370
12377
|
),
|
|
12371
12378
|
terminal_outbox AS (
|
|
@@ -12420,16 +12427,26 @@ export async function cancelPostgresSchedulerRun(
|
|
|
12420
12427
|
// commits so the request can still project the winning terminal state
|
|
12421
12428
|
// without waiting for a background lease.
|
|
12422
12429
|
await client.query(
|
|
12423
|
-
`
|
|
12424
|
-
|
|
12425
|
-
|
|
12426
|
-
|
|
12427
|
-
|
|
12428
|
-
|
|
12429
|
-
|
|
12430
|
-
|
|
12431
|
-
|
|
12432
|
-
|
|
12430
|
+
`WITH terminal_pending AS MATERIALIZED (
|
|
12431
|
+
SELECT pending.event_id,
|
|
12432
|
+
row_number() OVER (ORDER BY ${runLedgerOrderSql('pending')}) AS position
|
|
12433
|
+
FROM ${tableName('outbox', options)} AS pending
|
|
12434
|
+
JOIN ${tableName('runs', options)} AS terminal_run
|
|
12435
|
+
ON terminal_run.run_id = pending.run_id
|
|
12436
|
+
WHERE pending.run_id = $1
|
|
12437
|
+
AND terminal_run.status IN ('completed', 'failed', 'cancelled')
|
|
12438
|
+
AND pending.delivered_at IS NULL
|
|
12439
|
+
AND pending.failed_at IS NULL
|
|
12440
|
+
)
|
|
12441
|
+
UPDATE ${tableName('outbox', options)} AS pending
|
|
12442
|
+
SET lease_owner = CASE WHEN prefix.position = 1 THEN $2 ELSE NULL END,
|
|
12443
|
+
lease_expires_at = CASE
|
|
12444
|
+
WHEN prefix.position = 1 THEN now() + make_interval(secs => $3::integer)
|
|
12445
|
+
ELSE NULL
|
|
12446
|
+
END,
|
|
12447
|
+
next_attempt_at = NULL
|
|
12448
|
+
FROM terminal_pending AS prefix
|
|
12449
|
+
WHERE pending.event_id = prefix.event_id`,
|
|
12433
12450
|
[input.runId, outboxLeaseOwner, outboxLeaseSeconds],
|
|
12434
12451
|
);
|
|
12435
12452
|
}
|
|
@@ -15697,7 +15714,7 @@ export async function renewPostgresRunLedgerDelivery(
|
|
|
15697
15714
|
throw new RunLedgerProjectionLeaseLostError();
|
|
15698
15715
|
}
|
|
15699
15716
|
|
|
15700
|
-
/** Only
|
|
15717
|
+
/** Only the currently claimed causal prefix may use the projection write path. */
|
|
15701
15718
|
export async function assertPostgresRunLedgerDelivery(
|
|
15702
15719
|
client: PostgresSchedulerQueryClient,
|
|
15703
15720
|
input: {
|
|
@@ -15711,64 +15728,106 @@ export async function assertPostgresRunLedgerDelivery(
|
|
|
15711
15728
|
const result = await client.query<{
|
|
15712
15729
|
delivery_key: string;
|
|
15713
15730
|
event_type: string;
|
|
15714
|
-
lease_owner: string;
|
|
15731
|
+
lease_owner: string | null;
|
|
15732
|
+
lease_live: boolean;
|
|
15733
|
+
due: boolean;
|
|
15734
|
+
blocked: boolean;
|
|
15715
15735
|
}>(
|
|
15716
15736
|
`
|
|
15717
|
-
SELECT outbox.delivery_key,
|
|
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
|
|
15718
15743
|
FROM ${tableName('outbox', options)} AS outbox
|
|
15719
15744
|
JOIN ${tableName('runs', options)} AS runs ON runs.run_id = outbox.run_id
|
|
15720
15745
|
WHERE outbox.run_id = $1 AND runs.org_id = $2
|
|
15721
|
-
AND outbox.delivered_at IS NULL
|
|
15722
|
-
|
|
15723
|
-
AND ($3::text IS NULL OR outbox.lease_owner = $3)
|
|
15724
|
-
ORDER BY outbox.created_at, outbox.event_id
|
|
15746
|
+
AND outbox.delivered_at IS NULL
|
|
15747
|
+
ORDER BY ${runLedgerOrderSql('outbox')}
|
|
15725
15748
|
`,
|
|
15726
|
-
[input.runId, input.orgId
|
|
15749
|
+
[input.runId, input.orgId],
|
|
15727
15750
|
);
|
|
15728
|
-
const
|
|
15729
|
-
|
|
15730
|
-
|
|
15731
|
-
|
|
15732
|
-
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();
|
|
15733
15755
|
}
|
|
15734
|
-
|
|
15735
|
-
|
|
15736
|
-
|
|
15737
|
-
|
|
15738
|
-
|
|
15739
|
-
|
|
15740
|
-
|
|
15741
|
-
|
|
15742
|
-
|
|
15743
|
-
|
|
15744
|
-
.
|
|
15745
|
-
|
|
15746
|
-
|
|
15747
|
-
|
|
15748
|
-
|
|
15749
|
-
|
|
15750
|
-
|
|
15751
|
-
|
|
15752
|
-
|
|
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({
|
|
15753
15815
|
runId: input.runId,
|
|
15754
|
-
deliveryKey:
|
|
15816
|
+
deliveryKey: first.delivery_key,
|
|
15755
15817
|
})
|
|
15756
|
-
|
|
15757
|
-
const startKey =
|
|
15758
|
-
rows.length === 1 &&
|
|
15759
|
-
single &&
|
|
15760
|
-
['run.created', 'run.started'].includes(single.event_type)
|
|
15761
|
-
? buildRunLedgerStartIdempotencyKey({
|
|
15762
|
-
runId: input.runId,
|
|
15763
|
-
deliveryKey: single.delivery_key,
|
|
15764
|
-
})
|
|
15765
|
-
: null;
|
|
15766
|
-
if (
|
|
15767
|
-
input.idempotencyKey === batchKey ||
|
|
15768
|
-
input.idempotencyKey === terminalKey ||
|
|
15769
|
-
input.idempotencyKey === startKey
|
|
15770
|
-
)
|
|
15818
|
+
) {
|
|
15771
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
|
+
}
|
|
15772
15831
|
}
|
|
15773
15832
|
throw new RunLedgerProjectionLeaseLostError();
|
|
15774
15833
|
}
|
|
@@ -16948,6 +17007,7 @@ export async function dispatchRunLedgerOutboxToLedgerThroughEventWithClientScope
|
|
|
16948
17007
|
withClient,
|
|
16949
17008
|
{
|
|
16950
17009
|
...input,
|
|
17010
|
+
waitForRunLock: true,
|
|
16951
17011
|
limit:
|
|
16952
17012
|
remaining === null ? batchSize : Math.min(batchSize, remaining),
|
|
16953
17013
|
},
|
|
@@ -17022,7 +17082,7 @@ export async function dispatchRunLedgerOutboxToLedgerBatchOnceWithClientScope(
|
|
|
17022
17082
|
withClient: <T>(
|
|
17023
17083
|
operation: (client: PostgresSchedulerQueryClient) => Promise<T>,
|
|
17024
17084
|
) => Promise<T>,
|
|
17025
|
-
input: RunLedgerOutboxDispatchInput,
|
|
17085
|
+
input: RunLedgerOutboxDispatchInput & { waitForRunLock?: boolean },
|
|
17026
17086
|
options?: Pick<PostgresSchedulerOptions, 'schema'>,
|
|
17027
17087
|
): Promise<PostgresSchedulerOutboxProjectionResult> {
|
|
17028
17088
|
const leaseOwner =
|
|
@@ -17035,6 +17095,26 @@ export async function dispatchRunLedgerOutboxToLedgerBatchOnceWithClientScope(
|
|
|
17035
17095
|
await client.query('BEGIN');
|
|
17036
17096
|
try {
|
|
17037
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
|
+
}
|
|
17038
17118
|
// A worker fast path always names one run. Keep that lookup on the
|
|
17039
17119
|
// run-scoped outbox prefix: combining it with global recovery through a
|
|
17040
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.
|
|
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
|
|
@@ -4699,10 +4699,30 @@ var DOCFLOW_NODE_IO_LIMITS = {
|
|
|
4699
4699
|
};
|
|
4700
4700
|
var utf8Encoder = new TextEncoder();
|
|
4701
4701
|
var PLAY_DATASET_BRAND = Symbol.for("deepline.play.dataset");
|
|
4702
|
+
var MAX_CONVEX_PREVIEW_KEY_LENGTH = 1024;
|
|
4702
4703
|
function ledgerSafeDocflowPreviewKey(key) {
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4704
|
+
const source = key.startsWith("$") ? key.replace(/^\$+/, "") || "output" : key || "output";
|
|
4705
|
+
let encoded = "";
|
|
4706
|
+
for (const character of source) {
|
|
4707
|
+
const code = character.charCodeAt(0);
|
|
4708
|
+
if (code >= 32 && code < 127) {
|
|
4709
|
+
encoded += character;
|
|
4710
|
+
continue;
|
|
4711
|
+
}
|
|
4712
|
+
for (const byte of utf8Encoder.encode(character)) {
|
|
4713
|
+
encoded += `%${byte.toString(16).toUpperCase().padStart(2, "0")}`;
|
|
4714
|
+
}
|
|
4715
|
+
}
|
|
4716
|
+
if (encoded.length <= MAX_CONVEX_PREVIEW_KEY_LENGTH) return encoded;
|
|
4717
|
+
let hashA = 2166136261;
|
|
4718
|
+
let hashB = 2654435769;
|
|
4719
|
+
for (let index = 0; index < encoded.length; index += 1) {
|
|
4720
|
+
const code = encoded.charCodeAt(index);
|
|
4721
|
+
hashA = Math.imul(hashA ^ code, 16777619);
|
|
4722
|
+
hashB = Math.imul(hashB ^ code, 16777619);
|
|
4723
|
+
}
|
|
4724
|
+
const suffix = `~${(hashA >>> 0).toString(16).padStart(8, "0")}${(hashB >>> 0).toString(16).padStart(8, "0")}`;
|
|
4725
|
+
return `${encoded.slice(0, MAX_CONVEX_PREVIEW_KEY_LENGTH - suffix.length)}${suffix}`;
|
|
4706
4726
|
}
|
|
4707
4727
|
function utf8Bytes(value) {
|
|
4708
4728
|
return utf8Encoder.encode(value).byteLength;
|
package/dist/cli/index.mjs
CHANGED
|
@@ -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.
|
|
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
|
|
@@ -4694,10 +4694,30 @@ var DOCFLOW_NODE_IO_LIMITS = {
|
|
|
4694
4694
|
};
|
|
4695
4695
|
var utf8Encoder = new TextEncoder();
|
|
4696
4696
|
var PLAY_DATASET_BRAND = Symbol.for("deepline.play.dataset");
|
|
4697
|
+
var MAX_CONVEX_PREVIEW_KEY_LENGTH = 1024;
|
|
4697
4698
|
function ledgerSafeDocflowPreviewKey(key) {
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4699
|
+
const source = key.startsWith("$") ? key.replace(/^\$+/, "") || "output" : key || "output";
|
|
4700
|
+
let encoded = "";
|
|
4701
|
+
for (const character of source) {
|
|
4702
|
+
const code = character.charCodeAt(0);
|
|
4703
|
+
if (code >= 32 && code < 127) {
|
|
4704
|
+
encoded += character;
|
|
4705
|
+
continue;
|
|
4706
|
+
}
|
|
4707
|
+
for (const byte of utf8Encoder.encode(character)) {
|
|
4708
|
+
encoded += `%${byte.toString(16).toUpperCase().padStart(2, "0")}`;
|
|
4709
|
+
}
|
|
4710
|
+
}
|
|
4711
|
+
if (encoded.length <= MAX_CONVEX_PREVIEW_KEY_LENGTH) return encoded;
|
|
4712
|
+
let hashA = 2166136261;
|
|
4713
|
+
let hashB = 2654435769;
|
|
4714
|
+
for (let index = 0; index < encoded.length; index += 1) {
|
|
4715
|
+
const code = encoded.charCodeAt(index);
|
|
4716
|
+
hashA = Math.imul(hashA ^ code, 16777619);
|
|
4717
|
+
hashB = Math.imul(hashB ^ code, 16777619);
|
|
4718
|
+
}
|
|
4719
|
+
const suffix = `~${(hashA >>> 0).toString(16).padStart(8, "0")}${(hashB >>> 0).toString(16).padStart(8, "0")}`;
|
|
4720
|
+
return `${encoded.slice(0, MAX_CONVEX_PREVIEW_KEY_LENGTH - suffix.length)}${suffix}`;
|
|
4701
4721
|
}
|
|
4702
4722
|
function utf8Bytes(value) {
|
|
4703
4723
|
return utf8Encoder.encode(value).byteLength;
|
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.
|
|
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
|
|
@@ -2460,10 +2460,30 @@ var DOCFLOW_NODE_IO_LIMITS = {
|
|
|
2460
2460
|
};
|
|
2461
2461
|
var utf8Encoder = new TextEncoder();
|
|
2462
2462
|
var PLAY_DATASET_BRAND = Symbol.for("deepline.play.dataset");
|
|
2463
|
+
var MAX_CONVEX_PREVIEW_KEY_LENGTH = 1024;
|
|
2463
2464
|
function ledgerSafeDocflowPreviewKey(key) {
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2465
|
+
const source = key.startsWith("$") ? key.replace(/^\$+/, "") || "output" : key || "output";
|
|
2466
|
+
let encoded = "";
|
|
2467
|
+
for (const character of source) {
|
|
2468
|
+
const code = character.charCodeAt(0);
|
|
2469
|
+
if (code >= 32 && code < 127) {
|
|
2470
|
+
encoded += character;
|
|
2471
|
+
continue;
|
|
2472
|
+
}
|
|
2473
|
+
for (const byte of utf8Encoder.encode(character)) {
|
|
2474
|
+
encoded += `%${byte.toString(16).toUpperCase().padStart(2, "0")}`;
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
if (encoded.length <= MAX_CONVEX_PREVIEW_KEY_LENGTH) return encoded;
|
|
2478
|
+
let hashA = 2166136261;
|
|
2479
|
+
let hashB = 2654435769;
|
|
2480
|
+
for (let index = 0; index < encoded.length; index += 1) {
|
|
2481
|
+
const code = encoded.charCodeAt(index);
|
|
2482
|
+
hashA = Math.imul(hashA ^ code, 16777619);
|
|
2483
|
+
hashB = Math.imul(hashB ^ code, 16777619);
|
|
2484
|
+
}
|
|
2485
|
+
const suffix = `~${(hashA >>> 0).toString(16).padStart(8, "0")}${(hashB >>> 0).toString(16).padStart(8, "0")}`;
|
|
2486
|
+
return `${encoded.slice(0, MAX_CONVEX_PREVIEW_KEY_LENGTH - suffix.length)}${suffix}`;
|
|
2467
2487
|
}
|
|
2468
2488
|
function utf8Bytes(value) {
|
|
2469
2489
|
return utf8Encoder.encode(value).byteLength;
|
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.
|
|
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
|
|
@@ -2364,10 +2364,30 @@ var DOCFLOW_NODE_IO_LIMITS = {
|
|
|
2364
2364
|
};
|
|
2365
2365
|
var utf8Encoder = new TextEncoder();
|
|
2366
2366
|
var PLAY_DATASET_BRAND = Symbol.for("deepline.play.dataset");
|
|
2367
|
+
var MAX_CONVEX_PREVIEW_KEY_LENGTH = 1024;
|
|
2367
2368
|
function ledgerSafeDocflowPreviewKey(key) {
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2369
|
+
const source = key.startsWith("$") ? key.replace(/^\$+/, "") || "output" : key || "output";
|
|
2370
|
+
let encoded = "";
|
|
2371
|
+
for (const character of source) {
|
|
2372
|
+
const code = character.charCodeAt(0);
|
|
2373
|
+
if (code >= 32 && code < 127) {
|
|
2374
|
+
encoded += character;
|
|
2375
|
+
continue;
|
|
2376
|
+
}
|
|
2377
|
+
for (const byte of utf8Encoder.encode(character)) {
|
|
2378
|
+
encoded += `%${byte.toString(16).toUpperCase().padStart(2, "0")}`;
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
if (encoded.length <= MAX_CONVEX_PREVIEW_KEY_LENGTH) return encoded;
|
|
2382
|
+
let hashA = 2166136261;
|
|
2383
|
+
let hashB = 2654435769;
|
|
2384
|
+
for (let index = 0; index < encoded.length; index += 1) {
|
|
2385
|
+
const code = encoded.charCodeAt(index);
|
|
2386
|
+
hashA = Math.imul(hashA ^ code, 16777619);
|
|
2387
|
+
hashB = Math.imul(hashB ^ code, 16777619);
|
|
2388
|
+
}
|
|
2389
|
+
const suffix = `~${(hashA >>> 0).toString(16).padStart(8, "0")}${(hashB >>> 0).toString(16).padStart(8, "0")}`;
|
|
2390
|
+
return `${encoded.slice(0, MAX_CONVEX_PREVIEW_KEY_LENGTH - suffix.length)}${suffix}`;
|
|
2371
2391
|
}
|
|
2372
2392
|
function utf8Bytes(value) {
|
|
2373
2393
|
return utf8Encoder.encode(value).byteLength;
|
package/dist/release.d.mts
CHANGED
|
@@ -149,7 +149,7 @@ type SdkRelease = {
|
|
|
149
149
|
supportPolicy: SdkSupportPolicy;
|
|
150
150
|
};
|
|
151
151
|
declare const SDK_RELEASE: {
|
|
152
|
-
readonly version: "0.3.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|