@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,783 @@
|
|
|
1
|
+
import xstatePkg from 'xstate/package.json' with { type: 'json' };
|
|
2
|
+
import { AbortedError, RecoveryRequired } from '../machine/errors.js';
|
|
3
|
+
import { errorMessage, isRecord } from '../guards.js';
|
|
4
|
+
import { apply, diff } from './patch.js';
|
|
5
|
+
import { stableStringify } from '../machine/stable-stringify.js';
|
|
6
|
+
import { compactMachineEvent, compactSnapshotHistory, materializeMachineEvent, materializeSnapshotHistory, FAILURE_RESULT_REF_MACHINE_VERSION, snapshotFailureResultRefs, requestMessages, validateHistoryProjection, validateSnapshotHistory } from './history-codec.js';
|
|
7
|
+
import { RUN_LOG_VERSION, applyEngineStep, brandCapturedRecord, buildEngineBoot, createEngineBoot, prepareEngineBoot, captureRunRecord, InvalidEngineStep, isCapturedJson, validateEngineBoot, pendingCallNames, pendingCallArgs, pendingToolArgs, unsettledRedactedIds } from './protocol.js';
|
|
8
|
+
export { RUN_LOG_VERSION, RecoveryRequired, pendingCallNames, pendingToolArgs, unsettledRedactedIds };
|
|
9
|
+
export const MACHINE_VERSION = 12;
|
|
10
|
+
export const XSTATE_VERSION = xstatePkg.version;
|
|
11
|
+
function compatibleXStateVersion(found) {
|
|
12
|
+
if (found === null)
|
|
13
|
+
return false;
|
|
14
|
+
const saved = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.exec(found);
|
|
15
|
+
if (!saved) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
const [major, minor, patch] = XSTATE_VERSION.split('.').map(Number);
|
|
19
|
+
return Number(saved[1]) === major && Number(saved[2]) === minor && Number(saved[3]) <= patch;
|
|
20
|
+
}
|
|
21
|
+
export const ENGINE_BOUNDARY_BOOT = Symbol('engineBoundaryBoot');
|
|
22
|
+
export class RunIncompatible extends Error {
|
|
23
|
+
runId;
|
|
24
|
+
found;
|
|
25
|
+
expected;
|
|
26
|
+
kind;
|
|
27
|
+
constructor(opts) {
|
|
28
|
+
super(`Incompatible run log: ${opts.kind} version ${opts.found} (expected ${opts.expected})` +
|
|
29
|
+
(opts.runId ? ` for run ${opts.runId}` : ''));
|
|
30
|
+
this.name = 'RunIncompatible';
|
|
31
|
+
this.runId = opts.runId;
|
|
32
|
+
this.found = opts.found;
|
|
33
|
+
this.expected = opts.expected;
|
|
34
|
+
this.kind = opts.kind;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export function copy(value) {
|
|
38
|
+
if (value === undefined)
|
|
39
|
+
return value;
|
|
40
|
+
return JSON.parse(JSON.stringify(value));
|
|
41
|
+
}
|
|
42
|
+
function pendingWriteReceipts(receipts) {
|
|
43
|
+
return [...receipts].filter((receipt) => receipt.effect === 'write' && receipt.status !== 'result' && !receipt.name.startsWith('hook:'));
|
|
44
|
+
}
|
|
45
|
+
export function unresolvedWriteIds(receipts) {
|
|
46
|
+
return pendingWriteReceipts(receipts)
|
|
47
|
+
.filter((receipt) => receipt.status === 'intent')
|
|
48
|
+
.map((receipt) => receipt.id);
|
|
49
|
+
}
|
|
50
|
+
export function forkRepeatsSettledWrite(receipts, forkBeforeWrite, forkAt) {
|
|
51
|
+
if (forkAt === null)
|
|
52
|
+
return false;
|
|
53
|
+
for (const receipt of receipts) {
|
|
54
|
+
if (receipt.effect !== 'write' || receipt.name.startsWith('hook:'))
|
|
55
|
+
continue;
|
|
56
|
+
if (receipt.status === 'result' && receipt.abandoned === true)
|
|
57
|
+
continue;
|
|
58
|
+
if (receipt.status !== 'result' && receipt.status !== 'intent') {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (forkBeforeWrite.get(receipt.id) === forkAt) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
export function safeForkLength(receipts, forkBeforeWrite, unredactedLength) {
|
|
68
|
+
const pendingWrites = pendingWriteReceipts(receipts);
|
|
69
|
+
if (pendingWrites.length) {
|
|
70
|
+
const lengths = [];
|
|
71
|
+
for (const receipt of pendingWrites) {
|
|
72
|
+
const length = forkBeforeWrite.get(receipt.id);
|
|
73
|
+
if (typeof length !== 'number') {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
lengths.push(length);
|
|
77
|
+
}
|
|
78
|
+
const length = Math.min(...lengths);
|
|
79
|
+
return length >= 1 ? length : null;
|
|
80
|
+
}
|
|
81
|
+
return unredactedLength >= 1 ? unredactedLength : null;
|
|
82
|
+
}
|
|
83
|
+
export function recoveryAdvice(ids, receipts, redactedPendingIds) {
|
|
84
|
+
const byId = new Map();
|
|
85
|
+
const redacted = new Set(redactedPendingIds);
|
|
86
|
+
for (const receipt of receipts) {
|
|
87
|
+
byId.set(receipt.id, receipt);
|
|
88
|
+
}
|
|
89
|
+
const dispatchedWrites = [];
|
|
90
|
+
const others = [];
|
|
91
|
+
for (const id of ids) {
|
|
92
|
+
const receipt = byId.get(id);
|
|
93
|
+
if (receipt?.status === 'intent' && receipt.effect === 'write' && !redacted.has(receipt.id)) {
|
|
94
|
+
dispatchedWrites.push(id);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
others.push(id);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const clauses = [];
|
|
101
|
+
if (dispatchedWrites.length > 0) {
|
|
102
|
+
clauses.push(`resolve ${dispatchedWrites.join(', ')} with result or retry`);
|
|
103
|
+
}
|
|
104
|
+
if (others.length > 0) {
|
|
105
|
+
clauses.push(`resolve ${others.join(', ')} with result or skip`);
|
|
106
|
+
}
|
|
107
|
+
return clauses.join('; ');
|
|
108
|
+
}
|
|
109
|
+
const RUN_RECORD_TYPES = new Set(['metadata', 'checkpoint', 'delta', 'operation', 'fork']);
|
|
110
|
+
function readBoundaryBoot(core) {
|
|
111
|
+
const candidate = Reflect.get(core, ENGINE_BOUNDARY_BOOT);
|
|
112
|
+
if (typeof candidate !== 'function') {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
return () => candidate();
|
|
116
|
+
}
|
|
117
|
+
function eventRecord(value) {
|
|
118
|
+
if (!isRecord(value) || Array.isArray(value)) {
|
|
119
|
+
throw new Error('Run log event must be an object');
|
|
120
|
+
}
|
|
121
|
+
return value;
|
|
122
|
+
}
|
|
123
|
+
function inspectionRecord(value, sourceVersion) {
|
|
124
|
+
if (!isRecord(value)) {
|
|
125
|
+
throw new InvalidEngineStep('Malformed run record');
|
|
126
|
+
}
|
|
127
|
+
if (typeof value.type !== 'string' || !RUN_RECORD_TYPES.has(value.type)) {
|
|
128
|
+
throw new Error(`Unknown run-log record type: ${String(value.type)}`);
|
|
129
|
+
}
|
|
130
|
+
const normalized = sourceVersion === RUN_LOG_VERSION ? value : { ...value, version: RUN_LOG_VERSION };
|
|
131
|
+
return captureRunRecord(normalized);
|
|
132
|
+
}
|
|
133
|
+
export class RunJournal {
|
|
134
|
+
snapshotSavedAt;
|
|
135
|
+
firstSavedAt;
|
|
136
|
+
deltasSinceCheckpoint = 0;
|
|
137
|
+
redactedPendingArgs = null;
|
|
138
|
+
unredactedLength = 0;
|
|
139
|
+
restoredRedacted = false;
|
|
140
|
+
forkBeforeWrite = new Map();
|
|
141
|
+
knownLength = 0;
|
|
142
|
+
metadata;
|
|
143
|
+
receipts = new Map();
|
|
144
|
+
intentArgs = new Map();
|
|
145
|
+
tail = Promise.resolve();
|
|
146
|
+
settledTail = this.tail;
|
|
147
|
+
uncertainWrite() {
|
|
148
|
+
for (const receipt of this.receipts.values()) {
|
|
149
|
+
if (receipt.effect === 'write' && receipt.status === 'intent') {
|
|
150
|
+
return receipt;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
failure = null;
|
|
156
|
+
detachedEffects = null;
|
|
157
|
+
sealed = false;
|
|
158
|
+
onFailure;
|
|
159
|
+
core;
|
|
160
|
+
restored = false;
|
|
161
|
+
redact;
|
|
162
|
+
redactShared;
|
|
163
|
+
redactOperationResult;
|
|
164
|
+
redactRecord;
|
|
165
|
+
hasRedactionProvenance;
|
|
166
|
+
initialBoot;
|
|
167
|
+
engineBoot;
|
|
168
|
+
get snapshot() {
|
|
169
|
+
return materializeSnapshotHistory(this.engineBoot.snapshot, requestMessages(this.engineBoot.metadata?.request));
|
|
170
|
+
}
|
|
171
|
+
get requestHistoryBase() {
|
|
172
|
+
return requestMessages(this.engineBoot.metadata?.request);
|
|
173
|
+
}
|
|
174
|
+
compactSnapshot(snapshot) {
|
|
175
|
+
const compacted = compactSnapshotHistory(snapshot, this.requestHistoryBase);
|
|
176
|
+
return isCapturedJson(snapshot) ? brandCapturedRecord(compacted) : compacted;
|
|
177
|
+
}
|
|
178
|
+
constructor(core, opts = {}) {
|
|
179
|
+
this.core = core;
|
|
180
|
+
const coreRedact = readCoreRedactor(core);
|
|
181
|
+
this.redact = opts.redact ?? coreRedact ?? ((value) => value);
|
|
182
|
+
this.redactShared = opts.redactShared ?? opts.redact ?? coreRedact ?? ((value) => value);
|
|
183
|
+
this.redactOperationResult =
|
|
184
|
+
opts.redactOperationResult ?? ((_operation, value) => this.redact(value));
|
|
185
|
+
this.redactRecord = opts.redactRecord;
|
|
186
|
+
this.hasRedactionProvenance =
|
|
187
|
+
opts.redact !== undefined ||
|
|
188
|
+
opts.redactShared !== undefined ||
|
|
189
|
+
opts.redactRecord !== undefined ||
|
|
190
|
+
coreRedact !== undefined;
|
|
191
|
+
this.initialBoot = opts.boot ? prepareEngineBoot(opts.boot) : undefined;
|
|
192
|
+
this.engineBoot = this.initialBoot ?? createEngineBoot();
|
|
193
|
+
}
|
|
194
|
+
redactValue(value) {
|
|
195
|
+
return this.redactShared(value);
|
|
196
|
+
}
|
|
197
|
+
async restore() {
|
|
198
|
+
await this.core.ready();
|
|
199
|
+
if (this.initialBoot) {
|
|
200
|
+
this.assertCompatibleBoot(this.initialBoot);
|
|
201
|
+
this.restoreBoot(this.initialBoot);
|
|
202
|
+
this.restored = true;
|
|
203
|
+
this.knownLength = this.initialBoot.length;
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const records = [];
|
|
207
|
+
for (let i = 0; i < this.core.length; i++) {
|
|
208
|
+
const raw = await this.core.get(i);
|
|
209
|
+
const found = isRecord(raw) && typeof raw.version === 'number' ? raw.version : -1;
|
|
210
|
+
if (found !== RUN_LOG_VERSION) {
|
|
211
|
+
throw new RunIncompatible({
|
|
212
|
+
runId: this.metadata?.runId,
|
|
213
|
+
found,
|
|
214
|
+
expected: RUN_LOG_VERSION,
|
|
215
|
+
kind: 'log'
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
const record = captureRunRecord(raw);
|
|
219
|
+
records.push(record);
|
|
220
|
+
}
|
|
221
|
+
let boot;
|
|
222
|
+
try {
|
|
223
|
+
boot = buildEngineBoot(records, { nextSeq: records.length });
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
if (error instanceof InvalidEngineStep) {
|
|
227
|
+
throw error;
|
|
228
|
+
}
|
|
229
|
+
throw new InvalidEngineStep(`Could not restore run boot: ${String(error)}`);
|
|
230
|
+
}
|
|
231
|
+
this.assertCompatibleBoot(boot);
|
|
232
|
+
this.restoreBoot(boot);
|
|
233
|
+
this.restored = true;
|
|
234
|
+
this.knownLength = boot.length;
|
|
235
|
+
}
|
|
236
|
+
assertCompatibleBoot(boot) {
|
|
237
|
+
validateEngineBoot(boot);
|
|
238
|
+
if (this.core.length !== boot.length) {
|
|
239
|
+
throw new Error('Run boot length does not match the opened store');
|
|
240
|
+
}
|
|
241
|
+
if (!boot.metadata) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (boot.machineVersion === MACHINE_VERSION) {
|
|
245
|
+
const base = requestMessages(boot.metadata.request);
|
|
246
|
+
const allowLegacyFailureResults = this.allowLegacyFailureResults(boot);
|
|
247
|
+
if (boot.snapshot !== undefined) {
|
|
248
|
+
validateSnapshotHistory(boot.snapshot, base, {
|
|
249
|
+
allowLegacyInlineFailureResults: allowLegacyFailureResults
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
if (boot.eventState !== null) {
|
|
253
|
+
validateHistoryProjection(boot.eventState, {
|
|
254
|
+
base,
|
|
255
|
+
failureRefs: boot.snapshot === undefined
|
|
256
|
+
? new Set()
|
|
257
|
+
: snapshotFailureResultRefs(boot.snapshot, allowLegacyFailureResults),
|
|
258
|
+
validateFailureOutputs: !allowLegacyFailureResults
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (boot.machineVersion !== MACHINE_VERSION) {
|
|
263
|
+
throw new RunIncompatible({
|
|
264
|
+
runId: boot.metadata.runId,
|
|
265
|
+
found: boot.machineVersion ?? -1,
|
|
266
|
+
expected: MACHINE_VERSION,
|
|
267
|
+
kind: 'machine'
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
if (!compatibleXStateVersion(boot.xstateVersion)) {
|
|
271
|
+
throw new RunIncompatible({
|
|
272
|
+
runId: boot.metadata.runId,
|
|
273
|
+
found: boot.xstateVersion ?? 'missing',
|
|
274
|
+
expected: XSTATE_VERSION,
|
|
275
|
+
kind: 'xstate'
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
allowLegacyFailureResults(boot) {
|
|
280
|
+
return (boot.machineVersion ?? 0) < FAILURE_RESULT_REF_MACHINE_VERSION;
|
|
281
|
+
}
|
|
282
|
+
restoreBoot(boot) {
|
|
283
|
+
this.engineBoot = prepareEngineBoot(boot);
|
|
284
|
+
this.metadata = boot.metadata ? copy(boot.metadata) : undefined;
|
|
285
|
+
this.snapshotSavedAt = boot.snapshotSavedAt;
|
|
286
|
+
this.firstSavedAt = boot.firstSavedAt;
|
|
287
|
+
this.deltasSinceCheckpoint = boot.deltasSinceCheckpoint;
|
|
288
|
+
this.redactedPendingArgs = boot.redactedPendingArgs;
|
|
289
|
+
this.unredactedLength = boot.unredactedLength;
|
|
290
|
+
this.restoredRedacted = boot.restoredRedacted;
|
|
291
|
+
this.forkBeforeWrite.clear();
|
|
292
|
+
for (const [id, length] of boot.forkBeforeWrite) {
|
|
293
|
+
this.forkBeforeWrite.set(id, length);
|
|
294
|
+
}
|
|
295
|
+
this.receipts.clear();
|
|
296
|
+
for (const receipt of boot.receipts) {
|
|
297
|
+
this.receipts.set(receipt.id, copy(receipt));
|
|
298
|
+
}
|
|
299
|
+
this.intentArgs.clear();
|
|
300
|
+
for (const [id, args] of boot.intentArgs) {
|
|
301
|
+
this.intentArgs.set(id, copy(args));
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
exportBoot() {
|
|
305
|
+
return copy(this.engineBoot);
|
|
306
|
+
}
|
|
307
|
+
exportSnapshot() {
|
|
308
|
+
return copy(this.engineBoot.snapshot);
|
|
309
|
+
}
|
|
310
|
+
trackForkPoint(receipt) {
|
|
311
|
+
if (receipt.status === 'result' && receipt.abandoned === true)
|
|
312
|
+
return;
|
|
313
|
+
if (receipt.effect !== 'write' || receipt.name.startsWith('hook:'))
|
|
314
|
+
return;
|
|
315
|
+
if (receipt.status === 'intent') {
|
|
316
|
+
this.forkBeforeWrite.set(receipt.id, this.unredactedLength);
|
|
317
|
+
this.unredactedLength = 0;
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
this.unredactedLength = this.forkBeforeWrite.get(receipt.id) ?? 0;
|
|
321
|
+
}
|
|
322
|
+
settledCall(receiptId) {
|
|
323
|
+
const receipt = this.receipts.get(receiptId);
|
|
324
|
+
return receipt?.status === 'result';
|
|
325
|
+
}
|
|
326
|
+
unresolvedWriteIds() {
|
|
327
|
+
const dispatched = unresolvedWriteIds(this.receipts.values());
|
|
328
|
+
const queued = unsettledRedactedIds(this.engineBoot.redactedPendingIds, this.receipts.values()).filter((id) => !dispatched.includes(id));
|
|
329
|
+
return [...dispatched, ...queued];
|
|
330
|
+
}
|
|
331
|
+
redactedPendingCallIds(snapshot) {
|
|
332
|
+
if (!this.hasRedactionProvenance)
|
|
333
|
+
return [];
|
|
334
|
+
const isSettled = (receiptId) => this.settledCall(receiptId);
|
|
335
|
+
const original = pendingCallArgs(snapshot, isSettled);
|
|
336
|
+
const redacted = pendingCallArgs(this.redactShared(snapshot), isSettled);
|
|
337
|
+
const ids = [];
|
|
338
|
+
for (const [id, args] of original) {
|
|
339
|
+
if (stableStringify(args) !== stableStringify(redacted.get(id))) {
|
|
340
|
+
ids.push(id);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return ids;
|
|
344
|
+
}
|
|
345
|
+
safeForkLength() {
|
|
346
|
+
return safeForkLength(this.receipts.values(), this.forkBeforeWrite, this.unredactedLength);
|
|
347
|
+
}
|
|
348
|
+
getReceipt(receiptId) {
|
|
349
|
+
const receipt = this.receipts.get(receiptId);
|
|
350
|
+
return receipt === undefined ? undefined : copy(receipt);
|
|
351
|
+
}
|
|
352
|
+
async commitResultFacts(receiptId, facts) {
|
|
353
|
+
await this.flush();
|
|
354
|
+
if (this.sealed) {
|
|
355
|
+
throw new AbortedError();
|
|
356
|
+
}
|
|
357
|
+
const receipt = this.receipts.get(receiptId);
|
|
358
|
+
if (!receipt || receipt.status !== 'result') {
|
|
359
|
+
throw new Error(`Cannot commit result facts without a settled receipt: ${receiptId}`);
|
|
360
|
+
}
|
|
361
|
+
const enriched = {
|
|
362
|
+
id: receipt.id,
|
|
363
|
+
name: receipt.name,
|
|
364
|
+
effect: receipt.effect,
|
|
365
|
+
status: 'result',
|
|
366
|
+
result: copy(receipt.result),
|
|
367
|
+
resultUndefined: receipt.resultUndefined,
|
|
368
|
+
facts: this.redact(copy(facts)),
|
|
369
|
+
abandoned: receipt.abandoned === true ? true : undefined
|
|
370
|
+
};
|
|
371
|
+
await this.append({ version: RUN_LOG_VERSION, type: 'operation', receipt: enriched });
|
|
372
|
+
this.receipts.set(receiptId, copy(enriched));
|
|
373
|
+
}
|
|
374
|
+
retryLifecycleReceipts() {
|
|
375
|
+
return [...this.receipts.values()]
|
|
376
|
+
.filter((receipt) => receipt.status === 'retry' &&
|
|
377
|
+
(receipt.name === 'hook:runPause' || receipt.name === 'hook:runResume'))
|
|
378
|
+
.map(copy);
|
|
379
|
+
}
|
|
380
|
+
pendingArgumentsWereRedacted(snapshot) {
|
|
381
|
+
if (!this.hasRedactionProvenance)
|
|
382
|
+
return null;
|
|
383
|
+
const isSettled = (receiptId) => this.settledCall(receiptId);
|
|
384
|
+
const pending = pendingToolArgs(snapshot, isSettled);
|
|
385
|
+
const redacted = pendingToolArgs(this.redactShared(snapshot), isSettled);
|
|
386
|
+
if (pending.some((value, index) => stableStringify(value) !== stableStringify(redacted[index]))) {
|
|
387
|
+
return true;
|
|
388
|
+
}
|
|
389
|
+
if (pending.length > 0 && this.restoredRedacted && this.snapshot !== undefined) {
|
|
390
|
+
const saved = pendingToolArgs(this.snapshot, isSettled);
|
|
391
|
+
return stableStringify(pending) === stableStringify(saved);
|
|
392
|
+
}
|
|
393
|
+
return false;
|
|
394
|
+
}
|
|
395
|
+
append(record) {
|
|
396
|
+
let captured;
|
|
397
|
+
try {
|
|
398
|
+
captured = captureRunRecord(this.redactRecord ? this.redactRecord(record) : record);
|
|
399
|
+
}
|
|
400
|
+
catch (error) {
|
|
401
|
+
this.fail(error);
|
|
402
|
+
return Promise.reject(this.failure);
|
|
403
|
+
}
|
|
404
|
+
const next = this.tail.then(async () => {
|
|
405
|
+
if (this.failure) {
|
|
406
|
+
throw this.failure;
|
|
407
|
+
}
|
|
408
|
+
if (this.sealed)
|
|
409
|
+
throw new AbortedError();
|
|
410
|
+
const boundaryBoot = readBoundaryBoot(this.core);
|
|
411
|
+
if (boundaryBoot) {
|
|
412
|
+
const current = boundaryBoot();
|
|
413
|
+
if (current.length !== this.engineBoot.length ||
|
|
414
|
+
current.nextSeq !== this.engineBoot.nextSeq) {
|
|
415
|
+
throw new Error('Internal engine boundary and run journal are out of sync');
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
const step = {
|
|
419
|
+
seq: this.engineBoot.nextSeq,
|
|
420
|
+
expectedLength: this.engineBoot.length,
|
|
421
|
+
records: [captured]
|
|
422
|
+
};
|
|
423
|
+
let candidate;
|
|
424
|
+
if (!boundaryBoot) {
|
|
425
|
+
candidate = applyEngineStep(this.engineBoot, step);
|
|
426
|
+
}
|
|
427
|
+
await this.core.append(captured);
|
|
428
|
+
if (boundaryBoot) {
|
|
429
|
+
const acknowledged = boundaryBoot();
|
|
430
|
+
if (acknowledged.length !== this.engineBoot.length + 1 ||
|
|
431
|
+
acknowledged.nextSeq !== this.engineBoot.nextSeq + 1) {
|
|
432
|
+
throw new Error('Internal engine boundary did not acknowledge exactly one run record');
|
|
433
|
+
}
|
|
434
|
+
this.engineBoot = acknowledged;
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
if (!candidate) {
|
|
438
|
+
throw new Error('Run journal did not fold its acknowledged step');
|
|
439
|
+
}
|
|
440
|
+
this.engineBoot = candidate;
|
|
441
|
+
}
|
|
442
|
+
this.knownLength = this.engineBoot.length;
|
|
443
|
+
this.metadata = this.engineBoot.metadata ?? undefined;
|
|
444
|
+
this.snapshotSavedAt = this.engineBoot.snapshotSavedAt;
|
|
445
|
+
this.firstSavedAt = this.engineBoot.firstSavedAt;
|
|
446
|
+
this.deltasSinceCheckpoint = this.engineBoot.deltasSinceCheckpoint;
|
|
447
|
+
this.redactedPendingArgs = this.engineBoot.redactedPendingArgs;
|
|
448
|
+
this.unredactedLength = this.engineBoot.unredactedLength;
|
|
449
|
+
this.restoredRedacted = this.engineBoot.restoredRedacted;
|
|
450
|
+
this.forkBeforeWrite.clear();
|
|
451
|
+
for (const [id, length] of this.engineBoot.forkBeforeWrite) {
|
|
452
|
+
this.forkBeforeWrite.set(id, length);
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
this.tail = next.catch((error) => {
|
|
456
|
+
this.fail(error);
|
|
457
|
+
});
|
|
458
|
+
return next;
|
|
459
|
+
}
|
|
460
|
+
fail(error) {
|
|
461
|
+
if (this.failure)
|
|
462
|
+
return;
|
|
463
|
+
this.failure = error instanceof Error ? error : new Error(String(error));
|
|
464
|
+
this.onFailure?.(this.failure);
|
|
465
|
+
}
|
|
466
|
+
hasPendingFlush() {
|
|
467
|
+
return this.failure !== null || this.tail !== this.settledTail;
|
|
468
|
+
}
|
|
469
|
+
async flush() {
|
|
470
|
+
let pending;
|
|
471
|
+
do {
|
|
472
|
+
pending = this.tail;
|
|
473
|
+
await pending;
|
|
474
|
+
} while (pending !== this.tail);
|
|
475
|
+
if (this.failure) {
|
|
476
|
+
throw this.failure;
|
|
477
|
+
}
|
|
478
|
+
this.settledTail = pending;
|
|
479
|
+
}
|
|
480
|
+
async resolve(resolutions) {
|
|
481
|
+
if (!Array.isArray(resolutions)) {
|
|
482
|
+
throw new Error('Recovery resolutions must be an array');
|
|
483
|
+
}
|
|
484
|
+
const pendingNames = pendingCallNames(this.snapshot);
|
|
485
|
+
const ids = new Set();
|
|
486
|
+
const settledIds = new Set();
|
|
487
|
+
for (const resolution of resolutions) {
|
|
488
|
+
if (!resolution ||
|
|
489
|
+
typeof resolution.operationId !== 'string' ||
|
|
490
|
+
(resolution.decision !== 'retry' &&
|
|
491
|
+
resolution.decision !== 'result' &&
|
|
492
|
+
resolution.decision !== 'skip') ||
|
|
493
|
+
(resolution.decision === 'result' && !('result' in resolution))) {
|
|
494
|
+
throw new Error('Invalid recovery resolution');
|
|
495
|
+
}
|
|
496
|
+
if (ids.has(resolution.operationId)) {
|
|
497
|
+
throw new Error(`Stale or mismatched recovery resolution: ${resolution.operationId}`);
|
|
498
|
+
}
|
|
499
|
+
const receipt = this.receipts.get(resolution.operationId);
|
|
500
|
+
if (receipt) {
|
|
501
|
+
const redactedWrite = receipt.effect === 'write' &&
|
|
502
|
+
receipt.status === 'intent' &&
|
|
503
|
+
this.engineBoot.redactedPendingIds.includes(receipt.id) &&
|
|
504
|
+
JSON.stringify(receipt.args).includes('[redacted]');
|
|
505
|
+
if (receipt.status !== 'intent' ||
|
|
506
|
+
(resolution.decision === 'skip' && receipt.effect === 'write' && !redactedWrite)) {
|
|
507
|
+
throw new Error(`Stale or mismatched recovery resolution: ${resolution.operationId}`);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
else if (resolution.decision === 'retry' || !pendingNames.has(resolution.operationId)) {
|
|
511
|
+
throw new Error(`Stale or mismatched recovery resolution: ${resolution.operationId}`);
|
|
512
|
+
}
|
|
513
|
+
ids.add(resolution.operationId);
|
|
514
|
+
if (resolution.decision === 'result' || resolution.decision === 'skip') {
|
|
515
|
+
settledIds.add(resolution.operationId);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
const stillUnsettled = unsettledRedactedIds(this.engineBoot.redactedPendingIds, this.receipts.values()).filter((id) => !settledIds.has(id));
|
|
519
|
+
if (resolutions.some((resolution) => resolution.decision === 'retry') &&
|
|
520
|
+
stillUnsettled.length > 0) {
|
|
521
|
+
const forkAt = this.safeForkLength();
|
|
522
|
+
const recoveryOperationIds = this.unresolvedWriteIds();
|
|
523
|
+
const forkSafe = forkAt !== null &&
|
|
524
|
+
!forkRepeatsSettledWrite(this.receipts.values(), this.forkBeforeWrite, forkAt);
|
|
525
|
+
let message = 'This run cannot resume because pending tool arguments were redacted';
|
|
526
|
+
if (forkSafe) {
|
|
527
|
+
message += `; fork at ${forkAt} to continue without them`;
|
|
528
|
+
}
|
|
529
|
+
else if (recoveryOperationIds.length > 0) {
|
|
530
|
+
message += `; ${recoveryAdvice(recoveryOperationIds, this.receipts.values(), this.engineBoot.redactedPendingIds)}`;
|
|
531
|
+
}
|
|
532
|
+
throw Object.assign(new Error(message), {
|
|
533
|
+
code: 'PENDING_ARGS_REDACTED',
|
|
534
|
+
forkAt,
|
|
535
|
+
forkSafe,
|
|
536
|
+
recoveryOperationIds
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
for (const resolution of resolutions) {
|
|
540
|
+
const previous = this.receipts.get(resolution.operationId);
|
|
541
|
+
const providedResult = resolution.decision === 'result' ? resolution.result : undefined;
|
|
542
|
+
if (previous) {
|
|
543
|
+
if (previous.status === 'result') {
|
|
544
|
+
throw new Error(`Stale or mismatched recovery resolution: ${resolution.operationId}`);
|
|
545
|
+
}
|
|
546
|
+
const body = resolution.decision === 'skip'
|
|
547
|
+
? { skipped: true, reason: 'not executed; arguments unavailable after resume' }
|
|
548
|
+
: (providedResult ?? null);
|
|
549
|
+
const result = this.redactOperationResult(previous, copy(body));
|
|
550
|
+
const receipt = resolution.decision === 'retry'
|
|
551
|
+
? { ...previous, status: 'retry' }
|
|
552
|
+
: {
|
|
553
|
+
id: previous.id,
|
|
554
|
+
name: previous.name,
|
|
555
|
+
effect: previous.effect,
|
|
556
|
+
status: 'result',
|
|
557
|
+
result,
|
|
558
|
+
resultUndefined: resolution.decision === 'result' && providedResult === undefined
|
|
559
|
+
? true
|
|
560
|
+
: undefined
|
|
561
|
+
};
|
|
562
|
+
await this.append({ version: RUN_LOG_VERSION, type: 'operation', receipt });
|
|
563
|
+
this.receipts.set(receipt.id, copy(receipt));
|
|
564
|
+
this.trackForkPoint(receipt);
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
567
|
+
const name = pendingNames.get(resolution.operationId);
|
|
568
|
+
if (name === undefined) {
|
|
569
|
+
throw new Error(`Stale or mismatched recovery resolution: ${resolution.operationId}`);
|
|
570
|
+
}
|
|
571
|
+
const body = resolution.decision === 'skip'
|
|
572
|
+
? { skipped: true, reason: 'not executed; arguments unavailable after resume' }
|
|
573
|
+
: (providedResult ?? null);
|
|
574
|
+
const result = this.redactOperationResult({ id: resolution.operationId, name, effect: 'write', args: undefined }, copy(body));
|
|
575
|
+
const receipt = {
|
|
576
|
+
id: resolution.operationId,
|
|
577
|
+
name,
|
|
578
|
+
effect: 'write',
|
|
579
|
+
status: 'result',
|
|
580
|
+
result,
|
|
581
|
+
resultUndefined: undefined,
|
|
582
|
+
abandoned: true
|
|
583
|
+
};
|
|
584
|
+
await this.append({ version: RUN_LOG_VERSION, type: 'operation', receipt });
|
|
585
|
+
this.receipts.set(receipt.id, copy(receipt));
|
|
586
|
+
this.trackForkPoint(receipt);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
async operation(operation, execute, facts, signal) {
|
|
590
|
+
if (this.sealed)
|
|
591
|
+
throw new AbortedError();
|
|
592
|
+
operation = this.redact(copy(operation));
|
|
593
|
+
await this.flush();
|
|
594
|
+
if (this.sealed) {
|
|
595
|
+
throw new AbortedError();
|
|
596
|
+
}
|
|
597
|
+
const previous = this.receipts.get(operation.id);
|
|
598
|
+
const previousArgs = this.intentArgs.get(operation.id);
|
|
599
|
+
const abandoned = previous?.status === 'result' && previous.abandoned === true;
|
|
600
|
+
if (previous &&
|
|
601
|
+
!abandoned &&
|
|
602
|
+
(previous.name !== operation.name ||
|
|
603
|
+
stableStringify(previousArgs) !== stableStringify(operation.args) ||
|
|
604
|
+
previous.effect !== operation.effect)) {
|
|
605
|
+
throw new Error(`Operation identity mismatch: ${operation.id}`);
|
|
606
|
+
}
|
|
607
|
+
if (previous?.status === 'result') {
|
|
608
|
+
if (previous.facts !== undefined) {
|
|
609
|
+
facts?.restore(copy(previous.facts));
|
|
610
|
+
}
|
|
611
|
+
if (previous.resultUndefined) {
|
|
612
|
+
return JSON.parse(JSON.stringify({ value: undefined })).value;
|
|
613
|
+
}
|
|
614
|
+
return JSON.parse(JSON.stringify(previous.result));
|
|
615
|
+
}
|
|
616
|
+
if (previous?.status === 'intent' && previous.effect === 'write') {
|
|
617
|
+
throw new RecoveryRequired(previous);
|
|
618
|
+
}
|
|
619
|
+
const intent = { ...operation, status: 'intent' };
|
|
620
|
+
await this.append({ version: RUN_LOG_VERSION, type: 'operation', receipt: intent });
|
|
621
|
+
this.receipts.set(intent.id, copy(intent));
|
|
622
|
+
this.intentArgs.set(intent.id, copy(intent.args));
|
|
623
|
+
if (this.sealed) {
|
|
624
|
+
throw new AbortedError();
|
|
625
|
+
}
|
|
626
|
+
if (signal?.aborted) {
|
|
627
|
+
const retry = { ...operation, status: 'retry' };
|
|
628
|
+
await this.append({ version: RUN_LOG_VERSION, type: 'operation', receipt: retry });
|
|
629
|
+
this.receipts.set(retry.id, copy(retry));
|
|
630
|
+
throw new AbortedError();
|
|
631
|
+
}
|
|
632
|
+
let result;
|
|
633
|
+
try {
|
|
634
|
+
result = await execute();
|
|
635
|
+
}
|
|
636
|
+
catch (error) {
|
|
637
|
+
if (operation.effect === 'write') {
|
|
638
|
+
throw error instanceof RecoveryRequired || error instanceof AbortedError
|
|
639
|
+
? error
|
|
640
|
+
: new RecoveryRequired({
|
|
641
|
+
...operation,
|
|
642
|
+
reason: this.redact(errorMessage(error))
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
throw error;
|
|
646
|
+
}
|
|
647
|
+
try {
|
|
648
|
+
const resultValue = this.redactOperationResult(operation, copy(result === undefined ? null : result));
|
|
649
|
+
const receipt = copy({
|
|
650
|
+
id: operation.id,
|
|
651
|
+
name: operation.name,
|
|
652
|
+
effect: operation.effect,
|
|
653
|
+
status: 'result',
|
|
654
|
+
result: resultValue,
|
|
655
|
+
resultUndefined: result === undefined ? true : undefined,
|
|
656
|
+
facts: facts ? this.redact(copy(facts.capture())) : undefined
|
|
657
|
+
});
|
|
658
|
+
await this.append({ version: RUN_LOG_VERSION, type: 'operation', receipt });
|
|
659
|
+
this.receipts.set(receipt.id, copy(receipt));
|
|
660
|
+
}
|
|
661
|
+
catch (error) {
|
|
662
|
+
this.fail(error);
|
|
663
|
+
throw error;
|
|
664
|
+
}
|
|
665
|
+
return result;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
function readCoreRedactor(core) {
|
|
669
|
+
const candidate = Reflect.get(core, 'redactValue');
|
|
670
|
+
if (typeof candidate !== 'function') {
|
|
671
|
+
return undefined;
|
|
672
|
+
}
|
|
673
|
+
return (value) => candidate(value);
|
|
674
|
+
}
|
|
675
|
+
const owners = new WeakMap();
|
|
676
|
+
export function acquireRun(owner, runId) {
|
|
677
|
+
const runs = owners.get(owner) ?? new Set();
|
|
678
|
+
if (runs.has(runId)) {
|
|
679
|
+
throw new Error(`Run ${runId} is already active`);
|
|
680
|
+
}
|
|
681
|
+
runs.add(runId);
|
|
682
|
+
owners.set(owner, runs);
|
|
683
|
+
return () => {
|
|
684
|
+
runs.delete(runId);
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
export class EventCodec {
|
|
688
|
+
base = Object.create(null);
|
|
689
|
+
historyBase;
|
|
690
|
+
constructor(historyBase) {
|
|
691
|
+
this.historyBase = historyBase;
|
|
692
|
+
}
|
|
693
|
+
reset() {
|
|
694
|
+
this.base = Object.create(null);
|
|
695
|
+
}
|
|
696
|
+
encode(event) {
|
|
697
|
+
const redacted = isCapturedJson(event);
|
|
698
|
+
const compacted = compactMachineEvent(event, this.historyBase);
|
|
699
|
+
const plain = eventRecord(redacted ? compacted : copy(compacted));
|
|
700
|
+
const patch = diff(this.base, plain) ?? { o: {} };
|
|
701
|
+
this.merge(plain);
|
|
702
|
+
return redacted ? brandCapturedRecord(patch) : patch;
|
|
703
|
+
}
|
|
704
|
+
decode(patch) {
|
|
705
|
+
const event = eventRecord(apply(copy(this.base), patch));
|
|
706
|
+
this.merge(copy(event));
|
|
707
|
+
return eventRecord(materializeMachineEvent(event, this.historyBase));
|
|
708
|
+
}
|
|
709
|
+
decodeLegacy(patch) {
|
|
710
|
+
const event = eventRecord(apply(copy(this.base), patch));
|
|
711
|
+
this.merge(copy(event));
|
|
712
|
+
return event;
|
|
713
|
+
}
|
|
714
|
+
merge(event) {
|
|
715
|
+
for (const key of Object.keys(event)) {
|
|
716
|
+
const value = event[key];
|
|
717
|
+
const previous = Object.hasOwn(this.base, key) ? this.base[key] : undefined;
|
|
718
|
+
Object.defineProperty(this.base, key, {
|
|
719
|
+
value: isRecord(value) && isRecord(previous) ? { ...previous, ...value } : value,
|
|
720
|
+
writable: true,
|
|
721
|
+
enumerable: true,
|
|
722
|
+
configurable: true
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
export async function* readRunLog(core, opts = {}) {
|
|
728
|
+
await core.ready();
|
|
729
|
+
let boot = createEngineBoot();
|
|
730
|
+
let codec = new EventCodec();
|
|
731
|
+
for (let index = 0; index < core.length; index++) {
|
|
732
|
+
const raw = await core.get(index);
|
|
733
|
+
const sourceVersion = isRecord(raw) && typeof raw.version === 'number' ? raw.version : -1;
|
|
734
|
+
if (!isRecord(raw) ||
|
|
735
|
+
(sourceVersion !== RUN_LOG_VERSION &&
|
|
736
|
+
!(opts.legacyInspection === true && sourceVersion === 4))) {
|
|
737
|
+
throw new RunIncompatible({
|
|
738
|
+
runId: boot.metadata?.runId,
|
|
739
|
+
found: sourceVersion,
|
|
740
|
+
expected: RUN_LOG_VERSION,
|
|
741
|
+
kind: 'log'
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
const record = inspectionRecord(raw, sourceVersion);
|
|
745
|
+
const foldable = sourceVersion === 4 && (record.type === 'checkpoint' || record.type === 'delta')
|
|
746
|
+
? { ...record, events: [] }
|
|
747
|
+
: record;
|
|
748
|
+
const next = applyEngineStep(boot, {
|
|
749
|
+
seq: boot.nextSeq,
|
|
750
|
+
expectedLength: boot.length,
|
|
751
|
+
records: [foldable]
|
|
752
|
+
});
|
|
753
|
+
if (record.type === 'metadata') {
|
|
754
|
+
codec = new EventCodec(requestMessages(record.request));
|
|
755
|
+
if (record.machine !== MACHINE_VERSION && !opts.anyMachine) {
|
|
756
|
+
throw new RunIncompatible({
|
|
757
|
+
runId: record.runId,
|
|
758
|
+
found: record.machine,
|
|
759
|
+
expected: MACHINE_VERSION,
|
|
760
|
+
kind: 'machine'
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
let events = [];
|
|
765
|
+
if (record.type === 'checkpoint' || record.type === 'delta') {
|
|
766
|
+
if (record.reset) {
|
|
767
|
+
codec.reset();
|
|
768
|
+
}
|
|
769
|
+
events = (record.events ?? []).map((patch) => sourceVersion === 4 ? codec.decodeLegacy(patch) : codec.decode(patch));
|
|
770
|
+
if (record.type === 'checkpoint') {
|
|
771
|
+
codec.reset();
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
boot = next;
|
|
775
|
+
yield {
|
|
776
|
+
index,
|
|
777
|
+
sourceVersion,
|
|
778
|
+
record,
|
|
779
|
+
snapshot: materializeSnapshotHistory(boot.snapshot, requestMessages(boot.metadata?.request), boot.machineVersion !== null && boot.machineVersion < FAILURE_RESULT_REF_MACHINE_VERSION),
|
|
780
|
+
events
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
}
|