deepline 0.3.392 → 0.3.394
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/client.ts +156 -24
- package/dist/bundling-sources/sdk/src/compat.ts +9 -0
- package/dist/bundling-sources/sdk/src/http.ts +5 -1
- package/dist/bundling-sources/sdk/src/index.ts +5 -0
- package/dist/bundling-sources/sdk/src/plays/run-request.ts +182 -6
- package/dist/bundling-sources/sdk/src/release.ts +4 -1
- package/dist/bundling-sources/sdk/src/types.ts +13 -0
- package/dist/bundling-sources/shared_libs/integration-contracts/request-warnings.ts +64 -0
- package/dist/bundling-sources/shared_libs/play-data-plane/runtime-sheet-storage-contract.ts +315 -0
- package/dist/bundling-sources/shared_libs/play-runtime/async-operation.ts +41 -8
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +895 -54
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +41 -4
- package/dist/bundling-sources/shared_libs/play-runtime/durable-call-policy.ts +28 -0
- package/dist/bundling-sources/shared_libs/play-runtime/durable-invocation-fence.ts +36 -0
- package/dist/bundling-sources/shared_libs/play-runtime/freshness-receipt-execution.ts +251 -0
- package/dist/bundling-sources/shared_libs/play-runtime/map-execution-frame.ts +11 -3
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/gateway-adapter.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-semantics.ts +30 -1
- package/dist/bundling-sources/shared_libs/play-runtime/run-identity.ts +26 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +168 -10
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +252 -10
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +92 -58
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-pool.ts +7 -1
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +30 -7
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +1161 -316
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-event-search.ts +28 -15
- package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +24 -3
- package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +32 -0
- package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +103 -23
- package/dist/bundling-sources/shared_libs/plays/core.ts +1 -1
- package/dist/bundling-sources/shared_libs/plays/dataset.ts +172 -79
- package/dist/bundling-sources/shared_libs/plays/static-pipeline.ts +173 -1
- package/dist/bundling-sources/shared_libs/plays/tool-result-types.ts +18 -0
- package/dist/cli/auth-IHKISLJQ.js +39 -0
- package/dist/cli/{auth-6JSA2ZCW.mjs → auth-OSXDAVCF.mjs} +4 -4
- package/dist/cli/{billing-Q5BLQHIU.js → billing-EKLYOXUY.js} +101 -74
- package/dist/cli/{billing-MRRO643H.mjs → billing-ZNNKX3UR.mjs} +32 -5
- package/dist/cli/{chunk-4SFPVW7A.js → chunk-2MDDA7A4.js} +26 -26
- package/dist/cli/{chunk-JOFPQGXW.js → chunk-354JCXA2.js} +50 -50
- package/dist/cli/{chunk-7E66A24V.mjs → chunk-3HU47EGZ.mjs} +71 -9
- package/dist/cli/{chunk-DSWSEHRX.mjs → chunk-3HWALOL2.mjs} +1 -1
- package/dist/cli/{chunk-T7OKJ2T6.js → chunk-4P27AXEA.js} +73 -63
- package/dist/cli/{chunk-24VEN5PQ.mjs → chunk-5BB2RGAX.mjs} +190 -18
- package/dist/cli/{chunk-JSLPHFP7.js → chunk-6YAUMG27.js} +3 -2
- package/dist/cli/{chunk-MU6M3URX.mjs → chunk-BP33SIZF.mjs} +1 -1
- package/dist/cli/{chunk-WMS5U6YB.js → chunk-CS6RAIVY.js} +20 -19
- package/dist/cli/{chunk-CVLM3WMJ.js → chunk-E23BQ5SC.js} +242 -183
- package/dist/cli/{chunk-2GAHHSLB.mjs → chunk-FJGW5GXU.mjs} +3 -2
- package/dist/cli/{chunk-YN6YA7ZZ.mjs → chunk-JHIJIHFB.mjs} +79 -20
- package/dist/cli/{chunk-Q6EOHNRH.mjs → chunk-JI2B4RZP.mjs} +119 -6
- package/dist/cli/{chunk-5O4B6AUN.js → chunk-KB4SDX5S.js} +71 -9
- package/dist/cli/{chunk-RV6ALZIX.js → chunk-KGNTKMRG.js} +32 -7
- package/dist/cli/{chunk-6DBOU7UH.js → chunk-KI635JN4.js} +176 -87
- package/dist/cli/{chunk-2FQG2HU4.js → chunk-KIUMEOM5.js} +8 -8
- package/dist/cli/{chunk-Y6VOGHF2.mjs → chunk-KZZC33PY.mjs} +0 -181
- package/dist/cli/{chunk-2Q3GEFL4.mjs → chunk-M57J2SK4.mjs} +2 -2
- package/dist/cli/{chunk-R3O2CZ4H.mjs → chunk-MFB2VKBO.mjs} +2 -2
- package/dist/cli/{chunk-QGKPTBRR.js → chunk-MH73HMPV.js} +3 -3
- package/dist/cli/{chunk-BB3FNYFY.js → chunk-MLPU5SMZ.js} +2 -2
- package/dist/cli/{chunk-2G7DXFHI.mjs → chunk-NR3DFKNF.mjs} +27 -2
- package/dist/cli/{chunk-EY7RFC6A.js → chunk-QIQBYKLB.js} +15 -15
- package/dist/cli/{chunk-RIV2GS5E.js → chunk-RBJYWRCF.js} +638 -466
- package/dist/cli/{chunk-3VN4FTE3.js → chunk-RDXFGPJU.js} +1 -182
- package/dist/cli/{chunk-YAZCDBXX.mjs → chunk-TJI2EN6K.mjs} +3 -2
- package/dist/cli/chunk-UBKA33KY.mjs +310 -0
- package/dist/cli/{chunk-PAIMOGCM.js → chunk-UHNDVD3P.js} +125 -12
- package/dist/cli/{chunk-4TCT77UF.js → chunk-URWF7NGQ.js} +6 -1
- package/dist/cli/{chunk-2QLML6G4.mjs → chunk-UXFSWG3Q.mjs} +4 -4
- package/dist/cli/{chunk-UCKJQG25.js → chunk-VK6FYZLP.js} +8 -8
- package/dist/cli/{chunk-ZTACINO5.mjs → chunk-X63FDZJZ.mjs} +13 -3
- package/dist/cli/chunk-XDVT3GOM.js +310 -0
- package/dist/cli/{chunk-A3APGZFP.mjs → chunk-XRQ2AQL7.mjs} +2 -2
- package/dist/cli/{chunk-3FV4GBRK.mjs → chunk-XXUDVEXK.mjs} +97 -8
- package/dist/cli/{chunk-PQSA443V.mjs → chunk-ZI2ZNDJK.mjs} +6 -1
- package/dist/cli/{chunk-HGYBEAQP.mjs → chunk-ZYBGA4AR.mjs} +3 -3
- package/dist/cli/command-docs.js +2 -2
- package/dist/cli/command-docs.mjs +1 -1
- package/dist/cli/{csv-E4QGUIGI.mjs → csv-5AXRB35H.mjs} +3 -3
- package/dist/cli/{csv-26U5T24Z.js → csv-LU7O75WE.js} +12 -12
- package/dist/cli/{db-JTRJIAQW.mjs → db-7XP5UWEW.mjs} +6 -5
- package/dist/cli/{db-QHBFLMVL.js → db-F7YTFKVM.js} +31 -30
- package/dist/cli/{describe-FDV2V74F.js → describe-FRH32RDV.js} +24 -23
- package/dist/cli/{describe-E3DE3ZK3.mjs → describe-K4ES5LIL.mjs} +17 -16
- package/dist/cli/{executions-B3GTNPRE.mjs → executions-IEHEHJUO.mjs} +6 -5
- package/dist/cli/{executions-VZYOGVQY.js → executions-RZV5XPT2.js} +8 -7
- package/dist/cli/{feedback-CR55FJA2.js → feedback-3P65QTX3.js} +7 -7
- package/dist/cli/{feedback-4CEBR3OV.mjs → feedback-FPUVZBAN.mjs} +3 -3
- package/dist/cli/{git-NM2PHHZ4.mjs → git-GLVVP7TY.mjs} +4 -4
- package/dist/cli/git-MFFAWVVX.js +23 -0
- package/dist/cli/index.js +100 -96
- package/dist/cli/index.mjs +47 -43
- package/dist/cli/{legacy-noops-A7MSW5ND.mjs → legacy-noops-3MOI6NBM.mjs} +4 -4
- package/dist/cli/{legacy-noops-ZUA4YFIH.js → legacy-noops-XKSGZR7Q.js} +7 -7
- package/dist/cli/{monitors-NVPRQYP3.mjs → monitors-FTBGNEFG.mjs} +6 -5
- package/dist/cli/{monitors-ZE3TLZ4H.js → monitors-NZ2DYNST.js} +87 -86
- package/dist/cli/{org-U7A6DV5D.js → org-MCKDQXIV.js} +66 -65
- package/dist/cli/{org-KUBILNQV.mjs → org-V2J5RBCN.mjs} +6 -5
- package/dist/cli/{play-HRDXQ4QE.mjs → play-AJFGME3B.mjs} +11 -10
- package/dist/cli/play-WLSN2EO2.js +158 -0
- package/dist/cli/{providers-VD6YTVQZ.js → providers-2JEZ2264.js} +9 -8
- package/dist/cli/{providers-OHIWI453.mjs → providers-I6RQJZFZ.mjs} +6 -5
- package/dist/cli/{quickstart-UBV2FONF.mjs → quickstart-BIDQDMO4.mjs} +3 -3
- package/dist/cli/{quickstart-ETTCWPA4.js → quickstart-GJ2RPDU3.js} +5 -5
- package/dist/cli/run-identity-G257GQED.js +25 -0
- package/dist/cli/run-identity-MRNCEGV2.mjs +25 -0
- package/dist/cli/search-5VN4YD4I.js +27 -0
- package/dist/cli/{search-F44IZRT7.mjs → search-ELRXFWG5.mjs} +7 -6
- package/dist/cli/{secrets-CAEMRORF.js → secrets-53IT6VXD.js} +15 -14
- package/dist/cli/{secrets-R6XOJZKU.mjs → secrets-SQXGZXIC.mjs} +6 -5
- package/dist/cli/{sessions-RLN6UQS3.mjs → sessions-2X5QF7SL.mjs} +4 -4
- package/dist/cli/{sessions-6GC7OFTT.js → sessions-CAV5M77Y.js} +5 -5
- package/dist/cli/{settings-DPNVUAV2.js → settings-532WNAZO.js} +76 -75
- package/dist/cli/{settings-KIPAMRPP.mjs → settings-7P67VGCJ.mjs} +9 -8
- package/dist/cli/{setup-DTL3L5SG.js → setup-6L2VSUWJ.js} +41 -41
- package/dist/cli/{setup-TU2F7PU5.mjs → setup-QGGBV2LE.mjs} +6 -6
- package/dist/cli/{skills-UV5MYJ46.mjs → skills-3FE62WBG.mjs} +4 -4
- package/dist/cli/skills-S6NPZAN7.js +28 -0
- package/dist/cli/tools-647RLXQS.js +41 -0
- package/dist/cli/{tools-QRRNDCQ7.mjs → tools-DMR25DEF.mjs} +10 -9
- package/dist/cli/{update-4426TF35.mjs → update-6RZMHJZP.mjs} +6 -6
- package/dist/cli/update-WVTSC3EM.js +28 -0
- package/dist/cli/{workflow-N4U5VLZ3.mjs → workflow-L2DQRE6R.mjs} +7 -6
- package/dist/cli/{workflow-HNLOVISB.js → workflow-TX4F2PIX.js} +17 -16
- package/dist/{client-e6Qh5ZIg.d.mts → client-BktIPdIj.d.mts} +153 -4
- package/dist/{client-Uxuy-xQ2.d.ts → client-C3O1Ipf3.d.ts} +153 -4
- package/dist/{compiler-manifest-CY2v8ab0.d.ts → compiler-manifest-DLxUY5kI.d.mts} +124 -10
- package/dist/{compiler-manifest-CY2v8ab0.d.mts → compiler-manifest-DLxUY5kI.d.ts} +124 -10
- package/dist/{index-BZ6uzZJl.d.ts → index-B_GM9E3q.d.ts} +1 -1
- package/dist/{index-B9fBZH1g.d.mts → index-SCb0NvDb.d.mts} +1 -1
- package/dist/index.d.mts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +120 -29
- package/dist/index.mjs +111 -20
- package/dist/install-integrity.json +94 -88
- package/dist/plays/bundle-play-file.d.mts +3 -3
- package/dist/plays/bundle-play-file.d.ts +3 -3
- package/dist/plays/bundle-play-file.mjs +2 -2
- package/dist/plays/run-request.d.mts +2 -2
- package/dist/plays/run-request.d.ts +2 -2
- package/dist/plays/run-request.js +5 -2
- package/dist/plays/run-request.mjs +4 -1
- package/dist/release.d.mts +4 -1
- package/dist/release.d.ts +4 -1
- package/dist/release.js +2 -2
- package/dist/release.mjs +1 -1
- package/package.json +1 -1
- package/dist/cli/auth-67TAELQL.js +0 -39
- package/dist/cli/git-I5JTK6YU.js +0 -23
- package/dist/cli/play-A2V7YDU7.js +0 -157
- package/dist/cli/search-WWXMBVPH.js +0 -26
- package/dist/cli/skills-SWOSEZLB.js +0 -28
- package/dist/cli/tools-IL2YCDQ6.js +0 -40
- package/dist/cli/update-BERDPEYR.js +0 -28
|
@@ -338,17 +338,19 @@ export interface RuntimeDatasetOptions<TItem = Record<string, unknown>> {
|
|
|
338
338
|
* Set staleAfterSeconds to rerun after that many seconds while retries/replays
|
|
339
339
|
* inside the same window still hit the cache. Daily refreshes use 86400.
|
|
340
340
|
*/
|
|
341
|
-
staleAfterSeconds?: number;
|
|
341
|
+
staleAfterSeconds?: number | null;
|
|
342
342
|
/**
|
|
343
343
|
* Optional stable key per row. When provided, row identity is derived from
|
|
344
344
|
* the selected input field(s) or returned value instead of hashing the full
|
|
345
345
|
* row. Use this to pin identity to primary columns (e.g. `{ key: "email" }`
|
|
346
346
|
* or `{ key: ["first_name", "last_name", "domain"] }`) so harmless
|
|
347
347
|
* mutations to other columns don't invalidate the cache.
|
|
348
|
+
* Providing a key selects key identity for non-CSV sources and retained
|
|
349
|
+
* authoring editions.
|
|
348
350
|
*
|
|
349
351
|
* Must return a non-empty string (numbers are coerced). Empty keys throw
|
|
350
352
|
* loudly before any provider work runs. Rows whose keys duplicate an earlier
|
|
351
|
-
* row are
|
|
353
|
+
* row are deduped (first occurrence wins) and counted, never failing
|
|
352
354
|
* the run. The key function should reference *input*
|
|
353
355
|
* columns only — values produced by this dataset's field resolvers are stripped
|
|
354
356
|
* before identity is computed.
|
|
@@ -357,6 +359,14 @@ export interface RuntimeDatasetOptions<TItem = Record<string, unknown>> {
|
|
|
357
359
|
| (keyof TItem & string)
|
|
358
360
|
| readonly (keyof TItem & string)[]
|
|
359
361
|
| ((row: TItem, index: number) => string | number | readonly unknown[]);
|
|
362
|
+
/**
|
|
363
|
+
* Edition 11 CSV inputs always use source_row: the original CSV record
|
|
364
|
+
* ordinal distinguishes equal records and every occurrence is processed,
|
|
365
|
+
* even when key or rowIdentity: 'key' is supplied. For non-CSV sources and
|
|
366
|
+
* retained editions, rowIdentity: 'key' keeps the existing first-wins
|
|
367
|
+
* dedupe behavior.
|
|
368
|
+
*/
|
|
369
|
+
rowIdentity?: 'source_row' | 'key';
|
|
360
370
|
/**
|
|
361
371
|
* Row failure policy for this dataset run.
|
|
362
372
|
*
|
|
@@ -400,7 +410,12 @@ export type RuntimeDatasetDefinitionOptions<TItem = Record<string, unknown>> =
|
|
|
400
410
|
|
|
401
411
|
export type RuntimeDatasetRunOptions<TItem = Record<string, unknown>> = Pick<
|
|
402
412
|
RuntimeDatasetOptions<TItem>,
|
|
403
|
-
|
|
413
|
+
| 'description'
|
|
414
|
+
| 'key'
|
|
415
|
+
| 'rowIdentity'
|
|
416
|
+
| 'onRowError'
|
|
417
|
+
| 'mode'
|
|
418
|
+
| 'undrawnColumns'
|
|
404
419
|
>;
|
|
405
420
|
|
|
406
421
|
/** Runtime-only receipt recovery bit; never part of the customer authoring API. */
|
|
@@ -500,6 +515,11 @@ export interface MapExecutionFrame {
|
|
|
500
515
|
suspendedInputRows?: Array<{
|
|
501
516
|
inputIndex: number;
|
|
502
517
|
data: Record<string, unknown>;
|
|
518
|
+
/** Original admission input, separate from restored execution/output cells. */
|
|
519
|
+
authoredInput?: {
|
|
520
|
+
data: Record<string, unknown>;
|
|
521
|
+
source?: Readonly<{ datasetId: string; rowIndex: number }>;
|
|
522
|
+
};
|
|
503
523
|
/** Digest of the authored input before Runtime Sheet outputs were merged. */
|
|
504
524
|
authoredInputDigest?: string;
|
|
505
525
|
}>;
|
|
@@ -507,6 +527,13 @@ export interface MapExecutionFrame {
|
|
|
507
527
|
completedRowsCount?: number;
|
|
508
528
|
pendingRowsCount?: number;
|
|
509
529
|
failedRowsCount?: number;
|
|
530
|
+
/** Original source cardinality retained across pending-only checkpoint replay. */
|
|
531
|
+
inputCardinality?: {
|
|
532
|
+
sourceRows: number;
|
|
533
|
+
uniqueInputRows: number;
|
|
534
|
+
duplicateOccurrences: number;
|
|
535
|
+
rowIdentity: 'source_row' | 'key';
|
|
536
|
+
};
|
|
510
537
|
/** Bounded final-row causes, additive for checkpoint compatibility. */
|
|
511
538
|
failureCounts?: import('./row-failure-summary').RowFailureCounts;
|
|
512
539
|
/** Terminal rows omitted from this run's latest view because a newer write won. */
|
|
@@ -988,7 +1015,9 @@ export interface ContextOptions {
|
|
|
988
1015
|
tableNamespace: string;
|
|
989
1016
|
succeededRows: number;
|
|
990
1017
|
failedRows: number;
|
|
991
|
-
|
|
1018
|
+
expectedRows?: number;
|
|
1019
|
+
staticPipeline?: PlayStaticPipeline | null;
|
|
1020
|
+
}) => void | Promise<void>;
|
|
992
1021
|
/**
|
|
993
1022
|
* Persists coalesced, non-terminal row patches before a map suspends.
|
|
994
1023
|
* Terminal rows use onMapRowsCompleted; this narrow seam only preserves
|
|
@@ -1054,6 +1083,9 @@ export interface ContextOptions {
|
|
|
1054
1083
|
/** Identity policy is required before reading receipts; lookup errors are
|
|
1055
1084
|
* fatal, unlike the legacy best-effort retry advice below. */
|
|
1056
1085
|
getToolIdempotencyScope?: (toolId: string) => Promise<'invocation' | null>;
|
|
1086
|
+
getToolReusableResultSupport?: (
|
|
1087
|
+
toolId: string,
|
|
1088
|
+
) => Promise<{ supported: boolean; unavailableReason: string | null }>;
|
|
1057
1089
|
getToolRetryPolicy?: (
|
|
1058
1090
|
toolId: string,
|
|
1059
1091
|
input: Record<string, unknown>,
|
|
@@ -1071,6 +1103,10 @@ export interface ContextOptions {
|
|
|
1071
1103
|
toolId: string,
|
|
1072
1104
|
) => Promise<string[]> | string[];
|
|
1073
1105
|
getToolAuthScopeDigest?: (toolId: string) => Promise<string> | string;
|
|
1106
|
+
/** Absent on older app readers; only the declared async boundary may opt in. */
|
|
1107
|
+
getToolAsyncExecutionRecovery?: (
|
|
1108
|
+
toolId: string,
|
|
1109
|
+
) => Promise<{ origin: string } | null>;
|
|
1074
1110
|
/** Original run for an explicit customer rerun, never an automatic retry. */
|
|
1075
1111
|
replayedFromRunId?: string | null;
|
|
1076
1112
|
getToolRateScope?: (
|
|
@@ -1332,6 +1368,7 @@ export type PlayFetchResponse = {
|
|
|
1332
1368
|
headers: Record<string, string>;
|
|
1333
1369
|
bodyText: string;
|
|
1334
1370
|
json: unknown | null;
|
|
1371
|
+
freshness?: import('../plays/tool-result-types').CallFreshnessMetadata;
|
|
1335
1372
|
};
|
|
1336
1373
|
|
|
1337
1374
|
export interface BatchResult {
|
|
@@ -13,6 +13,34 @@ export type DurableCallCachePolicy = {
|
|
|
13
13
|
staleAfterSeconds: number | null;
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
+
/** Future artifact semantics; legacy optional callers retain the resolver below. */
|
|
17
|
+
export function resolveExplicitDurableCallPolicy(input: {
|
|
18
|
+
staleAfterSeconds?: number | null;
|
|
19
|
+
force?: boolean;
|
|
20
|
+
}): { requested: number | null; effective: number | null } {
|
|
21
|
+
if (input.staleAfterSeconds === undefined) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
'Choose staleAfterSeconds explicitly: 0, a positive whole number of seconds, or null.',
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
resolveDurableCallCachePolicy(input.staleAfterSeconds);
|
|
27
|
+
if (
|
|
28
|
+
input.staleAfterSeconds !== null &&
|
|
29
|
+
!Number.isSafeInteger(input.staleAfterSeconds)
|
|
30
|
+
) {
|
|
31
|
+
throw new Error(DURABLE_CALL_STALE_AFTER_SECONDS_ERROR);
|
|
32
|
+
}
|
|
33
|
+
if (input.force === true && input.staleAfterSeconds !== 0) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
'force: true contradicts reusable freshness. Use staleAfterSeconds: 0.',
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
requested: input.staleAfterSeconds,
|
|
40
|
+
effective: input.staleAfterSeconds,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
16
44
|
/** Normalize the public freshness contract shared by durable calls. */
|
|
17
45
|
export function resolveDurableCallCachePolicy(
|
|
18
46
|
staleAfterSeconds?: number | null,
|
|
@@ -1,3 +1,39 @@
|
|
|
1
|
+
import { sha256Hex } from '../plays/row-identity';
|
|
2
|
+
|
|
3
|
+
export const RUNTIME_EXECUTION_RECOVERY_KEY_PREFIX = 'runtime-gateway:';
|
|
4
|
+
export const RUNTIME_EXECUTION_RECOVERY_SUPPORTED_HEADER =
|
|
5
|
+
'x-deepline-idempotency-supported';
|
|
6
|
+
export const RUNTIME_EXECUTION_RECOVERY_ONLY_HEADER =
|
|
7
|
+
'x-deepline-execution-recovery-only';
|
|
8
|
+
/** Gateway-owned support for forwarding a keyed async call and its app ACK. */
|
|
9
|
+
export const RUNTIME_ASYNC_EXECUTION_RECOVERY_TRANSPORT_HEADER =
|
|
10
|
+
'x-deepline-async-execution-recovery-transport';
|
|
11
|
+
|
|
12
|
+
/** Same-run delivery recovery must not coalesce separate Play runs. */
|
|
13
|
+
export function buildRuntimeExecutionRecoveryKey(input: {
|
|
14
|
+
parentRunId: string;
|
|
15
|
+
invocationKey: string;
|
|
16
|
+
}): string {
|
|
17
|
+
if (!input.parentRunId.trim() || !input.invocationKey.trim()) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
'Runtime execution recovery requires run and invocation identities.',
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
return `${RUNTIME_EXECUTION_RECOVERY_KEY_PREFIX}${sha256Hex(input.parentRunId)}:${sha256Hex(input.invocationKey)}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** A known key is not authority to read another executor run's response. */
|
|
26
|
+
export function isRuntimeExecutionRecoveryKeyForRun(
|
|
27
|
+
key: string,
|
|
28
|
+
runId: string,
|
|
29
|
+
): boolean {
|
|
30
|
+
if (!runId.trim()) return false;
|
|
31
|
+
const prefix = `${RUNTIME_EXECUTION_RECOVERY_KEY_PREFIX}${sha256Hex(runId)}:`;
|
|
32
|
+
return (
|
|
33
|
+
key.startsWith(prefix) && /^[a-f0-9]{64}$/.test(key.slice(prefix.length))
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
1
37
|
/**
|
|
2
38
|
* `execute-fenced-v2` is the first operation that requires semantic provider
|
|
3
39
|
* identity. It is intentionally a named operation rather than a runtime-wide
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { sha256Hex, stableStringify } from '../plays/row-identity';
|
|
2
|
+
import type { CallFreshnessMetadata } from '../plays/tool-result-types';
|
|
3
|
+
import type { RuntimeStepReceipt } from './ctx-types';
|
|
4
|
+
import {
|
|
5
|
+
COMPLETED_RECEIPT_CACHE_INSERT_LEASE_ID,
|
|
6
|
+
type DurableReceiptExecutionStore,
|
|
7
|
+
} from './durable-receipt-execution';
|
|
8
|
+
import { assertNoSecretTaint } from './secret-capability';
|
|
9
|
+
import {
|
|
10
|
+
deserializeToolExecuteResult,
|
|
11
|
+
isSerializedToolExecuteResult,
|
|
12
|
+
isToolExecuteResult,
|
|
13
|
+
relayPublishedToolResultReference,
|
|
14
|
+
serializeToolExecuteResult,
|
|
15
|
+
} from './tool-result';
|
|
16
|
+
|
|
17
|
+
type CompletedCall = {
|
|
18
|
+
completedAt: number;
|
|
19
|
+
value?: unknown;
|
|
20
|
+
/**
|
|
21
|
+
* Large tool results already live in their canonical result receipt. Keep a
|
|
22
|
+
* receipt-key indirection here instead of copying the full result into each
|
|
23
|
+
* operation and reusable freshness receipt.
|
|
24
|
+
*/
|
|
25
|
+
valueReceiptKey?: string;
|
|
26
|
+
undefinedValue?: true;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
type FreshnessStore = Pick<
|
|
30
|
+
DurableReceiptExecutionStore,
|
|
31
|
+
'enabled' | 'get' | 'complete'
|
|
32
|
+
>;
|
|
33
|
+
|
|
34
|
+
function completedCall(
|
|
35
|
+
receipt: RuntimeStepReceipt | null,
|
|
36
|
+
): CompletedCall | null {
|
|
37
|
+
if (!receipt || !['completed', 'skipped'].includes(receipt.status)) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
const output = receipt.output;
|
|
41
|
+
if (!output || typeof output !== 'object') {
|
|
42
|
+
throw new Error('Invalid completed freshness receipt.');
|
|
43
|
+
}
|
|
44
|
+
const candidate = output as Partial<CompletedCall>;
|
|
45
|
+
const hasInlineValue = 'value' in candidate;
|
|
46
|
+
const hasReceiptReference =
|
|
47
|
+
typeof candidate.valueReceiptKey === 'string' &&
|
|
48
|
+
candidate.valueReceiptKey.trim().length > 0;
|
|
49
|
+
if (
|
|
50
|
+
!Number.isSafeInteger(candidate.completedAt) ||
|
|
51
|
+
candidate.completedAt! < 0 ||
|
|
52
|
+
hasInlineValue === hasReceiptReference ||
|
|
53
|
+
(candidate.valueReceiptKey !== undefined && !hasReceiptReference) ||
|
|
54
|
+
(candidate.undefinedValue !== undefined &&
|
|
55
|
+
candidate.undefinedValue !== true) ||
|
|
56
|
+
(candidate.undefinedValue === true &&
|
|
57
|
+
(hasReceiptReference || candidate.value !== null))
|
|
58
|
+
) {
|
|
59
|
+
throw new Error('Invalid completed freshness receipt.');
|
|
60
|
+
}
|
|
61
|
+
return candidate as CompletedCall;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Operation recovery is run-local. Reusable results are a separate completed
|
|
66
|
+
* cache. Adjacent partitions bound lookup, while admission checks actual age.
|
|
67
|
+
* A concurrent miss executes independently and publishes its own operation.
|
|
68
|
+
*/
|
|
69
|
+
export async function executeWithFreshnessReceipt<T>(input: {
|
|
70
|
+
semanticKey: string;
|
|
71
|
+
orgId: string;
|
|
72
|
+
invocationId: string;
|
|
73
|
+
runId: string;
|
|
74
|
+
requestedStaleAfterSeconds: number | null;
|
|
75
|
+
effectiveStaleAfterSeconds: number | null;
|
|
76
|
+
store: FreshnessStore;
|
|
77
|
+
execute: () => Promise<T>;
|
|
78
|
+
completedAt?: (value: T) => number | undefined;
|
|
79
|
+
onOutcome: (value: T, metadata: CallFreshnessMetadata) => T;
|
|
80
|
+
withHydration: <Result>(
|
|
81
|
+
read: (
|
|
82
|
+
measure?: (receipt: RuntimeStepReceipt | null) => void,
|
|
83
|
+
) => Promise<Result>,
|
|
84
|
+
) => Promise<Result>;
|
|
85
|
+
now?: () => number;
|
|
86
|
+
}): Promise<T> {
|
|
87
|
+
if (!input.store.enabled) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
'Durable operation recovery is unavailable in this runtime.',
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
const now = input.now ?? Date.now;
|
|
93
|
+
const cachePrefix = `ctx:${input.orgId}:freshness:${sha256Hex(input.semanticKey)}`;
|
|
94
|
+
const operationKey = `ctx:${input.orgId}:freshness:operation:${sha256Hex(
|
|
95
|
+
stableStringify({ runId: input.runId, invocationId: input.invocationId }),
|
|
96
|
+
)}`;
|
|
97
|
+
const read = (key: string) =>
|
|
98
|
+
input.withHydration(async (measure) => {
|
|
99
|
+
const receipt = await input.store.get(key);
|
|
100
|
+
measure?.(receipt);
|
|
101
|
+
return completedCall(receipt);
|
|
102
|
+
});
|
|
103
|
+
const readReferencedResult = (key: string) =>
|
|
104
|
+
input.withHydration(async (measure) => {
|
|
105
|
+
const receipt = await input.store.get(key);
|
|
106
|
+
measure?.(receipt);
|
|
107
|
+
if (
|
|
108
|
+
!receipt ||
|
|
109
|
+
!['completed', 'skipped'].includes(receipt.status) ||
|
|
110
|
+
receipt.output === undefined
|
|
111
|
+
) {
|
|
112
|
+
throw new Error(
|
|
113
|
+
'Completed freshness result receipt could not be recovered.',
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
const value = receipt.output;
|
|
117
|
+
if (
|
|
118
|
+
!isSerializedToolExecuteResult(value) &&
|
|
119
|
+
!isToolExecuteResult(value)
|
|
120
|
+
) {
|
|
121
|
+
throw new Error(
|
|
122
|
+
'Completed freshness result receipt has an invalid tool result.',
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
return value;
|
|
126
|
+
});
|
|
127
|
+
const outcome = async (
|
|
128
|
+
call: CompletedCall,
|
|
129
|
+
source: CallFreshnessMetadata['source'],
|
|
130
|
+
liveResult?: { value: T },
|
|
131
|
+
): Promise<T> => {
|
|
132
|
+
let value: unknown;
|
|
133
|
+
if (liveResult !== undefined) {
|
|
134
|
+
value = liveResult.value;
|
|
135
|
+
} else if (call.undefinedValue) {
|
|
136
|
+
value = undefined;
|
|
137
|
+
} else if (call.valueReceiptKey) {
|
|
138
|
+
value = await readReferencedResult(call.valueReceiptKey);
|
|
139
|
+
} else {
|
|
140
|
+
value = call.value;
|
|
141
|
+
}
|
|
142
|
+
value = isSerializedToolExecuteResult(value)
|
|
143
|
+
? deserializeToolExecuteResult(value)
|
|
144
|
+
: value;
|
|
145
|
+
return input.onOutcome(value as T, {
|
|
146
|
+
requestedStaleAfterSeconds: input.requestedStaleAfterSeconds,
|
|
147
|
+
effectiveStaleAfterSeconds: input.effectiveStaleAfterSeconds,
|
|
148
|
+
source,
|
|
149
|
+
resultAgeMs: Math.max(0, now() - call.completedAt),
|
|
150
|
+
completedAt: call.completedAt,
|
|
151
|
+
operationReceiptKey: operationKey,
|
|
152
|
+
});
|
|
153
|
+
};
|
|
154
|
+
const publish = async (
|
|
155
|
+
key: string,
|
|
156
|
+
call: CompletedCall,
|
|
157
|
+
): Promise<CompletedCall> => {
|
|
158
|
+
const receipt = await input.store.complete(
|
|
159
|
+
key,
|
|
160
|
+
input.runId,
|
|
161
|
+
call,
|
|
162
|
+
COMPLETED_RECEIPT_CACHE_INSERT_LEASE_ID,
|
|
163
|
+
);
|
|
164
|
+
const stored =
|
|
165
|
+
receipt?.output != null ? completedCall(receipt) : await read(key);
|
|
166
|
+
if (!stored) {
|
|
167
|
+
throw new Error('Completed freshness receipt publication failed.');
|
|
168
|
+
}
|
|
169
|
+
return stored;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const recovered = await read(operationKey);
|
|
173
|
+
if (recovered) return outcome(recovered, 'operation_recovery');
|
|
174
|
+
|
|
175
|
+
const ttl = input.effectiveStaleAfterSeconds;
|
|
176
|
+
const admissionTime = now();
|
|
177
|
+
const currentPartition =
|
|
178
|
+
ttl === null ? null : Math.floor(admissionTime / (ttl * 1000));
|
|
179
|
+
const reusableKey =
|
|
180
|
+
ttl === null
|
|
181
|
+
? `${cachePrefix}:reusable:indefinite`
|
|
182
|
+
: `${cachePrefix}:reusable:${ttl}:${currentPartition}`;
|
|
183
|
+
|
|
184
|
+
if (ttl !== 0) {
|
|
185
|
+
const candidates =
|
|
186
|
+
ttl === null
|
|
187
|
+
? [reusableKey]
|
|
188
|
+
: [
|
|
189
|
+
reusableKey,
|
|
190
|
+
`${cachePrefix}:reusable:${ttl}:${currentPartition! - 1}`,
|
|
191
|
+
];
|
|
192
|
+
for (const key of candidates) {
|
|
193
|
+
const cached = await read(key);
|
|
194
|
+
if (!cached) continue;
|
|
195
|
+
const age = now() - cached.completedAt;
|
|
196
|
+
if (age < 0 || (ttl !== null && age >= ttl * 1000)) continue;
|
|
197
|
+
const own = await publish(operationKey, cached);
|
|
198
|
+
return outcome(own, 'reusable_result');
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const value = await input.execute();
|
|
203
|
+
assertNoSecretTaint(value, 'Freshness call result');
|
|
204
|
+
const completedAt = input.completedAt?.(value) ?? now();
|
|
205
|
+
if (!Number.isSafeInteger(completedAt) || completedAt < 0) {
|
|
206
|
+
throw new Error('Freshness call returned an invalid completion time.');
|
|
207
|
+
}
|
|
208
|
+
const relayReference = isToolExecuteResult(value)
|
|
209
|
+
? relayPublishedToolResultReference(value)
|
|
210
|
+
: null;
|
|
211
|
+
const resultReceiptKey =
|
|
212
|
+
isToolExecuteResult(value) && relayReference
|
|
213
|
+
? value._metadata.execution.resultReceiptKey?.trim() ||
|
|
214
|
+
value._metadata.execution.receiptKey?.trim() ||
|
|
215
|
+
''
|
|
216
|
+
: '';
|
|
217
|
+
if (relayReference && !resultReceiptKey) {
|
|
218
|
+
throw new Error(
|
|
219
|
+
'Referenced freshness result has no canonical result receipt identity.',
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
const completed: CompletedCall = {
|
|
223
|
+
completedAt,
|
|
224
|
+
...(value === undefined
|
|
225
|
+
? { value: null, undefinedValue: true as const }
|
|
226
|
+
: resultReceiptKey
|
|
227
|
+
? { valueReceiptKey: resultReceiptKey }
|
|
228
|
+
: {
|
|
229
|
+
value: isToolExecuteResult(value)
|
|
230
|
+
? serializeToolExecuteResult(value)
|
|
231
|
+
: value,
|
|
232
|
+
}),
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
// Recover this exact operation even if reusable publication fails afterwards.
|
|
236
|
+
await publish(operationKey, completed);
|
|
237
|
+
if (ttl !== 0) {
|
|
238
|
+
const completionPartition =
|
|
239
|
+
ttl === null
|
|
240
|
+
? 'indefinite'
|
|
241
|
+
: Math.floor(completed.completedAt / (ttl * 1000));
|
|
242
|
+
const resultKey =
|
|
243
|
+
ttl === null
|
|
244
|
+
? `${cachePrefix}:reusable:indefinite`
|
|
245
|
+
: `${cachePrefix}:reusable:${ttl}:${completionPartition}`;
|
|
246
|
+
await publish(resultKey, completed);
|
|
247
|
+
}
|
|
248
|
+
// Each overlapping miss owns its live result even if the immutable receipt
|
|
249
|
+
// insert loses to a sibling. The stored winner is for later recovery.
|
|
250
|
+
return outcome(completed, 'executed', { value });
|
|
251
|
+
}
|
|
@@ -24,10 +24,18 @@ export function cloneMapFrame(frame: MapExecutionFrame): MapExecutionFrame {
|
|
|
24
24
|
...(frame.suspendedInputRows
|
|
25
25
|
? {
|
|
26
26
|
suspendedInputRows: frame.suspendedInputRows.map((row) => ({
|
|
27
|
-
|
|
27
|
+
...row,
|
|
28
28
|
data: cloneCsvAliasedRow(row.data),
|
|
29
|
-
...(row.
|
|
30
|
-
? {
|
|
29
|
+
...(row.authoredInput
|
|
30
|
+
? {
|
|
31
|
+
authoredInput: {
|
|
32
|
+
...row.authoredInput,
|
|
33
|
+
data: cloneCsvAliasedRow(row.authoredInput.data),
|
|
34
|
+
...(row.authoredInput.source
|
|
35
|
+
? { source: { ...row.authoredInput.source } }
|
|
36
|
+
: {}),
|
|
37
|
+
},
|
|
38
|
+
}
|
|
31
39
|
: {}),
|
|
32
40
|
})),
|
|
33
41
|
}
|
package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/gateway-adapter.ts
CHANGED
|
@@ -35,6 +35,7 @@ export function createGatewayRunLedgerProjectionAdapter(
|
|
|
35
35
|
runId: input.runId,
|
|
36
36
|
runtimeApi: input.runtimeApi,
|
|
37
37
|
tokenAuthority: 'gateway' as const,
|
|
38
|
+
...(input.runtimeApi.signal ? { signal: input.runtimeApi.signal } : {}),
|
|
38
39
|
};
|
|
39
40
|
return {
|
|
40
41
|
async deliver(request) {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
+
EXPLICIT_FRESHNESS_PLAY_AUTHORING_THRESHOLD,
|
|
2
3
|
normalizePlayAuthoringContractEdition,
|
|
3
4
|
playToolListGetterMode,
|
|
5
|
+
playCsvDatasetRowIdentity,
|
|
4
6
|
type PlayAuthoringContractEdition,
|
|
5
7
|
} from '../plays/authoring-contract';
|
|
6
8
|
|
|
@@ -11,6 +13,9 @@ export type PlayRuntimeSecretValueSemantics =
|
|
|
11
13
|
export type PlayRuntimeFetchHttpFailureSemantics =
|
|
12
14
|
| 'return_response'
|
|
13
15
|
| 'throw_typed_error';
|
|
16
|
+
export type PlayRuntimeFreshnessSemantics =
|
|
17
|
+
| 'legacy_bucket'
|
|
18
|
+
| 'actual_age_with_operation_recovery';
|
|
14
19
|
|
|
15
20
|
/**
|
|
16
21
|
* Immutable worker-side projection of the semantics pinned by a Play artifact.
|
|
@@ -35,6 +40,7 @@ export type PlayRuntimeSemantics = Readonly<{
|
|
|
35
40
|
*/
|
|
36
41
|
receiptIdentityEdition: PlayAuthoringContractEdition | null;
|
|
37
42
|
}>;
|
|
43
|
+
freshness: PlayRuntimeFreshnessSemantics;
|
|
38
44
|
inlineChild: Readonly<{
|
|
39
45
|
/**
|
|
40
46
|
* Whether a scalar child may rely solely on its execution-time
|
|
@@ -50,11 +56,12 @@ export type PlayRuntimeSemantics = Readonly<{
|
|
|
50
56
|
slackHitl: Readonly<{
|
|
51
57
|
defaultTimeoutMs: number;
|
|
52
58
|
}>;
|
|
59
|
+
datasets: Readonly<{ csvRowIdentity: 'source_row' | 'key' }>;
|
|
53
60
|
}>;
|
|
54
61
|
|
|
55
62
|
type PlayRuntimeSemanticsTemplate = Omit<
|
|
56
63
|
PlayRuntimeSemantics,
|
|
57
|
-
'authoringContractEdition' | 'tools'
|
|
64
|
+
'authoringContractEdition' | 'freshness' | 'tools' | 'datasets'
|
|
58
65
|
> & { tools: Omit<PlayRuntimeSemantics['tools'], 'listGetters'> };
|
|
59
66
|
|
|
60
67
|
function definePlayRuntimeSemantics(
|
|
@@ -66,12 +73,22 @@ function definePlayRuntimeSemantics(
|
|
|
66
73
|
...semantics,
|
|
67
74
|
secrets: Object.freeze({ ...semantics.secrets }),
|
|
68
75
|
fetch: Object.freeze({ ...semantics.fetch }),
|
|
76
|
+
freshness:
|
|
77
|
+
semantics.authoringContractEdition >=
|
|
78
|
+
EXPLICIT_FRESHNESS_PLAY_AUTHORING_THRESHOLD
|
|
79
|
+
? 'actual_age_with_operation_recovery'
|
|
80
|
+
: 'legacy_bucket',
|
|
69
81
|
inlineChild: Object.freeze({ ...semantics.inlineChild }),
|
|
70
82
|
tools: Object.freeze({
|
|
71
83
|
...semantics.tools,
|
|
72
84
|
listGetters: playToolListGetterMode(semantics.authoringContractEdition),
|
|
73
85
|
}),
|
|
74
86
|
slackHitl: Object.freeze({ ...semantics.slackHitl }),
|
|
87
|
+
datasets: Object.freeze({
|
|
88
|
+
csvRowIdentity: playCsvDatasetRowIdentity(
|
|
89
|
+
semantics.authoringContractEdition,
|
|
90
|
+
),
|
|
91
|
+
}),
|
|
75
92
|
});
|
|
76
93
|
}
|
|
77
94
|
|
|
@@ -198,6 +215,18 @@ const PLAY_RUNTIME_SEMANTICS_BY_AUTHORING = {
|
|
|
198
215
|
tools: { nativeBatchResponse: 'logical_response' },
|
|
199
216
|
slackHitl: WEEK_LONG_SLACK_HITL_SEMANTICS,
|
|
200
217
|
},
|
|
218
|
+
11: {
|
|
219
|
+
...PLAINTEXT_SECRET_SEMANTICS,
|
|
220
|
+
fetch: {
|
|
221
|
+
httpFailure: 'throw_typed_error',
|
|
222
|
+
persistHttpFailure: false,
|
|
223
|
+
// Freshness policy does not create another fetch receipt identity.
|
|
224
|
+
receiptIdentityEdition: FETCH_RECEIPT_IDENTITIES.typedHttpFailure,
|
|
225
|
+
},
|
|
226
|
+
inlineChild: { runtimeOnly: true },
|
|
227
|
+
tools: { nativeBatchResponse: 'logical_response' },
|
|
228
|
+
slackHitl: WEEK_LONG_SLACK_HITL_SEMANTICS,
|
|
229
|
+
},
|
|
201
230
|
} satisfies Record<PlayAuthoringContractEdition, PlayRuntimeSemanticsTemplate>;
|
|
202
231
|
|
|
203
232
|
const PLAY_RUNTIME_SEMANTICS = Object.fromEntries(
|
|
@@ -90,6 +90,32 @@ export function buildPlayWorkflowId(
|
|
|
90
90
|
return `play/${familyKey}/run/${buildPlayWorkflowRunSuffix(timestamp)}${entropy}`;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
/** Caller-owned identity binds the complete submitted JSON before admission. */
|
|
94
|
+
export function buildCallerPlayWorkflowId(
|
|
95
|
+
playName: string,
|
|
96
|
+
request: Record<string, unknown>,
|
|
97
|
+
now?: Date,
|
|
98
|
+
): string {
|
|
99
|
+
const { workflowId: _ignored, ...intent } = request;
|
|
100
|
+
const wireIntent = JSON.parse(JSON.stringify(intent));
|
|
101
|
+
return `${buildPlayWorkflowId(playName, now, { unique: true })}-caller-${sha256Hex(stableStringify(wireIntent))}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function callerPlayWorkflowIdMatchesRequest(
|
|
105
|
+
workflowId: string,
|
|
106
|
+
request: Record<string, unknown>,
|
|
107
|
+
): boolean {
|
|
108
|
+
const match =
|
|
109
|
+
/^play\/[a-z0-9/-]+\/run\/\d{8}t\d{6}-\d{3}-[a-z0-9]{8}-caller-([a-f0-9]{64})$/.exec(
|
|
110
|
+
workflowId,
|
|
111
|
+
);
|
|
112
|
+
if (!match || canonicalPlayRunStartedAtMs(workflowId) === null) return false;
|
|
113
|
+
const { workflowId: _ignored, ...intent } = request;
|
|
114
|
+
return (
|
|
115
|
+
match[1] === sha256Hex(stableStringify(JSON.parse(JSON.stringify(intent))))
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
93
119
|
export type PlayRunIdentity = {
|
|
94
120
|
workflowId: string;
|
|
95
121
|
runId: string;
|