@aiwg/cli 2026.9.18 → 2026.9.19
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/src/api/index.d.ts +1 -0
- package/dist/src/api/index.js +1 -0
- package/dist/src/artifacts/index-builder.js +44 -9
- package/dist/src/artifacts/query-engine.js +4 -1
- package/dist/src/artifacts/types.js +3 -0
- package/dist/src/cli/handlers/bot-handoff.js +35 -0
- package/dist/src/cli/handlers/build-verify.js +50 -0
- package/dist/src/cli/handlers/index.js +5 -1
- package/dist/src/cli/handlers/provider-command-options.js +24 -0
- package/dist/src/cli/services/deployment-verification.js +8 -5
- package/dist/src/decision/adapters/jev.js +515 -0
- package/dist/src/decision/adapters/llm-subagent.js +174 -0
- package/dist/src/decision/compose.js +54 -0
- package/dist/src/decision/convert.js +14 -0
- package/dist/src/decision/entry.js +183 -0
- package/dist/src/decision/evaluate.js +508 -0
- package/dist/src/decision/export.js +16 -0
- package/dist/src/decision/index.js +12 -0
- package/dist/src/decision/predicates.js +74 -0
- package/dist/src/decision/receipts.js +291 -0
- package/dist/src/decision/types.js +3 -0
- package/dist/src/decision/validate.js +303 -0
- package/dist/src/extensions/commands/definitions.js +20 -0
- package/dist/src/providers/bot-handoff.js +83 -0
- package/dist/src/providers/grokbot-handoff-guidance.js +26 -0
- package/dist/src/providers/grokbot-natives/connector-install-profile.js +59 -0
- package/dist/src/providers/grokbot-natives/create-agent.js +54 -0
- package/dist/src/providers/grokbot-natives/evidence.js +92 -0
- package/dist/src/providers/grokbot-natives/flags.js +47 -0
- package/dist/src/providers/grokbot-natives/index.js +67 -0
- package/dist/src/providers/grokbot-natives/memory-reference-helper.js +114 -0
- package/dist/src/providers/grokbot-natives/registered-machine-health.js +51 -0
- package/dist/src/providers/grokbot-natives/routines.js +57 -0
- package/dist/src/providers/grokbot-natives/types.js +20 -0
- package/package.json +1 -1
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { composeRuleset } from './compose.js';
|
|
3
|
+
import { DecisionPreDispatchError, decisionInvocationFingerprint, nextReceipt } from './receipts.js';
|
|
4
|
+
import { admitEntry, EntryAdmissionError } from './entry.js';
|
|
5
|
+
import { DECISION_API_VERSION, DECISION_API_VERSION_STRUCTURED } from './types.js';
|
|
6
|
+
import { artifactPin, assertArtifactPin, DecisionValidationError, resolveJsonPointer, validateAgainstSchema, validateBinding, validateDecisionValue, validateDefinition, validateDistribution, validateRuleset, } from './validate.js';
|
|
7
|
+
const RETRIABLE = new Set([
|
|
8
|
+
'timeout', 'network-transient', 'rate-limited', 'overloaded', 'service-error',
|
|
9
|
+
]);
|
|
10
|
+
export async function evaluateDecisionRuleset(request) {
|
|
11
|
+
let rulesetPin;
|
|
12
|
+
let bindingPin;
|
|
13
|
+
let base;
|
|
14
|
+
let resolved;
|
|
15
|
+
let admissionStage = 'artifact';
|
|
16
|
+
try {
|
|
17
|
+
admitEntry(request.ruleset);
|
|
18
|
+
admitEntry(request.binding);
|
|
19
|
+
admitEntry(request.definitions);
|
|
20
|
+
admissionStage = 'input';
|
|
21
|
+
admitEntry(request.input);
|
|
22
|
+
admissionStage = 'artifact';
|
|
23
|
+
validateRuleset(request.ruleset);
|
|
24
|
+
rulesetPin = artifactPin(request.ruleset);
|
|
25
|
+
bindingPin = artifactPin(request.binding);
|
|
26
|
+
// Pin and execute immutable snapshots; adapters receive separate per-attempt copies.
|
|
27
|
+
request = {
|
|
28
|
+
...request,
|
|
29
|
+
ruleset: structuredClone(request.ruleset), binding: structuredClone(request.binding),
|
|
30
|
+
definitions: structuredClone(request.definitions), input: structuredClone(request.input),
|
|
31
|
+
};
|
|
32
|
+
base = resultBase(request, rulesetPin, bindingPin);
|
|
33
|
+
validateBinding(request.binding, request.ruleset);
|
|
34
|
+
validateAgainstSchema(request.ruleset.spec.inputSchema, request.input, 'ruleset input');
|
|
35
|
+
resolved = resolveDefinitions(request);
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
const reason = error instanceof EntryAdmissionError && admissionStage === 'input'
|
|
39
|
+
? 'invalid-input' : classifyValidationFailure(error);
|
|
40
|
+
return failureResult(base ?? invalidResultBase(request), reason);
|
|
41
|
+
}
|
|
42
|
+
if (!base)
|
|
43
|
+
return failureResult(invalidResultBase(request), 'invalid-definition');
|
|
44
|
+
const fingerprint = decisionInvocationFingerprint({
|
|
45
|
+
invocationId: request.invocationId,
|
|
46
|
+
value: request.input,
|
|
47
|
+
definitions: resolved.map(item => item.pin),
|
|
48
|
+
ruleset: rulesetPin,
|
|
49
|
+
binding: bindingPin,
|
|
50
|
+
policy: request.policyPin,
|
|
51
|
+
calibration: request.calibrationPin,
|
|
52
|
+
});
|
|
53
|
+
let receipt;
|
|
54
|
+
let reconciled = null;
|
|
55
|
+
const projectId = request.receiptProjectId ?? 'default';
|
|
56
|
+
if (request.receiptStore) {
|
|
57
|
+
try {
|
|
58
|
+
const acquisition = await request.receiptStore.acquire(request.invocationId, projectId, fingerprint);
|
|
59
|
+
if (!acquisition.owner) {
|
|
60
|
+
if (acquisition.receipt.fingerprint !== fingerprint)
|
|
61
|
+
return failureResult(base, 'replay-mismatch');
|
|
62
|
+
if (acquisition.receipt.state === 'completed')
|
|
63
|
+
return structuredClone(acquisition.receipt.result);
|
|
64
|
+
if (acquisition.receipt.state === 'failed' || acquisition.receipt.state === 'execution-uncertain')
|
|
65
|
+
return failureResult(base, 'execution-uncertain');
|
|
66
|
+
try {
|
|
67
|
+
const terminal = await request.receiptStore.waitForTerminal(request.invocationId, projectId, fingerprint, AbortSignal.timeout(request.binding.spec.totalTimeoutMs + 1000));
|
|
68
|
+
return terminal.state === 'completed' ? structuredClone(terminal.result) : failureResult(base, 'execution-uncertain');
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
try {
|
|
72
|
+
const pending = await request.receiptStore.read(request.invocationId, projectId);
|
|
73
|
+
if (pending?.state === 'completed')
|
|
74
|
+
return structuredClone(pending.result);
|
|
75
|
+
if (pending && pending.fingerprint === fingerprint && (pending.state === 'remote-handle-known' || pending.state === 'observation-received')
|
|
76
|
+
&& pending.pending && pending.remoteHandles.length && request.reconcileRemote) {
|
|
77
|
+
const authorized = await request.receiptStore.read(request.invocationId, projectId);
|
|
78
|
+
if (!authorized || authorized.revision !== pending.revision || authorized.fingerprint !== fingerprint) {
|
|
79
|
+
throw new ReceiptPersistenceError();
|
|
80
|
+
}
|
|
81
|
+
reconciled = await request.reconcileRemote(pending.remoteHandles.at(-1), request.signal ?? new AbortController().signal);
|
|
82
|
+
if (reconciled?.status === 'success') {
|
|
83
|
+
receipt = pending;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (!receipt && pending && pending.fingerprint === fingerprint && pending.state !== 'execution-uncertain' && pending.state !== 'failed') {
|
|
87
|
+
await request.receiptStore.compareAndSwap(request.invocationId, projectId, pending.revision, nextReceipt(pending, 'execution-uncertain'));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return failureResult(base, 'persistence-error');
|
|
92
|
+
}
|
|
93
|
+
if (!receipt)
|
|
94
|
+
return failureResult(base, 'execution-uncertain');
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (acquisition.owner)
|
|
98
|
+
receipt = acquisition.receipt;
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return failureResult(base, 'persistence-error');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const advance = async (state, extra = {}) => {
|
|
105
|
+
if (!request.receiptStore || !receipt)
|
|
106
|
+
return;
|
|
107
|
+
try {
|
|
108
|
+
const next = nextReceipt(receipt, state, extra);
|
|
109
|
+
if (!await request.receiptStore.compareAndSwap(request.invocationId, projectId, receipt.revision, next)) {
|
|
110
|
+
throw new ReceiptPersistenceError();
|
|
111
|
+
}
|
|
112
|
+
receipt = next;
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
throw new ReceiptPersistenceError();
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
const now = request.now ?? Date.now;
|
|
119
|
+
const totalDeadline = now() + request.binding.spec.totalTimeoutMs;
|
|
120
|
+
const totalTimeout = AbortSignal.timeout(request.binding.spec.totalTimeoutMs);
|
|
121
|
+
const totalAbort = AbortSignal.any([request.signal ?? new AbortController().signal, totalTimeout]);
|
|
122
|
+
const evaluations = structuredClone(receipt?.evaluations ?? {});
|
|
123
|
+
let attemptsUsed = Object.values(evaluations).reduce((sum, result) => sum + result.spec.attempts.length, 0);
|
|
124
|
+
try {
|
|
125
|
+
if (reconciled && receipt?.pending) {
|
|
126
|
+
const pending = receipt.pending;
|
|
127
|
+
const item = resolved.find(candidate => candidate.alias === pending.alias);
|
|
128
|
+
const target = request.binding.spec.evaluations[pending.alias]?.targets[pending.targetIndex];
|
|
129
|
+
if (!item || !target)
|
|
130
|
+
throw new RemoteUncertainError();
|
|
131
|
+
let observation;
|
|
132
|
+
try {
|
|
133
|
+
observation = normalizeObservation(item.definition, target, reconciled);
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
throw new RemoteUncertainError();
|
|
137
|
+
}
|
|
138
|
+
if (observation.status !== 'success')
|
|
139
|
+
throw new RemoteUncertainError();
|
|
140
|
+
const context = { request, item, rulesetPin, bindingPin, totalDeadline, signal: totalAbort, totalTimeout,
|
|
141
|
+
attemptsAvailable: request.binding.spec.maxAttempts, now, advance };
|
|
142
|
+
const attempts = [...pending.attempts, toAttempt(target, pending.ordinal, observation, 0)];
|
|
143
|
+
evaluations[item.alias] = decisionResult(context, observation, attempts);
|
|
144
|
+
attemptsUsed += attempts.length;
|
|
145
|
+
await advance('observation-received', { evaluations, pending: null });
|
|
146
|
+
}
|
|
147
|
+
for (const item of resolved) {
|
|
148
|
+
if (evaluations[item.alias])
|
|
149
|
+
continue;
|
|
150
|
+
if (totalAbort.aborted) {
|
|
151
|
+
const reason = request.signal?.aborted ? 'cancelled' : 'timeout';
|
|
152
|
+
evaluations[item.alias] = emptyDecisionResult(request, item, rulesetPin, bindingPin, reason === 'cancelled' ? 'cancelled' : 'error', reason);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
const execution = await evaluateOne({
|
|
156
|
+
request,
|
|
157
|
+
item,
|
|
158
|
+
rulesetPin,
|
|
159
|
+
bindingPin,
|
|
160
|
+
totalDeadline,
|
|
161
|
+
signal: totalAbort,
|
|
162
|
+
totalTimeout,
|
|
163
|
+
attemptsAvailable: request.binding.spec.maxAttempts - attemptsUsed,
|
|
164
|
+
now,
|
|
165
|
+
advance,
|
|
166
|
+
});
|
|
167
|
+
attemptsUsed += execution.spec.attempts.length;
|
|
168
|
+
evaluations[item.alias] = execution;
|
|
169
|
+
await advance('observation-received', { evaluations, pending: null });
|
|
170
|
+
}
|
|
171
|
+
if (receipt?.state === 'execution-uncertain')
|
|
172
|
+
return failureResult(base, 'execution-uncertain', evaluations);
|
|
173
|
+
let result;
|
|
174
|
+
if (totalAbort.aborted) {
|
|
175
|
+
const reason = request.signal?.aborted ? 'cancelled' : 'timeout';
|
|
176
|
+
result = { ...base, spec: { ...base.spec, status: reason === 'cancelled' ? 'cancelled' : 'error', reason, matchedRules: [], evaluations } };
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
try {
|
|
180
|
+
const composition = composeRuleset(request.ruleset, request.input, evaluations);
|
|
181
|
+
result = {
|
|
182
|
+
...base,
|
|
183
|
+
spec: {
|
|
184
|
+
...base.spec,
|
|
185
|
+
status: composition.status,
|
|
186
|
+
reason: composition.reason,
|
|
187
|
+
...(composition.outcome !== undefined ? { outcome: composition.outcome } : {}),
|
|
188
|
+
matchedRules: composition.matchedRules,
|
|
189
|
+
evaluations,
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
result = { ...base, spec: { ...base.spec, status: 'error', reason: 'invalid-output', matchedRules: [], evaluations } };
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
await advance('composed');
|
|
198
|
+
await advance('completed', { result });
|
|
199
|
+
return result;
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
if (error instanceof RemoteUncertainError) {
|
|
203
|
+
try {
|
|
204
|
+
await advance('execution-uncertain');
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
return failureResult(base, 'persistence-error', evaluations);
|
|
208
|
+
}
|
|
209
|
+
return failureResult(base, 'execution-uncertain', evaluations);
|
|
210
|
+
}
|
|
211
|
+
return failureResult(base, 'persistence-error', evaluations);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
class ReceiptPersistenceError extends Error {
|
|
215
|
+
}
|
|
216
|
+
class RemoteUncertainError extends Error {
|
|
217
|
+
}
|
|
218
|
+
async function evaluateOne(context) {
|
|
219
|
+
const evaluation = context.request.binding.spec.evaluations[context.item.alias];
|
|
220
|
+
const attempts = [];
|
|
221
|
+
let final = observationFailure('budget-exhausted');
|
|
222
|
+
for (let targetIndex = 0; targetIndex < evaluation.targets.length; targetIndex += 1) {
|
|
223
|
+
const target = evaluation.targets[targetIndex];
|
|
224
|
+
if (context.signal.aborted || context.now() >= context.totalDeadline) {
|
|
225
|
+
final = interruption(context);
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
if (attempts.length >= context.attemptsAvailable) {
|
|
229
|
+
final = observationFailure('budget-exhausted');
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
const adapter = context.request.adapters[target.adapter];
|
|
233
|
+
const capabilityFailure = await checkCapabilities(adapter, target, context.item.definition);
|
|
234
|
+
if (capabilityFailure) {
|
|
235
|
+
final = capabilityFailure;
|
|
236
|
+
attempts.push(toAttempt(target, attempts.length + 1, final, 0));
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
for (let retry = 0; retry <= target.retry.maxRetries; retry += 1) {
|
|
240
|
+
if (context.signal.aborted || context.now() >= context.totalDeadline) {
|
|
241
|
+
final = interruption(context);
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
if (attempts.length >= context.attemptsAvailable) {
|
|
245
|
+
final = observationFailure('budget-exhausted');
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
const started = context.now();
|
|
249
|
+
const attemptDeadline = Math.min(context.totalDeadline, started + target.timeoutMs);
|
|
250
|
+
try {
|
|
251
|
+
await context.advance('dispatched', { pending: { alias: context.item.alias, targetIndex, ordinal: attempts.length + 1, attempts } });
|
|
252
|
+
try {
|
|
253
|
+
final = await invokeWithDeadline(context, adapter, target, attemptDeadline, attempts.length + 1);
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
if (context.request.receiptStore && !(error instanceof DecisionPreDispatchError))
|
|
257
|
+
throw new RemoteUncertainError();
|
|
258
|
+
await context.advance('observation-received');
|
|
259
|
+
throw error;
|
|
260
|
+
}
|
|
261
|
+
if (context.request.receiptStore && final.status !== 'success'
|
|
262
|
+
&& final.dispatchCertainty !== 'terminal-response' && final.dispatchCertainty !== 'not-sent') {
|
|
263
|
+
throw new RemoteUncertainError();
|
|
264
|
+
}
|
|
265
|
+
await context.advance('observation-received');
|
|
266
|
+
final = normalizeObservation(context.item.definition, target, final);
|
|
267
|
+
}
|
|
268
|
+
catch (error) {
|
|
269
|
+
if (error instanceof ReceiptPersistenceError || error instanceof RemoteUncertainError)
|
|
270
|
+
throw error;
|
|
271
|
+
final = observationFailure(error instanceof DecisionValidationError ? 'invalid-output' : 'service-error');
|
|
272
|
+
}
|
|
273
|
+
attempts.push(toAttempt(target, attempts.length + 1, final, Math.max(0, context.now() - started)));
|
|
274
|
+
if (final.status === 'success')
|
|
275
|
+
break;
|
|
276
|
+
if (!RETRIABLE.has(final.reason) || retry === target.retry.maxRetries)
|
|
277
|
+
break;
|
|
278
|
+
const remaining = Math.max(0, context.totalDeadline - context.now());
|
|
279
|
+
const baseDelay = Math.min(target.retry.maxDelayMs, final.retryAfterMs ?? target.retry.initialDelayMs * 2 ** retry);
|
|
280
|
+
const random = context.request.random?.() ?? Math.random();
|
|
281
|
+
const jitter = Number.isFinite(random) ? Math.min(1, Math.max(0, random)) : 0.5;
|
|
282
|
+
const jittered = final.retryAfterMs === undefined ? Math.round(baseDelay * (0.75 + 0.5 * jitter)) : baseDelay;
|
|
283
|
+
const delayMs = Math.min(remaining, target.retry.maxDelayMs, Math.max(0, jittered));
|
|
284
|
+
if (context.signal.aborted || remaining <= delayMs) {
|
|
285
|
+
final = interruption(context);
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
attempts[attempts.length - 1].retryDelayMs = delayMs;
|
|
289
|
+
try {
|
|
290
|
+
await (context.request.delay ?? abortableDelay)(delayMs, context.signal);
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
final = interruption(context);
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
if (context.signal.aborted || context.now() >= context.totalDeadline) {
|
|
297
|
+
final = interruption(context);
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
if (final.status === 'success' || final.reason === 'cancelled')
|
|
303
|
+
break;
|
|
304
|
+
const hasNext = targetIndex + 1 < evaluation.targets.length;
|
|
305
|
+
if (!hasNext || !evaluation.fallbackOn.includes(final.reason))
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
return decisionResult(context, final, attempts);
|
|
309
|
+
}
|
|
310
|
+
async function invokeWithDeadline(context, adapter, target, deadlineEpochMs, ordinal) {
|
|
311
|
+
const controller = new AbortController();
|
|
312
|
+
const signal = AbortSignal.any([context.signal, controller.signal]);
|
|
313
|
+
const timeoutMs = Math.max(1, deadlineEpochMs - context.now());
|
|
314
|
+
let timer;
|
|
315
|
+
let removeAbortListener = () => undefined;
|
|
316
|
+
const boundary = new Promise(resolve => {
|
|
317
|
+
timer = setTimeout(() => {
|
|
318
|
+
controller.abort(new DOMException('Decision target timed out', 'TimeoutError'));
|
|
319
|
+
resolve(context.request.signal?.aborted ? interruption(context) : observationFailure('timeout', 'error', undefined, 'target-timeout'));
|
|
320
|
+
}, timeoutMs);
|
|
321
|
+
const cancelled = () => resolve(interruption(context));
|
|
322
|
+
if (context.signal.aborted)
|
|
323
|
+
cancelled();
|
|
324
|
+
else {
|
|
325
|
+
context.signal.addEventListener('abort', cancelled, { once: true });
|
|
326
|
+
removeAbortListener = () => context.signal.removeEventListener('abort', cancelled);
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
try {
|
|
330
|
+
const observed = await Promise.race([
|
|
331
|
+
adapter.evaluate({
|
|
332
|
+
alias: context.item.alias,
|
|
333
|
+
definition: structuredClone(context.item.definition),
|
|
334
|
+
input: structuredClone(context.item.input),
|
|
335
|
+
target: structuredClone(target),
|
|
336
|
+
invocationId: `${context.request.invocationId}:${context.item.alias}:${ordinal}`,
|
|
337
|
+
deadlineEpochMs,
|
|
338
|
+
signal,
|
|
339
|
+
callerSignal: context.request.signal ?? new AbortController().signal,
|
|
340
|
+
totalSignal: context.signal,
|
|
341
|
+
resolveCredential: context.request.resolveCredential ?? unauthorizedCredential,
|
|
342
|
+
onRemoteHandle: async (handle) => {
|
|
343
|
+
try {
|
|
344
|
+
const previous = await context.request.receiptStore?.read(context.request.invocationId, context.request.receiptProjectId ?? 'default');
|
|
345
|
+
if (previous && !previous.remoteHandles.includes(handle))
|
|
346
|
+
await context.advance('remote-handle-known', { remoteHandles: [...previous.remoteHandles, handle] });
|
|
347
|
+
}
|
|
348
|
+
catch {
|
|
349
|
+
throw new ReceiptPersistenceError();
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
}),
|
|
353
|
+
boundary,
|
|
354
|
+
]);
|
|
355
|
+
return context.request.signal?.aborted ? observationFailure('cancelled', 'cancelled', observed, 'caller-cancelled') : observed;
|
|
356
|
+
}
|
|
357
|
+
finally {
|
|
358
|
+
if (timer)
|
|
359
|
+
clearTimeout(timer);
|
|
360
|
+
removeAbortListener();
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function interruption(context) {
|
|
364
|
+
if (context.request.signal?.aborted)
|
|
365
|
+
return observationFailure('cancelled', 'cancelled', undefined, 'caller-cancelled');
|
|
366
|
+
return observationFailure('timeout', 'error', undefined, context.totalTimeout.aborted || context.now() >= context.totalDeadline ? 'total-deadline' : 'target-timeout');
|
|
367
|
+
}
|
|
368
|
+
function resolveDefinitions(request) {
|
|
369
|
+
const values = Object.values(request.definitions);
|
|
370
|
+
return request.ruleset.spec.evaluations.map(evaluation => {
|
|
371
|
+
const definition = values.find(candidate => candidate.metadata.id === evaluation.decision.id);
|
|
372
|
+
if (!definition)
|
|
373
|
+
throw new DecisionValidationError(`missing decision '${evaluation.decision.id}'`);
|
|
374
|
+
validateDefinition(definition);
|
|
375
|
+
assertArtifactPin(definition, evaluation.decision, `decision ${evaluation.alias}`);
|
|
376
|
+
const projection = resolveJsonPointer(request.input, evaluation.inputPointer);
|
|
377
|
+
if (!projection.found)
|
|
378
|
+
throw new DecisionValidationError(`input projection for '${evaluation.alias}' is missing`);
|
|
379
|
+
validateAgainstSchema(definition.spec.inputSchema, projection.value, `${evaluation.alias} input`);
|
|
380
|
+
return { alias: evaluation.alias, definition, pin: evaluation.decision, input: structuredClone(projection.value) };
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
async function checkCapabilities(adapter, target, definition) {
|
|
384
|
+
if (!adapter || adapter.id !== target.adapter || adapter.version !== target.adapterVersion)
|
|
385
|
+
return observationFailure('executor-unavailable');
|
|
386
|
+
const capabilities = await adapter.capabilities();
|
|
387
|
+
if (!capabilities.executable || !capabilities.answerKinds.includes(definition.spec.answer.kind))
|
|
388
|
+
return observationFailure('unsupported-capability', 'unsupported');
|
|
389
|
+
if (definition.apiVersion === DECISION_API_VERSION_STRUCTURED && !capabilities.features.includes('structured-entries')) {
|
|
390
|
+
return observationFailure('unsupported-capability', 'unsupported');
|
|
391
|
+
}
|
|
392
|
+
const required = new Set([...definition.spec.requiredCapabilities, ...target.requiredCapabilities]);
|
|
393
|
+
const available = new Set([...capabilities.answerKinds, ...capabilities.features]);
|
|
394
|
+
if ([...required].some(capability => !available.has(capability)))
|
|
395
|
+
return observationFailure('unsupported-capability', 'unsupported');
|
|
396
|
+
if (definition.spec.answer.kind === 'choice' && capabilities.maxOptions !== null && definition.spec.answer.options.length > capabilities.maxOptions) {
|
|
397
|
+
return observationFailure('unsupported-capability', 'unsupported');
|
|
398
|
+
}
|
|
399
|
+
if (definition.spec.answer.kind === 'ordinal-score' && capabilities.maxLevels !== null && definition.spec.answer.levels.length > capabilities.maxLevels) {
|
|
400
|
+
return observationFailure('unsupported-capability', 'unsupported');
|
|
401
|
+
}
|
|
402
|
+
if (target.acceptance.mode === 'confidence-threshold' && !capabilities.confidenceProfiles.includes(target.acceptance.profile)) {
|
|
403
|
+
return observationFailure('confidence-profile-mismatch', 'abstained');
|
|
404
|
+
}
|
|
405
|
+
return null;
|
|
406
|
+
}
|
|
407
|
+
function normalizeObservation(definition, target, observation) {
|
|
408
|
+
if (observation.status !== 'success')
|
|
409
|
+
return observation;
|
|
410
|
+
validateDecisionValue(definition, observation.value);
|
|
411
|
+
if (observation.uncertainty?.distribution)
|
|
412
|
+
validateDistribution(definition, observation.uncertainty.distribution);
|
|
413
|
+
if (target.acceptance.mode === 'typed-value')
|
|
414
|
+
return observation;
|
|
415
|
+
if (!observation.uncertainty || observation.uncertainty.confidence === null)
|
|
416
|
+
return observationFailure('missing-confidence', 'abstained', observation);
|
|
417
|
+
if (observation.uncertainty.profile !== target.acceptance.profile)
|
|
418
|
+
return observationFailure('confidence-profile-mismatch', 'abstained', observation);
|
|
419
|
+
if (observation.uncertainty.confidence * 10_000 < target.acceptance.minimumBps)
|
|
420
|
+
return observationFailure('low-confidence', 'abstained', observation);
|
|
421
|
+
return observation;
|
|
422
|
+
}
|
|
423
|
+
function decisionResult(context, observation, attempts) {
|
|
424
|
+
return {
|
|
425
|
+
apiVersion: resultVersion(context.request), kind: 'DecisionResult',
|
|
426
|
+
metadata: { id: `${context.request.invocationId}-${context.item.alias}`, version: '1.0.0', description: `Decision result for ${context.item.alias}` },
|
|
427
|
+
spec: {
|
|
428
|
+
decision: context.item.pin, ruleset: context.rulesetPin, binding: context.bindingPin,
|
|
429
|
+
alias: context.item.alias, runId: context.request.runId, invocationId: context.request.invocationId,
|
|
430
|
+
status: observation.status, ...(observation.status === 'success' ? { value: observation.value } : {}),
|
|
431
|
+
reason: observation.reason, uncertainty: observation.uncertainty, attempts,
|
|
432
|
+
},
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
function emptyDecisionResult(request, item, rulesetPin, bindingPin, status, reason) {
|
|
436
|
+
return decisionResult({ request, item, rulesetPin, bindingPin, totalDeadline: 0, signal: new AbortController().signal,
|
|
437
|
+
totalTimeout: new AbortController().signal, attemptsAvailable: 0, now: Date.now, advance: async () => undefined }, observationFailure(reason, status), []);
|
|
438
|
+
}
|
|
439
|
+
function resultBase(request, ruleset, binding) {
|
|
440
|
+
return {
|
|
441
|
+
apiVersion: resultVersion(request), kind: 'RulesetResult',
|
|
442
|
+
metadata: { id: request.invocationId, version: '1.0.0', description: `Ruleset result for ${request.ruleset.metadata.id}` },
|
|
443
|
+
spec: { ruleset, binding, runId: request.runId, invocationId: request.invocationId, status: 'error', reason: 'evaluation-failed', matchedRules: [], evaluations: {} },
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
function resultVersion(request) {
|
|
447
|
+
if (request.ruleset.apiVersion === DECISION_API_VERSION_STRUCTURED || request.binding.apiVersion === DECISION_API_VERSION_STRUCTURED
|
|
448
|
+
|| Object.values(request.definitions).some(definition => definition.apiVersion === DECISION_API_VERSION_STRUCTURED)) {
|
|
449
|
+
return DECISION_API_VERSION_STRUCTURED;
|
|
450
|
+
}
|
|
451
|
+
return DECISION_API_VERSION;
|
|
452
|
+
}
|
|
453
|
+
function invalidResultBase(request) {
|
|
454
|
+
const invalidPin = { id: 'invalid', version: '0.0.0', digest: `sha256:${'0'.repeat(64)}` };
|
|
455
|
+
return {
|
|
456
|
+
apiVersion: DECISION_API_VERSION, kind: 'RulesetResult',
|
|
457
|
+
metadata: { id: request.invocationId, version: '1.0.0', description: 'Rejected decision evaluation' },
|
|
458
|
+
spec: { ruleset: invalidPin, binding: invalidPin, runId: request.runId, invocationId: request.invocationId,
|
|
459
|
+
status: 'error', reason: 'invalid-definition', matchedRules: [], evaluations: {} },
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
function failureResult(base, reason, evaluations = {}) {
|
|
463
|
+
return { ...base, spec: { ...base.spec, status: reason === 'cancelled' ? 'cancelled' : 'error', reason, matchedRules: [], evaluations } };
|
|
464
|
+
}
|
|
465
|
+
function observationFailure(reason, status = reason === 'cancelled' ? 'cancelled' : reason === 'unsupported-capability' ? 'unsupported' : 'error', inherit, termination) {
|
|
466
|
+
return {
|
|
467
|
+
status, reason, uncertainty: inherit?.uncertainty ?? null,
|
|
468
|
+
actualModel: inherit?.actualModel ?? null,
|
|
469
|
+
usage: inherit?.usage ?? { inputTokens: null, outputTokens: null, costUsd: null },
|
|
470
|
+
requestId: inherit?.requestId ?? null,
|
|
471
|
+
...(termination ? { termination } : {}),
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
function toAttempt(target, ordinal, observation, durationMs) {
|
|
475
|
+
return {
|
|
476
|
+
ordinal, adapter: target.adapter, adapterVersion: target.adapterVersion, requestedModel: target.model,
|
|
477
|
+
actualModel: observation.actualModel, subagent: target.subagent ?? null, status: observation.status,
|
|
478
|
+
reason: observation.reason, durationMs, usage: observation.usage, requestId: observation.requestId,
|
|
479
|
+
...(observation.requestIdSource ? { requestIdSource: observation.requestIdSource } : {}),
|
|
480
|
+
...(observation.httpStatus !== undefined ? { httpStatus: observation.httpStatus } : {}),
|
|
481
|
+
...(observation.termination ? { termination: observation.termination } : {}),
|
|
482
|
+
...(observation.remoteExecution ? { remoteExecution: observation.remoteExecution } : {}),
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
function classifyValidationFailure(error) {
|
|
486
|
+
if (!(error instanceof DecisionValidationError))
|
|
487
|
+
return 'invalid-definition';
|
|
488
|
+
if (/digest/.test(error.message))
|
|
489
|
+
return 'digest-mismatch';
|
|
490
|
+
if (/input|projection/.test(error.message))
|
|
491
|
+
return 'invalid-input';
|
|
492
|
+
return 'invalid-definition';
|
|
493
|
+
}
|
|
494
|
+
async function unauthorizedCredential() {
|
|
495
|
+
throw new DecisionValidationError('credential resolver is not configured');
|
|
496
|
+
}
|
|
497
|
+
async function abortableDelay(ms, signal) {
|
|
498
|
+
if (ms <= 0)
|
|
499
|
+
return;
|
|
500
|
+
await new Promise((resolve, reject) => {
|
|
501
|
+
const timer = setTimeout(resolve, ms);
|
|
502
|
+
signal.addEventListener('abort', () => { clearTimeout(timer); reject(new DOMException('Aborted', 'AbortError')); }, { once: true });
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
export function createDecisionInvocationId(prefix = 'decision') {
|
|
506
|
+
return `${prefix}-${randomUUID()}`;
|
|
507
|
+
}
|
|
508
|
+
//# sourceMappingURL=evaluate.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Create an export copy. Provider correlation IDs remain internal unless policy permits them. */
|
|
2
|
+
export function decisionResultForExport(result, policy = {}) {
|
|
3
|
+
const copy = structuredClone(result);
|
|
4
|
+
if (policy.includeProviderRequestIds)
|
|
5
|
+
return copy;
|
|
6
|
+
const decisions = copy.kind === 'DecisionResult'
|
|
7
|
+
? [copy] : Object.values(copy.spec.evaluations);
|
|
8
|
+
for (const decision of decisions) {
|
|
9
|
+
for (const attempt of decision.spec.attempts) {
|
|
10
|
+
attempt.requestId = null;
|
|
11
|
+
delete attempt.requestIdSource;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return copy;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=export.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * from './types.js';
|
|
2
|
+
export * from './validate.js';
|
|
3
|
+
export * from './entry.js';
|
|
4
|
+
export * from './convert.js';
|
|
5
|
+
export * from './predicates.js';
|
|
6
|
+
export * from './compose.js';
|
|
7
|
+
export * from './receipts.js';
|
|
8
|
+
export * from './evaluate.js';
|
|
9
|
+
export * from './export.js';
|
|
10
|
+
export * from './adapters/jev.js';
|
|
11
|
+
export * from './adapters/llm-subagent.js';
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { resolveJsonPointer } from './validate.js';
|
|
2
|
+
export function evaluatePredicate(predicate, input, decisions) {
|
|
3
|
+
if ('all' in predicate) {
|
|
4
|
+
const values = predicate.all.map(item => evaluatePredicate(item, input, decisions));
|
|
5
|
+
if (values.includes(false))
|
|
6
|
+
return false;
|
|
7
|
+
return values.includes('unknown') ? 'unknown' : true;
|
|
8
|
+
}
|
|
9
|
+
if ('any' in predicate) {
|
|
10
|
+
const values = predicate.any.map(item => evaluatePredicate(item, input, decisions));
|
|
11
|
+
if (values.includes(true))
|
|
12
|
+
return true;
|
|
13
|
+
return values.includes('unknown') ? 'unknown' : false;
|
|
14
|
+
}
|
|
15
|
+
if ('not' in predicate) {
|
|
16
|
+
const value = evaluatePredicate(predicate.not, input, decisions);
|
|
17
|
+
return value === 'unknown' ? value : !value;
|
|
18
|
+
}
|
|
19
|
+
const source = predicate.left.source === 'input'
|
|
20
|
+
? input
|
|
21
|
+
: predicate.left.alias
|
|
22
|
+
? decisions[predicate.left.alias]?.spec
|
|
23
|
+
: undefined;
|
|
24
|
+
const resolved = resolveJsonPointer(source, predicate.left.pointer);
|
|
25
|
+
if (predicate.op === 'exists')
|
|
26
|
+
return resolved.found;
|
|
27
|
+
if (!resolved.found)
|
|
28
|
+
return 'unknown';
|
|
29
|
+
const left = resolved.value;
|
|
30
|
+
const right = predicate.right;
|
|
31
|
+
if (predicate.op === 'eq' || predicate.op === 'ne') {
|
|
32
|
+
const equal = structurallyEqual(left, right);
|
|
33
|
+
return predicate.op === 'eq' ? equal : !equal;
|
|
34
|
+
}
|
|
35
|
+
if ((typeof left !== 'number' && typeof left !== 'string') || typeof left !== typeof right)
|
|
36
|
+
return 'unknown';
|
|
37
|
+
if (typeof left === 'number' && (!Number.isFinite(left) || !Number.isFinite(right)))
|
|
38
|
+
return 'unknown';
|
|
39
|
+
if (typeof left === 'number' && typeof right === 'number') {
|
|
40
|
+
switch (predicate.op) {
|
|
41
|
+
case 'lt': return left < right;
|
|
42
|
+
case 'lte': return left <= right;
|
|
43
|
+
case 'gt': return left > right;
|
|
44
|
+
case 'gte': return left >= right;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (typeof left === 'string' && typeof right === 'string') {
|
|
48
|
+
switch (predicate.op) {
|
|
49
|
+
case 'lt': return left < right;
|
|
50
|
+
case 'lte': return left <= right;
|
|
51
|
+
case 'gt': return left > right;
|
|
52
|
+
case 'gte': return left >= right;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return 'unknown';
|
|
56
|
+
}
|
|
57
|
+
function structurallyEqual(left, right) {
|
|
58
|
+
if (Object.is(left, right))
|
|
59
|
+
return true;
|
|
60
|
+
if (Array.isArray(left) && Array.isArray(right)) {
|
|
61
|
+
return left.length === right.length && left.every((value, index) => structurallyEqual(value, right[index]));
|
|
62
|
+
}
|
|
63
|
+
if (isRecord(left) && isRecord(right)) {
|
|
64
|
+
const leftKeys = Object.keys(left).sort();
|
|
65
|
+
const rightKeys = Object.keys(right).sort();
|
|
66
|
+
return leftKeys.length === rightKeys.length
|
|
67
|
+
&& leftKeys.every((key, index) => key === rightKeys[index] && structurallyEqual(left[key], right[key]));
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
function isRecord(value) {
|
|
72
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=predicates.js.map
|