@aiwg/cli 2026.9.18 → 2026.9.20

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.
Files changed (35) hide show
  1. package/dist/src/api/index.d.ts +1 -0
  2. package/dist/src/api/index.js +1 -0
  3. package/dist/src/artifacts/index-builder.js +44 -9
  4. package/dist/src/artifacts/query-engine.js +4 -1
  5. package/dist/src/artifacts/types.js +3 -0
  6. package/dist/src/cli/handlers/bot-handoff.js +35 -0
  7. package/dist/src/cli/handlers/build-verify.js +50 -0
  8. package/dist/src/cli/handlers/index.js +5 -1
  9. package/dist/src/cli/handlers/provider-command-options.js +24 -0
  10. package/dist/src/cli/services/deployment-verification.js +8 -5
  11. package/dist/src/decision/adapters/jev.js +515 -0
  12. package/dist/src/decision/adapters/llm-subagent.js +174 -0
  13. package/dist/src/decision/compose.js +54 -0
  14. package/dist/src/decision/convert.js +14 -0
  15. package/dist/src/decision/entry.js +183 -0
  16. package/dist/src/decision/evaluate.js +508 -0
  17. package/dist/src/decision/export.js +16 -0
  18. package/dist/src/decision/index.js +12 -0
  19. package/dist/src/decision/predicates.js +74 -0
  20. package/dist/src/decision/receipts.js +291 -0
  21. package/dist/src/decision/types.js +3 -0
  22. package/dist/src/decision/validate.js +303 -0
  23. package/dist/src/extensions/commands/definitions.js +20 -0
  24. package/dist/src/providers/bot-handoff.js +83 -0
  25. package/dist/src/providers/grokbot-handoff-guidance.js +26 -0
  26. package/dist/src/providers/grokbot-natives/connector-install-profile.js +59 -0
  27. package/dist/src/providers/grokbot-natives/create-agent.js +54 -0
  28. package/dist/src/providers/grokbot-natives/evidence.js +92 -0
  29. package/dist/src/providers/grokbot-natives/flags.js +47 -0
  30. package/dist/src/providers/grokbot-natives/index.js +67 -0
  31. package/dist/src/providers/grokbot-natives/memory-reference-helper.js +114 -0
  32. package/dist/src/providers/grokbot-natives/registered-machine-health.js +51 -0
  33. package/dist/src/providers/grokbot-natives/routines.js +57 -0
  34. package/dist/src/providers/grokbot-natives/types.js +20 -0
  35. package/package.json +1 -1
@@ -0,0 +1,291 @@
1
+ import { createHash, createHmac, randomUUID, timingSafeEqual } from 'node:crypto';
2
+ import { link, mkdir, open, readFile, readdir, rm } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { canonicalJson } from '../security/artifact-trust.js';
5
+ export function decisionInvocationFingerprint(input) {
6
+ return `sha256:${createHash('sha256').update(canonicalJson({
7
+ invocationId: input.invocationId, input: input.value, definitions: input.definitions,
8
+ ruleset: input.ruleset, binding: input.binding,
9
+ policy: input.policy ?? null, calibration: input.calibration ?? null,
10
+ })).digest('hex')}`;
11
+ }
12
+ const terminal = new Set(['completed', 'failed', 'execution-uncertain']);
13
+ const allowed = {
14
+ acquired: ['dispatched', 'observation-received', 'composed', 'failed', 'execution-uncertain'],
15
+ dispatched: ['remote-handle-known', 'observation-received', 'execution-uncertain'],
16
+ 'remote-handle-known': ['observation-received', 'execution-uncertain'],
17
+ 'observation-received': ['observation-received', 'dispatched', 'composed', 'failed', 'execution-uncertain'],
18
+ composed: ['completed', 'failed', 'execution-uncertain'],
19
+ completed: [], failed: [], 'execution-uncertain': [],
20
+ };
21
+ export class DecisionReceiptIntegrityError extends Error {
22
+ }
23
+ export class DecisionReceiptAccessError extends Error {
24
+ }
25
+ /** A transport may throw this only before it has attempted remote dispatch. */
26
+ export class DecisionPreDispatchError extends Error {
27
+ }
28
+ export function validateReceipt(receipt, invocationId, projectId) {
29
+ if (!receipt || receipt.schema !== 'decision-receipt/v2' || receipt.invocationId !== invocationId || receipt.projectId !== projectId
30
+ || !Number.isSafeInteger(receipt.revision) || receipt.revision < 1
31
+ || !Number.isSafeInteger(receipt.acquiredAtEpochMs) || receipt.acquiredAtEpochMs < 0
32
+ || !Number.isSafeInteger(receipt.updatedAtEpochMs) || receipt.updatedAtEpochMs < receipt.acquiredAtEpochMs
33
+ || (receipt.state === 'completed' ? !Number.isSafeInteger(receipt.completedAtEpochMs)
34
+ || receipt.completedAtEpochMs < receipt.acquiredAtEpochMs
35
+ : receipt.completedAtEpochMs !== undefined)
36
+ || !/^sha256:[a-f0-9]{64}$/.test(receipt.fingerprint)
37
+ || !Object.hasOwn(allowed, receipt.state) || !Array.isArray(receipt.remoteHandles)
38
+ || receipt.remoteHandles.some(handle => typeof handle !== 'string' || !handle.length)
39
+ || !receipt.evaluations || typeof receipt.evaluations !== 'object' || Array.isArray(receipt.evaluations)
40
+ || Object.entries(receipt.evaluations).some(([alias, result]) => result.spec.alias !== alias || result.spec.invocationId !== invocationId)
41
+ || (receipt.pending !== null && (!receipt.pending || typeof receipt.pending.alias !== 'string'
42
+ || !Number.isSafeInteger(receipt.pending.targetIndex) || receipt.pending.targetIndex < 0
43
+ || !Number.isSafeInteger(receipt.pending.ordinal) || receipt.pending.ordinal < 1
44
+ || !Array.isArray(receipt.pending.attempts)))
45
+ || (receipt.state === 'completed' && (!receipt.result || receipt.result.spec.invocationId !== invocationId
46
+ || receipt.result.spec.status === 'error' && receipt.result.spec.reason === 'execution-uncertain'))
47
+ || (receipt.state !== 'completed' && receipt.result !== undefined)) {
48
+ throw new DecisionReceiptIntegrityError('Invalid decision receipt');
49
+ }
50
+ }
51
+ export function nextReceipt(previous, state, extra = {}) {
52
+ if (!allowed[previous.state].includes(state))
53
+ throw new DecisionReceiptIntegrityError('Illegal receipt transition');
54
+ const updatedAtEpochMs = Math.max(previous.updatedAtEpochMs, extra.updatedAtEpochMs ?? Date.now());
55
+ const next = { ...structuredClone(previous), ...structuredClone(extra), state, revision: previous.revision + 1,
56
+ updatedAtEpochMs, ...(state === 'completed' ? { completedAtEpochMs: extra.completedAtEpochMs ?? updatedAtEpochMs } : {}) };
57
+ if (next.remoteHandles.length < previous.remoteHandles.length || previous.remoteHandles.some((handle, index) => next.remoteHandles[index] !== handle)) {
58
+ throw new DecisionReceiptIntegrityError('Remote handle lineage changed');
59
+ }
60
+ if (Object.entries(previous.evaluations).some(([alias, result]) => canonicalJson(next.evaluations[alias]) !== canonicalJson(result))) {
61
+ throw new DecisionReceiptIntegrityError('Completed evaluation lineage changed');
62
+ }
63
+ validateReceipt(next, previous.invocationId, previous.projectId);
64
+ return next;
65
+ }
66
+ function initial(invocationId, projectId, fingerprint) {
67
+ const acquiredAtEpochMs = Date.now();
68
+ const receipt = { schema: 'decision-receipt/v2', revision: 1, acquiredAtEpochMs, updatedAtEpochMs: acquiredAtEpochMs,
69
+ projectId, invocationId, fingerprint, state: 'acquired', remoteHandles: [], evaluations: {}, pending: null };
70
+ validateReceipt(receipt, invocationId, projectId);
71
+ return receipt;
72
+ }
73
+ function assertTransition(previous, next) {
74
+ const expected = nextReceipt(previous, next.state, {
75
+ ...(next.result !== undefined ? { result: next.result } : {}), remoteHandles: next.remoteHandles,
76
+ evaluations: next.evaluations, pending: next.pending, updatedAtEpochMs: next.updatedAtEpochMs,
77
+ ...(next.completedAtEpochMs !== undefined ? { completedAtEpochMs: next.completedAtEpochMs } : {}),
78
+ });
79
+ if (canonicalJson(expected) !== canonicalJson(next))
80
+ throw new DecisionReceiptIntegrityError('Receipt mutation outside legal transition');
81
+ }
82
+ export class MemoryDecisionReceiptStore {
83
+ authorize;
84
+ receipts = new Map();
85
+ constructor(authorize = () => true) {
86
+ this.authorize = authorize;
87
+ }
88
+ async check(projectId) {
89
+ if (!await this.authorize(projectId))
90
+ throw new DecisionReceiptAccessError('Decision receipt access denied');
91
+ }
92
+ async read(invocationId, projectId = 'default') {
93
+ await this.check(projectId);
94
+ const receipt = this.receipts.get(invocationId);
95
+ if (!receipt)
96
+ return null;
97
+ validateReceipt(receipt, invocationId, projectId);
98
+ return structuredClone(receipt);
99
+ }
100
+ async acquire(invocationId, projectId, fingerprint) {
101
+ await this.check(projectId);
102
+ const existing = this.receipts.get(invocationId);
103
+ if (existing) {
104
+ validateReceipt(existing, invocationId, projectId);
105
+ return { owner: false, receipt: structuredClone(existing) };
106
+ }
107
+ const receipt = initial(invocationId, projectId, fingerprint);
108
+ this.receipts.set(invocationId, receipt);
109
+ return { owner: true, receipt: structuredClone(receipt) };
110
+ }
111
+ async compareAndSwap(invocationId, projectId, expectedRevision, next) {
112
+ await this.check(projectId);
113
+ const current = this.receipts.get(invocationId);
114
+ if (!current)
115
+ throw new DecisionReceiptIntegrityError('Missing decision receipt');
116
+ validateReceipt(current, invocationId, projectId);
117
+ if (current.revision !== expectedRevision)
118
+ return false;
119
+ assertTransition(current, next);
120
+ this.receipts.set(invocationId, structuredClone(next));
121
+ return true;
122
+ }
123
+ async waitForTerminal(invocationId, projectId, fingerprint, signal) {
124
+ for (;;) {
125
+ const receipt = await this.read(invocationId, projectId);
126
+ if (!receipt || receipt.fingerprint !== fingerprint)
127
+ throw new DecisionReceiptIntegrityError('Receipt fingerprint mismatch');
128
+ if (terminal.has(receipt.state))
129
+ return receipt;
130
+ if (signal?.aborted)
131
+ throw signal.reason ?? new Error('Aborted');
132
+ await sleep(20, signal);
133
+ }
134
+ }
135
+ }
136
+ export class FileDecisionReceiptStore {
137
+ directory;
138
+ options;
139
+ constructor(directory, options) {
140
+ this.directory = directory;
141
+ this.options = options;
142
+ if (options.integrityKey.length < 32)
143
+ throw new Error('Decision receipt integrity key must be at least 32 bytes');
144
+ }
145
+ async check(projectId) {
146
+ if (this.options.authorize && !await this.options.authorize(projectId))
147
+ throw new DecisionReceiptAccessError('Decision receipt access denied');
148
+ }
149
+ prefixFor(invocationId) {
150
+ return createHash('sha256').update(invocationId).digest('hex');
151
+ }
152
+ pathFor(invocationId, revision) {
153
+ return join(this.directory, `${this.prefixFor(invocationId)}.r${revision}.json`);
154
+ }
155
+ mac(receipt) {
156
+ return createHmac('sha256', this.options.integrityKey).update(canonicalJson(receipt)).digest('hex');
157
+ }
158
+ async read(invocationId, projectId = 'default') {
159
+ await this.check(projectId);
160
+ let names;
161
+ try {
162
+ names = await readdir(this.directory);
163
+ }
164
+ catch (error) {
165
+ if (error.code === 'ENOENT')
166
+ return null;
167
+ throw error;
168
+ }
169
+ const prefix = `${this.prefixFor(invocationId)}.r`;
170
+ const revisions = names.filter(name => name.startsWith(prefix) && name.endsWith('.json'))
171
+ .map(name => Number(name.slice(prefix.length, -'.json'.length)));
172
+ if (!revisions.length)
173
+ return null;
174
+ // A previous writer may have died after link() but before directory sync().
175
+ // Synchronize any observed publication before treating it as ownership evidence.
176
+ const dir = await open(this.directory, 'r');
177
+ try {
178
+ await dir.sync();
179
+ }
180
+ finally {
181
+ await dir.close();
182
+ }
183
+ if (revisions.some(revision => !Number.isSafeInteger(revision) || revision < 1))
184
+ throw new DecisionReceiptIntegrityError('Corrupt decision receipt revision index');
185
+ revisions.sort((a, b) => a - b);
186
+ let previous = null;
187
+ for (let index = 0; index < revisions.length; index += 1) {
188
+ const revision = revisions[index];
189
+ if (revision !== index + 1)
190
+ throw new DecisionReceiptIntegrityError('Decision receipt revision gap');
191
+ let document;
192
+ try {
193
+ document = JSON.parse(await readFile(this.pathFor(invocationId, revision), 'utf8'));
194
+ }
195
+ catch {
196
+ throw new DecisionReceiptIntegrityError('Corrupt decision receipt');
197
+ }
198
+ if (!document || typeof document.mac !== 'string' || !/^[a-f0-9]{64}$/.test(document.mac))
199
+ throw new DecisionReceiptIntegrityError('Corrupt decision receipt envelope');
200
+ if (!timingSafeEqual(Buffer.from(document.mac, 'hex'), Buffer.from(this.mac(document.receipt), 'hex'))) {
201
+ throw new DecisionReceiptIntegrityError('Decision receipt integrity check failed');
202
+ }
203
+ validateReceipt(document.receipt, invocationId, projectId);
204
+ if (document.receipt.revision !== revision || (revision === 1 && document.receipt.state !== 'acquired')) {
205
+ throw new DecisionReceiptIntegrityError('Decision receipt revision mismatch');
206
+ }
207
+ if (previous)
208
+ assertTransition(previous, document.receipt);
209
+ previous = document.receipt;
210
+ }
211
+ return structuredClone(previous);
212
+ }
213
+ async acquire(invocationId, projectId, fingerprint) {
214
+ await this.check(projectId);
215
+ const existing = await this.read(invocationId, projectId);
216
+ if (existing)
217
+ return { owner: false, receipt: existing };
218
+ const receipt = initial(invocationId, projectId, fingerprint);
219
+ if (await this.persist(invocationId, receipt))
220
+ return { owner: true, receipt };
221
+ const winner = await this.read(invocationId, projectId);
222
+ if (!winner)
223
+ throw new DecisionReceiptIntegrityError('Missing winning receipt');
224
+ return { owner: false, receipt: winner };
225
+ }
226
+ async compareAndSwap(invocationId, projectId, expectedRevision, next) {
227
+ await this.check(projectId);
228
+ const current = await this.read(invocationId, projectId);
229
+ if (!current)
230
+ throw new DecisionReceiptIntegrityError('Missing decision receipt');
231
+ if (current.revision !== expectedRevision)
232
+ return false;
233
+ assertTransition(current, next);
234
+ return this.persist(invocationId, next);
235
+ }
236
+ async waitForTerminal(invocationId, projectId, fingerprint, signal) {
237
+ for (;;) {
238
+ const receipt = await this.read(invocationId, projectId);
239
+ if (!receipt || receipt.fingerprint !== fingerprint)
240
+ throw new DecisionReceiptIntegrityError('Receipt fingerprint mismatch');
241
+ if (terminal.has(receipt.state))
242
+ return receipt;
243
+ if (signal?.aborted)
244
+ throw signal.reason ?? new Error('Aborted');
245
+ await sleep(30, signal);
246
+ }
247
+ }
248
+ async persist(invocationId, receipt) {
249
+ await mkdir(this.directory, { recursive: true, mode: 0o700 });
250
+ const destination = this.pathFor(invocationId, receipt.revision);
251
+ const temporary = `${destination}.${randomUUID()}.tmp`;
252
+ try {
253
+ const file = await open(temporary, 'wx', 0o600);
254
+ try {
255
+ await file.writeFile(`${JSON.stringify({ receipt, mac: this.mac(receipt) })}\n`);
256
+ await file.sync();
257
+ }
258
+ finally {
259
+ await file.close();
260
+ }
261
+ await this.options.onPublish?.('before-link', receipt);
262
+ try {
263
+ await link(temporary, destination);
264
+ }
265
+ catch (error) {
266
+ if (error.code === 'EEXIST')
267
+ return false;
268
+ throw error;
269
+ }
270
+ await this.options.onPublish?.('after-link', receipt);
271
+ const dir = await open(this.directory, 'r');
272
+ try {
273
+ await dir.sync();
274
+ }
275
+ finally {
276
+ await dir.close();
277
+ }
278
+ return true;
279
+ }
280
+ finally {
281
+ await rm(temporary, { force: true });
282
+ }
283
+ }
284
+ }
285
+ async function sleep(ms, signal) {
286
+ await new Promise((resolve, reject) => {
287
+ const timer = setTimeout(resolve, ms);
288
+ signal?.addEventListener('abort', () => { clearTimeout(timer); reject(signal.reason ?? new Error('Aborted')); }, { once: true });
289
+ });
290
+ }
291
+ //# sourceMappingURL=receipts.js.map
@@ -0,0 +1,3 @@
1
+ export const DECISION_API_VERSION = 'decision.aiwg.io/v1alpha1';
2
+ export const DECISION_API_VERSION_STRUCTURED = 'decision.aiwg.io/v1alpha2';
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,303 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { dirname, resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import Ajv2020 from 'ajv/dist/2020.js';
6
+ import { canonicalJson } from '../security/artifact-trust.js';
7
+ import { admitEntry, EntryAdmissionError } from './entry.js';
8
+ import { DECISION_API_VERSION, DECISION_API_VERSION_STRUCTURED } from './types.js';
9
+ const schemaFiles = {
10
+ DecisionBinding: 'DecisionBinding.schema.json',
11
+ DecisionDefinition: 'DecisionDefinition.schema.json',
12
+ DecisionResult: 'DecisionResult.schema.json',
13
+ DecisionRuleset: 'DecisionRuleset.schema.json',
14
+ RulesetResult: 'RulesetResult.schema.json',
15
+ };
16
+ const structuredSchemaFiles = {
17
+ DecisionDefinition: 'DecisionDefinition.v1alpha2.schema.json',
18
+ DecisionRuleset: 'DecisionRuleset.v1alpha2.schema.json',
19
+ DecisionBinding: 'DecisionBinding.v1alpha2.schema.json',
20
+ DecisionResult: 'DecisionResult.v1alpha2.schema.json',
21
+ RulesetResult: 'RulesetResult.v1alpha2.schema.json',
22
+ };
23
+ export class DecisionValidationError extends Error {
24
+ errors;
25
+ constructor(message, errors = []) {
26
+ super(message);
27
+ this.errors = errors;
28
+ this.name = 'DecisionValidationError';
29
+ }
30
+ }
31
+ let validators = null;
32
+ function schemaRoot() {
33
+ const here = dirname(fileURLToPath(import.meta.url));
34
+ const candidates = [resolve(here, '../../schemas/decision'), resolve(here, '../../../schemas/decision')];
35
+ const found = candidates.find(candidate => existsSync(resolve(candidate, schemaFiles.DecisionDefinition)));
36
+ if (!found)
37
+ throw new DecisionValidationError('Decision schema directory is unavailable');
38
+ return found;
39
+ }
40
+ function getValidators() {
41
+ if (validators)
42
+ return validators;
43
+ const ajv = new Ajv2020({ strict: false, allErrors: true, validateFormats: false });
44
+ validators = new Map();
45
+ for (const [kind, filename] of Object.entries(schemaFiles)) {
46
+ const schema = JSON.parse(readFileSync(resolve(schemaRoot(), filename), 'utf8'));
47
+ validators.set(`decision.aiwg.io/v1alpha1:${kind}`, ajv.compile(schema));
48
+ }
49
+ for (const [kind, filename] of Object.entries(structuredSchemaFiles)) {
50
+ const schema = JSON.parse(readFileSync(resolve(schemaRoot(), filename), 'utf8'));
51
+ validators.set(`decision.aiwg.io/v1alpha2:${kind}`, ajv.compile(schema));
52
+ }
53
+ return validators;
54
+ }
55
+ export function artifactDigest(value) {
56
+ const started = performance.now();
57
+ admitEntry(value);
58
+ const canonical = canonicalJson(value);
59
+ if (performance.now() - started > 1000) {
60
+ throw new EntryAdmissionError('time-budget', { bytes: Buffer.byteLength(canonical), entries: 0 });
61
+ }
62
+ return `sha256:${createHash('sha256').update(canonical).digest('hex')}`;
63
+ }
64
+ export function artifactPin(value) {
65
+ return { id: value.metadata.id, version: value.metadata.version, digest: artifactDigest(value) };
66
+ }
67
+ export function assertArtifactPin(value, pin, label) {
68
+ if (value.metadata.id !== pin.id || value.metadata.version !== pin.version) {
69
+ throw new DecisionValidationError(`${label} identity/version does not match its pin`);
70
+ }
71
+ if (artifactDigest(value) !== pin.digest)
72
+ throw new DecisionValidationError(`${label} digest does not match its pin`);
73
+ }
74
+ export function validateDecisionDocument(value) {
75
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
76
+ throw new DecisionValidationError('Decision document must be an object');
77
+ }
78
+ try {
79
+ admitEntry(value);
80
+ }
81
+ catch (error) {
82
+ if (error instanceof EntryAdmissionError)
83
+ throw new DecisionValidationError(error.message);
84
+ throw error;
85
+ }
86
+ const kind = value.kind;
87
+ const version = value.apiVersion;
88
+ const key = `${version}:${kind}`;
89
+ if (!kind || !getValidators().has(key))
90
+ throw new DecisionValidationError(`Unsupported decision kind/version '${key}'`);
91
+ const validate = getValidators().get(key);
92
+ if (!validate(value)) {
93
+ throw new DecisionValidationError(`${kind} schema validation failed: ${ajvMessage(validate.errors)}`, validate.errors ?? []);
94
+ }
95
+ }
96
+ export const DECISION_CHANGED_SEMANTICS = [
97
+ 'structured-entry', 'batch-receipt', 'acceptance-uncertainty', 'calibration-pin', 'trust-projection',
98
+ ];
99
+ /** Writer gate shared by D02 and future D07-D10 emitters. */
100
+ export function assertDecisionWriterVersion(value, semantic) {
101
+ validateDecisionDocument(value);
102
+ if (value.apiVersion !== DECISION_API_VERSION_STRUCTURED) {
103
+ throw new DecisionValidationError(`${semantic} requires ${DECISION_API_VERSION_STRUCTURED}`);
104
+ }
105
+ }
106
+ /** A rollback reader may inspect v1alpha2, but cannot execute or rewrite it. */
107
+ export function readDecisionDocumentForRollback(value, mode) {
108
+ validateDecisionDocument(value);
109
+ if (mode === 'execute' && value.apiVersion !== DECISION_API_VERSION) {
110
+ throw new DecisionValidationError('Rollback executor cannot execute v1alpha2 artifacts');
111
+ }
112
+ const document = structuredClone(value);
113
+ if (mode === 'read-only')
114
+ deepFreeze(document);
115
+ return { document, writable: mode === 'execute' };
116
+ }
117
+ function deepFreeze(value) {
118
+ if (!value || typeof value !== 'object' || Object.isFrozen(value))
119
+ return;
120
+ for (const child of Object.values(value))
121
+ deepFreeze(child);
122
+ Object.freeze(value);
123
+ }
124
+ export function validateAgainstSchema(schema, value, label) {
125
+ assertLocalSchema(schema, label);
126
+ const ajv = new Ajv2020({ strict: false, allErrors: true, validateFormats: false });
127
+ let validate;
128
+ try {
129
+ validate = ajv.compile(schema);
130
+ }
131
+ catch (error) {
132
+ throw new DecisionValidationError(`${label} is not a supported local draft 2020-12 schema: ${errorMessage(error)}`);
133
+ }
134
+ if (!validate(value))
135
+ throw new DecisionValidationError(`${label} rejected value: ${ajvMessage(validate.errors)}`, validate.errors ?? []);
136
+ }
137
+ export function validateDefinition(definition) {
138
+ validateDecisionDocument(definition);
139
+ assertLocalSchema(definition.spec.inputSchema, `${definition.metadata.id}.inputSchema`);
140
+ if (definition.spec.answer.kind === 'choice') {
141
+ assertUnique(definition.spec.answer.options.map(option => option.id), 'choice option IDs');
142
+ }
143
+ if (definition.spec.answer.kind === 'ordinal-score') {
144
+ const keys = definition.spec.answer.levels.map(level => canonicalJson(level));
145
+ assertUnique(keys, 'ordinal levels');
146
+ }
147
+ }
148
+ export function validateRuleset(ruleset) {
149
+ validateDecisionDocument(ruleset);
150
+ assertLocalSchema(ruleset.spec.inputSchema, `${ruleset.metadata.id}.inputSchema`);
151
+ assertLocalSchema(ruleset.spec.outputSchema, `${ruleset.metadata.id}.outputSchema`);
152
+ const aliases = ruleset.spec.evaluations.map(item => item.alias);
153
+ assertUnique(aliases, 'evaluation aliases');
154
+ assertUnique(ruleset.spec.rules.map(rule => rule.id), 'rule IDs');
155
+ for (const rule of ruleset.spec.rules)
156
+ assertPredicateAliases(rule.when, new Set(aliases));
157
+ if (ruleset.spec.composition === 'collect') {
158
+ const output = ruleset.spec.outputSchema;
159
+ if (output.type !== 'array' || !output.items || Array.isArray(output.items) || output.prefixItems !== undefined) {
160
+ throw new DecisionValidationError('collect outputSchema must be an array schema with one object-valued items schema');
161
+ }
162
+ const itemSchema = output.items;
163
+ if (itemSchema.type !== 'object')
164
+ throw new DecisionValidationError('collect outputSchema.items must describe objects');
165
+ for (const rule of ruleset.spec.rules)
166
+ validateAgainstSchema(itemSchema, rule.outcome, `rule ${rule.id} outcome`);
167
+ }
168
+ else {
169
+ for (const rule of ruleset.spec.rules)
170
+ validateAgainstSchema(ruleset.spec.outputSchema, rule.outcome, `rule ${rule.id} outcome`);
171
+ }
172
+ validateAgainstSchema(ruleset.spec.outputSchema, ruleset.spec.defaultOutcome, 'defaultOutcome');
173
+ validateAgainstSchema(ruleset.spec.outputSchema, ruleset.spec.failureOutcome, 'failureOutcome');
174
+ }
175
+ export function validateBinding(binding, ruleset) {
176
+ validateDecisionDocument(binding);
177
+ assertArtifactPin(ruleset, binding.spec.ruleset, 'ruleset');
178
+ const declared = [...ruleset.spec.evaluations.map(item => item.alias)].sort();
179
+ const bound = Object.keys(binding.spec.evaluations).sort();
180
+ if (canonicalJson(declared) !== canonicalJson(bound)) {
181
+ throw new DecisionValidationError('binding must map every ruleset evaluation alias exactly once');
182
+ }
183
+ for (const [alias, evaluation] of Object.entries(binding.spec.evaluations)) {
184
+ for (const target of evaluation.targets) {
185
+ if (target.retry.maxDelayMs < target.retry.initialDelayMs) {
186
+ throw new DecisionValidationError(`${alias} retry maxDelayMs must be >= initialDelayMs`);
187
+ }
188
+ }
189
+ }
190
+ }
191
+ export function resolveJsonPointer(root, pointer) {
192
+ if (pointer === '')
193
+ return { found: true, value: root };
194
+ if (!pointer.startsWith('/'))
195
+ return { found: false };
196
+ let current = root;
197
+ for (const encoded of pointer.slice(1).split('/')) {
198
+ const token = encoded.replace(/~1/g, '/').replace(/~0/g, '~');
199
+ if (Array.isArray(current)) {
200
+ if (!/^(0|[1-9]\d*)$/.test(token))
201
+ return { found: false };
202
+ const index = Number(token);
203
+ if (index >= current.length)
204
+ return { found: false };
205
+ current = current[index];
206
+ }
207
+ else if (current && typeof current === 'object' && Object.prototype.hasOwnProperty.call(current, token)) {
208
+ current = current[token];
209
+ }
210
+ else {
211
+ return { found: false };
212
+ }
213
+ }
214
+ return { found: true, value: current };
215
+ }
216
+ export function validateDecisionValue(definition, value) {
217
+ const answer = definition.spec.answer;
218
+ if (answer.kind === 'choice') {
219
+ if (typeof value !== 'string' || !answer.options.some(option => option.id === value)) {
220
+ throw new DecisionValidationError(`choice value must be one of: ${answer.options.map(option => option.id).join(', ')}`);
221
+ }
222
+ return;
223
+ }
224
+ if (typeof value !== 'number' || !Number.isFinite(value))
225
+ throw new DecisionValidationError(`${answer.kind} value must be finite`);
226
+ if (answer.kind === 'truth-probability' && (value < 0 || value > 1)) {
227
+ throw new DecisionValidationError('truth-probability must be within [0,1]');
228
+ }
229
+ if (answer.kind === 'ordinal-score' && (value < 0 || value > answer.levels.length - 1)) {
230
+ throw new DecisionValidationError(`ordinal-score must be within [0,${answer.levels.length - 1}]`);
231
+ }
232
+ }
233
+ export function validateDistribution(definition, distribution) {
234
+ const expected = definition.spec.answer.kind === 'choice'
235
+ ? definition.spec.answer.options.map(option => option.id)
236
+ : definition.spec.answer.kind === 'ordinal-score'
237
+ ? definition.spec.answer.levels.map((_, index) => String(index))
238
+ : ['false', 'true'];
239
+ if (canonicalJson(Object.keys(distribution).sort()) !== canonicalJson([...expected].sort())) {
240
+ throw new DecisionValidationError('distribution support must exactly match the declared answer domain');
241
+ }
242
+ const values = Object.values(distribution);
243
+ if (values.some(value => !Number.isFinite(value) || value < 0 || value > 1)) {
244
+ throw new DecisionValidationError('distribution values must be finite probabilities');
245
+ }
246
+ if (Math.abs(values.reduce((sum, value) => sum + value, 0) - 1) > 1e-6) {
247
+ throw new DecisionValidationError('distribution probabilities must sum to one');
248
+ }
249
+ }
250
+ function assertLocalSchema(schema, label) {
251
+ const visit = (value, stack, depth) => {
252
+ if (depth > 64)
253
+ throw new DecisionValidationError(`${label} exceeds maximum schema depth`);
254
+ if (!value || typeof value !== 'object')
255
+ return;
256
+ if (stack.has(value))
257
+ throw new DecisionValidationError(`${label} contains a cycle`);
258
+ stack.add(value);
259
+ if (!Array.isArray(value)) {
260
+ const ref = value.$ref;
261
+ if (typeof ref === 'string' && !ref.startsWith('#/')) {
262
+ throw new DecisionValidationError(`${label} contains a non-local $ref`);
263
+ }
264
+ }
265
+ for (const child of Array.isArray(value) ? value : Object.values(value))
266
+ visit(child, stack, depth + 1);
267
+ stack.delete(value);
268
+ };
269
+ visit(schema, new Set(), 0);
270
+ try {
271
+ new Ajv2020({ strictSchema: true, strictTypes: false, strictTuples: false,
272
+ strictRequired: false, validateFormats: false }).compile(schema);
273
+ }
274
+ catch (error) {
275
+ throw new DecisionValidationError(`${label} uses an unsupported schema construct: ${errorMessage(error)}`);
276
+ }
277
+ }
278
+ function assertPredicateAliases(predicate, aliases) {
279
+ if ('all' in predicate)
280
+ return predicate.all.forEach(item => assertPredicateAliases(item, aliases));
281
+ if ('any' in predicate)
282
+ return predicate.any.forEach(item => assertPredicateAliases(item, aliases));
283
+ if ('not' in predicate)
284
+ return assertPredicateAliases(predicate.not, aliases);
285
+ if (predicate.left.source === 'decision' && (!predicate.left.alias || !aliases.has(predicate.left.alias))) {
286
+ throw new DecisionValidationError(`predicate references undeclared decision alias '${predicate.left.alias ?? ''}'`);
287
+ }
288
+ }
289
+ function assertUnique(values, label) {
290
+ if (new Set(values).size !== values.length)
291
+ throw new DecisionValidationError(`${label} must be unique`);
292
+ }
293
+ function ajvMessage(errors) {
294
+ return (errors ?? []).map(error => `${error.instancePath || '/'} ${error.message ?? 'invalid'}`).join('; ');
295
+ }
296
+ function errorMessage(error) {
297
+ return error instanceof Error ? error.message : String(error);
298
+ }
299
+ export function asJsonValue(value) {
300
+ canonicalJson(value);
301
+ return value;
302
+ }
303
+ //# sourceMappingURL=validate.js.map
@@ -1101,6 +1101,24 @@ export const writerProfileCommand = {
1101
1101
  allowedTools: ['Read', 'Write', 'Bash'],
1102
1102
  },
1103
1103
  };
1104
+ export const botHandoffCommand = {
1105
+ id: 'bot-handoff', type: 'command', name: 'Bot Handoff',
1106
+ description: 'Generate operator-reviewed routine, teammate, connector and memory drafts; no native installation',
1107
+ version: '1.0.0', capabilities: ['cli', 'providers', 'handoff'],
1108
+ keywords: ['provider', 'handoff', 'routines', 'teammates', 'connectors', 'memory'],
1109
+ category: 'utility', platforms: { generic: 'full' },
1110
+ deployment: { pathTemplate: '.{platform}/commands/{id}.md', core: true },
1111
+ metadata: { type: 'command', template: 'utility', argumentHint: '--input <proposal.json> [--provider <id>] [--dry-run]', allowedTools: ['Read'] },
1112
+ };
1113
+ export const buildVerifyCommand = {
1114
+ id: 'build-verify', type: 'command', name: 'Build Verification',
1115
+ description: 'Verify AIWG deployment and supported native discovery before any provider runs a build or CI task',
1116
+ version: '1.0.0', capabilities: ['cli', 'providers', 'verification'],
1117
+ keywords: ['provider', 'inspect', 'ci', 'verification'],
1118
+ category: 'utility', platforms: { generic: 'full' },
1119
+ deployment: { pathTemplate: '.{platform}/commands/{id}.md', core: true },
1120
+ metadata: { type: 'command', template: 'utility', argumentHint: '[--provider <id>]', allowedTools: ['Read', 'Bash'] },
1121
+ };
1104
1122
  export const outputModeCommand = {
1105
1123
  id: 'output-mode',
1106
1124
  type: 'command',
@@ -3824,6 +3842,8 @@ export const commandDefinitions = [
3824
3842
  sessionsCommand,
3825
3843
  outputModeCommand,
3826
3844
  writerProfileCommand,
3845
+ botHandoffCommand,
3846
+ buildVerifyCommand,
3827
3847
  writingCommand,
3828
3848
  schemaCommand,
3829
3849
  datasetCommand,