@qvac/harness 0.7.1 → 1.0.0-rc.1
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/README.md +224 -0
- package/dist-lib/index.d.ts +55 -3
- package/dist-lib/index.js +721 -236
- package/dist-lib/lib/capabilities/completion-engine/abort-polyfill.d.ts +1 -0
- package/dist-lib/lib/capabilities/completion-engine/abort-polyfill.js +10 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/answering.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/answering.js +17 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/approval.d.ts +4 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/approval.js +29 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/direct-search.d.ts +4 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/direct-search.js +73 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/execute.d.ts +41 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/execute.js +49 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/executing.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/executing.js +102 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/execution-plan.d.ts +47 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/execution-plan.js +87 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/finishing.d.ts +2 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/finishing.js +32 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/inspect-preload.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/inspect-preload.js +75 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/inspecting.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/inspecting.js +152 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/loading-skill.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/loading-skill.js +94 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/preload-failure.d.ts +9 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/preload-failure.js +11 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/preloading.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/preloading.js +114 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/preparing.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/preparing.js +198 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/rejecting.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/rejecting.js +71 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/rejection.d.ts +18 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/rejection.js +70 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/resolving.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/resolving.js +60 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/stream.d.ts +21 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/stream.js +48 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/streaming.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/streaming.js +156 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/wrap-up.d.ts +7 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/wrap-up.js +61 -0
- package/dist-lib/lib/capabilities/completion-engine/driver.d.ts +25 -0
- package/dist-lib/lib/capabilities/completion-engine/driver.js +553 -0
- package/dist-lib/lib/capabilities/completion-engine/durable.d.ts +61 -0
- package/dist-lib/lib/capabilities/completion-engine/durable.js +150 -0
- package/dist-lib/lib/capabilities/completion-engine/operations.d.ts +6 -0
- package/dist-lib/lib/capabilities/completion-engine/operations.js +239 -0
- package/dist-lib/lib/capabilities/completion-engine/ports.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/ports.js +267 -0
- package/dist-lib/lib/capabilities/completion-engine/session.d.ts +76 -0
- package/dist-lib/lib/capabilities/completion-engine/session.js +70 -0
- package/dist-lib/lib/capabilities/completion-loop/index.d.ts +80 -0
- package/dist-lib/lib/capabilities/completion-loop/index.js +158 -0
- package/dist-lib/lib/capabilities/completion-loop/metrics.d.ts +5 -0
- package/dist-lib/lib/capabilities/completion-loop/metrics.js +29 -0
- package/dist-lib/lib/capabilities/completion-loop/round.d.ts +33 -0
- package/dist-lib/lib/capabilities/completion-loop/round.js +353 -0
- package/dist-lib/lib/capabilities/completion-loop/tool-execution.d.ts +60 -0
- package/dist-lib/lib/capabilities/completion-loop/tool-execution.js +270 -0
- package/dist-lib/lib/capabilities/completion-loop/turns.d.ts +23 -0
- package/dist-lib/lib/capabilities/completion-loop/turns.js +112 -0
- package/dist-lib/lib/capabilities/completion-runtime.d.ts +46 -0
- package/dist-lib/lib/capabilities/completion-runtime.js +74 -0
- package/dist-lib/lib/capabilities/completion.d.ts +18 -335
- package/dist-lib/lib/capabilities/completion.js +20 -1301
- package/dist-lib/lib/capabilities/download.d.ts +1 -1
- package/dist-lib/lib/capabilities/download.js +7 -2
- package/dist-lib/lib/capabilities/invoke.js +5 -3
- package/dist-lib/lib/capabilities/tool-plan.d.ts +35 -0
- package/dist-lib/lib/capabilities/tool-plan.js +66 -0
- package/dist-lib/lib/capabilities/tool-turns.d.ts +11 -2
- package/dist-lib/lib/capabilities/tool-turns.js +30 -2
- package/dist-lib/lib/capabilities/types.d.ts +287 -0
- package/dist-lib/lib/capabilities/types.js +1 -0
- package/dist-lib/lib/completion-lifecycle.d.ts +14 -0
- package/dist-lib/lib/completion-lifecycle.js +126 -0
- package/dist-lib/lib/connect.d.ts +10 -3
- package/dist-lib/lib/connect.js +329 -23
- package/dist-lib/lib/contract-types.d.ts +19 -2
- package/dist-lib/lib/harness-lifecycle.d.ts +66 -0
- package/dist-lib/lib/harness-lifecycle.js +129 -0
- package/dist-lib/lib/helpers/detach-json.d.ts +1 -0
- package/dist-lib/lib/helpers/detach-json.js +146 -0
- package/dist-lib/lib/helpers/markup-scanner.d.ts +26 -0
- package/dist-lib/lib/helpers/markup-scanner.js +156 -0
- package/dist-lib/lib/helpers/markup-stream-filter.js +138 -35
- package/dist-lib/lib/helpers/race-abort.d.ts +9 -4
- package/dist-lib/lib/helpers/race-abort.js +2 -7
- package/dist-lib/lib/helpers/recovery-required.d.ts +1 -0
- package/dist-lib/lib/helpers/recovery-required.js +1 -0
- package/dist-lib/lib/helpers/run-outcome.d.ts +10 -2
- package/dist-lib/lib/helpers/run-outcome.js +8 -5
- package/dist-lib/lib/helpers/tool-call-markup.d.ts +2 -0
- package/dist-lib/lib/helpers/tool-call-markup.js +197 -82
- package/dist-lib/lib/hooks/built-in/arg-repair.d.ts +1 -0
- package/dist-lib/lib/hooks/built-in/arg-repair.js +1 -0
- package/dist-lib/lib/hooks/built-in/egress.d.ts +1 -0
- package/dist-lib/lib/hooks/built-in/egress.js +2 -1
- package/dist-lib/lib/hooks/built-in/sandbox.d.ts +1 -0
- package/dist-lib/lib/hooks/built-in/sandbox.js +6 -1
- package/dist-lib/lib/hooks/built-in/skill-catalog.d.ts +1 -0
- package/dist-lib/lib/hooks/built-in/skill-catalog.js +2 -1
- package/dist-lib/lib/hooks/pipeline.d.ts +4 -1
- package/dist-lib/lib/hooks/pipeline.js +24 -2
- package/dist-lib/lib/hooks/run.d.ts +2 -1
- package/dist-lib/lib/hooks/run.js +19 -7
- package/dist-lib/lib/hooks/types.d.ts +17 -6
- package/dist-lib/lib/machine/completion/actions/approval.d.ts +3 -0
- package/dist-lib/lib/machine/completion/actions/approval.js +16 -0
- package/dist-lib/lib/machine/completion/actions/completion.d.ts +23 -0
- package/dist-lib/lib/machine/completion/actions/completion.js +142 -0
- package/dist-lib/lib/machine/completion/actions/skills.d.ts +9 -0
- package/dist-lib/lib/machine/completion/actions/skills.js +83 -0
- package/dist-lib/lib/machine/completion/actions/tools.d.ts +16 -0
- package/dist-lib/lib/machine/completion/actions/tools.js +173 -0
- package/dist-lib/lib/machine/completion/context.d.ts +9 -0
- package/dist-lib/lib/machine/completion/context.js +44 -0
- package/dist-lib/lib/machine/completion/contract.d.ts +6 -0
- package/dist-lib/lib/machine/completion/contract.js +1 -0
- package/dist-lib/lib/machine/completion/guards/retry.d.ts +9 -0
- package/dist-lib/lib/machine/completion/guards/retry.js +25 -0
- package/dist-lib/lib/machine/completion/implementations.d.ts +81 -0
- package/dist-lib/lib/machine/completion/implementations.js +152 -0
- package/dist-lib/lib/machine/completion/machine.d.ts +375 -0
- package/dist-lib/lib/machine/completion/machine.js +511 -0
- package/dist-lib/lib/machine/completion/ports.d.ts +65 -0
- package/dist-lib/lib/machine/completion/ports.js +224 -0
- package/dist-lib/lib/machine/completion/rules.d.ts +39 -0
- package/dist-lib/lib/machine/completion/rules.js +115 -0
- package/dist-lib/lib/machine/completion/types.d.ts +493 -0
- package/dist-lib/lib/machine/completion/types.js +1 -0
- package/dist-lib/lib/machine/engine.d.ts +93 -0
- package/dist-lib/lib/machine/engine.js +483 -0
- package/dist-lib/lib/machine/errors.d.ts +44 -0
- package/dist-lib/lib/machine/errors.js +373 -0
- package/dist-lib/lib/machine/guards.d.ts +2 -0
- package/dist-lib/lib/machine/guards.js +6 -0
- package/dist-lib/lib/machine/index.d.ts +20 -0
- package/dist-lib/lib/machine/index.js +11 -0
- package/dist-lib/lib/machine/replay-output.d.ts +24 -0
- package/dist-lib/lib/machine/replay-output.js +50 -0
- package/dist-lib/lib/machine/stable-stringify.d.ts +1 -0
- package/dist-lib/lib/machine/stable-stringify.js +11 -0
- package/dist-lib/lib/models/model-pool.d.ts +14 -6
- package/dist-lib/lib/models/model-pool.js +95 -30
- package/dist-lib/lib/run-log/history-codec.d.ts +23 -0
- package/dist-lib/lib/run-log/history-codec.js +973 -0
- package/dist-lib/lib/run-log/patch.d.ts +6 -0
- package/dist-lib/lib/run-log/patch.js +93 -0
- package/dist-lib/lib/run-log/persisted-actor.d.ts +18 -0
- package/dist-lib/lib/run-log/persisted-actor.js +218 -0
- package/dist-lib/lib/run-log/protocol-types.d.ts +153 -0
- package/dist-lib/lib/run-log/protocol-types.js +1 -0
- package/dist-lib/lib/run-log/protocol.d.ts +27 -0
- package/dist-lib/lib/run-log/protocol.js +1056 -0
- package/dist-lib/lib/run-log/replay.d.ts +25 -0
- package/dist-lib/lib/run-log/replay.js +129 -0
- package/dist-lib/lib/run-log/run-journal.d.ts +125 -0
- package/dist-lib/lib/run-log/run-journal.js +783 -0
- package/dist-lib/lib/run-log-inspect.d.ts +25 -0
- package/dist-lib/lib/run-log-inspect.js +627 -0
- package/dist-lib/lib/run-log-protocol.d.ts +2 -0
- package/dist-lib/lib/run-log-protocol.js +1 -0
- package/dist-lib/lib/run-log-redact.d.ts +27 -0
- package/dist-lib/lib/run-log-redact.js +1107 -0
- package/dist-lib/lib/serve.js +307 -11
- package/dist-lib/lib/spawn.js +3 -2
- package/dist-lib/lib/tool-call-display.d.ts +1 -21
- package/dist-lib/lib/tools/exec/adapters/python/wheels.js +2 -1
- package/dist-lib/lib/tools/exec.d.ts +2 -1
- package/dist-lib/lib/tools/exec.js +57 -2
- package/dist-lib/lib/tools/generate-image.js +1 -1
- package/dist-lib/lib/tools/generate-music.js +1 -1
- package/dist-lib/lib/tools/http-request.d.ts +2 -1
- package/dist-lib/lib/tools/http-request.js +2 -1
- package/dist-lib/lib/tools/mcp-call.js +8 -1
- package/dist-lib/lib/tools/operations/builders.d.ts +1 -0
- package/dist-lib/lib/tools/operations/builders.js +1 -0
- package/dist-lib/lib/tools/operations/compile.js +11 -1
- package/dist-lib/lib/tools/operations/manifest.js +1 -0
- package/dist-lib/lib/tools/outbound-guard.d.ts +8 -6
- package/dist-lib/lib/tools/outbound-guard.js +25 -8
- package/dist-lib/lib/tools/skills/catalog.d.ts +1 -0
- package/dist-lib/lib/tools/skills/catalog.js +35 -0
- package/dist-lib/lib/tools/skills/cli-schema-types.d.ts +1 -0
- package/dist-lib/lib/tools/skills/cli-schema.js +25 -0
- package/dist-lib/lib/wire.d.ts +5 -0
- package/dist-lib/lib/wire.js +128 -5
- package/dist-lib/schema/harness.js +20 -5
- package/dist-lib/spec/hrpc/messages.js +95 -15
- package/dist-lib/spec/hyperschema/index.js +95 -15
- package/dist-lib/spec/hyperschema/schema.json +69 -1
- package/dist-lib/spec/hyperschema/types.d.ts +10 -3
- package/package.json +20 -2
- package/patches/@qvac%2Finference@0.20.0.patch +67 -0
- package/patches/bare-stow@0.1.5.patch +68 -0
- package/patches/hypercore-blob-server@1.15.0.patch +15 -0
- package/patches/localwatch@1.4.3.patch +13 -0
- package/patches/pyodide@314.0.2.patch +144 -0
|
@@ -0,0 +1,1056 @@
|
|
|
1
|
+
import { isRecord } from './patch.js';
|
|
2
|
+
import { stableStringify } from '../machine/stable-stringify.js';
|
|
3
|
+
import { foldMachineEventHistory, FAILURE_RESULT_REF_MACHINE_VERSION, materializeSnapshotHistory, requestMessages } from './history-codec.js';
|
|
4
|
+
export const RUN_LOG_PROTOCOL_VERSION = 1;
|
|
5
|
+
export const RUN_LOG_VERSION = 5;
|
|
6
|
+
export function transitionName(named) {
|
|
7
|
+
if (typeof named === 'string')
|
|
8
|
+
return named;
|
|
9
|
+
if (named && (typeof named === 'object' || typeof named === 'function')) {
|
|
10
|
+
const guards = Reflect.get(named, 'guards');
|
|
11
|
+
const name = Reflect.get(named, 'name');
|
|
12
|
+
const type = Reflect.get(named, 'type');
|
|
13
|
+
if (Array.isArray(guards)) {
|
|
14
|
+
return `${typeof name === 'string' ? name : 'inline'}(${guards
|
|
15
|
+
.map(transitionName)
|
|
16
|
+
.join(', ')})`;
|
|
17
|
+
}
|
|
18
|
+
if (typeof type === 'string') {
|
|
19
|
+
return type;
|
|
20
|
+
}
|
|
21
|
+
if (typeof name === 'string') {
|
|
22
|
+
return name;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return 'inline';
|
|
26
|
+
}
|
|
27
|
+
const trustedBoots = new WeakSet();
|
|
28
|
+
const trustedJson = new WeakSet();
|
|
29
|
+
const redactedJson = new WeakSet();
|
|
30
|
+
export class InvalidEngineStep extends Error {
|
|
31
|
+
constructor(message) {
|
|
32
|
+
super(message);
|
|
33
|
+
this.name = 'InvalidEngineStep';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function pendingCall(value) {
|
|
37
|
+
if (!isRecord(value)) {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
id: typeof value.id === 'string' ? value.id : undefined,
|
|
42
|
+
name: typeof value.name === 'string' ? value.name : undefined,
|
|
43
|
+
args: value.args,
|
|
44
|
+
arguments: value.arguments
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function pendingContext(value) {
|
|
48
|
+
if (!isRecord(value)) {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
const currentCall = pendingCall(value.currentCall);
|
|
52
|
+
const toolQueue = Array.isArray(value.toolQueue)
|
|
53
|
+
? value.toolQueue.map(pendingCall).filter((call) => call !== undefined)
|
|
54
|
+
: undefined;
|
|
55
|
+
const lastRound = isRecord(value.lastRound) ? value.lastRound : undefined;
|
|
56
|
+
const toolCalls = Array.isArray(lastRound?.toolCalls)
|
|
57
|
+
? lastRound.toolCalls.map(pendingCall).filter((call) => call !== undefined)
|
|
58
|
+
: undefined;
|
|
59
|
+
const pending = isRecord(value.pendingCall) ? value.pendingCall : undefined;
|
|
60
|
+
const skillPreload = isRecord(value.skillPreload) ? value.skillPreload : undefined;
|
|
61
|
+
return {
|
|
62
|
+
runId: typeof value.runId === 'string' ? value.runId : undefined,
|
|
63
|
+
round: typeof value.round === 'number' ? value.round : undefined,
|
|
64
|
+
currentCall,
|
|
65
|
+
pendingCall: pending ? { arguments: pending.arguments } : undefined,
|
|
66
|
+
skillPreload: skillPreload
|
|
67
|
+
? {
|
|
68
|
+
name: typeof skillPreload.name === 'string' ? skillPreload.name : undefined,
|
|
69
|
+
args: skillPreload.args
|
|
70
|
+
}
|
|
71
|
+
: undefined,
|
|
72
|
+
toolQueue,
|
|
73
|
+
lastRound: toolCalls ? { toolCalls } : undefined
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function pendingSnapshot(value) {
|
|
77
|
+
if (!isRecord(value)) {
|
|
78
|
+
return {};
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
value: value.value,
|
|
82
|
+
historyValue: value.historyValue,
|
|
83
|
+
context: pendingContext(value.context)
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
export function pendingToolArgs(snapshot, settled) {
|
|
87
|
+
const { value, historyValue, context } = pendingSnapshot(snapshot);
|
|
88
|
+
const isSettled = (id) => id !== undefined && settled?.(`${context?.runId}:round-${context?.round}:${id}:execute`);
|
|
89
|
+
const isResolving = JSON.stringify(value ?? '').includes('resolving') ||
|
|
90
|
+
(!JSON.stringify(value ?? '').includes('"active"') &&
|
|
91
|
+
JSON.stringify(historyValue ?? '').includes('resolving'));
|
|
92
|
+
return [
|
|
93
|
+
...(context?.skillPreload?.args !== undefined &&
|
|
94
|
+
!isSettled(`preload-${context.skillPreload.name ?? ''}`)
|
|
95
|
+
? [context.skillPreload.args]
|
|
96
|
+
: []),
|
|
97
|
+
...(context?.currentCall && !isSettled(context.currentCall.id)
|
|
98
|
+
? [context.currentCall.args]
|
|
99
|
+
: []),
|
|
100
|
+
...(context?.pendingCall ? [context.pendingCall.arguments] : []),
|
|
101
|
+
...(context?.toolQueue?.filter((call) => !isSettled(call.id)).map((call) => call.args) ?? []),
|
|
102
|
+
...(isResolving
|
|
103
|
+
? (context?.lastRound?.toolCalls
|
|
104
|
+
?.filter((call) => !isSettled(call.id))
|
|
105
|
+
.map((call) => call.arguments) ?? [])
|
|
106
|
+
: [])
|
|
107
|
+
].filter((args) => args !== undefined);
|
|
108
|
+
}
|
|
109
|
+
export function pendingCallNames(snapshot) {
|
|
110
|
+
const { value, historyValue, context } = pendingSnapshot(snapshot);
|
|
111
|
+
const isResolving = JSON.stringify(value ?? '').includes('resolving') ||
|
|
112
|
+
(!JSON.stringify(value ?? '').includes('"active"') &&
|
|
113
|
+
JSON.stringify(historyValue ?? '').includes('resolving'));
|
|
114
|
+
const names = new Map();
|
|
115
|
+
const put = (id, name) => {
|
|
116
|
+
if (id === undefined || name === undefined)
|
|
117
|
+
return;
|
|
118
|
+
names.set(`${context?.runId}:round-${context?.round}:${id}:execute`, name);
|
|
119
|
+
};
|
|
120
|
+
if (context?.skillPreload?.args !== undefined) {
|
|
121
|
+
put(`preload-${context.skillPreload.name ?? ''}`, context.skillPreload.name);
|
|
122
|
+
}
|
|
123
|
+
put(context?.currentCall?.id, context?.currentCall?.name);
|
|
124
|
+
for (const call of context?.toolQueue ?? []) {
|
|
125
|
+
put(call.id, call.name);
|
|
126
|
+
}
|
|
127
|
+
if (isResolving) {
|
|
128
|
+
for (const call of context?.lastRound?.toolCalls ?? []) {
|
|
129
|
+
put(call.id, call.name);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return names;
|
|
133
|
+
}
|
|
134
|
+
export function pendingCallArgs(snapshot, settled) {
|
|
135
|
+
const { value, historyValue, context } = pendingSnapshot(snapshot);
|
|
136
|
+
const isSettled = (receiptId) => settled?.(receiptId) === true;
|
|
137
|
+
const isResolving = JSON.stringify(value ?? '').includes('resolving') ||
|
|
138
|
+
(!JSON.stringify(value ?? '').includes('"active"') &&
|
|
139
|
+
JSON.stringify(historyValue ?? '').includes('resolving'));
|
|
140
|
+
const args = new Map();
|
|
141
|
+
const put = (id, callArgs) => {
|
|
142
|
+
if (id === undefined)
|
|
143
|
+
return;
|
|
144
|
+
const receiptId = `${context?.runId}:round-${context?.round}:${id}:execute`;
|
|
145
|
+
if (!isSettled(receiptId)) {
|
|
146
|
+
args.set(receiptId, callArgs);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
if (context?.skillPreload?.args !== undefined) {
|
|
150
|
+
put(`preload-${context.skillPreload.name ?? ''}`, context.skillPreload.args);
|
|
151
|
+
}
|
|
152
|
+
if (context?.currentCall) {
|
|
153
|
+
put(context.currentCall.id, context.currentCall.args);
|
|
154
|
+
}
|
|
155
|
+
for (const call of context?.toolQueue ?? []) {
|
|
156
|
+
put(call.id, call.args);
|
|
157
|
+
}
|
|
158
|
+
if (isResolving) {
|
|
159
|
+
for (const call of context?.lastRound?.toolCalls ?? []) {
|
|
160
|
+
put(call.id, call.arguments);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return args;
|
|
164
|
+
}
|
|
165
|
+
export function unsettledRedactedIds(redactedPendingIds, receipts) {
|
|
166
|
+
const settledIds = new Set([...receipts].filter((receipt) => receipt.status === 'result').map((receipt) => receipt.id));
|
|
167
|
+
return redactedPendingIds.filter((id) => !settledIds.has(id));
|
|
168
|
+
}
|
|
169
|
+
export function createEngineBoot() {
|
|
170
|
+
return freezeBoot(emptyBoot());
|
|
171
|
+
}
|
|
172
|
+
export function prepareEngineBoot(value) {
|
|
173
|
+
if (value === undefined) {
|
|
174
|
+
return createEngineBoot();
|
|
175
|
+
}
|
|
176
|
+
if (trustedBoots.has(value)) {
|
|
177
|
+
return value;
|
|
178
|
+
}
|
|
179
|
+
const captured = captureJson(value);
|
|
180
|
+
validateBoot(captured);
|
|
181
|
+
return freezeBoot(captured);
|
|
182
|
+
}
|
|
183
|
+
export function captureRunRecord(record) {
|
|
184
|
+
const captured = captureJson(record);
|
|
185
|
+
if (!isRunRecord(captured)) {
|
|
186
|
+
throw new InvalidEngineStep('Malformed run record');
|
|
187
|
+
}
|
|
188
|
+
return captured;
|
|
189
|
+
}
|
|
190
|
+
export function brandCapturedRecord(value) {
|
|
191
|
+
if (!value || typeof value !== 'object' || redactedJson.has(value)) {
|
|
192
|
+
return value;
|
|
193
|
+
}
|
|
194
|
+
for (const key of Object.keys(value)) {
|
|
195
|
+
brandCapturedRecord(Reflect.get(value, key));
|
|
196
|
+
}
|
|
197
|
+
Object.freeze(value);
|
|
198
|
+
trustedJson.add(value);
|
|
199
|
+
redactedJson.add(value);
|
|
200
|
+
return value;
|
|
201
|
+
}
|
|
202
|
+
export function isCapturedJson(value) {
|
|
203
|
+
return isRecord(value) && trustedJson.has(value);
|
|
204
|
+
}
|
|
205
|
+
export function createEngineStep(seq, expectedLength, records) {
|
|
206
|
+
assertCounter(seq, 'step sequence');
|
|
207
|
+
assertCounter(expectedLength, 'expected length');
|
|
208
|
+
if (!Array.isArray(records) || records.length === 0) {
|
|
209
|
+
throw new InvalidEngineStep('Engine step must contain at least one run record');
|
|
210
|
+
}
|
|
211
|
+
for (let index = 0; index < records.length; index++) {
|
|
212
|
+
const descriptor = Object.getOwnPropertyDescriptor(records, String(index));
|
|
213
|
+
if (!descriptor || !('value' in descriptor) || !trustedJson.has(descriptor.value)) {
|
|
214
|
+
throw new InvalidEngineStep('Engine step records must be captured before use');
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
const frozenRecords = Object.freeze(records.slice());
|
|
218
|
+
trustedJson.add(frozenRecords);
|
|
219
|
+
const step = Object.freeze({ seq, expectedLength, records: frozenRecords });
|
|
220
|
+
trustedJson.add(step);
|
|
221
|
+
return step;
|
|
222
|
+
}
|
|
223
|
+
export function captureEngineStep(step) {
|
|
224
|
+
if (step && typeof step === 'object' && trustedJson.has(step))
|
|
225
|
+
return step;
|
|
226
|
+
if (!step || typeof step !== 'object') {
|
|
227
|
+
throw new InvalidEngineStep('Engine step is required');
|
|
228
|
+
}
|
|
229
|
+
const seq = readDataProperty(step, 'seq');
|
|
230
|
+
const expectedLength = readDataProperty(step, 'expectedLength');
|
|
231
|
+
assertCounter(seq, 'step sequence');
|
|
232
|
+
assertCounter(expectedLength, 'expected length');
|
|
233
|
+
const recordsValue = readDataProperty(step, 'records');
|
|
234
|
+
if (!Array.isArray(recordsValue)) {
|
|
235
|
+
throw new InvalidEngineStep('Engine step records must be an array');
|
|
236
|
+
}
|
|
237
|
+
const records = [];
|
|
238
|
+
for (let index = 0; index < recordsValue.length; index++) {
|
|
239
|
+
const descriptor = Object.getOwnPropertyDescriptor(recordsValue, String(index));
|
|
240
|
+
if (!descriptor || !('value' in descriptor)) {
|
|
241
|
+
throw new InvalidEngineStep('Engine step contains a missing or accessor record');
|
|
242
|
+
}
|
|
243
|
+
records.push(captureRunRecord(descriptor.value));
|
|
244
|
+
}
|
|
245
|
+
return createEngineStep(seq, expectedLength, records);
|
|
246
|
+
}
|
|
247
|
+
function emptyBoot() {
|
|
248
|
+
return {
|
|
249
|
+
protocolVersion: RUN_LOG_PROTOCOL_VERSION,
|
|
250
|
+
length: 0,
|
|
251
|
+
nextSeq: 0,
|
|
252
|
+
machineVersion: null,
|
|
253
|
+
xstateVersion: null,
|
|
254
|
+
metadata: null,
|
|
255
|
+
eventState: null,
|
|
256
|
+
deltasSinceCheckpoint: 0,
|
|
257
|
+
redactedPendingArgs: null,
|
|
258
|
+
redactedPendingIds: [],
|
|
259
|
+
unredactedLength: 0,
|
|
260
|
+
restoredRedacted: false,
|
|
261
|
+
forkBeforeWrite: [],
|
|
262
|
+
receipts: [],
|
|
263
|
+
intentArgs: []
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
export function requestFromBoot(boot) {
|
|
267
|
+
validateEngineBoot(boot);
|
|
268
|
+
if (!boot.metadata) {
|
|
269
|
+
throw new InvalidEngineStep('Run boot has no original request');
|
|
270
|
+
}
|
|
271
|
+
return copy(boot.metadata.request);
|
|
272
|
+
}
|
|
273
|
+
export function contextFromBoot(boot) {
|
|
274
|
+
validateEngineBoot(boot);
|
|
275
|
+
const snapshot = materializeSnapshotHistory(boot.snapshot, historyBase(boot), allowsLegacyFailureResults(boot));
|
|
276
|
+
if (!isRecord(snapshot) || !isRecord(snapshot.context)) {
|
|
277
|
+
return undefined;
|
|
278
|
+
}
|
|
279
|
+
return copy(snapshot.context);
|
|
280
|
+
}
|
|
281
|
+
export function buildEngineBoot(records, opts) {
|
|
282
|
+
if (!Array.isArray(records)) {
|
|
283
|
+
throw new InvalidEngineStep('Run records must be an array');
|
|
284
|
+
}
|
|
285
|
+
assertCounter(opts?.nextSeq, 'nextSeq');
|
|
286
|
+
const boot = emptyBoot();
|
|
287
|
+
for (const raw of records) {
|
|
288
|
+
foldRecord(boot, raw);
|
|
289
|
+
}
|
|
290
|
+
boot.nextSeq = opts.nextSeq;
|
|
291
|
+
validateBoot(boot);
|
|
292
|
+
return freezeBoot(boot);
|
|
293
|
+
}
|
|
294
|
+
export function applyEngineStep(boot, step) {
|
|
295
|
+
const capturedBoot = prepareEngineBoot(boot);
|
|
296
|
+
const capturedStep = captureEngineStep(step);
|
|
297
|
+
if (!capturedStep || typeof capturedStep !== 'object') {
|
|
298
|
+
throw new InvalidEngineStep('Engine step is required');
|
|
299
|
+
}
|
|
300
|
+
assertCounter(capturedStep.seq, 'step sequence');
|
|
301
|
+
assertCounter(capturedStep.expectedLength, 'expected length');
|
|
302
|
+
if (capturedStep.seq !== capturedBoot.nextSeq) {
|
|
303
|
+
throw new InvalidEngineStep(`Unexpected engine step sequence ${capturedStep.seq}; expected ${capturedBoot.nextSeq}`);
|
|
304
|
+
}
|
|
305
|
+
if (capturedStep.expectedLength !== capturedBoot.length) {
|
|
306
|
+
throw new InvalidEngineStep(`Unexpected engine step length ${capturedStep.expectedLength}; expected ${capturedBoot.length}`);
|
|
307
|
+
}
|
|
308
|
+
if (!Array.isArray(capturedStep.records) || capturedStep.records.length === 0) {
|
|
309
|
+
throw new InvalidEngineStep('Engine step must contain at least one run record');
|
|
310
|
+
}
|
|
311
|
+
if (!Number.isSafeInteger(capturedBoot.length + capturedStep.records.length)) {
|
|
312
|
+
throw new InvalidEngineStep('Engine step exceeds the supported run length');
|
|
313
|
+
}
|
|
314
|
+
if (capturedBoot.nextSeq === Number.MAX_SAFE_INTEGER) {
|
|
315
|
+
throw new InvalidEngineStep('Engine step sequence exceeds the supported range');
|
|
316
|
+
}
|
|
317
|
+
const next = cloneBoot(capturedBoot);
|
|
318
|
+
for (const record of capturedStep.records) {
|
|
319
|
+
foldRecord(next, record);
|
|
320
|
+
}
|
|
321
|
+
next.nextSeq++;
|
|
322
|
+
validateBootEnvelope(next);
|
|
323
|
+
return freezeBoot(next);
|
|
324
|
+
}
|
|
325
|
+
function validateBoot(boot) {
|
|
326
|
+
if (!boot ||
|
|
327
|
+
typeof boot !== 'object' ||
|
|
328
|
+
!Object.hasOwn(boot, 'metadata') ||
|
|
329
|
+
boot.protocolVersion !== RUN_LOG_PROTOCOL_VERSION) {
|
|
330
|
+
throw new InvalidEngineStep('Unsupported engine boot protocol version');
|
|
331
|
+
}
|
|
332
|
+
for (const key of [
|
|
333
|
+
'length',
|
|
334
|
+
'nextSeq',
|
|
335
|
+
'machineVersion',
|
|
336
|
+
'xstateVersion',
|
|
337
|
+
'deltasSinceCheckpoint',
|
|
338
|
+
'redactedPendingArgs',
|
|
339
|
+
'redactedPendingIds',
|
|
340
|
+
'unredactedLength',
|
|
341
|
+
'restoredRedacted',
|
|
342
|
+
'forkBeforeWrite',
|
|
343
|
+
'receipts',
|
|
344
|
+
'intentArgs',
|
|
345
|
+
'eventState'
|
|
346
|
+
]) {
|
|
347
|
+
if (!Object.hasOwn(boot, key)) {
|
|
348
|
+
throw new InvalidEngineStep(`Engine boot is missing ${key}`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
assertCounter(boot.length, 'boot length');
|
|
352
|
+
assertCounter(boot.nextSeq, 'boot nextSeq');
|
|
353
|
+
assertCounter(boot.deltasSinceCheckpoint, 'delta count');
|
|
354
|
+
assertCounter(boot.unredactedLength, 'unredacted length');
|
|
355
|
+
if (!Array.isArray(boot.receipts) ||
|
|
356
|
+
!Array.isArray(boot.intentArgs) ||
|
|
357
|
+
!Array.isArray(boot.forkBeforeWrite) ||
|
|
358
|
+
!Array.isArray(boot.redactedPendingIds) ||
|
|
359
|
+
boot.redactedPendingIds.some((id) => typeof id !== 'string') ||
|
|
360
|
+
boot.nextSeq > boot.length ||
|
|
361
|
+
boot.unredactedLength > boot.length ||
|
|
362
|
+
boot.deltasSinceCheckpoint > boot.length ||
|
|
363
|
+
(boot.redactedPendingArgs !== null && typeof boot.redactedPendingArgs !== 'boolean') ||
|
|
364
|
+
boot.restoredRedacted !== (boot.redactedPendingArgs === true)) {
|
|
365
|
+
throw new InvalidEngineStep('Malformed engine boot maps');
|
|
366
|
+
}
|
|
367
|
+
if (boot.metadata === null) {
|
|
368
|
+
if (boot.machineVersion !== null ||
|
|
369
|
+
boot.xstateVersion !== null ||
|
|
370
|
+
boot.length !== 0 ||
|
|
371
|
+
boot.snapshot !== undefined ||
|
|
372
|
+
boot.eventState !== null ||
|
|
373
|
+
boot.snapshotSavedAt !== undefined ||
|
|
374
|
+
boot.firstSavedAt !== undefined ||
|
|
375
|
+
boot.deltasSinceCheckpoint !== 0 ||
|
|
376
|
+
boot.unredactedLength !== 0 ||
|
|
377
|
+
boot.redactedPendingArgs !== null ||
|
|
378
|
+
boot.redactedPendingIds.length !== 0 ||
|
|
379
|
+
boot.restoredRedacted ||
|
|
380
|
+
boot.receipts.length !== 0 ||
|
|
381
|
+
boot.intentArgs.length !== 0 ||
|
|
382
|
+
boot.forkBeforeWrite.length !== 0) {
|
|
383
|
+
throw new InvalidEngineStep('Engine boot metadata does not match its version fields');
|
|
384
|
+
}
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
if (typeof boot.metadata !== 'object') {
|
|
388
|
+
throw new InvalidEngineStep('Engine boot is missing trusted metadata');
|
|
389
|
+
}
|
|
390
|
+
if (boot.machineVersion !== boot.metadata.machine ||
|
|
391
|
+
boot.xstateVersion !== (boot.metadata.xstate ?? null) ||
|
|
392
|
+
boot.metadata.version !== RUN_LOG_VERSION ||
|
|
393
|
+
typeof boot.metadata.runId !== 'string' ||
|
|
394
|
+
boot.metadata.runId.length === 0 ||
|
|
395
|
+
!Object.hasOwn(boot.metadata, 'request') ||
|
|
396
|
+
!validBudget(boot.metadata.budget) ||
|
|
397
|
+
boot.length < 1 ||
|
|
398
|
+
boot.nextSeq < 1 ||
|
|
399
|
+
(boot.metadata.requestDigest !== undefined &&
|
|
400
|
+
(typeof boot.metadata.requestDigest !== 'string' ||
|
|
401
|
+
typeof boot.metadata.digestRunId !== 'string' ||
|
|
402
|
+
boot.metadata.digestRunId.length === 0)) ||
|
|
403
|
+
(boot.metadata.startedAt !== undefined && !validTime(boot.metadata.startedAt)) ||
|
|
404
|
+
(boot.metadata.xstate !== undefined && typeof boot.metadata.xstate !== 'string')) {
|
|
405
|
+
throw new InvalidEngineStep('Engine boot metadata does not match its version fields');
|
|
406
|
+
}
|
|
407
|
+
if (boot.snapshot === undefined &&
|
|
408
|
+
(boot.snapshotSavedAt !== undefined || boot.deltasSinceCheckpoint > 0)) {
|
|
409
|
+
throw new InvalidEngineStep('Engine boot has checkpoint counters without a snapshot');
|
|
410
|
+
}
|
|
411
|
+
if (boot.firstSavedAt !== undefined && boot.snapshotSavedAt === undefined) {
|
|
412
|
+
throw new InvalidEngineStep('Engine boot has a first checkpoint time without a snapshot');
|
|
413
|
+
}
|
|
414
|
+
if ((boot.snapshotSavedAt !== undefined && !validTime(boot.snapshotSavedAt)) ||
|
|
415
|
+
(boot.firstSavedAt !== undefined && !validTime(boot.firstSavedAt))) {
|
|
416
|
+
throw new InvalidEngineStep('Engine boot has invalid checkpoint timestamps');
|
|
417
|
+
}
|
|
418
|
+
if (boot.snapshot !== undefined && !isRecord(boot.snapshot)) {
|
|
419
|
+
throw new InvalidEngineStep('Engine boot checkpoint is not a persisted snapshot');
|
|
420
|
+
}
|
|
421
|
+
if (boot.eventState !== null && !isRecord(boot.eventState)) {
|
|
422
|
+
throw new InvalidEngineStep('Engine boot event-history projection is malformed');
|
|
423
|
+
}
|
|
424
|
+
if (new Set(boot.receipts.map((receipt) => receipt?.id)).size !== boot.receipts.length) {
|
|
425
|
+
throw new InvalidEngineStep('Engine boot has duplicate operation receipts');
|
|
426
|
+
}
|
|
427
|
+
if ([...boot.intentArgs, ...boot.forkBeforeWrite].some((entry) => !Array.isArray(entry) || entry.length !== 2) ||
|
|
428
|
+
new Set(boot.intentArgs.map(([id]) => id)).size !== boot.intentArgs.length ||
|
|
429
|
+
new Set(boot.forkBeforeWrite.map(([id]) => id)).size !== boot.forkBeforeWrite.length) {
|
|
430
|
+
throw new InvalidEngineStep('Engine boot has malformed operation maps');
|
|
431
|
+
}
|
|
432
|
+
const intentArgs = new Map();
|
|
433
|
+
for (const [id, args] of boot.intentArgs) {
|
|
434
|
+
if (typeof id !== 'string' || !id) {
|
|
435
|
+
throw new InvalidEngineStep('Engine boot has invalid receipt args');
|
|
436
|
+
}
|
|
437
|
+
intentArgs.set(id, args);
|
|
438
|
+
}
|
|
439
|
+
const receiptIds = new Set();
|
|
440
|
+
let abandonedCount = 0;
|
|
441
|
+
for (const receipt of boot.receipts) {
|
|
442
|
+
validateReceipt(receipt);
|
|
443
|
+
const abandoned = receipt.status === 'result' && receipt.abandoned === true;
|
|
444
|
+
if (receiptIds.has(receipt.id) ||
|
|
445
|
+
(abandoned && intentArgs.has(receipt.id)) ||
|
|
446
|
+
(!abandoned && !intentArgs.has(receipt.id))) {
|
|
447
|
+
throw new InvalidEngineStep('Engine boot has inconsistent operation receipts');
|
|
448
|
+
}
|
|
449
|
+
receiptIds.add(receipt.id);
|
|
450
|
+
if (abandoned) {
|
|
451
|
+
abandonedCount++;
|
|
452
|
+
}
|
|
453
|
+
if (receipt.status !== 'result' &&
|
|
454
|
+
stableStringify(receipt.args) !== stableStringify(intentArgs.get(receipt.id))) {
|
|
455
|
+
throw new InvalidEngineStep('Engine boot operation arguments do not match their receipt');
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
if (receiptIds.size - abandonedCount !== intentArgs.size) {
|
|
459
|
+
throw new InvalidEngineStep('Engine boot has orphaned operation arguments');
|
|
460
|
+
}
|
|
461
|
+
for (const [id, length] of boot.forkBeforeWrite) {
|
|
462
|
+
assertCounter(length, 'fork checkpoint length');
|
|
463
|
+
if (length > boot.length) {
|
|
464
|
+
throw new InvalidEngineStep('Engine boot fork point exceeds its length');
|
|
465
|
+
}
|
|
466
|
+
const receipt = boot.receipts.find((item) => item.id === id);
|
|
467
|
+
if (!receipt || receipt.effect !== 'write' || receipt.name.startsWith('hook:')) {
|
|
468
|
+
throw new InvalidEngineStep('Engine boot has an invalid write fork point');
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
function validateBootEnvelope(boot) {
|
|
473
|
+
if (!boot ||
|
|
474
|
+
typeof boot !== 'object' ||
|
|
475
|
+
!Object.hasOwn(boot, 'metadata') ||
|
|
476
|
+
!Object.hasOwn(boot, 'eventState') ||
|
|
477
|
+
boot.protocolVersion !== RUN_LOG_PROTOCOL_VERSION) {
|
|
478
|
+
throw new InvalidEngineStep('Unsupported engine boot protocol version');
|
|
479
|
+
}
|
|
480
|
+
assertCounter(boot.length, 'boot length');
|
|
481
|
+
assertCounter(boot.nextSeq, 'boot nextSeq');
|
|
482
|
+
assertCounter(boot.deltasSinceCheckpoint, 'delta count');
|
|
483
|
+
assertCounter(boot.unredactedLength, 'unredacted length');
|
|
484
|
+
if (!Array.isArray(boot.receipts) ||
|
|
485
|
+
!Array.isArray(boot.intentArgs) ||
|
|
486
|
+
!Array.isArray(boot.forkBeforeWrite) ||
|
|
487
|
+
!Array.isArray(boot.redactedPendingIds) ||
|
|
488
|
+
boot.nextSeq > boot.length ||
|
|
489
|
+
boot.unredactedLength > boot.length ||
|
|
490
|
+
boot.deltasSinceCheckpoint > boot.length ||
|
|
491
|
+
(boot.redactedPendingArgs !== null && typeof boot.redactedPendingArgs !== 'boolean') ||
|
|
492
|
+
boot.restoredRedacted !== (boot.redactedPendingArgs === true)) {
|
|
493
|
+
throw new InvalidEngineStep('Malformed engine boot envelope');
|
|
494
|
+
}
|
|
495
|
+
if (boot.eventState !== null && !isRecord(boot.eventState)) {
|
|
496
|
+
throw new InvalidEngineStep('Malformed engine boot event-history projection');
|
|
497
|
+
}
|
|
498
|
+
if (boot.metadata === null) {
|
|
499
|
+
if (boot.length !== 0 || boot.nextSeq !== 0) {
|
|
500
|
+
throw new InvalidEngineStep('Engine boot is missing trusted metadata');
|
|
501
|
+
}
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
if (!isRecord(boot.metadata) ||
|
|
505
|
+
boot.metadata.version !== RUN_LOG_VERSION ||
|
|
506
|
+
boot.machineVersion !== boot.metadata.machine ||
|
|
507
|
+
boot.xstateVersion !== (boot.metadata.xstate ?? null) ||
|
|
508
|
+
typeof boot.metadata.runId !== 'string' ||
|
|
509
|
+
boot.metadata.runId.length === 0 ||
|
|
510
|
+
boot.length < 1 ||
|
|
511
|
+
boot.nextSeq < 1) {
|
|
512
|
+
throw new InvalidEngineStep('Engine boot metadata does not match its version fields');
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
export function validateEngineBoot(boot) {
|
|
516
|
+
if (trustedBoots.has(boot)) {
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
validateBoot(boot);
|
|
520
|
+
}
|
|
521
|
+
function assertCounter(value, label) {
|
|
522
|
+
if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0) {
|
|
523
|
+
throw new InvalidEngineStep(`Invalid ${label}: expected a non-negative integer`);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
function validTime(value) {
|
|
527
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= 0;
|
|
528
|
+
}
|
|
529
|
+
function validBudget(value) {
|
|
530
|
+
return (isRecord(value) &&
|
|
531
|
+
typeof value.deadlineMs === 'number' &&
|
|
532
|
+
Number.isFinite(value.deadlineMs) &&
|
|
533
|
+
typeof value.outputBytesUsed === 'number' &&
|
|
534
|
+
Number.isSafeInteger(value.outputBytesUsed) &&
|
|
535
|
+
value.outputBytesUsed >= 0);
|
|
536
|
+
}
|
|
537
|
+
function validateReceipt(receipt) {
|
|
538
|
+
if (!isRecord(receipt) ||
|
|
539
|
+
!['id', 'name', 'effect', 'status'].every((key) => Object.hasOwn(receipt, key)) ||
|
|
540
|
+
typeof receipt.id !== 'string' ||
|
|
541
|
+
receipt.id.length === 0 ||
|
|
542
|
+
typeof receipt.name !== 'string' ||
|
|
543
|
+
receipt.name.length === 0 ||
|
|
544
|
+
(receipt.effect !== 'read' && receipt.effect !== 'write') ||
|
|
545
|
+
(receipt.status !== 'intent' && receipt.status !== 'retry' && receipt.status !== 'result')) {
|
|
546
|
+
throw new InvalidEngineStep('Malformed operation receipt');
|
|
547
|
+
}
|
|
548
|
+
if (receipt.status === 'result') {
|
|
549
|
+
if (!Object.hasOwn(receipt, 'result') ||
|
|
550
|
+
(receipt.resultUndefined !== undefined && receipt.resultUndefined !== true) ||
|
|
551
|
+
(receipt.resultUndefined === true && receipt.result !== null) ||
|
|
552
|
+
(receipt.abandoned !== undefined && receipt.abandoned !== true)) {
|
|
553
|
+
throw new InvalidEngineStep('Malformed operation result receipt');
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
else {
|
|
557
|
+
if (!Object.hasOwn(receipt, 'args')) {
|
|
558
|
+
throw new InvalidEngineStep('Operation intent is missing its arguments');
|
|
559
|
+
}
|
|
560
|
+
if (receipt.facts !== undefined) {
|
|
561
|
+
throw new InvalidEngineStep('Unsettled operation receipt cannot carry result facts');
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
function isRunRecord(value) {
|
|
566
|
+
if (!isRecord(value) || Array.isArray(value) || value.version !== RUN_LOG_VERSION) {
|
|
567
|
+
return false;
|
|
568
|
+
}
|
|
569
|
+
return ['metadata', 'fork', 'checkpoint', 'delta', 'operation'].includes(String(value.type));
|
|
570
|
+
}
|
|
571
|
+
function resolveRedactedPendingIds(redactedPendingArgs, redactedPendingIds, snapshot) {
|
|
572
|
+
if (redactedPendingIds === undefined) {
|
|
573
|
+
return redactedPendingArgs === true ? [...pendingCallNames(snapshot).keys()] : [];
|
|
574
|
+
}
|
|
575
|
+
const pending = pendingCallNames(snapshot);
|
|
576
|
+
for (const id of redactedPendingIds) {
|
|
577
|
+
if (!pending.has(id)) {
|
|
578
|
+
throw new InvalidEngineStep(`redactedPendingIds names a call that is not pending: ${id}`);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
return redactedPendingIds.slice();
|
|
582
|
+
}
|
|
583
|
+
function foldRecord(boot, input) {
|
|
584
|
+
const record = captureRunRecord(input);
|
|
585
|
+
if (!isRecord(record) ||
|
|
586
|
+
!Object.hasOwn(record, 'version') ||
|
|
587
|
+
!Object.hasOwn(record, 'type') ||
|
|
588
|
+
record.version !== RUN_LOG_VERSION) {
|
|
589
|
+
throw new InvalidEngineStep(`Unsupported run record version: ${record?.version ?? 'missing'}`);
|
|
590
|
+
}
|
|
591
|
+
const index = boot.length;
|
|
592
|
+
if (record.type !== 'metadata' && boot.metadata === null) {
|
|
593
|
+
throw new InvalidEngineStep('Run record appeared before trusted metadata');
|
|
594
|
+
}
|
|
595
|
+
switch (record.type) {
|
|
596
|
+
case 'metadata':
|
|
597
|
+
if (boot.metadata !== null || index !== 0) {
|
|
598
|
+
throw new InvalidEngineStep('Duplicate or misplaced run metadata');
|
|
599
|
+
}
|
|
600
|
+
if (!Number.isSafeInteger(record.machine) || record.machine < 0) {
|
|
601
|
+
throw new InvalidEngineStep('Run metadata has an invalid machine version');
|
|
602
|
+
}
|
|
603
|
+
if (typeof record.runId !== 'string' ||
|
|
604
|
+
!Object.hasOwn(record, 'runId') ||
|
|
605
|
+
record.runId.length === 0 ||
|
|
606
|
+
!Object.hasOwn(record, 'request') ||
|
|
607
|
+
!validBudget(record.budget) ||
|
|
608
|
+
(record.startedAt !== undefined && !validTime(record.startedAt)) ||
|
|
609
|
+
(record.requestDigest !== undefined &&
|
|
610
|
+
(typeof record.requestDigest !== 'string' ||
|
|
611
|
+
typeof record.digestRunId !== 'string' ||
|
|
612
|
+
record.digestRunId.length === 0))) {
|
|
613
|
+
throw new InvalidEngineStep('Run metadata is missing required identity fields');
|
|
614
|
+
}
|
|
615
|
+
if (record.xstate !== undefined && typeof record.xstate !== 'string') {
|
|
616
|
+
throw new InvalidEngineStep('Run metadata has an invalid XState version');
|
|
617
|
+
}
|
|
618
|
+
boot.metadata = record;
|
|
619
|
+
boot.machineVersion = record.machine;
|
|
620
|
+
boot.xstateVersion = record.xstate ?? null;
|
|
621
|
+
boot.unredactedLength = index + 1;
|
|
622
|
+
break;
|
|
623
|
+
case 'checkpoint':
|
|
624
|
+
if (!Object.hasOwn(record, 'snapshot') ||
|
|
625
|
+
!Object.hasOwn(record, 'savedAt') ||
|
|
626
|
+
!isRecord(record.snapshot) ||
|
|
627
|
+
!validTime(record.savedAt)) {
|
|
628
|
+
throw new InvalidEngineStep('Malformed run-log checkpoint');
|
|
629
|
+
}
|
|
630
|
+
validateCheckpointFields({
|
|
631
|
+
redactedPendingArgs: record.redactedPendingArgs,
|
|
632
|
+
redactedPendingIds: record.redactedPendingIds,
|
|
633
|
+
events: record.events,
|
|
634
|
+
trace: record.trace
|
|
635
|
+
});
|
|
636
|
+
if (record.reset !== undefined && record.reset !== true) {
|
|
637
|
+
throw new InvalidEngineStep('Invalid checkpoint event reset marker');
|
|
638
|
+
}
|
|
639
|
+
boot.snapshot = captureJson(record.snapshot);
|
|
640
|
+
foldEventRecords(boot, {
|
|
641
|
+
events: record.events,
|
|
642
|
+
reset: record.reset === true,
|
|
643
|
+
resetAfter: true
|
|
644
|
+
});
|
|
645
|
+
boot.snapshotSavedAt = record.savedAt;
|
|
646
|
+
boot.firstSavedAt ??= record.savedAt;
|
|
647
|
+
boot.deltasSinceCheckpoint = 0;
|
|
648
|
+
boot.redactedPendingArgs = record.redactedPendingArgs ?? null;
|
|
649
|
+
boot.redactedPendingIds = resolveRedactedPendingIds(record.redactedPendingArgs, record.redactedPendingIds, boot.snapshot);
|
|
650
|
+
if (boot.redactedPendingArgs === false) {
|
|
651
|
+
boot.unredactedLength = index + 1;
|
|
652
|
+
}
|
|
653
|
+
break;
|
|
654
|
+
case 'delta':
|
|
655
|
+
if (boot.snapshot === undefined) {
|
|
656
|
+
throw new InvalidEngineStep('Run-log delta without a checkpoint');
|
|
657
|
+
}
|
|
658
|
+
if (!validTime(record.savedAt)) {
|
|
659
|
+
throw new InvalidEngineStep('Malformed run-log delta timestamp');
|
|
660
|
+
}
|
|
661
|
+
validateCheckpointFields({
|
|
662
|
+
redactedPendingArgs: record.redactedPendingArgs,
|
|
663
|
+
redactedPendingIds: record.redactedPendingIds,
|
|
664
|
+
events: record.events,
|
|
665
|
+
trace: record.trace
|
|
666
|
+
});
|
|
667
|
+
if (record.reset !== undefined && record.reset !== true) {
|
|
668
|
+
throw new InvalidEngineStep('Invalid delta event reset marker');
|
|
669
|
+
}
|
|
670
|
+
if (record.patch !== undefined) {
|
|
671
|
+
validatePatch(record.patch);
|
|
672
|
+
}
|
|
673
|
+
if (record.patch) {
|
|
674
|
+
boot.snapshot = applyImmutable(boot.snapshot, record.patch);
|
|
675
|
+
}
|
|
676
|
+
foldEventRecords(boot, {
|
|
677
|
+
events: record.events,
|
|
678
|
+
reset: record.reset === true,
|
|
679
|
+
resetAfter: false
|
|
680
|
+
});
|
|
681
|
+
boot.snapshotSavedAt = record.savedAt;
|
|
682
|
+
boot.firstSavedAt ??= record.savedAt;
|
|
683
|
+
boot.deltasSinceCheckpoint++;
|
|
684
|
+
boot.redactedPendingArgs = record.redactedPendingArgs ?? null;
|
|
685
|
+
boot.redactedPendingIds = resolveRedactedPendingIds(record.redactedPendingArgs, record.redactedPendingIds, boot.snapshot);
|
|
686
|
+
if (boot.redactedPendingArgs === false) {
|
|
687
|
+
boot.unredactedLength = index + 1;
|
|
688
|
+
}
|
|
689
|
+
break;
|
|
690
|
+
case 'operation': {
|
|
691
|
+
if (!Object.hasOwn(record, 'receipt')) {
|
|
692
|
+
throw new InvalidEngineStep('Operation record is missing its receipt');
|
|
693
|
+
}
|
|
694
|
+
validateReceipt(record.receipt);
|
|
695
|
+
validateReceiptTransition(boot, record.receipt);
|
|
696
|
+
trackForkPoint(boot, record.receipt);
|
|
697
|
+
setById(boot.receipts, record.receipt);
|
|
698
|
+
if (record.receipt.status !== 'result') {
|
|
699
|
+
setPair(boot.intentArgs, record.receipt.id, record.receipt.args);
|
|
700
|
+
}
|
|
701
|
+
break;
|
|
702
|
+
}
|
|
703
|
+
case 'fork': {
|
|
704
|
+
if (!boot.metadata) {
|
|
705
|
+
throw new InvalidEngineStep('Run-log fork without metadata');
|
|
706
|
+
}
|
|
707
|
+
if (typeof record.runId !== 'string' ||
|
|
708
|
+
record.runId.length === 0 ||
|
|
709
|
+
record.runId === boot.metadata.runId ||
|
|
710
|
+
!isRecord(record.forkedFrom) ||
|
|
711
|
+
record.forkedFrom.runId !== boot.metadata.runId ||
|
|
712
|
+
!Number.isSafeInteger(record.forkedFrom.length) ||
|
|
713
|
+
record.forkedFrom.length < 1 ||
|
|
714
|
+
record.forkedFrom.length > index ||
|
|
715
|
+
(record.forkedFrom.key !== undefined && typeof record.forkedFrom.key !== 'string') ||
|
|
716
|
+
(record.segments !== undefined &&
|
|
717
|
+
(!Array.isArray(record.segments) ||
|
|
718
|
+
record.segments.some((segment) => !isRecord(segment) ||
|
|
719
|
+
typeof segment.key !== 'string' ||
|
|
720
|
+
!Number.isSafeInteger(segment.length) ||
|
|
721
|
+
segment.length < 0))) ||
|
|
722
|
+
(record.request !== undefined && !isRecord(record.request))) {
|
|
723
|
+
throw new InvalidEngineStep('Malformed run-log fork');
|
|
724
|
+
}
|
|
725
|
+
const from = boot.metadata.runId;
|
|
726
|
+
const rename = (id) => id.startsWith(`${from}:`) ? record.runId + id.slice(from.length) : id;
|
|
727
|
+
const request = Object.assign({}, isRecord(boot.metadata.request) ? boot.metadata.request : {}, record.request);
|
|
728
|
+
if (stableStringify(request) !==
|
|
729
|
+
stableStringify(isRecord(boot.metadata.request) ? boot.metadata.request : {})) {
|
|
730
|
+
throw new InvalidEngineStep('A fork cannot change the recorded request identity');
|
|
731
|
+
}
|
|
732
|
+
const metadata = {
|
|
733
|
+
...boot.metadata,
|
|
734
|
+
runId: record.runId,
|
|
735
|
+
request: boot.metadata.request,
|
|
736
|
+
forkedFrom: captureJson(record.forkedFrom)
|
|
737
|
+
};
|
|
738
|
+
boot.metadata = sealCapturedJson(metadata);
|
|
739
|
+
boot.snapshot = isRecord(boot.snapshot)
|
|
740
|
+
? {
|
|
741
|
+
...boot.snapshot,
|
|
742
|
+
context: isRecord(boot.snapshot.context)
|
|
743
|
+
? { ...boot.snapshot.context, runId: record.runId }
|
|
744
|
+
: boot.snapshot.context
|
|
745
|
+
}
|
|
746
|
+
: boot.snapshot;
|
|
747
|
+
const writes = boot.receipts.filter((receipt) => receipt.effect === 'write');
|
|
748
|
+
const writeIds = new Set(writes.map((receipt) => receipt.id));
|
|
749
|
+
boot.receipts = writes.map((receipt) => sealCapturedJson({ ...receipt, id: rename(receipt.id) }));
|
|
750
|
+
boot.intentArgs = boot.intentArgs
|
|
751
|
+
.filter(([id]) => writeIds.has(id))
|
|
752
|
+
.map(([id, args]) => frozenPair(rename(id), captureJson(args)));
|
|
753
|
+
boot.forkBeforeWrite = boot.forkBeforeWrite
|
|
754
|
+
.filter(([id]) => writeIds.has(id))
|
|
755
|
+
.map(([id, length]) => frozenPair(rename(id), length));
|
|
756
|
+
boot.redactedPendingIds = [];
|
|
757
|
+
break;
|
|
758
|
+
}
|
|
759
|
+
default:
|
|
760
|
+
throw new InvalidEngineStep('Unknown run-log record type');
|
|
761
|
+
}
|
|
762
|
+
boot.length++;
|
|
763
|
+
boot.restoredRedacted = boot.redactedPendingArgs === true;
|
|
764
|
+
}
|
|
765
|
+
function validateCheckpointFields(fields) {
|
|
766
|
+
const { redactedPendingArgs, redactedPendingIds, events, trace } = fields;
|
|
767
|
+
if (redactedPendingArgs !== undefined && typeof redactedPendingArgs !== 'boolean') {
|
|
768
|
+
throw new InvalidEngineStep('Invalid pending-argument provenance flag');
|
|
769
|
+
}
|
|
770
|
+
if (redactedPendingIds !== undefined &&
|
|
771
|
+
(!Array.isArray(redactedPendingIds) || redactedPendingIds.some((id) => typeof id !== 'string'))) {
|
|
772
|
+
throw new InvalidEngineStep('Invalid pending-argument provenance ids');
|
|
773
|
+
}
|
|
774
|
+
if (events !== undefined) {
|
|
775
|
+
if (!Array.isArray(events)) {
|
|
776
|
+
throw new InvalidEngineStep('Invalid event patch list');
|
|
777
|
+
}
|
|
778
|
+
for (const event of events) {
|
|
779
|
+
validatePatch(event);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
if (trace !== undefined) {
|
|
783
|
+
if (!Array.isArray(trace)) {
|
|
784
|
+
throw new InvalidEngineStep('Invalid machine trace');
|
|
785
|
+
}
|
|
786
|
+
for (const step of trace) {
|
|
787
|
+
if (!isRecord(step) ||
|
|
788
|
+
typeof step.eventType !== 'string' ||
|
|
789
|
+
!Array.isArray(step.sourceIds) ||
|
|
790
|
+
!Array.isArray(step.targetIds) ||
|
|
791
|
+
!Array.isArray(step.guardNames) ||
|
|
792
|
+
!Array.isArray(step.actionNames) ||
|
|
793
|
+
[...step.sourceIds, ...step.targetIds, ...step.guardNames, ...step.actionNames].some((value) => typeof value !== 'string')) {
|
|
794
|
+
throw new InvalidEngineStep('Malformed machine transition trace');
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
function foldEventRecords(boot, { events, reset, resetAfter }) {
|
|
800
|
+
let state = reset ? null : boot.eventState;
|
|
801
|
+
try {
|
|
802
|
+
for (const event of events ?? []) {
|
|
803
|
+
state = foldMachineEventHistory(state, event, {
|
|
804
|
+
base: undefined,
|
|
805
|
+
validate: false
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
boot.eventState = resetAfter ? null : state;
|
|
809
|
+
}
|
|
810
|
+
catch (error) {
|
|
811
|
+
throw new InvalidEngineStep(`Malformed history reference in machine event: ${String(error)}`);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
function historyBase(boot) {
|
|
815
|
+
return boot.metadata ? requestMessages(boot.metadata.request) : undefined;
|
|
816
|
+
}
|
|
817
|
+
function allowsLegacyFailureResults(boot) {
|
|
818
|
+
return boot.machineVersion !== null && boot.machineVersion < FAILURE_RESULT_REF_MACHINE_VERSION;
|
|
819
|
+
}
|
|
820
|
+
function validatePatch(value) {
|
|
821
|
+
if (!isRecord(value))
|
|
822
|
+
throw new InvalidEngineStep('Malformed JSON patch');
|
|
823
|
+
const variants = ['$', 't', 'o'].filter((key) => Object.hasOwn(value, key)).length;
|
|
824
|
+
if (variants !== 1) {
|
|
825
|
+
throw new InvalidEngineStep('Malformed JSON patch variant');
|
|
826
|
+
}
|
|
827
|
+
if (Object.hasOwn(value, '$')) {
|
|
828
|
+
if (Object.keys(value).some((key) => key !== '$')) {
|
|
829
|
+
throw new InvalidEngineStep('Malformed replacement patch');
|
|
830
|
+
}
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
if (Object.hasOwn(value, 't')) {
|
|
834
|
+
if (Object.keys(value).some((key) => key !== 't' && key !== 'a') ||
|
|
835
|
+
typeof value.t !== 'number' ||
|
|
836
|
+
!Number.isSafeInteger(value.t) ||
|
|
837
|
+
value.t < 0 ||
|
|
838
|
+
!Array.isArray(value.a)) {
|
|
839
|
+
throw new InvalidEngineStep('Malformed array patch');
|
|
840
|
+
}
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
const deletes = value.d;
|
|
844
|
+
if (Object.keys(value).some((key) => key !== 'o' && key !== 'd') ||
|
|
845
|
+
!isRecord(value.o) ||
|
|
846
|
+
(deletes !== undefined &&
|
|
847
|
+
(!Array.isArray(deletes) ||
|
|
848
|
+
deletes.some((key) => typeof key !== 'string') ||
|
|
849
|
+
new Set(deletes).size !== deletes.length))) {
|
|
850
|
+
throw new InvalidEngineStep('Malformed object patch');
|
|
851
|
+
}
|
|
852
|
+
const objectPatch = value.o;
|
|
853
|
+
for (const key of Object.keys(value.o)) {
|
|
854
|
+
if (deletes?.includes(key)) {
|
|
855
|
+
throw new InvalidEngineStep('Patch key is both deleted and updated');
|
|
856
|
+
}
|
|
857
|
+
validatePatch(objectPatch[key]);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
function validateReceiptTransition(boot, receipt) {
|
|
861
|
+
const previous = boot.receipts.find((item) => item.id === receipt.id);
|
|
862
|
+
if (!previous) {
|
|
863
|
+
const abandoned = receipt.status === 'result' && receipt.abandoned === true;
|
|
864
|
+
if (!abandoned) {
|
|
865
|
+
if (receipt.status !== 'intent') {
|
|
866
|
+
throw new InvalidEngineStep('Operation result or retry appeared without an intent');
|
|
867
|
+
}
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
if (!pendingCallNames(boot.snapshot).has(receipt.id)) {
|
|
871
|
+
throw new InvalidEngineStep('Abandoned receipt does not match a pending operation');
|
|
872
|
+
}
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
if (previous.name !== receipt.name || previous.effect !== receipt.effect) {
|
|
876
|
+
throw new InvalidEngineStep('Operation receipt identity changed');
|
|
877
|
+
}
|
|
878
|
+
if (receipt.status === 'result' && receipt.abandoned === true && previous.status !== 'result') {
|
|
879
|
+
throw new InvalidEngineStep('A dispatched operation cannot be marked abandoned');
|
|
880
|
+
}
|
|
881
|
+
if (previous.status === 'result' && receipt.status !== 'result') {
|
|
882
|
+
throw new InvalidEngineStep('A settled operation receipt cannot be downgraded');
|
|
883
|
+
}
|
|
884
|
+
if (receipt.status === 'result' && previous.status === 'retry') {
|
|
885
|
+
throw new InvalidEngineStep('A retried operation must persist a new intent before its result');
|
|
886
|
+
}
|
|
887
|
+
if (receipt.status !== 'result') {
|
|
888
|
+
const previousArgs = boot.intentArgs.find(([id]) => id === receipt.id)?.[1];
|
|
889
|
+
if (stableStringify(previousArgs) !== stableStringify(receipt.args)) {
|
|
890
|
+
throw new InvalidEngineStep('Operation arguments changed across attempts');
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
if (receipt.status === 'retry' && previous.status !== 'intent') {
|
|
894
|
+
throw new InvalidEngineStep('Only an unresolved operation can be marked for retry');
|
|
895
|
+
}
|
|
896
|
+
if (receipt.status === 'result' && previous.status === 'result') {
|
|
897
|
+
const sameResult = previous.resultUndefined === receipt.resultUndefined &&
|
|
898
|
+
stableStringify(previous.result) === stableStringify(receipt.result);
|
|
899
|
+
const factsAdded = previous.facts === undefined && receipt.facts !== undefined;
|
|
900
|
+
if (!sameResult ||
|
|
901
|
+
(!factsAdded && stableStringify(previous.facts) !== stableStringify(receipt.facts))) {
|
|
902
|
+
throw new InvalidEngineStep('Settled operation result changed');
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
function trackForkPoint(boot, receipt) {
|
|
907
|
+
if (receipt.status === 'result' && receipt.abandoned === true)
|
|
908
|
+
return;
|
|
909
|
+
if (receipt.effect !== 'write' || receipt.name.startsWith('hook:'))
|
|
910
|
+
return;
|
|
911
|
+
const points = new Map(boot.forkBeforeWrite);
|
|
912
|
+
if (receipt.status === 'intent') {
|
|
913
|
+
setMap(points, receipt.id, boot.unredactedLength);
|
|
914
|
+
boot.unredactedLength = 0;
|
|
915
|
+
boot.forkBeforeWrite = [...points].map(([id, length]) => frozenPair(id, length));
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
boot.unredactedLength = points.get(receipt.id) ?? 0;
|
|
919
|
+
boot.forkBeforeWrite = [...points].map(([id, length]) => frozenPair(id, length));
|
|
920
|
+
}
|
|
921
|
+
function setById(receipts, receipt) {
|
|
922
|
+
const index = receipts.findIndex((item) => item.id === receipt.id);
|
|
923
|
+
const saved = captureJson(receipt);
|
|
924
|
+
if (index < 0) {
|
|
925
|
+
receipts.push(saved);
|
|
926
|
+
}
|
|
927
|
+
else {
|
|
928
|
+
receipts[index] = saved;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
function setPair(pairs, key, value) {
|
|
932
|
+
const index = pairs.findIndex(([id]) => id === key);
|
|
933
|
+
const saved = frozenPair(key, captureJson(value));
|
|
934
|
+
if (index < 0) {
|
|
935
|
+
pairs.push(saved);
|
|
936
|
+
}
|
|
937
|
+
else {
|
|
938
|
+
pairs[index] = saved;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
function frozenPair(first, second) {
|
|
942
|
+
const pair = [first, second];
|
|
943
|
+
Object.freeze(pair);
|
|
944
|
+
return pair;
|
|
945
|
+
}
|
|
946
|
+
function setMap(map, key, value) {
|
|
947
|
+
map.set(key, value);
|
|
948
|
+
}
|
|
949
|
+
function defineOwn(record, key, value) {
|
|
950
|
+
Object.defineProperty(record, key, {
|
|
951
|
+
value,
|
|
952
|
+
writable: true,
|
|
953
|
+
enumerable: true,
|
|
954
|
+
configurable: true
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
function cloneBoot(boot) {
|
|
958
|
+
return {
|
|
959
|
+
...boot,
|
|
960
|
+
forkBeforeWrite: boot.forkBeforeWrite.slice(),
|
|
961
|
+
receipts: boot.receipts.slice(),
|
|
962
|
+
intentArgs: boot.intentArgs.slice(),
|
|
963
|
+
redactedPendingIds: boot.redactedPendingIds.slice()
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
function applyImmutable(base, patch) {
|
|
967
|
+
if ('$' in patch) {
|
|
968
|
+
return captureJson(patch.$);
|
|
969
|
+
}
|
|
970
|
+
if ('t' in patch) {
|
|
971
|
+
const array = Array.isArray(base) ? base.slice(0, Math.min(base.length, patch.t)) : [];
|
|
972
|
+
return sealCapturedJson(array.concat(patch.a));
|
|
973
|
+
}
|
|
974
|
+
const objectPatch = patch;
|
|
975
|
+
const record = isRecord(base) ? { ...base } : {};
|
|
976
|
+
for (const key of (Object.hasOwn(objectPatch, 'd') ? objectPatch.d : undefined) ?? []) {
|
|
977
|
+
if (Object.hasOwn(record, key)) {
|
|
978
|
+
delete record[key];
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
for (const key of Object.keys(objectPatch.o)) {
|
|
982
|
+
const child = objectPatch.o[key];
|
|
983
|
+
if (child === undefined) {
|
|
984
|
+
throw new InvalidEngineStep('Malformed object patch');
|
|
985
|
+
}
|
|
986
|
+
const value = applyImmutable(Object.hasOwn(record, key) ? record[key] : undefined, child);
|
|
987
|
+
defineOwn(record, key, value);
|
|
988
|
+
}
|
|
989
|
+
return sealCapturedJson(record);
|
|
990
|
+
}
|
|
991
|
+
function freezeBoot(boot) {
|
|
992
|
+
Object.freeze(boot.forkBeforeWrite);
|
|
993
|
+
Object.freeze(boot.receipts);
|
|
994
|
+
Object.freeze(boot.intentArgs);
|
|
995
|
+
Object.freeze(boot.redactedPendingIds);
|
|
996
|
+
Object.freeze(boot);
|
|
997
|
+
trustedBoots.add(boot);
|
|
998
|
+
return boot;
|
|
999
|
+
}
|
|
1000
|
+
function copy(value) {
|
|
1001
|
+
if (value === undefined) {
|
|
1002
|
+
return value;
|
|
1003
|
+
}
|
|
1004
|
+
return JSON.parse(JSON.stringify(value));
|
|
1005
|
+
}
|
|
1006
|
+
function captureJson(value) {
|
|
1007
|
+
if (value && typeof value === 'object' && trustedJson.has(value))
|
|
1008
|
+
return value;
|
|
1009
|
+
return brandJson(copy(value));
|
|
1010
|
+
}
|
|
1011
|
+
function brandJson(value) {
|
|
1012
|
+
if (!value || typeof value !== 'object' || trustedJson.has(value)) {
|
|
1013
|
+
return value;
|
|
1014
|
+
}
|
|
1015
|
+
if (Array.isArray(value)) {
|
|
1016
|
+
for (let index = 0; index < value.length; index++) {
|
|
1017
|
+
brandJson(value[index]);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
else {
|
|
1021
|
+
for (const key of Object.keys(value)) {
|
|
1022
|
+
brandJson(Reflect.get(value, key));
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
Object.freeze(value);
|
|
1026
|
+
trustedJson.add(value);
|
|
1027
|
+
return value;
|
|
1028
|
+
}
|
|
1029
|
+
function sealCapturedJson(value) {
|
|
1030
|
+
if (Array.isArray(value)) {
|
|
1031
|
+
for (let index = 0; index < value.length; index++) {
|
|
1032
|
+
const child = value[index];
|
|
1033
|
+
if (child && typeof child === 'object' && !trustedJson.has(child)) {
|
|
1034
|
+
throw new InvalidEngineStep('Internal JSON subtree was not captured before reuse');
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
else {
|
|
1039
|
+
for (const key of Object.keys(value)) {
|
|
1040
|
+
const child = Reflect.get(value, key);
|
|
1041
|
+
if (child && typeof child === 'object' && !trustedJson.has(child)) {
|
|
1042
|
+
throw new InvalidEngineStep('Internal JSON subtree was not captured before reuse');
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
Object.freeze(value);
|
|
1047
|
+
trustedJson.add(value);
|
|
1048
|
+
return value;
|
|
1049
|
+
}
|
|
1050
|
+
function readDataProperty(record, key) {
|
|
1051
|
+
const descriptor = Object.getOwnPropertyDescriptor(record, key);
|
|
1052
|
+
if (!descriptor || !('value' in descriptor)) {
|
|
1053
|
+
throw new InvalidEngineStep(`Engine step is missing its ${key} field`);
|
|
1054
|
+
}
|
|
1055
|
+
return descriptor.value;
|
|
1056
|
+
}
|