@qaick/executor 0.2.6 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/execution-plan.d.ts +63 -0
- package/dist/execution-plan.js +235 -0
- package/dist/executor.d.ts +7 -3
- package/dist/executor.js +135 -63
- package/dist/migrated-executor.d.ts +5 -3
- package/dist/migrated-executor.js +4 -0
- package/dist/types.d.ts +61 -2
- package/package.json +2 -2
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { NormalizedExecutionDocument, ExecutionPackageV7, ExecutableNodeV8, ControlV8, ConditionV8, ContextNode, ExecutionBindingV8, HierarchyAddress, ScopeIteration, IterationSelection, ExecutableInput } from '@qaick/contract';
|
|
2
|
+
export declare const addressKey: (a: HierarchyAddress) => string;
|
|
3
|
+
export type RunIteration = ScopeIteration | IterationSelection;
|
|
4
|
+
export type PlanStep = Omit<ExecutableNodeV8, 'children' | 'control'> & {
|
|
5
|
+
structuralAddress?: HierarchyAddress;
|
|
6
|
+
children?: PlanStep[];
|
|
7
|
+
control?: Omit<ControlV8, 'branches'> & {
|
|
8
|
+
branches?: Array<{
|
|
9
|
+
id: string;
|
|
10
|
+
occurrence: string;
|
|
11
|
+
condition: boolean | ConditionV8;
|
|
12
|
+
children: PlanStep[];
|
|
13
|
+
}>;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
export type PlanFlow = {
|
|
17
|
+
id: string;
|
|
18
|
+
name: string;
|
|
19
|
+
occurrence: string;
|
|
20
|
+
steps: PlanStep[];
|
|
21
|
+
inputDeclarations?: ExecutableInput[];
|
|
22
|
+
requiredInputs?: string[];
|
|
23
|
+
outputBindings?: Record<string, ExecutionBindingV8>;
|
|
24
|
+
};
|
|
25
|
+
export type PlanIssue = {
|
|
26
|
+
code: string;
|
|
27
|
+
message: string;
|
|
28
|
+
stepId?: string;
|
|
29
|
+
};
|
|
30
|
+
/** References into the immutable canonical document, not a second executable package. */
|
|
31
|
+
export declare class ExecutionPlan {
|
|
32
|
+
readonly document: NormalizedExecutionDocument;
|
|
33
|
+
readonly legacy?: ExecutionPackageV7 | undefined;
|
|
34
|
+
readonly context: Map<string, ContextNode>;
|
|
35
|
+
readonly nodes: Map<string, PlanStep>;
|
|
36
|
+
readonly ordinals: Map<string, string>;
|
|
37
|
+
readonly inScope: Set<string>;
|
|
38
|
+
readonly steps: PlanStep[];
|
|
39
|
+
readonly flows: PlanFlow[];
|
|
40
|
+
readonly target: {
|
|
41
|
+
kind: "flow" | "project";
|
|
42
|
+
id: string;
|
|
43
|
+
name: string;
|
|
44
|
+
};
|
|
45
|
+
readonly packageId: string;
|
|
46
|
+
readonly inputDeclarations: ExecutableInput[] | undefined;
|
|
47
|
+
readonly requiredInputs: string[] | undefined;
|
|
48
|
+
readonly runtimeRequirements: string[] | undefined;
|
|
49
|
+
readonly project: boolean;
|
|
50
|
+
constructor(document: NormalizedExecutionDocument, legacy?: ExecutionPackageV7 | undefined);
|
|
51
|
+
ancestors(key: string): ContextNode[];
|
|
52
|
+
loops(key: string): string[];
|
|
53
|
+
dependency(operand: ExecutionBindingV8, consumer: PlanStep, phase: 'entry' | 'completion', actual?: RunIteration[]): PlanIssue[];
|
|
54
|
+
source(operand: Exclude<ExecutionBindingV8, {
|
|
55
|
+
type: 'input' | 'unresolved-output';
|
|
56
|
+
}>): {
|
|
57
|
+
step: PlanStep;
|
|
58
|
+
iterations: RunIteration[];
|
|
59
|
+
};
|
|
60
|
+
/** Prove unavailable dependencies, never evaluate a future Action's output value. */
|
|
61
|
+
preflight(inputs: Record<string, unknown>, runtime: Record<string, unknown>, supports: (action: string) => boolean): PlanIssue[];
|
|
62
|
+
}
|
|
63
|
+
export declare function journalKey(step: Pick<PlanStep, 'occurrence' | 'structuralAddress'>, iterations: RunIteration[], version: 7 | 8): string;
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { executionOperandDependencyIssues } from '@qaick/contract';
|
|
2
|
+
import { resolveDeclaredInputs } from './declared-inputs.js';
|
|
3
|
+
export const addressKey = (a) => JSON.stringify([a.scope, a.segments.map(s => s.kind === 'body' ? ['body'] : [s.kind, s.position])]);
|
|
4
|
+
/** References into the immutable canonical document, not a second executable package. */
|
|
5
|
+
export class ExecutionPlan {
|
|
6
|
+
document;
|
|
7
|
+
legacy;
|
|
8
|
+
context;
|
|
9
|
+
nodes = new Map();
|
|
10
|
+
ordinals = new Map();
|
|
11
|
+
inScope = new Set();
|
|
12
|
+
steps;
|
|
13
|
+
flows;
|
|
14
|
+
target;
|
|
15
|
+
packageId;
|
|
16
|
+
inputDeclarations;
|
|
17
|
+
requiredInputs;
|
|
18
|
+
runtimeRequirements;
|
|
19
|
+
project;
|
|
20
|
+
constructor(document, legacy) {
|
|
21
|
+
this.document = document;
|
|
22
|
+
this.legacy = legacy;
|
|
23
|
+
this.context = new Map(document.context.nodes.map(n => [n.key, n]));
|
|
24
|
+
this.target = document.target;
|
|
25
|
+
this.packageId = document.packageId;
|
|
26
|
+
this.runtimeRequirements = document.selection.runtimeRequirements;
|
|
27
|
+
for (const n of this.context.values())
|
|
28
|
+
if (n.originalAddress)
|
|
29
|
+
this.ordinals.set(addressKey(n.originalAddress), n.key);
|
|
30
|
+
for (const n of document.nodes)
|
|
31
|
+
this.nodes.set(n.occurrence, { ...n, children: undefined, control: undefined, structuralAddress: this.context.get(n.occurrence)?.originalAddress });
|
|
32
|
+
const list = (keys) => keys.map(k => this.nodes.get(k));
|
|
33
|
+
for (const n of document.nodes) {
|
|
34
|
+
const step = this.nodes.get(n.occurrence);
|
|
35
|
+
if (n.children)
|
|
36
|
+
step.children = list(n.children);
|
|
37
|
+
if (n.control)
|
|
38
|
+
step.control = { ...n.control, branches: n.control.branches?.map(b => {
|
|
39
|
+
const locator = this.context.get(b.occurrence).locator;
|
|
40
|
+
const last = locator.kind === 'source' ? locator.path.at(-1) : undefined;
|
|
41
|
+
return { ...b, id: last && 'id' in last ? last.id : b.occurrence, children: list(b.children) };
|
|
42
|
+
}) };
|
|
43
|
+
}
|
|
44
|
+
const flow = (key) => {
|
|
45
|
+
const f = document.flows.find(f => f.occurrence === key);
|
|
46
|
+
const c = this.context.get(key);
|
|
47
|
+
return { ...f, id: c.locator.kind === 'source' ? c.locator.path.at(-1)?.kind === 'flow' ? c.locator.path.at(-1).id : c.locator.root.id : key, name: c.name, steps: list(f.children) };
|
|
48
|
+
};
|
|
49
|
+
const root = document.selection.root.kind === 'occurrence' ? document.selection.root.key : document.context.root;
|
|
50
|
+
const selected = this.context.get(root);
|
|
51
|
+
const visit = (key) => { this.inScope.add(key); for (const child of this.context.get(key).children)
|
|
52
|
+
visit(child); };
|
|
53
|
+
visit(root);
|
|
54
|
+
const entryPayload = selected.kind === 'flow' ? document.flows.find(f => f.occurrence === root) : document.nodes.find(n => n.occurrence === root);
|
|
55
|
+
this.inputDeclarations = entryPayload?.inputDeclarations ?? document.selection.inputDeclarations;
|
|
56
|
+
this.requiredInputs = selected.kind === 'flow' ? [...new Set([...(document.flows.find(f => f.occurrence === root)?.requiredInputs ?? []), ...(document.selection.requiredInputs ?? [])])] : document.selection.requiredInputs;
|
|
57
|
+
this.project = selected.kind === 'project';
|
|
58
|
+
this.flows = !document.selection.executable ? [] : this.project ? (selected.sequence ?? []).map(flow) : [];
|
|
59
|
+
this.steps = !document.selection.executable ? [] : selected.kind === 'flow' ? flow(root).steps : this.project ? [] : [this.nodes.get(root)];
|
|
60
|
+
if (document.inputVersion === 8) {
|
|
61
|
+
const freeze = (v) => { if (v && typeof v === 'object') {
|
|
62
|
+
for (const child of Object.values(v))
|
|
63
|
+
freeze(child);
|
|
64
|
+
Object.freeze(v);
|
|
65
|
+
} };
|
|
66
|
+
for (const node of this.nodes.values())
|
|
67
|
+
freeze(node);
|
|
68
|
+
}
|
|
69
|
+
// Only the selected entry is dispatched; full unselected context is retained unchanged.
|
|
70
|
+
}
|
|
71
|
+
ancestors(key) {
|
|
72
|
+
const path = [];
|
|
73
|
+
for (let n = this.context.get(key); n; n = n.parent === null ? undefined : this.context.get(n.parent))
|
|
74
|
+
path.unshift(n);
|
|
75
|
+
return path;
|
|
76
|
+
}
|
|
77
|
+
loops(key) { return this.ancestors(key).slice(0, -1).filter(n => this.inScope.has(n.key) && this.nodes.get(n.key)?.control?.type === 'repeat').map(n => n.key); }
|
|
78
|
+
dependency(operand, consumer, phase, actual) {
|
|
79
|
+
if (this.document.inputVersion === 7)
|
|
80
|
+
return executionOperandDependencyIssues(this.legacy, operand, phase === 'entry' ? { phase, address: consumer.structuralAddress } : { phase, address: consumer.structuralAddress, iterations: actual }).map(i => ({ code: i.code, message: i.reason }));
|
|
81
|
+
const fail = (code, message) => [{ code, message: `${consumer.name}: ${message}`, stepId: consumer.id }];
|
|
82
|
+
if (operand.type === 'input')
|
|
83
|
+
return [];
|
|
84
|
+
if (operand.type === 'unresolved-output')
|
|
85
|
+
return fail('UNRESOLVED_SCOPE_BINDING', 'The output reference requires repair.');
|
|
86
|
+
const producer = operand.type === 'occurrence-output' ? operand.producer : this.ordinals.get(addressKey(operand.address));
|
|
87
|
+
if (!producer || !this.inScope.has(producer))
|
|
88
|
+
return fail('UNAVAILABLE_SCOPE_DEPENDENCY', 'The required output is outside this execution scope or has no original structural address.');
|
|
89
|
+
const source = this.nodes.get(producer);
|
|
90
|
+
if (!source || source.kind !== 'action' || !Object.hasOwn(source.output, operand.outputName))
|
|
91
|
+
return fail('UNRESOLVED_SCOPE_BINDING', 'The producer does not declare the required Action output.');
|
|
92
|
+
const a = this.ancestors(producer), b = this.ancestors(consumer.occurrence);
|
|
93
|
+
const descendant = a.some(n => n.key === consumer.occurrence) && producer !== consumer.occurrence;
|
|
94
|
+
if (phase === 'completion' && descendant) {
|
|
95
|
+
const path = a.slice(a.findIndex(n => n.key === consumer.occurrence));
|
|
96
|
+
if (path.some(n => n.kind === 'branch' || ['condition', 'parallel', 'branch'].includes(this.nodes.get(n.key)?.control?.type ?? '')))
|
|
97
|
+
return fail('UNAVAILABLE_SCOPE_CONTEXT', 'Conditional or parallel output exposure is not proven.');
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
let i = 0;
|
|
101
|
+
while (i < a.length && i < b.length && a[i].key === b[i].key)
|
|
102
|
+
i++;
|
|
103
|
+
const parent = a[i - 1], left = a[i], right = b[i];
|
|
104
|
+
if (!parent || !left || !right || left.kind === 'branch' || right.kind === 'branch' || !parent.sequence || parent.sequence.indexOf(left.key) >= parent.sequence.indexOf(right.key))
|
|
105
|
+
return fail('UNAVAILABLE_SCOPE_DEPENDENCY', 'The producer does not precede this consumer on a proven sequential path.');
|
|
106
|
+
}
|
|
107
|
+
const loops = this.loops(producer);
|
|
108
|
+
const selected = operand.type === 'occurrence-output' ? operand.iterations : operand.iterations.map(i => ({ loop: this.ordinals.get(addressKey(i.loopAddress)) ?? '', iteration: i.iteration }));
|
|
109
|
+
if (selected.length !== loops.length || loops.some(k => !selected.some(i => i.loop === k)))
|
|
110
|
+
return fail('UNAVAILABLE_SCOPE_CONTEXT', 'Select every actual in-scope producer loop; enclosing unexecuted loops cannot supply an iteration.');
|
|
111
|
+
const shared = this.loops(consumer.occurrence).filter(k => loops.includes(k));
|
|
112
|
+
if (phase === 'entry' && shared.length)
|
|
113
|
+
return fail('UNAVAILABLE_SCOPE_CONTEXT', 'A fixed iteration cannot precede every consumer iteration.');
|
|
114
|
+
for (const i of selected) {
|
|
115
|
+
const count = this.nodes.get(i.loop)?.control?.iterations;
|
|
116
|
+
if (count !== undefined && i.iteration > count)
|
|
117
|
+
return fail('UNAVAILABLE_SCOPE_CONTEXT', 'The selected iteration exceeds the configured repeat count.');
|
|
118
|
+
if (phase === 'completion' && shared.includes(i.loop) && actual && !actual.some(a => 'loop' in a && a.loop === i.loop && a.iteration === i.iteration))
|
|
119
|
+
return fail('UNAVAILABLE_SCOPE_CONTEXT', 'The selected iteration differs from the completing invocation.');
|
|
120
|
+
}
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
source(operand) {
|
|
124
|
+
const key = operand.type === 'occurrence-output' ? operand.producer : this.ordinals.get(addressKey(operand.address));
|
|
125
|
+
const step = this.nodes.get(key);
|
|
126
|
+
if (this.document.inputVersion === 7)
|
|
127
|
+
return { step, iterations: this.loops(key).map(loop => ({ loopAddress: this.context.get(loop).originalAddress, iteration: operand.iterations.find(i => addressKey(i.loopAddress) === addressKey(this.context.get(loop).originalAddress)).iteration })) };
|
|
128
|
+
const selected = operand.type === 'occurrence-output' ? operand.iterations : operand.iterations.map(i => ({ loop: this.ordinals.get(addressKey(i.loopAddress)), iteration: i.iteration }));
|
|
129
|
+
return { step, iterations: this.loops(key).map(loop => ({ loop, iteration: selected.find(i => i.loop === loop).iteration })) };
|
|
130
|
+
}
|
|
131
|
+
/** Prove unavailable dependencies, never evaluate a future Action's output value. */
|
|
132
|
+
preflight(inputs, runtime, supports) {
|
|
133
|
+
if (this.document.inputVersion === 7)
|
|
134
|
+
return [];
|
|
135
|
+
const issues = [];
|
|
136
|
+
if (!this.document.selection.executable)
|
|
137
|
+
return this.document.selection.issues.map(issue => ({ code: 'INCOMPLETE_SCOPE', message: `${this.context.get(issue.at)?.name ?? 'Selected scope'}: This content is not ready to run.` }));
|
|
138
|
+
for (const key of this.inScope) {
|
|
139
|
+
const step = this.nodes.get(key);
|
|
140
|
+
if (!step)
|
|
141
|
+
continue;
|
|
142
|
+
if (step.kind === 'action' && !supports(step.action))
|
|
143
|
+
issues.push({ code: 'UNSUPPORTED_SCOPE_CAPABILITY', message: `${step.name}: No Action Executor supports this Action.`, stepId: step.id });
|
|
144
|
+
// Existing engine intentionally does not implement these completion projections.
|
|
145
|
+
if (step.control?.mergeBindings)
|
|
146
|
+
issues.push({ code: 'UNSUPPORTED_SCOPE_CAPABILITY', message: `${step.name}: Parallel output projections are unsupported.`, stepId: step.id });
|
|
147
|
+
}
|
|
148
|
+
const rootKey = this.document.selection.root.kind === 'occurrence' ? this.document.selection.root.key : this.document.context.root;
|
|
149
|
+
const relevantFlows = this.document.flows.filter(f => this.inScope.has(f.occurrence));
|
|
150
|
+
for (const flow of relevantFlows)
|
|
151
|
+
if (Object.keys(flow.outputBindings ?? {}).length)
|
|
152
|
+
issues.push({ code: 'UNSUPPORTED_SCOPE_CAPABILITY', message: `${this.context.get(flow.occurrence).name}: Flow completion exports are unsupported.` });
|
|
153
|
+
const scopeFrom = (values) => new Map(Object.entries(values).map(([k, value]) => [k, { known: true, value }]));
|
|
154
|
+
const binding = (op, step, scope, phase) => {
|
|
155
|
+
issues.push(...this.dependency(op, step, phase));
|
|
156
|
+
if (op.type !== 'input')
|
|
157
|
+
return { known: false };
|
|
158
|
+
let v = scope.get(op.path[0]);
|
|
159
|
+
if (!v) {
|
|
160
|
+
issues.push({ code: 'UNAVAILABLE_SCOPE_CONTEXT', message: `${step.name}: Required input ${op.path.join('.')} is unavailable in this scope.`, stepId: step.id });
|
|
161
|
+
return { known: false };
|
|
162
|
+
}
|
|
163
|
+
if (v.known) {
|
|
164
|
+
let value = v.value;
|
|
165
|
+
for (const part of op.path.slice(1)) {
|
|
166
|
+
if (value === null || typeof value !== 'object' || !Object.hasOwn(value, part)) {
|
|
167
|
+
issues.push({ code: 'UNAVAILABLE_SCOPE_CONTEXT', message: `${step.name}: Required input path ${op.path.join('.')} is unavailable.`, stepId: step.id });
|
|
168
|
+
return { known: false };
|
|
169
|
+
}
|
|
170
|
+
value = value[part];
|
|
171
|
+
}
|
|
172
|
+
if (value === undefined) {
|
|
173
|
+
issues.push({ code: 'UNAVAILABLE_SCOPE_CONTEXT', message: `${step.name}: Required input ${op.path.join('.')} is unavailable.`, stepId: step.id });
|
|
174
|
+
return { known: false };
|
|
175
|
+
}
|
|
176
|
+
return { known: true, value };
|
|
177
|
+
}
|
|
178
|
+
return v;
|
|
179
|
+
};
|
|
180
|
+
const condition = (c, s, scope) => { if (c !== undefined && typeof c !== 'boolean')
|
|
181
|
+
binding(c.binding, s, scope, 'entry'); };
|
|
182
|
+
const visit = (s, scope) => {
|
|
183
|
+
const args = scopeFrom(s.occurrence === rootKey ? { ...inputs, ...s.staticInputs } : s.staticInputs ?? {});
|
|
184
|
+
for (const [name, op] of Object.entries(s.bindings ?? {}))
|
|
185
|
+
args.set(name, binding(op, s, scope, 'entry'));
|
|
186
|
+
const child = new Map(scope);
|
|
187
|
+
if (s.kind === 'operation') {
|
|
188
|
+
const known = Object.fromEntries([...args].filter(([, v]) => v.known).map(([k, v]) => [k, v.value]));
|
|
189
|
+
const resolved = resolveDeclaredInputs((s.inputDeclarations ?? []).filter(d => args.get(d.name)?.known !== false), known);
|
|
190
|
+
issues.push(...resolved.issues.map(i => ({ ...i, message: `${s.name}: ${i.message}`, stepId: s.id })));
|
|
191
|
+
for (const d of s.inputDeclarations ?? [])
|
|
192
|
+
child.delete(d.name);
|
|
193
|
+
for (const [k, v] of Object.entries(resolved.inputs))
|
|
194
|
+
child.set(k, { known: true, value: v });
|
|
195
|
+
for (const [k, v] of args)
|
|
196
|
+
if (!v.known)
|
|
197
|
+
child.set(k, v);
|
|
198
|
+
}
|
|
199
|
+
else
|
|
200
|
+
for (const [k, v] of args)
|
|
201
|
+
child.set(k, v);
|
|
202
|
+
condition(s.control?.condition, s, child);
|
|
203
|
+
for (const branch of s.control?.branches ?? []) {
|
|
204
|
+
condition(branch.condition, s, child);
|
|
205
|
+
for (const c of branch.children)
|
|
206
|
+
visit(c, child);
|
|
207
|
+
}
|
|
208
|
+
if (!s.control?.branches)
|
|
209
|
+
for (const c of s.children ?? [])
|
|
210
|
+
visit(c, child);
|
|
211
|
+
for (const op of Object.values(s.outputBindings ?? {}))
|
|
212
|
+
binding(op, s, child, 'completion');
|
|
213
|
+
for (const op of Object.values(s.control?.mergeBindings ?? {}))
|
|
214
|
+
binding(op, s, child, 'completion');
|
|
215
|
+
};
|
|
216
|
+
if (this.project) {
|
|
217
|
+
for (const flow of this.flows) {
|
|
218
|
+
const resolved = resolveDeclaredInputs(flow.inputDeclarations, inputs);
|
|
219
|
+
issues.push(...resolved.issues);
|
|
220
|
+
for (const name of flow.requiredInputs ?? [])
|
|
221
|
+
if (resolved.inputs[name] === undefined)
|
|
222
|
+
issues.push({ code: 'MISSING_INPUT', message: `${flow.name}: Required input ${name} is missing.` });
|
|
223
|
+
for (const s of flow.steps)
|
|
224
|
+
visit(s, scopeFrom(resolved.inputs));
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
else
|
|
228
|
+
for (const s of this.steps)
|
|
229
|
+
visit(s, scopeFrom(inputs));
|
|
230
|
+
return issues;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
export function journalKey(step, iterations, version) {
|
|
234
|
+
return version === 7 ? JSON.stringify([addressKey(step.structuralAddress), iterations.map(i => [addressKey(i.loopAddress), i.iteration])]) : JSON.stringify([step.occurrence, iterations.map(i => [i.loop, i.iteration])]);
|
|
235
|
+
}
|
package/dist/executor.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AcceptedExecutorRequest, AcceptedExecutorResult, AcceptedExecutorEvent, ExecutorRequestV8, ExecutorRunV8 } from './types.js';
|
|
2
|
+
import type { ActReplayTarget, ActReplayHandle, OperationReplayHandle, ActionExecutor, ControlCommand, ExecutionControlState, Executor, ExecutorConfiguration, ExecutorRequest, ExecutorRun } from './types.js';
|
|
2
3
|
export declare function createExecutor(configuration?: ExecutorConfiguration): Executor;
|
|
3
4
|
export declare class DefaultExecutor implements Executor {
|
|
4
5
|
private readonly runs;
|
|
@@ -7,12 +8,14 @@ export declare class DefaultExecutor implements Executor {
|
|
|
7
8
|
constructor(configuration?: ExecutorConfiguration);
|
|
8
9
|
registerActionExecutor(executor: ActionExecutor): void;
|
|
9
10
|
execute(request: ExecutorRequest): Promise<ExecutorRun>;
|
|
11
|
+
execute(request: ExecutorRequestV8): Promise<ExecutorRunV8>;
|
|
12
|
+
execute(request: AcceptedExecutorRequest): Promise<ExecutorRun | ExecutorRunV8>;
|
|
10
13
|
private startRun;
|
|
11
14
|
cancel(requestId: string): Promise<void>;
|
|
12
15
|
control(requestId: string, command: ControlCommand): Promise<void>;
|
|
13
16
|
controlState(requestId: string): ExecutionControlState | undefined;
|
|
14
|
-
events(requestId: string): AsyncIterable<
|
|
15
|
-
result(requestId: string): Promise<
|
|
17
|
+
events(requestId: string): AsyncIterable<AcceptedExecutorEvent>;
|
|
18
|
+
result(requestId: string): Promise<AcceptedExecutorResult>;
|
|
16
19
|
private replayTarget;
|
|
17
20
|
retryAction(requestId: string, target: ActReplayTarget): Promise<ActReplayHandle>;
|
|
18
21
|
private replayEvents;
|
|
@@ -39,5 +42,6 @@ export declare class DefaultExecutor implements Executor {
|
|
|
39
42
|
private emitReplayOccurrence;
|
|
40
43
|
private finish;
|
|
41
44
|
private baseResult;
|
|
45
|
+
private versionedResult;
|
|
42
46
|
private failure;
|
|
43
47
|
}
|
package/dist/executor.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ExecutionPlan, journalKey } from './execution-plan.js';
|
|
2
|
+
import { normalizeV7, normalizeV8, hashCanonicalJson } from '@qaick/contract';
|
|
1
3
|
import { resolveDeclaredInputs } from './declared-inputs.js';
|
|
2
4
|
import { isDeclaredInputNoValue, executionOccurrences, executionOperandDependencyIssues, hierarchyAddressIssues } from '@qaick/contract';
|
|
3
5
|
const addressKey = (address) => JSON.stringify([address.scope, address.segments.map(s => s.kind === 'body' ? ['body'] : [s.kind, s.position])]);
|
|
@@ -8,6 +10,11 @@ function freezePlan(value) {
|
|
|
8
10
|
Object.freeze(value);
|
|
9
11
|
}
|
|
10
12
|
}
|
|
13
|
+
function legacyPackage(run) {
|
|
14
|
+
if (run.request.executionPackage.schemaVersion !== 7)
|
|
15
|
+
throw failure('UNSUPPORTED_SCOPE_CAPABILITY', 'Replay of Execution v8 is unsupported.');
|
|
16
|
+
return run.request.executionPackage;
|
|
17
|
+
}
|
|
11
18
|
const MAX_TIMEOUT_MS = 300_000;
|
|
12
19
|
export function createExecutor(configuration = {}) {
|
|
13
20
|
return new DefaultExecutor(configuration);
|
|
@@ -27,26 +34,48 @@ export class DefaultExecutor {
|
|
|
27
34
|
async startRun(request, debugSeed, debugProvenance) {
|
|
28
35
|
if (this.runs.has(request.requestId))
|
|
29
36
|
throw failure('PREFLIGHT_ERROR', 'Execution request ID already belongs to a Run.');
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
let
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
// Register before awaiting native hashing, so concurrent duplicate IDs cannot race admission.
|
|
38
|
+
// Validate the original v8 JSON boundary before structuredClone could invoke a getter.
|
|
39
|
+
let preparedV8;
|
|
40
|
+
if (request.executionPackage.schemaVersion === 8) {
|
|
41
|
+
try {
|
|
42
|
+
preparedV8 = normalizeV8(request.executionPackage);
|
|
43
|
+
}
|
|
44
|
+
catch (cause) {
|
|
45
|
+
const issues = cause.issues;
|
|
46
|
+
throw { code: issues?.[0]?.code ?? 'INVALID_EXECUTION_DOCUMENT', message: 'Execution Document v8 could not be admitted.', ...(issues?.length ? { details: { issues } } : {}) };
|
|
47
|
+
}
|
|
40
48
|
}
|
|
49
|
+
const requestSnapshot = structuredClone(request);
|
|
50
|
+
freezePlan(requestSnapshot.executionPackage);
|
|
41
51
|
let resolve;
|
|
42
|
-
const result = new Promise(
|
|
52
|
+
const result = new Promise(r => { resolve = r; });
|
|
43
53
|
const run = {
|
|
44
|
-
debugSeed, debugProvenance, request: requestSnapshot, inputSelections: structuredClone(requestSnapshot.inputs), plan, journal: new Map(), replayCaptures: new Map(), resolve, events: [], listeners: [], abort: new AbortController(), cancelled: false,
|
|
54
|
+
debugSeed, debugProvenance, request: requestSnapshot, inputSelections: structuredClone(requestSnapshot.inputs), plan: new Map(), journal: new Map(), replayCaptures: new Map(), resolve, events: [], listeners: [], abort: new AbortController(), cancelled: false,
|
|
45
55
|
timedOut: false, controlState: 'idle', pauseRequested: false, stepRequested: false,
|
|
46
|
-
controlWaiters: [], hitBreakpoints: new Set(), stopAfter:
|
|
56
|
+
controlWaiters: [], hitBreakpoints: new Set(), stopAfter: requestSnapshot.debugSession && ('stopAfter' in requestSnapshot.debugSession ? requestSnapshot.debugSession.stopAfter : ('stopAfterStepId' in requestSnapshot.debugSession ? requestSnapshot.debugSession.stopAfterStepId : undefined)),
|
|
47
57
|
startedMs: Date.now(), pausedDurationMs: 0,
|
|
48
58
|
};
|
|
49
59
|
this.runs.set(request.requestId, run);
|
|
60
|
+
const validation = [];
|
|
61
|
+
try {
|
|
62
|
+
const pkg = requestSnapshot.executionPackage;
|
|
63
|
+
const document = pkg.schemaVersion === 8 ? preparedV8 : await normalizeV7(pkg);
|
|
64
|
+
if (pkg.schemaVersion === 8)
|
|
65
|
+
run.documentHash = await hashCanonicalJson(pkg);
|
|
66
|
+
run.execution = new ExecutionPlan(document, pkg.schemaVersion === 7 ? pkg : undefined);
|
|
67
|
+
if (pkg.schemaVersion === 7)
|
|
68
|
+
run.plan = new Map(executionOccurrences(pkg).map(o => [addressKey(o.address), o]));
|
|
69
|
+
}
|
|
70
|
+
catch (cause) {
|
|
71
|
+
const supplied = cause?.issues;
|
|
72
|
+
if (requestSnapshot.executionPackage.schemaVersion === 8) {
|
|
73
|
+
// No accepted v8 Run may pretend to have a hash/source envelope when admission failed.
|
|
74
|
+
this.runs.delete(request.requestId);
|
|
75
|
+
throw { code: supplied?.[0]?.code ?? 'INVALID_EXECUTION_DOCUMENT', message: 'Execution Document v8 could not be admitted.', ...(supplied?.length ? { details: { issues: supplied } } : {}) };
|
|
76
|
+
}
|
|
77
|
+
validation.push(...(supplied?.length ? supplied : [{ code: 'invalid-execution-package', message: cause instanceof Error ? cause.message : 'Execution admission failed.' }]));
|
|
78
|
+
}
|
|
50
79
|
void this.run(run, validation);
|
|
51
80
|
return { requestId: request.requestId, result, ...(debugProvenance ? { debugProvenance } : {}) };
|
|
52
81
|
}
|
|
@@ -67,6 +96,8 @@ export class DefaultExecutor {
|
|
|
67
96
|
if (command.type === 'stop')
|
|
68
97
|
return this.cancel(requestId);
|
|
69
98
|
if (command.type === 'stop-after') {
|
|
99
|
+
if (run.execution?.document.inputVersion === 8 && (!run.execution.inScope.has(command.stepId) || !run.execution.nodes.has(command.stepId)))
|
|
100
|
+
throw failure('INVALID_SCOPE', 'Stop After target is outside the selected execution scope.');
|
|
70
101
|
run.stopAfter = command.stepId;
|
|
71
102
|
return;
|
|
72
103
|
}
|
|
@@ -124,6 +155,7 @@ export class DefaultExecutor {
|
|
|
124
155
|
if (!parent?.result)
|
|
125
156
|
reject('Act replay requires a terminal original Run.');
|
|
126
157
|
const run = parent;
|
|
158
|
+
legacyPackage(run);
|
|
127
159
|
if (run.cancelled || run.timedOut)
|
|
128
160
|
reject('Interrupted original Runs cannot be replayed.');
|
|
129
161
|
if (run.activeReplay)
|
|
@@ -262,7 +294,7 @@ export class DefaultExecutor {
|
|
|
262
294
|
// Separate structural state, not a new Run: no registration, root result, resolver,
|
|
263
295
|
// historical journal, controls, captures or original event stream are reused.
|
|
264
296
|
const context = {
|
|
265
|
-
request: session.request, inputSelections: structuredClone(parent.inputSelections), plan: parent.plan, journal: new Map(), replayCaptures: new Map(),
|
|
297
|
+
request: session.request, execution: parent.execution, inputSelections: structuredClone(parent.inputSelections), plan: parent.plan, journal: new Map(), replayCaptures: new Map(),
|
|
266
298
|
replay: { session, capture, root: capture.structuralAddress },
|
|
267
299
|
events: [], listeners: [], abort: session.abort,
|
|
268
300
|
get cancelled() { return session.cancelled; }, get timedOut() { return session.timedOut; },
|
|
@@ -282,7 +314,7 @@ export class DefaultExecutor {
|
|
|
282
314
|
session.abort.abort();
|
|
283
315
|
}, timeoutMs);
|
|
284
316
|
this.emitReplay(session, 'replay_started', 'running');
|
|
285
|
-
operation = await this.executeStep(context, step, capture.lexicalScope, [], session.abort.signal, capture.iterations);
|
|
317
|
+
operation = await this.executeStep(context, parent.execution.nodes.get(parent.execution.ordinals.get(addressKey(step.structuralAddress))), capture.lexicalScope, [], session.abort.signal, capture.iterations);
|
|
286
318
|
this.assertActive(session, session.abort.signal);
|
|
287
319
|
if (operation.state !== 'finished') {
|
|
288
320
|
state = 'failed';
|
|
@@ -315,6 +347,8 @@ export class DefaultExecutor {
|
|
|
315
347
|
async runFromHere(sourceRequestId, target) {
|
|
316
348
|
const source = this.runs.get(sourceRequestId);
|
|
317
349
|
const reject = (message) => { throw failure('unsupported-execution-context', message); };
|
|
350
|
+
if (source)
|
|
351
|
+
legacyPackage(source);
|
|
318
352
|
if (!source?.result || source.cancelled || source.timedOut)
|
|
319
353
|
reject('Run From Here requires a terminal uninterrupted source Run.');
|
|
320
354
|
if (!isRecord(target) || Object.keys(target).some(k => !['structuralAddress', 'iterations'].includes(k))
|
|
@@ -327,10 +361,11 @@ export class DefaultExecutor {
|
|
|
327
361
|
if (!topLevel)
|
|
328
362
|
reject('Nested/control continuation is not supported by Run From Here.');
|
|
329
363
|
const parent = source;
|
|
364
|
+
legacyPackage(parent);
|
|
330
365
|
const capture = parent.replayCaptures.get(invocationKey(address, []));
|
|
331
366
|
if (!capture || (capture.kind !== 'action' && capture.kind !== 'operation'))
|
|
332
367
|
reject('No eligible private original input capture exists.');
|
|
333
|
-
const package_ = structuredClone(parent
|
|
368
|
+
const package_ = structuredClone(legacyPackage(parent));
|
|
334
369
|
const position = segments.at(-1);
|
|
335
370
|
if (position.kind !== 'step')
|
|
336
371
|
throw failure('unsupported-execution-context', 'Expected a top-level step.');
|
|
@@ -390,7 +425,7 @@ export class DefaultExecutor {
|
|
|
390
425
|
freezePlan(seed);
|
|
391
426
|
freezePlan(provenance);
|
|
392
427
|
// Normal Run creation is independent of replay admission and has no external evidence view.
|
|
393
|
-
return this.startRun({ ...structuredClone(parent.request), requestId: crypto.randomUUID(), executionPackage: package_, inputs: structuredClone(parent.inputSelections) }, seed, provenance);
|
|
428
|
+
return this.startRun({ ...structuredClone(parent.request), debugSession: parent.request.debugSession, requestId: crypto.randomUUID(), executionPackage: package_, inputs: structuredClone(parent.inputSelections) }, seed, provenance);
|
|
394
429
|
}
|
|
395
430
|
async run(run, validation) {
|
|
396
431
|
const startedAt = new Date().toISOString();
|
|
@@ -406,12 +441,15 @@ export class DefaultExecutor {
|
|
|
406
441
|
let timeout;
|
|
407
442
|
if (timeoutMs !== undefined)
|
|
408
443
|
timeout = setTimeout(() => { if (run.abort.signal.aborted)
|
|
409
|
-
return; run.timedOut = true; run.abort.abort();
|
|
444
|
+
return; run.timedOut = true; run.abort.abort(); if (run.request.executionPackage.schemaVersion === 8)
|
|
445
|
+
run.controlWaiters.splice(0).forEach(resolve => resolve()); }, timeoutMs);
|
|
410
446
|
this.emit(run, 'run_started', 'created', 'Execution accepted.');
|
|
411
447
|
this.emit(run, 'preflight_started', 'preflighting', 'Validating execution package.');
|
|
412
|
-
const arguments_ = resolveDeclaredInputs(run.
|
|
448
|
+
const arguments_ = resolveDeclaredInputs(run.execution.inputDeclarations, run.request.inputs);
|
|
413
449
|
run.request = { ...run.request, inputs: arguments_.inputs };
|
|
414
|
-
const
|
|
450
|
+
const debug = run.request.debugSession;
|
|
451
|
+
const debugIssues = run.execution.document.inputVersion === 8 && debug && [...debug.breakpoints, ...(debug.stopAfter ? [debug.stopAfter] : [])].some(k => !run.execution.inScope.has(k) || !run.execution.nodes.has(k)) ? [{ code: 'INVALID_SCOPE', message: 'Debug target is outside the selected executable scope.' }] : [];
|
|
452
|
+
const issues = [...debugIssues, ...arguments_.issues, ...preflight(run.execution, arguments_.inputs, run.request.runtimeValues ?? {}), ...run.execution.preflight(arguments_.inputs, run.request.runtimeValues ?? {}, action => this.actionExecutors.some(e => e.supports(action)))];
|
|
415
453
|
if (issues.length > 0 || run.request.options?.dryRun) {
|
|
416
454
|
const result = this.baseResult(run, startedAt, issues.length > 0 ? 'failed' : 'completed', undefined, issues);
|
|
417
455
|
if (timeout)
|
|
@@ -419,11 +457,17 @@ export class DefaultExecutor {
|
|
|
419
457
|
this.finish(run, result);
|
|
420
458
|
return;
|
|
421
459
|
}
|
|
460
|
+
if (run.cancelled || run.abort.signal.aborted) {
|
|
461
|
+
if (timeout)
|
|
462
|
+
clearTimeout(timeout);
|
|
463
|
+
this.finish(run, this.baseResult(run, startedAt, 'cancelled'));
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
422
466
|
run.controlState = 'running';
|
|
423
467
|
try {
|
|
424
|
-
const result = run.
|
|
468
|
+
const result = run.execution.project
|
|
425
469
|
? await this.executeProject(run, startedAt)
|
|
426
|
-
: await this.executeFlow(run, run.
|
|
470
|
+
: await this.executeFlow(run, run.execution.steps, run.request.inputs, [], startedAt);
|
|
427
471
|
if (timeout)
|
|
428
472
|
clearTimeout(timeout);
|
|
429
473
|
this.finish(run, result);
|
|
@@ -436,7 +480,7 @@ export class DefaultExecutor {
|
|
|
436
480
|
}
|
|
437
481
|
}
|
|
438
482
|
async executeProject(run, startedAt) {
|
|
439
|
-
const pkg = run.
|
|
483
|
+
const pkg = run.execution;
|
|
440
484
|
const children = [];
|
|
441
485
|
let inputs = { ...run.request.inputs };
|
|
442
486
|
for (const flow of pkg.flows ?? []) {
|
|
@@ -461,18 +505,31 @@ export class DefaultExecutor {
|
|
|
461
505
|
}
|
|
462
506
|
return this.baseResult(run, startedAt, 'completed', undefined, undefined, children);
|
|
463
507
|
}
|
|
464
|
-
async executeFlow(run, steps, initialInputs, path, startedAt, target = run.
|
|
508
|
+
async executeFlow(run, steps, initialInputs, path, startedAt, target = run.execution.target, signal = run.abort.signal, iterations = [], bodyFailurePolicy = 'stop') {
|
|
465
509
|
const results = [];
|
|
466
510
|
let state = 'completed';
|
|
467
511
|
let error;
|
|
468
512
|
for (const step of steps) {
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
513
|
+
try {
|
|
514
|
+
if (run.cancelled || signal.aborted)
|
|
515
|
+
throw new Error('Execution cancelled.');
|
|
516
|
+
await this.boundary(run, step, [...path, step.id], signal, iterations);
|
|
517
|
+
if (run.cancelled || signal.aborted)
|
|
518
|
+
throw new Error('Execution cancelled.');
|
|
519
|
+
}
|
|
520
|
+
catch (cause) {
|
|
521
|
+
if (run.execution.document.inputVersion === 7)
|
|
522
|
+
throw cause;
|
|
523
|
+
state = run.timedOut ? 'failed' : 'cancelled';
|
|
524
|
+
error = this.failure(cause, run, signal);
|
|
525
|
+
break;
|
|
526
|
+
}
|
|
474
527
|
const result = await this.executeStep(run, step, initialInputs, path, signal, iterations);
|
|
475
528
|
results.push(result);
|
|
529
|
+
if (run.execution.document.inputVersion === 8 && step.kind !== 'action' && run.stopAfter === step.occurrence) {
|
|
530
|
+
run.stopAfter = undefined;
|
|
531
|
+
await this.pauseAtBoundary(run, step, iterations);
|
|
532
|
+
}
|
|
476
533
|
if (result.state === 'failed' || result.state === 'cancelled') {
|
|
477
534
|
const interrupted = result.state === 'cancelled' || run.cancelled || run.timedOut || signal.aborted
|
|
478
535
|
|| result.terminalReason === 'timeout' || result.error?.category === 'ACTION_TIMEOUT'
|
|
@@ -496,20 +553,25 @@ export class DefaultExecutor {
|
|
|
496
553
|
async executeStep(run, step, scope, path, signal = run.abort.signal, iterations = []) {
|
|
497
554
|
const startedMs = Date.now();
|
|
498
555
|
const invocationPath = [...path, step.id];
|
|
499
|
-
const base = { id: step.id, name: step.name, kind: step.kind ?? 'action', inputs: {}, outputs: {}, structuralAddress: structuredClone(step.structuralAddress), iterations: structuredClone(iterations), invocationId: invocationPath.join('/'), invocationPath };
|
|
556
|
+
const base = { ...(run.execution.document.inputVersion === 8 ? { occurrence: step.occurrence } : {}), id: step.id, name: step.name, kind: step.kind ?? 'action', inputs: {}, outputs: {}, ...(step.structuralAddress ? { structuralAddress: structuredClone(step.structuralAddress) } : {}), iterations: structuredClone(iterations), invocationId: invocationPath.join('/'), invocationPath };
|
|
500
557
|
let inputs = {};
|
|
501
558
|
const attempts = [];
|
|
559
|
+
let actualChildren;
|
|
502
560
|
this.emit(run, 'step_started', 'running', `${step.name} started.`, step, undefined, iterations);
|
|
503
561
|
try {
|
|
504
562
|
const debugSeed = run.debugSeed && addressKey(run.debugSeed.structuralAddress) === addressKey(step.structuralAddress) ? run.debugSeed : undefined;
|
|
505
563
|
const rootCapture = run.replay && addressKey(run.replay.root) === addressKey(step.structuralAddress) ? run.replay.capture : debugSeed?.kind === 'operation' ? debugSeed : undefined;
|
|
506
|
-
|
|
564
|
+
const selected = run.execution.document.selection.root;
|
|
565
|
+
const entryInputs = run.execution.document.inputVersion === 8 && (selected.kind === 'occurrence' ? selected.key : run.execution.document.context.root) === step.occurrence ? scope : {};
|
|
566
|
+
inputs = debugSeed ? structuredClone(debugSeed.inputs) : rootCapture ? structuredClone(rootCapture.inputs) : { ...entryInputs, ...structuredClone(step.staticInputs ?? {}), ...this.resolveBindings(run, step.bindings ?? {}, scope, step) };
|
|
507
567
|
if (step.delayMs) {
|
|
508
568
|
this.emit(run, 'waiting', 'waiting', `${step.name} is waiting for ${step.delayMs} ms.`, step, undefined, iterations);
|
|
509
569
|
await delay(step.delayMs, signal);
|
|
510
570
|
}
|
|
511
571
|
if (step.control) {
|
|
512
572
|
const controlResult = await this.executeControl(run, step, inputs, scope, path, signal, iterations);
|
|
573
|
+
if (run.execution.document.inputVersion === 8)
|
|
574
|
+
this.emit(run, 'step_finished', controlResult.state, `${step.name} ${controlResult.state}.`, step, undefined, iterations);
|
|
513
575
|
return { ...base, ...controlResult, inputs, durationMs: Date.now() - startedMs };
|
|
514
576
|
}
|
|
515
577
|
if (step.kind === 'operation' && step.children) {
|
|
@@ -521,29 +583,30 @@ export class DefaultExecutor {
|
|
|
521
583
|
for (const declaration of step.inputDeclarations ?? [])
|
|
522
584
|
if (!Object.hasOwn(arguments_.inputs, declaration.name))
|
|
523
585
|
delete childScope[declaration.name];
|
|
524
|
-
if (!run.replay)
|
|
586
|
+
if (!run.replay && run.execution.document.inputVersion === 7)
|
|
525
587
|
this.capture(run, step, iterations, arguments_.inputs, {
|
|
526
588
|
kind: 'operation', lexicalScope: structuredClone(childScope),
|
|
527
589
|
externalEvidence: this.captureExternalEvidence(run, step, iterations),
|
|
528
590
|
});
|
|
529
|
-
const nested = await this.executeFlow(run, step.children, childScope, [...path, step.id], new Date().toISOString(), run.
|
|
591
|
+
const nested = await this.executeFlow(run, step.children, childScope, [...path, step.id], new Date().toISOString(), run.execution.target, signal, iterations);
|
|
592
|
+
actualChildren = nested.steps;
|
|
530
593
|
const outputs = nested.state === 'completed'
|
|
531
|
-
? { ...structuredClone(step.staticOutputs ?? {}), ...this.resolveBindings(run, step.outputBindings ?? {}, childScope, step
|
|
594
|
+
? { ...structuredClone(step.staticOutputs ?? {}), ...this.resolveBindings(run, step.outputBindings ?? {}, childScope, step, { phase: 'completion', iterations }) }
|
|
532
595
|
: {};
|
|
533
596
|
const result = { ...base, state: nested.state === 'completed' ? 'finished' : nested.state, inputs, outputs, durationMs: Date.now() - startedMs, children: nested.steps, terminalReason: nested.terminalReason ?? (nested.state === 'completed' ? 'completed' : 'failed'), error: nested.error };
|
|
534
597
|
this.emit(run, 'step_finished', result.state, `${step.name} ${result.state}.`, step, undefined, iterations);
|
|
535
598
|
return result;
|
|
536
599
|
}
|
|
537
|
-
if (!run.replay)
|
|
600
|
+
if (!run.replay && run.execution.document.inputVersion === 7)
|
|
538
601
|
this.capture(run, step, iterations, inputs, { kind: 'action' });
|
|
539
602
|
const actionResult = await this.executeAction(run, step, inputs, attempts, signal, run.replay ? (type, attempt, state) => this.emitReplayOccurrence(run.replay.session, type, state, step, iterations, attempt) : undefined);
|
|
540
603
|
if (signal.aborted || run.cancelled)
|
|
541
604
|
throw failure(run.timedOut ? 'ACTION_TIMEOUT' : 'ACTION_CANCELLED', 'Execution ended before evidence publication.');
|
|
542
|
-
run.journal.set(
|
|
605
|
+
run.journal.set(journalKey(step, iterations, run.execution.document.inputVersion), { outputs: structuredClone(actionResult.outputs), ...(actionResult.evidence ? { evidence: structuredClone(actionResult.evidence) } : {}) });
|
|
543
606
|
freezePlan(attempts);
|
|
544
607
|
const result = { ...base, state: 'finished', inputs, outputs: actionResult.outputs, durationMs: Date.now() - startedMs, terminalReason: 'completed', attempts: actionResult.attempts, evidence: actionResult.evidence };
|
|
545
608
|
this.emit(run, 'step_finished', 'finished', `${step.name} finished.`, step, undefined, iterations);
|
|
546
|
-
if (run.stopAfter === step.id) {
|
|
609
|
+
if (run.stopAfter === (run.execution.document.inputVersion === 8 ? step.occurrence : step.id)) {
|
|
547
610
|
run.stopAfter = undefined;
|
|
548
611
|
await this.pauseAtBoundary(run, step, iterations);
|
|
549
612
|
}
|
|
@@ -553,14 +616,14 @@ export class DefaultExecutor {
|
|
|
553
616
|
const failure = this.failure(cause, run, signal);
|
|
554
617
|
const state = run.timedOut ? 'failed' : run.cancelled || signal.aborted ? 'cancelled' : 'failed';
|
|
555
618
|
freezePlan(attempts);
|
|
556
|
-
const result = { ...base, state, inputs, durationMs: Date.now() - startedMs, ...(attempts.length ? { attempts } : {}), terminalReason: run.timedOut ? 'timeout' : state === 'cancelled' ? 'cancelled' : 'failed', error: failure };
|
|
619
|
+
const result = { ...base, ...(run.execution.document.inputVersion === 8 && actualChildren ? { children: actualChildren } : {}), state, inputs, durationMs: Date.now() - startedMs, ...(attempts.length ? { attempts } : {}), terminalReason: run.timedOut ? 'timeout' : state === 'cancelled' ? 'cancelled' : 'failed', error: failure };
|
|
557
620
|
this.emit(run, 'step_finished', state, `${step.name} ${state}.`, step, undefined, iterations);
|
|
558
621
|
return result;
|
|
559
622
|
}
|
|
560
623
|
}
|
|
561
624
|
async executeControl(run, step, inputs, scope, path, signal = run.abort.signal, iterations = []) {
|
|
562
625
|
const control = step.control;
|
|
563
|
-
if (control.condition !== undefined && !this.evaluateCondition(run, control.condition, { ...scope, ...inputs }, step
|
|
626
|
+
if (control.condition !== undefined && !this.evaluateCondition(run, control.condition, { ...scope, ...inputs }, step))
|
|
564
627
|
return { state: 'skipped', outputs: {}, terminalReason: 'condition-skipped' };
|
|
565
628
|
if (control.type === 'repeat') {
|
|
566
629
|
const total = Math.max(1, Math.min(control.iterations ?? (control.durationMs !== undefined ? 32 : 1), 32));
|
|
@@ -569,8 +632,8 @@ export class DefaultExecutor {
|
|
|
569
632
|
for (let current = 1; current <= total; current++) {
|
|
570
633
|
if (control.durationMs !== undefined && Date.now() - loopStarted >= control.durationMs)
|
|
571
634
|
break;
|
|
572
|
-
const child = await this.executeFlow(run, step.children ?? [], { ...scope, ...inputs }, [...path, step.id, String(current)], new Date().toISOString(), run.
|
|
573
|
-
children.push(...child.steps.map(item => ({ ...item, iteration: { loopId: step.id, current, total } })));
|
|
635
|
+
const child = await this.executeFlow(run, step.children ?? [], { ...scope, ...inputs }, [...path, step.id, String(current)], new Date().toISOString(), run.execution.target, signal, [...iterations, (run.execution.document.inputVersion === 8 ? { loop: step.occurrence, iteration: current } : { loopAddress: step.structuralAddress, iteration: current })]);
|
|
636
|
+
children.push(...child.steps.map(item => ({ ...item, iteration: run.execution.document.inputVersion === 8 ? { loop: step.occurrence, current, total } : { loopId: step.id, current, total } })));
|
|
574
637
|
if (child.state !== 'completed' && control.onError !== 'continue')
|
|
575
638
|
return { state: child.state, outputs: child.steps.at(-1)?.outputs ?? {}, children, terminalReason: child.terminalReason, error: child.error };
|
|
576
639
|
if (control.iterationDelayMs)
|
|
@@ -581,9 +644,9 @@ export class DefaultExecutor {
|
|
|
581
644
|
if (control.type === 'branch') {
|
|
582
645
|
const children = [];
|
|
583
646
|
for (const branch of control.branches ?? []) {
|
|
584
|
-
if (!this.evaluateCondition(run, branch.condition, { ...scope, ...inputs }, step
|
|
647
|
+
if (!this.evaluateCondition(run, branch.condition, { ...scope, ...inputs }, step))
|
|
585
648
|
continue;
|
|
586
|
-
const nested = await this.executeFlow(run, branch.children, { ...scope, ...inputs }, [...path, step.id, branch.id], new Date().toISOString(), run.
|
|
649
|
+
const nested = await this.executeFlow(run, branch.children, { ...scope, ...inputs }, [...path, step.id, branch.id], new Date().toISOString(), run.execution.target, signal, iterations);
|
|
587
650
|
children.push(...nested.steps);
|
|
588
651
|
if (nested.state !== 'completed' && control.onError !== 'continue')
|
|
589
652
|
return { state: nested.state, outputs: nested.steps.at(-1)?.outputs ?? {}, children, terminalReason: nested.terminalReason, error: nested.error };
|
|
@@ -592,7 +655,7 @@ export class DefaultExecutor {
|
|
|
592
655
|
}
|
|
593
656
|
if (control.type === 'parallel') {
|
|
594
657
|
const branches = control.branches ?? [];
|
|
595
|
-
const selected = branches.filter(branch => this.evaluateCondition(run, branch.condition, { ...scope, ...inputs }, step
|
|
658
|
+
const selected = branches.filter(branch => this.evaluateCondition(run, branch.condition, { ...scope, ...inputs }, step));
|
|
596
659
|
const controllers = selected.map(() => new AbortController());
|
|
597
660
|
const abortBranches = () => controllers.forEach(controller => controller.abort());
|
|
598
661
|
signal.addEventListener('abort', abortBranches, { once: true });
|
|
@@ -600,7 +663,7 @@ export class DefaultExecutor {
|
|
|
600
663
|
abortBranches();
|
|
601
664
|
const branchRuns = await Promise.all(selected.map(async (branch, index) => {
|
|
602
665
|
try {
|
|
603
|
-
const result = await this.executeFlow(run, branch.children, { ...scope, ...inputs }, [...path, step.id, branch.id], new Date().toISOString(), run.
|
|
666
|
+
const result = await this.executeFlow(run, branch.children, { ...scope, ...inputs }, [...path, step.id, branch.id], new Date().toISOString(), run.execution.target, controllers[index].signal, iterations);
|
|
604
667
|
return { branch, result };
|
|
605
668
|
}
|
|
606
669
|
catch (cause) {
|
|
@@ -615,16 +678,16 @@ export class DefaultExecutor {
|
|
|
615
678
|
throw failure('unsupported-context', 'Parallel output projections are outside this foundation slice.');
|
|
616
679
|
return { state: failed ? invocationState(failed.result.state) : 'finished', outputs: {}, children, terminalReason: failed?.result.terminalReason ?? 'parallel-joined', error: failed?.result.error };
|
|
617
680
|
}
|
|
618
|
-
const nested = await this.executeFlow(run, step.children ?? [], { ...scope, ...inputs }, [...path, step.id], new Date().toISOString(), run.
|
|
681
|
+
const nested = await this.executeFlow(run, step.children ?? [], { ...scope, ...inputs }, [...path, step.id], new Date().toISOString(), run.execution.target, signal, iterations, control.type === 'sequence' ? control.onError ?? 'stop' : 'stop');
|
|
619
682
|
return { state: nested.state === 'completed' ? 'finished' : nested.state, outputs: nested.steps.at(-1)?.outputs ?? {}, children: nested.steps, terminalReason: nested.terminalReason, error: nested.error };
|
|
620
683
|
}
|
|
621
|
-
resolveBindings(run, bindings, scope, consumer, context = { phase: 'entry'
|
|
684
|
+
resolveBindings(run, bindings, scope, consumer, context = { phase: 'entry' }) {
|
|
622
685
|
return Object.fromEntries(Object.entries(bindings).map(([name, operand]) => [name, this.resolveOperand(run, operand, scope, consumer, context)]));
|
|
623
686
|
}
|
|
624
|
-
resolveOperand(run, operand, scope, consumer, context = { phase: 'entry'
|
|
625
|
-
const issues =
|
|
687
|
+
resolveOperand(run, operand, scope, consumer, context = { phase: 'entry' }) {
|
|
688
|
+
const issues = run.execution.dependency(operand, consumer, context.phase, context.iterations);
|
|
626
689
|
if (issues.length)
|
|
627
|
-
throw { code: issues[0].code, message: issues[0].
|
|
690
|
+
throw { code: issues[0].code, message: issues[0].message, ...(run.execution.document.inputVersion === 7 ? { details: { consumer: consumer.structuralAddress, operand } } : {}) };
|
|
628
691
|
if (operand.type === 'input') {
|
|
629
692
|
let value = scope;
|
|
630
693
|
for (const part of operand.path) {
|
|
@@ -638,13 +701,11 @@ export class DefaultExecutor {
|
|
|
638
701
|
}
|
|
639
702
|
if (operand.type === 'unresolved-output')
|
|
640
703
|
throw failure(operand.code, operand.reason);
|
|
641
|
-
const source = run.
|
|
642
|
-
const
|
|
643
|
-
const
|
|
644
|
-
const invocation = run.replay && !ownsAddress(run.replay.root, source.address)
|
|
645
|
-
? run.replay.capture.externalEvidence.get(key) : run.journal.get(key);
|
|
704
|
+
const source = run.execution.source(operand);
|
|
705
|
+
const key = journalKey(source.step, source.iterations, run.execution.document.inputVersion);
|
|
706
|
+
const invocation = run.replay && !ownsAddress(run.replay.root, source.step.structuralAddress) ? run.replay.capture.externalEvidence.get(key) : run.journal.get(key);
|
|
646
707
|
if (!invocation || !Object.hasOwn(invocation.outputs, operand.outputName) || invocation.outputs[operand.outputName] === undefined)
|
|
647
|
-
throw { code: 'output-evidence-unavailable', message: run.replay ? 'Required successful Act output is unavailable in this replay evidence view.' : 'Required successful Act output is unavailable in this Run.', details: { missing: invocation ? 'output' : 'invocation', source:
|
|
708
|
+
throw { code: 'output-evidence-unavailable', message: run.execution.document.inputVersion === 7 ? run.replay ? 'Required successful Act output is unavailable in this replay evidence view.' : 'Required successful Act output is unavailable in this Run.' : 'Required successful Action output is unavailable in this Run.', details: { missing: invocation ? 'output' : 'invocation', source: source.step.structuralAddress, iterations: source.iterations, outputName: operand.outputName, consumer: consumer.structuralAddress } };
|
|
648
709
|
return structuredClone(invocation.outputs[operand.outputName]);
|
|
649
710
|
}
|
|
650
711
|
evaluateCondition(run, condition, scope, consumer) {
|
|
@@ -673,7 +734,7 @@ export class DefaultExecutor {
|
|
|
673
734
|
return;
|
|
674
735
|
// An exact external reference must already be causal at this boundary. No journal scan,
|
|
675
736
|
// legacy ID lookup, or inference of selectors from the current invocation is permitted.
|
|
676
|
-
if (executionOperandDependencyIssues(run
|
|
737
|
+
if (executionOperandDependencyIssues(legacyPackage(run), operand, { phase: 'completion', address: operation.structuralAddress, iterations }).length)
|
|
677
738
|
return;
|
|
678
739
|
const source = run.plan.get(addressKey(operand.address));
|
|
679
740
|
const selectors = source.loops.map(loop => ({ loopAddress: loop, iteration: operand.iterations.find(i => addressKey(i.loopAddress) === addressKey(loop)).iteration }));
|
|
@@ -756,7 +817,7 @@ export class DefaultExecutor {
|
|
|
756
817
|
}
|
|
757
818
|
const breakpointKey = stepPathForBoundary(step, run, path);
|
|
758
819
|
const breakpoints = run.request.debugSession?.breakpoints ?? [];
|
|
759
|
-
if (breakpoints.includes(step.id) && !run.hitBreakpoints.has(breakpointKey)) {
|
|
820
|
+
if (breakpoints.includes(run.execution.document.inputVersion === 8 ? step.occurrence : step.id) && !run.hitBreakpoints.has(breakpointKey)) {
|
|
760
821
|
run.hitBreakpoints.add(breakpointKey);
|
|
761
822
|
run.pauseRequested = true;
|
|
762
823
|
this.emit(run, 'waiting', 'waiting', `Breakpoint reached before ${step.name}.`, step, { type: 'pause', stepId: step.id }, iterations);
|
|
@@ -784,7 +845,12 @@ export class DefaultExecutor {
|
|
|
784
845
|
this.emitReplayOccurrence(run.replay.session, type, state, step, iterations);
|
|
785
846
|
return;
|
|
786
847
|
}
|
|
787
|
-
|
|
848
|
+
const base = { requestId: run.request.requestId, sequence: run.events.length + 1, type, state, message, timestamp: new Date().toISOString(), ...(step ? { stepId: step.id, stepName: step.name, ...(step.structuralAddress ? { structuralAddress: structuredClone(step.structuralAddress) } : {}), kind: step.kind ?? 'action' } : {}) };
|
|
849
|
+
const event = run.request.executionPackage.schemaVersion === 8
|
|
850
|
+
? { ...base, eventVersion: 2, ...(step ? { occurrence: step.occurrence, iterations: structuredClone(iterations) } : {}), ...(control ? { control: { type: control.type, ...(step ? { occurrence: step.occurrence } : {}) } } : {}) }
|
|
851
|
+
: { ...base, ...(step ? { iterations: structuredClone(iterations) } : {}), ...(control ? { control } : {}) };
|
|
852
|
+
freezePlan(event);
|
|
853
|
+
run.events.push(event);
|
|
788
854
|
run.listeners.splice(0).forEach(resolve => resolve());
|
|
789
855
|
}
|
|
790
856
|
emitReplayOccurrence(session, type, state, step, iterations, attempt) {
|
|
@@ -801,15 +867,21 @@ export class DefaultExecutor {
|
|
|
801
867
|
finish(run, result) {
|
|
802
868
|
const finishedAt = new Date().toISOString();
|
|
803
869
|
const finalResult = { ...result, finishedAt, executionTiming: { wallDurationMs: Date.now() - run.startedMs, activeDurationMs: Date.now() - run.startedMs - run.pausedDurationMs, pausedDurationMs: run.pausedDurationMs } };
|
|
804
|
-
run.
|
|
870
|
+
const evidence = run.request.executionPackage.schemaVersion === 8 ? this.versionedResult(run, finalResult) : finalResult;
|
|
871
|
+
if (run.request.executionPackage.schemaVersion === 8)
|
|
872
|
+
freezePlan(evidence);
|
|
873
|
+
run.result = evidence;
|
|
805
874
|
run.controlState = finalResult.state === 'completed' ? 'completed' : finalResult.state === 'cancelled' ? 'stopped' : 'failed';
|
|
806
875
|
this.emit(run, 'run_finished', finalResult.state, finalResult.error?.message ?? `Execution ${finalResult.state}.`);
|
|
807
|
-
run.resolve(
|
|
876
|
+
run.resolve(evidence);
|
|
808
877
|
run.listeners.splice(0).forEach(resolve => resolve());
|
|
809
878
|
}
|
|
810
879
|
baseResult(run, startedAt, state, error, issues, children) {
|
|
811
880
|
return { ...(run.debugProvenance ? { debugProvenance: run.debugProvenance } : {}), requestId: run.request.requestId, state, target: run.request.executionPackage.target, environment: run.request.environment, startedAt, finishedAt: new Date().toISOString(), steps: [], ...(children ? { children } : {}), ...(error ? { error } : {}), ...(issues ? { issues } : {}) };
|
|
812
881
|
}
|
|
882
|
+
versionedResult(run, result) {
|
|
883
|
+
return { ...result, resultVersion: 2, documentHash: run.documentHash, source: run.execution.document.source, scope: run.execution.document.selection.root, steps: result.steps, ...(result.children ? { children: result.children.map(r => this.versionedResult(run, r)) } : {}) };
|
|
884
|
+
}
|
|
813
885
|
failure(cause, run, signal = run.abort.signal) {
|
|
814
886
|
if (run.timedOut)
|
|
815
887
|
return failure('ACTION_TIMEOUT', 'Execution exceeded its timeout.');
|
|
@@ -827,9 +899,9 @@ function preflight(pkg, inputs, runtimeValues) {
|
|
|
827
899
|
const issues = [];
|
|
828
900
|
if (!pkg.packageId || !pkg.target?.id)
|
|
829
901
|
issues.push({ code: 'INVALID_PACKAGE', message: 'Execution package identity is incomplete.' });
|
|
830
|
-
const flowInputNames = new Set(pkg.
|
|
902
|
+
const flowInputNames = new Set(pkg.project ? (pkg.flows ?? []).flatMap(flow => Array.isArray(flow.inputDeclarations) ? flow.inputDeclarations.flatMap(input => typeof input?.name === 'string' ? [input.name] : []) : []) : []);
|
|
831
903
|
for (const name of pkg.requiredInputs ?? [])
|
|
832
|
-
if (inputs[name] === undefined && !flowInputNames.has(name))
|
|
904
|
+
if ((pkg instanceof ExecutionPlan && pkg.document.inputVersion === 8 ? isDeclaredInputNoValue(inputs[name]) : inputs[name] === undefined) && !flowInputNames.has(name))
|
|
833
905
|
issues.push({ code: 'MISSING_INPUT', message: `Required input '${name}' is missing.` });
|
|
834
906
|
for (const name of pkg.runtimeRequirements ?? [])
|
|
835
907
|
if (runtimeValues[name] === undefined)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ActReplayTarget, ActReplayHandle, OperationReplayHandle, ActionExecutor, Executor, ExecutorConfiguration,
|
|
1
|
+
import type { AcceptedExecutorRequest, AcceptedExecutorResult, AcceptedExecutorEvent, ExecutorRequestV8, ExecutorRunV8, ActReplayTarget, ActReplayHandle, OperationReplayHandle, ActionExecutor, Executor, ExecutorConfiguration, ExecutorRequest, ExecutorRun, ControlCommand, ExecutionControlState } from './types.js';
|
|
2
2
|
export declare function createMigratedExecutor(configuration?: ExecutorConfiguration): Executor;
|
|
3
3
|
/**
|
|
4
4
|
* Compatibility facade over the extracted Runner engine. The facade owns the
|
|
@@ -13,11 +13,13 @@ export declare class MigratedExecutor implements Executor {
|
|
|
13
13
|
constructor(configuration?: ExecutorConfiguration);
|
|
14
14
|
registerActionExecutor(executor: ActionExecutor): void;
|
|
15
15
|
execute(request: ExecutorRequest): Promise<ExecutorRun>;
|
|
16
|
+
execute(request: ExecutorRequestV8): Promise<ExecutorRunV8>;
|
|
17
|
+
execute(request: AcceptedExecutorRequest): Promise<ExecutorRun | ExecutorRunV8>;
|
|
16
18
|
cancel(requestId: string): Promise<void>;
|
|
17
19
|
control(requestId: string, command: ControlCommand): Promise<void>;
|
|
18
20
|
controlState(requestId: string): ExecutionControlState | undefined;
|
|
19
|
-
events(requestId: string): AsyncIterable<
|
|
20
|
-
result(requestId: string): Promise<
|
|
21
|
+
events(requestId: string): AsyncIterable<AcceptedExecutorEvent>;
|
|
22
|
+
result(requestId: string): Promise<AcceptedExecutorResult>;
|
|
21
23
|
retryAction(requestId: string, target: ActReplayTarget): Promise<ActReplayHandle>;
|
|
22
24
|
retryOperation(requestId: string, target: ActReplayTarget): Promise<OperationReplayHandle>;
|
|
23
25
|
runFromHere(requestId: string, target: ActReplayTarget): Promise<ExecutorRun>;
|
|
@@ -21,6 +21,10 @@ export class MigratedExecutor {
|
|
|
21
21
|
}
|
|
22
22
|
registerActionExecutor(executor) { this.routine.registerActionExecutor(executor); this.adapters.push(toActionAdapter(executor)); }
|
|
23
23
|
async execute(request) {
|
|
24
|
+
if (request.executionPackage.schemaVersion === 8) {
|
|
25
|
+
this.routineRuns.add(request.requestId);
|
|
26
|
+
return this.routine.execute(request);
|
|
27
|
+
}
|
|
24
28
|
if (request.executionPackage.ordering === 'routine') {
|
|
25
29
|
this.routineRuns.add(request.requestId);
|
|
26
30
|
return this.routine.execute(request);
|
package/dist/types.d.ts
CHANGED
|
@@ -223,13 +223,72 @@ export interface ExecutorConfiguration {
|
|
|
223
223
|
}
|
|
224
224
|
export interface Executor {
|
|
225
225
|
execute(request: ExecutorRequest): Promise<ExecutorRun>;
|
|
226
|
+
execute(request: ExecutorRequestV8): Promise<ExecutorRunV8>;
|
|
227
|
+
execute(request: AcceptedExecutorRequest): Promise<ExecutorRun | ExecutorRunV8>;
|
|
226
228
|
cancel(requestId: string): Promise<void>;
|
|
227
229
|
control(requestId: string, command: ControlCommand): Promise<void>;
|
|
228
230
|
controlState(requestId: string): ExecutionControlState | undefined;
|
|
229
|
-
events(requestId: string): AsyncIterable<
|
|
230
|
-
result(requestId: string): Promise<
|
|
231
|
+
events(requestId: string): AsyncIterable<AcceptedExecutorEvent>;
|
|
232
|
+
result(requestId: string): Promise<AcceptedExecutorResult>;
|
|
231
233
|
retryAction(requestId: string, target: ActReplayTarget): Promise<ActReplayHandle>;
|
|
232
234
|
retryOperation(requestId: string, target: ActReplayTarget): Promise<OperationReplayHandle>;
|
|
233
235
|
runFromHere(requestId: string, target: ActReplayTarget): Promise<ExecutorRun>;
|
|
234
236
|
registerActionExecutor(executor: ActionExecutor): void;
|
|
235
237
|
}
|
|
238
|
+
/** Execution v8 admission shares the existing lifecycle; only identity/evidence is versioned. */
|
|
239
|
+
export interface ExecutorRequestV8 extends Omit<ExecutorRequest, 'executionPackage' | 'debugSession'> {
|
|
240
|
+
executionPackage: import('@qaick/contract').ExecutionDocumentV8;
|
|
241
|
+
debugSession?: {
|
|
242
|
+
breakpoints: string[];
|
|
243
|
+
stopAfter?: string;
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
export type AcceptedExecutorRequest = ExecutorRequest | ExecutorRequestV8;
|
|
247
|
+
export interface StepResultV8 extends Omit<ExecutorStepResult, 'iterations' | 'children' | 'attempts' | 'iteration'> {
|
|
248
|
+
occurrence: string;
|
|
249
|
+
iterations: import('@qaick/contract').ScopeIteration[];
|
|
250
|
+
children?: StepResultV8[];
|
|
251
|
+
attempts?: AttemptV8[];
|
|
252
|
+
iteration?: {
|
|
253
|
+
loop: string;
|
|
254
|
+
current: number;
|
|
255
|
+
total: number;
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
export interface AttemptV8 extends Omit<ExecutionAttempt, 'children'> {
|
|
259
|
+
children?: StepResultV8[];
|
|
260
|
+
}
|
|
261
|
+
export interface ExecutionResultV8 extends Omit<ExecutorResult, 'steps' | 'children'> {
|
|
262
|
+
resultVersion: 2;
|
|
263
|
+
documentHash: string;
|
|
264
|
+
source: import('@qaick/contract').DocumentSource;
|
|
265
|
+
scope: import('@qaick/contract').ExecutionRoot;
|
|
266
|
+
steps: StepResultV8[];
|
|
267
|
+
children?: ExecutionResultV8[];
|
|
268
|
+
}
|
|
269
|
+
export interface ExecutionEventV8 extends Omit<ExecutorEvent, 'iterations' | 'iteration' | 'parallel' | 'control'> {
|
|
270
|
+
eventVersion: 2;
|
|
271
|
+
occurrence?: string;
|
|
272
|
+
iterations?: import('@qaick/contract').ScopeIteration[];
|
|
273
|
+
iteration?: {
|
|
274
|
+
loop: string;
|
|
275
|
+
current: number;
|
|
276
|
+
total: number;
|
|
277
|
+
};
|
|
278
|
+
parallel?: {
|
|
279
|
+
group: string;
|
|
280
|
+
completed: number;
|
|
281
|
+
total: number;
|
|
282
|
+
join: 'all' | 'first';
|
|
283
|
+
};
|
|
284
|
+
control?: {
|
|
285
|
+
type: ControlCommand['type'];
|
|
286
|
+
occurrence?: string;
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
export type AcceptedExecutorResult = ExecutorResult | ExecutionResultV8;
|
|
290
|
+
export type AcceptedExecutorEvent = ExecutorEvent | ExecutionEventV8;
|
|
291
|
+
export interface ExecutorRunV8 {
|
|
292
|
+
requestId: string;
|
|
293
|
+
result: Promise<ExecutionResultV8>;
|
|
294
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qaick/executor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Host-neutral QAick action execution primitives.",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"prepublishOnly": "npm run typecheck && npm test && npm run build"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@qaick/contract": "0.
|
|
37
|
+
"@qaick/contract": "0.6.1"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@types/node": "^24.13.3",
|