deepline 0.3.393 → 0.3.395
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 +30 -162
- package/dist/bundling-sources/sdk/src/index.ts +1 -0
- package/dist/bundling-sources/sdk/src/plays/play-description.ts +167 -0
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/sdk/src/types.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +477 -10
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +5 -1
- package/dist/bundling-sources/shared_libs/play-runtime/durable-call-policy.ts +28 -0
- package/dist/bundling-sources/shared_libs/play-runtime/freshness-receipt-execution.ts +251 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-semantics.ts +12 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +10 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +25 -16
- package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +1 -0
- package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +69 -22
- package/dist/bundling-sources/shared_libs/plays/core.ts +1 -1
- package/dist/bundling-sources/shared_libs/plays/tool-result-types.ts +18 -0
- package/dist/cli/{auth-5PXHA232.mjs → auth-NMQXZRS7.mjs} +4 -4
- package/dist/cli/auth-UYCOBAL5.js +39 -0
- package/dist/cli/{billing-Z2N7ZAGM.mjs → billing-5MVXZRS6.mjs} +5 -4
- package/dist/cli/{billing-JFB2XLSO.js → billing-Z4JTGUU3.js} +76 -75
- package/dist/cli/{chunk-2HAD745V.js → chunk-36ML3YKA.js} +241 -349
- package/dist/cli/{chunk-RKMB3N7K.mjs → chunk-3HU47EGZ.mjs} +48 -9
- package/dist/cli/{chunk-6I73X254.mjs → chunk-3HWALOL2.mjs} +1 -1
- package/dist/cli/{chunk-4PT2VA3A.mjs → chunk-3TRY7KAC.mjs} +2 -2
- package/dist/cli/{chunk-5PRVMXKB.mjs → chunk-47M6HPGP.mjs} +1 -1
- package/dist/cli/{chunk-VIWE6WRK.js → chunk-4KLCLLTO.js} +267 -255
- package/dist/cli/{chunk-CDFH2DYL.js → chunk-56IFFVIB.js} +4 -4
- package/dist/cli/chunk-5VCZ3WRH.mjs +129 -0
- package/dist/cli/{chunk-LMMEAMA7.mjs → chunk-6D7A2FSL.mjs} +1 -1
- package/dist/cli/{chunk-7HR7BGZL.mjs → chunk-7MVWUXOD.mjs} +3 -3
- package/dist/cli/{chunk-2LGC4EFT.mjs → chunk-7XAM7KDW.mjs} +3 -3
- package/dist/cli/{chunk-HLEESTCH.mjs → chunk-BLYL3KOC.mjs} +1 -1
- package/dist/cli/{chunk-LZPCDB7U.mjs → chunk-BON3DRC6.mjs} +1 -1
- package/dist/cli/chunk-DRM3CMHL.js +129 -0
- package/dist/cli/{chunk-STNYIMIV.mjs → chunk-EFLQHJZ6.mjs} +6 -6
- package/dist/cli/{chunk-Q26LUSQ2.mjs → chunk-G4IWGRMK.mjs} +23 -131
- package/dist/cli/{chunk-M7Y7JYZX.js → chunk-HRWUQJ4K.js} +6 -6
- package/dist/cli/{chunk-OXNOAD6Y.js → chunk-K5T3EN6D.js} +26 -26
- package/dist/cli/{chunk-PWSWAN7Q.js → chunk-KB4SDX5S.js} +48 -9
- package/dist/cli/{chunk-4XX6443T.js → chunk-LZLBR2SF.js} +59 -59
- package/dist/cli/{chunk-MS6GUPBB.js → chunk-MH73HMPV.js} +3 -3
- package/dist/cli/{chunk-AARX5L4B.js → chunk-MYIBHORT.js} +2 -2
- package/dist/cli/{chunk-5KWKZIQR.mjs → chunk-OCSG6X67.mjs} +4 -4
- package/dist/cli/{chunk-PTDECJGP.js → chunk-PZ5BZ76S.js} +50 -50
- package/dist/cli/{chunk-L2SYLGRP.js → chunk-R4XZO6Z5.js} +8 -8
- package/dist/cli/{chunk-RWHWF5ZJ.js → chunk-TO2MWPGR.js} +1 -1
- package/dist/cli/{chunk-W4WOMXR3.js → chunk-UCXK2DS4.js} +15 -15
- package/dist/cli/{chunk-Z3OOZM5L.js → chunk-UKU6JM3S.js} +8 -8
- package/dist/cli/{chunk-4TCT77UF.js → chunk-URWF7NGQ.js} +6 -1
- package/dist/cli/{chunk-VIQJIX2P.js → chunk-VYLJJQ7Y.js} +65 -65
- package/dist/cli/{chunk-KIYU2TGL.mjs → chunk-WCCKMLHL.mjs} +2 -2
- package/dist/cli/{chunk-UOGZUA24.mjs → chunk-XMAPE2KH.mjs} +27 -15
- package/dist/cli/{chunk-PQSA443V.mjs → chunk-ZI2ZNDJK.mjs} +6 -1
- package/dist/cli/{chunk-FQ6VHPW4.mjs → chunk-ZWH2TFA7.mjs} +2 -2
- package/dist/cli/command-docs.js +2 -2
- package/dist/cli/command-docs.mjs +1 -1
- package/dist/cli/{csv-P3Z3YE7H.js → csv-HTS5ZYQ4.js} +12 -12
- package/dist/cli/{csv-KJWZHKCH.mjs → csv-SVOD5UBH.mjs} +3 -3
- package/dist/cli/{db-NUZGJNQC.js → db-7AP4P52N.js} +30 -29
- package/dist/cli/{db-NCM4CQIS.mjs → db-7QLNI3Q4.mjs} +5 -4
- package/dist/cli/{describe-WT6MM2GO.mjs → describe-DBRY5BNK.mjs} +12 -11
- package/dist/cli/{describe-YGSEN5WC.js → describe-YKOE54ZO.js} +22 -21
- package/dist/cli/{executions-PCOLHN7Q.js → executions-GRQXTB2P.js} +7 -6
- package/dist/cli/{executions-XS2IPJ4W.mjs → executions-QNZTPNG4.mjs} +5 -4
- package/dist/cli/{feedback-CCLTS5SV.mjs → feedback-JMCSR624.mjs} +3 -3
- package/dist/cli/{feedback-4MSH7TFU.js → feedback-ZOLA2KY7.js} +7 -7
- package/dist/cli/git-EJQLRK27.js +23 -0
- package/dist/cli/{git-K2DHPMPC.mjs → git-LJGL2IHS.mjs} +4 -4
- package/dist/cli/index.js +98 -98
- package/dist/cli/index.mjs +43 -43
- package/dist/cli/{legacy-noops-B5S4AR3P.mjs → legacy-noops-F23PKVBN.mjs} +4 -4
- package/dist/cli/{legacy-noops-GPNOPFXV.js → legacy-noops-YSCIR2SD.js} +7 -7
- package/dist/cli/{monitors-JLMAIPRG.mjs → monitors-S4LOEL7W.mjs} +5 -4
- package/dist/cli/{monitors-ZAWD7NSN.js → monitors-WMZ6HJZX.js} +86 -85
- package/dist/cli/{org-P2KN4CES.js → org-PFHM5ZLY.js} +65 -64
- package/dist/cli/{org-T3JTHP4G.mjs → org-U3WA2NQM.mjs} +5 -4
- package/dist/cli/{play-NK6BLRGL.mjs → play-AGSECQFN.mjs} +10 -9
- package/dist/cli/play-BR2LWGXK.js +159 -0
- package/dist/cli/{providers-3NQRA7IT.mjs → providers-QBBRFBOH.mjs} +5 -4
- package/dist/cli/{providers-5QRZN3DT.js → providers-XYMHLKCA.js} +8 -7
- package/dist/cli/{quickstart-PIA2JGPV.mjs → quickstart-5JTDGISY.mjs} +3 -3
- package/dist/cli/{quickstart-R7NUZGNW.js → quickstart-IYGD6ZY3.js} +5 -5
- package/dist/cli/{search-AIDIIGLY.mjs → search-X6W6GRHS.mjs} +6 -5
- package/dist/cli/search-ZVSYH5GY.js +28 -0
- package/dist/cli/{secrets-OQNHL45O.mjs → secrets-N55B67H6.mjs} +5 -4
- package/dist/cli/{secrets-BT3PGMWN.js → secrets-UHG6OQFD.js} +14 -13
- package/dist/cli/{sessions-EYXWOSWK.js → sessions-57L5MGHN.js} +5 -5
- package/dist/cli/{sessions-SF7WJPJP.mjs → sessions-JTRSTDSS.mjs} +4 -4
- package/dist/cli/{settings-BA3GHBOD.js → settings-ATX4U5AS.js} +75 -74
- package/dist/cli/{settings-Y57JTFDD.mjs → settings-Q24HRAQI.mjs} +8 -7
- package/dist/cli/{setup-7GR5GBHA.mjs → setup-77ZWLVHT.mjs} +6 -6
- package/dist/cli/{setup-RERXYC66.js → setup-EJCNOUCE.js} +41 -41
- package/dist/cli/skills-SPXUBLII.js +28 -0
- package/dist/cli/{skills-SJWIQAV5.mjs → skills-XIS5AWQI.mjs} +4 -4
- package/dist/cli/tools-DBHUUZJX.js +42 -0
- package/dist/cli/{tools-ZF3JXPNL.mjs → tools-WCKLYEGQ.mjs} +8 -7
- package/dist/cli/{update-FFFYQAOJ.mjs → update-UT5WANMV.mjs} +6 -6
- package/dist/cli/update-VSNFXRHJ.js +28 -0
- package/dist/cli/{workflow-SZJVQH4T.js → workflow-226QYUN2.js} +16 -15
- package/dist/cli/{workflow-XVFEEGAS.mjs → workflow-ZMX3L4ML.mjs} +6 -5
- package/dist/{client-mCE88hC4.d.ts → client-CivP-PAI.d.ts} +22 -13
- package/dist/{client-DU5dtHDy.d.mts → client-hdFtwhTm.d.mts} +22 -13
- package/dist/{compiler-manifest-DE4RdAiY.d.ts → compiler-manifest-DLxUY5kI.d.mts} +65 -11
- package/dist/{compiler-manifest-DE4RdAiY.d.mts → compiler-manifest-DLxUY5kI.d.ts} +65 -11
- package/dist/{index-Bi-f7HFT.d.ts → index-B_GM9E3q.d.ts} +1 -1
- package/dist/{index-BI6nWgaV.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 +8 -7
- package/dist/index.mjs +5 -4
- package/dist/install-integrity.json +92 -84
- 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/play-description.d.mts +6 -0
- package/dist/plays/play-description.d.ts +6 -0
- package/dist/plays/play-description.js +9 -0
- package/dist/plays/play-description.mjs +9 -0
- package/dist/plays/run-request.d.mts +2 -2
- package/dist/plays/run-request.d.ts +2 -2
- package/dist/release.d.mts +1 -1
- package/dist/release.d.ts +1 -1
- package/dist/release.js +2 -2
- package/dist/release.mjs +1 -1
- package/package.json +6 -1
- package/dist/cli/auth-P3MVDTXM.js +0 -39
- package/dist/cli/git-RNM45FX4.js +0 -23
- package/dist/cli/play-PDXR6JFB.js +0 -158
- package/dist/cli/search-QRWL7MFM.js +0 -27
- package/dist/cli/skills-V7EY75HB.js +0 -28
- package/dist/cli/tools-H5HLBCHH.js +0 -41
- package/dist/cli/update-472HA3LT.js +0 -28
|
@@ -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,
|
|
@@ -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
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
EXPLICIT_FRESHNESS_PLAY_AUTHORING_THRESHOLD,
|
|
2
3
|
normalizePlayAuthoringContractEdition,
|
|
3
4
|
playToolListGetterMode,
|
|
4
5
|
playCsvDatasetRowIdentity,
|
|
@@ -12,6 +13,9 @@ export type PlayRuntimeSecretValueSemantics =
|
|
|
12
13
|
export type PlayRuntimeFetchHttpFailureSemantics =
|
|
13
14
|
| 'return_response'
|
|
14
15
|
| 'throw_typed_error';
|
|
16
|
+
export type PlayRuntimeFreshnessSemantics =
|
|
17
|
+
| 'legacy_bucket'
|
|
18
|
+
| 'actual_age_with_operation_recovery';
|
|
15
19
|
|
|
16
20
|
/**
|
|
17
21
|
* Immutable worker-side projection of the semantics pinned by a Play artifact.
|
|
@@ -36,6 +40,7 @@ export type PlayRuntimeSemantics = Readonly<{
|
|
|
36
40
|
*/
|
|
37
41
|
receiptIdentityEdition: PlayAuthoringContractEdition | null;
|
|
38
42
|
}>;
|
|
43
|
+
freshness: PlayRuntimeFreshnessSemantics;
|
|
39
44
|
inlineChild: Readonly<{
|
|
40
45
|
/**
|
|
41
46
|
* Whether a scalar child may rely solely on its execution-time
|
|
@@ -56,7 +61,7 @@ export type PlayRuntimeSemantics = Readonly<{
|
|
|
56
61
|
|
|
57
62
|
type PlayRuntimeSemanticsTemplate = Omit<
|
|
58
63
|
PlayRuntimeSemantics,
|
|
59
|
-
'authoringContractEdition' | 'tools' | 'datasets'
|
|
64
|
+
'authoringContractEdition' | 'freshness' | 'tools' | 'datasets'
|
|
60
65
|
> & { tools: Omit<PlayRuntimeSemantics['tools'], 'listGetters'> };
|
|
61
66
|
|
|
62
67
|
function definePlayRuntimeSemantics(
|
|
@@ -68,6 +73,11 @@ function definePlayRuntimeSemantics(
|
|
|
68
73
|
...semantics,
|
|
69
74
|
secrets: Object.freeze({ ...semantics.secrets }),
|
|
70
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',
|
|
71
81
|
inlineChild: Object.freeze({ ...semantics.inlineChild }),
|
|
72
82
|
tools: Object.freeze({
|
|
73
83
|
...semantics.tools,
|
|
@@ -210,6 +220,7 @@ const PLAY_RUNTIME_SEMANTICS_BY_AUTHORING = {
|
|
|
210
220
|
fetch: {
|
|
211
221
|
httpFailure: 'throw_typed_error',
|
|
212
222
|
persistHttpFailure: false,
|
|
223
|
+
// Freshness policy does not create another fetch receipt identity.
|
|
213
224
|
receiptIdentityEdition: FETCH_RECEIPT_IDENTITIES.typedHttpFailure,
|
|
214
225
|
},
|
|
215
226
|
inlineChild: { runtimeOnly: true },
|
|
@@ -357,6 +357,7 @@ export function playRunnerReceiptStoreAdapterOptions() {
|
|
|
357
357
|
type RuntimeStepReceiptHandlers = RuntimeReceiptStoreHandlers;
|
|
358
358
|
|
|
359
359
|
type RuntimeToolMetadata = {
|
|
360
|
+
reusableResult?: { supported: boolean; unavailableReason: string | null };
|
|
360
361
|
provider?: string | null;
|
|
361
362
|
operation?: string | null;
|
|
362
363
|
extractors?: ToolResultMetadataInput['extractors'];
|
|
@@ -2510,6 +2511,15 @@ async function run(
|
|
|
2510
2511
|
? 'invocation'
|
|
2511
2512
|
: null;
|
|
2512
2513
|
},
|
|
2514
|
+
getToolReusableResultSupport: async (toolId) => {
|
|
2515
|
+
const support = (await getRuntimeToolMetadata(toolId)).reusableResult;
|
|
2516
|
+
if (!support) {
|
|
2517
|
+
throw new Error(
|
|
2518
|
+
`Tool ${toolId} has no reusable-result capability metadata. Deploy the runtime metadata reader before admitting explicit-freshness artifacts.`,
|
|
2519
|
+
);
|
|
2520
|
+
}
|
|
2521
|
+
return support;
|
|
2522
|
+
},
|
|
2513
2523
|
getToolRetryPolicy: async (toolId) => {
|
|
2514
2524
|
const metadata = await getRuntimeToolMetadata(toolId);
|
|
2515
2525
|
return {
|
|
@@ -7037,10 +7037,13 @@ export async function startRuntimeSheetDataset(
|
|
|
7037
7037
|
{
|
|
7038
7038
|
row: Record<string, unknown>;
|
|
7039
7039
|
inputIndex: number | null;
|
|
7040
|
+
sourceRowIndex: number | null;
|
|
7040
7041
|
source: Record<string, unknown>;
|
|
7042
|
+
rowOffset: number;
|
|
7041
7043
|
}
|
|
7042
7044
|
>();
|
|
7043
|
-
for (
|
|
7045
|
+
for (let rowOffset = 0; rowOffset < input.rows.length; rowOffset += 1) {
|
|
7046
|
+
const row = input.rows[rowOffset]!;
|
|
7044
7047
|
// Materializes projected CSV aliases as visible cells and drops internal
|
|
7045
7048
|
// __deepline* keys — a plain spread would silently lose the
|
|
7046
7049
|
// non-enumerable alias fields on the JSON payload boundary.
|
|
@@ -7048,31 +7051,37 @@ export async function startRuntimeSheetDataset(
|
|
|
7048
7051
|
const key =
|
|
7049
7052
|
resolveMapRowOutcomeKey(row) ??
|
|
7050
7053
|
derivePlayRowIdentity(cleanedRow, input.tableNamespace);
|
|
7054
|
+
const source =
|
|
7055
|
+
row.__deeplineSourceRow && typeof row.__deeplineSourceRow === 'object'
|
|
7056
|
+
? (sanitizePostgresJsonValue(row.__deeplineSourceRow) as Record<
|
|
7057
|
+
string,
|
|
7058
|
+
unknown
|
|
7059
|
+
>)
|
|
7060
|
+
: {};
|
|
7061
|
+
const sourceRowIndex = normalizeRuntimeMapInputIndex(source.rowIndex);
|
|
7051
7062
|
if (key && !uniqueRows.has(key)) {
|
|
7052
7063
|
uniqueRows.set(key, {
|
|
7053
7064
|
row: sanitizePostgresJsonValue(cleanedRow),
|
|
7054
|
-
source
|
|
7055
|
-
|
|
7056
|
-
? (sanitizePostgresJsonValue(row.__deeplineSourceRow) as Record<
|
|
7057
|
-
string,
|
|
7058
|
-
unknown
|
|
7059
|
-
>)
|
|
7060
|
-
: {},
|
|
7065
|
+
source,
|
|
7066
|
+
sourceRowIndex,
|
|
7061
7067
|
inputIndex: normalizeRuntimeMapInputIndex(
|
|
7062
7068
|
row[MAP_ROW_OUTCOME_RUNTIME_FIELDS.inputIndex],
|
|
7063
7069
|
),
|
|
7070
|
+
rowOffset,
|
|
7064
7071
|
});
|
|
7065
7072
|
}
|
|
7066
7073
|
}
|
|
7067
7074
|
const inputOffset = Math.max(0, Math.floor(input.inputOffset ?? 0));
|
|
7068
|
-
const rowEntries = [...uniqueRows.entries()].map(
|
|
7069
|
-
|
|
7070
|
-
|
|
7071
|
-
|
|
7072
|
-
|
|
7073
|
-
|
|
7074
|
-
|
|
7075
|
-
|
|
7075
|
+
const rowEntries = [...uniqueRows.entries()].map(([key, entry]) => ({
|
|
7076
|
+
key,
|
|
7077
|
+
row: entry.row,
|
|
7078
|
+
source: entry.source,
|
|
7079
|
+
// A CSV transformation may filter or reorder the physical source rows.
|
|
7080
|
+
// Its source ordinal must win over the compacted input index stamped by
|
|
7081
|
+
// ctx.dataset while preparing this page.
|
|
7082
|
+
inputIndex:
|
|
7083
|
+
entry.sourceRowIndex ?? entry.inputIndex ?? inputOffset + entry.rowOffset,
|
|
7084
|
+
}));
|
|
7076
7085
|
timings.push({
|
|
7077
7086
|
phase: 'normalize_rows',
|
|
7078
7087
|
ms: Date.now() - normalizeStartedAt,
|