@bpmsoftwaresolutions/ai-engine-client 1.1.13 → 1.1.15
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/package.json +1 -1
- package/src/index.js +255 -0
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -93,6 +93,37 @@ function isJsonBody(value) {
|
|
|
93
93
|
return typeof value === 'object';
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
function cleanText(value) {
|
|
97
|
+
const text = String(value || '').trim();
|
|
98
|
+
return text || null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function isPlainObject(value) {
|
|
102
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function matchesExpectedState(actual, expected) {
|
|
106
|
+
if (typeof expected === 'function') {
|
|
107
|
+
throw new Error('matchesExpectedState does not support function expectations.');
|
|
108
|
+
}
|
|
109
|
+
if (Array.isArray(expected)) {
|
|
110
|
+
if (!Array.isArray(actual) || actual.length < expected.length) return false;
|
|
111
|
+
return expected.every((item, index) => matchesExpectedState(actual[index], item));
|
|
112
|
+
}
|
|
113
|
+
if (isPlainObject(expected)) {
|
|
114
|
+
if (!isPlainObject(actual)) return false;
|
|
115
|
+
return Object.entries(expected).every(([key, value]) => matchesExpectedState(actual[key], value));
|
|
116
|
+
}
|
|
117
|
+
return Object.is(actual, expected);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function buildVerificationError(message, details = {}) {
|
|
121
|
+
const error = new Error(message);
|
|
122
|
+
error.code = 'POST_CONDITION_VERIFICATION_FAILED';
|
|
123
|
+
error.details = details;
|
|
124
|
+
return error;
|
|
125
|
+
}
|
|
126
|
+
|
|
96
127
|
export class AIEngineClient {
|
|
97
128
|
constructor({ baseUrl, accessToken, tokenProvider, apiKey, clientId, actorId, fetchImpl, timeoutMs } = {}) {
|
|
98
129
|
if (!baseUrl) throw new Error('baseUrl is required.');
|
|
@@ -1181,6 +1212,230 @@ export class AIEngineClient {
|
|
|
1181
1212
|
return this._request(`/api/governed-implementation/workflows/${workflowId}/resume-context`);
|
|
1182
1213
|
}
|
|
1183
1214
|
|
|
1215
|
+
async executeVerifiedMutation({
|
|
1216
|
+
mutationName,
|
|
1217
|
+
mutationFn,
|
|
1218
|
+
verificationFn,
|
|
1219
|
+
expectedState,
|
|
1220
|
+
evidenceLabel,
|
|
1221
|
+
} = {}) {
|
|
1222
|
+
if (typeof mutationFn !== 'function') throw new Error('mutationFn is required.');
|
|
1223
|
+
if (typeof verificationFn !== 'function') throw new Error('verificationFn is required.');
|
|
1224
|
+
const normalizedMutationName = cleanText(mutationName) || 'verifiedMutation';
|
|
1225
|
+
const normalizedEvidenceLabel = cleanText(evidenceLabel) || normalizedMutationName;
|
|
1226
|
+
|
|
1227
|
+
let mutationResult;
|
|
1228
|
+
try {
|
|
1229
|
+
mutationResult = await mutationFn();
|
|
1230
|
+
} catch (error) {
|
|
1231
|
+
throw buildVerificationError(
|
|
1232
|
+
`${normalizedMutationName} mutation failed before post-condition verification.`,
|
|
1233
|
+
{
|
|
1234
|
+
mutation_name: normalizedMutationName,
|
|
1235
|
+
evidence_label: normalizedEvidenceLabel,
|
|
1236
|
+
mutation_attempted: true,
|
|
1237
|
+
authoritative_read_performed: false,
|
|
1238
|
+
post_condition_verified: false,
|
|
1239
|
+
expected_state: expectedState ?? null,
|
|
1240
|
+
mutation_error: {
|
|
1241
|
+
message: error?.message || String(error),
|
|
1242
|
+
status: error?.status || null,
|
|
1243
|
+
payload: error?.payload || null,
|
|
1244
|
+
},
|
|
1245
|
+
},
|
|
1246
|
+
);
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
let verificationResult;
|
|
1250
|
+
try {
|
|
1251
|
+
verificationResult = await verificationFn(mutationResult);
|
|
1252
|
+
} catch (error) {
|
|
1253
|
+
throw buildVerificationError(
|
|
1254
|
+
`${normalizedMutationName} authoritative read failed after mutation attempt.`,
|
|
1255
|
+
{
|
|
1256
|
+
mutation_name: normalizedMutationName,
|
|
1257
|
+
evidence_label: normalizedEvidenceLabel,
|
|
1258
|
+
mutation_attempted: true,
|
|
1259
|
+
authoritative_read_performed: false,
|
|
1260
|
+
post_condition_verified: false,
|
|
1261
|
+
expected_state: expectedState ?? null,
|
|
1262
|
+
mutation_result: mutationResult ?? null,
|
|
1263
|
+
verification_error: {
|
|
1264
|
+
message: error?.message || String(error),
|
|
1265
|
+
status: error?.status || null,
|
|
1266
|
+
payload: error?.payload || null,
|
|
1267
|
+
},
|
|
1268
|
+
},
|
|
1269
|
+
);
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
let evaluation;
|
|
1273
|
+
if (typeof expectedState === 'function') {
|
|
1274
|
+
evaluation = expectedState(verificationResult, mutationResult);
|
|
1275
|
+
} else {
|
|
1276
|
+
evaluation = {
|
|
1277
|
+
ok: matchesExpectedState(verificationResult, expectedState),
|
|
1278
|
+
verifiedCurrentState: verificationResult,
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
if (typeof evaluation === 'boolean') {
|
|
1282
|
+
evaluation = { ok: evaluation, verifiedCurrentState: verificationResult };
|
|
1283
|
+
}
|
|
1284
|
+
if (!isPlainObject(evaluation) || typeof evaluation.ok !== 'boolean') {
|
|
1285
|
+
throw new Error('expectedState must resolve to a boolean or an object with an ok field.');
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
const evidence = {
|
|
1289
|
+
mutation_name: normalizedMutationName,
|
|
1290
|
+
evidence_label: normalizedEvidenceLabel,
|
|
1291
|
+
mutation_attempted: true,
|
|
1292
|
+
authoritative_read_performed: true,
|
|
1293
|
+
post_condition_verified: evaluation.ok,
|
|
1294
|
+
expected_state: expectedState ?? null,
|
|
1295
|
+
verified_current_state: evaluation.verifiedCurrentState ?? verificationResult ?? null,
|
|
1296
|
+
mutation_result: mutationResult ?? null,
|
|
1297
|
+
verification_result: verificationResult ?? null,
|
|
1298
|
+
verification_message: cleanText(evaluation.message) || null,
|
|
1299
|
+
};
|
|
1300
|
+
|
|
1301
|
+
if (!evaluation.ok) {
|
|
1302
|
+
throw buildVerificationError(
|
|
1303
|
+
cleanText(evaluation.message) || `${normalizedMutationName} post-condition verification failed.`,
|
|
1304
|
+
evidence,
|
|
1305
|
+
);
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
return {
|
|
1309
|
+
mutationName: normalizedMutationName,
|
|
1310
|
+
evidenceLabel: normalizedEvidenceLabel,
|
|
1311
|
+
mutationResult,
|
|
1312
|
+
verificationResult,
|
|
1313
|
+
verifiedCurrentState: evidence.verified_current_state,
|
|
1314
|
+
mutationVerificationEvidence: evidence,
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
async updateImplementationItemStatusVerified(implementationItemId, status, body = {}) {
|
|
1319
|
+
const workflowId = cleanText(body.workflowId) || cleanText(body.workflow_id);
|
|
1320
|
+
if (!workflowId) {
|
|
1321
|
+
throw new Error('workflowId is required to verify implementation item status.');
|
|
1322
|
+
}
|
|
1323
|
+
return this.executeVerifiedMutation({
|
|
1324
|
+
mutationName: 'updateImplementationItemStatus',
|
|
1325
|
+
evidenceLabel: `implementation-item-status:${implementationItemId}`,
|
|
1326
|
+
mutationFn: () => this.updateImplementationItemStatus(implementationItemId, { ...body, status }),
|
|
1327
|
+
verificationFn: async () => {
|
|
1328
|
+
const roadmap = await this.getWorkflowImplementationRoadmap(workflowId);
|
|
1329
|
+
const items = Array.isArray(roadmap?.items) ? roadmap.items : [];
|
|
1330
|
+
const matchedItem = items.find((item) => item?.implementation_item_id === implementationItemId) || null;
|
|
1331
|
+
return {
|
|
1332
|
+
workflow_id: workflowId,
|
|
1333
|
+
item: matchedItem,
|
|
1334
|
+
roadmap,
|
|
1335
|
+
};
|
|
1336
|
+
},
|
|
1337
|
+
expectedState: (verificationResult) => {
|
|
1338
|
+
const item = verificationResult?.item || null;
|
|
1339
|
+
if (!item) {
|
|
1340
|
+
return {
|
|
1341
|
+
ok: false,
|
|
1342
|
+
message: `Implementation item ${implementationItemId} was not present in the authoritative roadmap read.`,
|
|
1343
|
+
verifiedCurrentState: verificationResult,
|
|
1344
|
+
};
|
|
1345
|
+
}
|
|
1346
|
+
return {
|
|
1347
|
+
ok: item.status === status,
|
|
1348
|
+
message: `Implementation item ${implementationItemId} status remained ${item.status ?? 'unknown'} after mutation attempt.`,
|
|
1349
|
+
verifiedCurrentState: item,
|
|
1350
|
+
};
|
|
1351
|
+
},
|
|
1352
|
+
});
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
async updateAcceptanceCheckStatusVerified(implementationItemId, acceptanceCheckId, status, body = {}) {
|
|
1356
|
+
return this.executeVerifiedMutation({
|
|
1357
|
+
mutationName: 'updateAcceptanceCheckStatus',
|
|
1358
|
+
evidenceLabel: `acceptance-check-status:${implementationItemId}:${acceptanceCheckId}`,
|
|
1359
|
+
mutationFn: () => this.updateAcceptanceCheckStatus(implementationItemId, acceptanceCheckId, { ...body, status }),
|
|
1360
|
+
verificationFn: async () => this.getImplementationItemAcceptanceChecks(implementationItemId),
|
|
1361
|
+
expectedState: (verificationResult) => {
|
|
1362
|
+
const checks = Array.isArray(verificationResult?.acceptance_checks) ? verificationResult.acceptance_checks : [];
|
|
1363
|
+
const targetCheck = checks.find((check) => check?.acceptance_check_id === acceptanceCheckId) || null;
|
|
1364
|
+
if (!targetCheck) {
|
|
1365
|
+
return {
|
|
1366
|
+
ok: false,
|
|
1367
|
+
message: `Acceptance check ${acceptanceCheckId} was not returned by the authoritative read.`,
|
|
1368
|
+
verifiedCurrentState: verificationResult,
|
|
1369
|
+
};
|
|
1370
|
+
}
|
|
1371
|
+
return {
|
|
1372
|
+
ok: targetCheck.status === status,
|
|
1373
|
+
message: `Acceptance check ${acceptanceCheckId} status remained ${targetCheck.status ?? 'unknown'} after mutation attempt.`,
|
|
1374
|
+
verifiedCurrentState: targetCheck,
|
|
1375
|
+
};
|
|
1376
|
+
},
|
|
1377
|
+
});
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
async verifyImplementationItemArtifacts(implementationItemId, { requiredArtifacts = [] } = {}) {
|
|
1381
|
+
const requiredKinds = Array.isArray(requiredArtifacts) ? requiredArtifacts : [];
|
|
1382
|
+
const verifications = [];
|
|
1383
|
+
const artifactReaders = {
|
|
1384
|
+
artifact_manifest: async () => this.getArtifactManifest(implementationItemId),
|
|
1385
|
+
decision_packet: async () => this.getDecisionPacket(implementationItemId),
|
|
1386
|
+
};
|
|
1387
|
+
|
|
1388
|
+
for (const kind of requiredKinds) {
|
|
1389
|
+
const reader = artifactReaders[kind];
|
|
1390
|
+
if (typeof reader !== 'function') {
|
|
1391
|
+
throw new Error(`Unsupported required artifact: ${kind}`);
|
|
1392
|
+
}
|
|
1393
|
+
const payload = await reader();
|
|
1394
|
+
if (payload?.source_truth !== 'sql') {
|
|
1395
|
+
throw buildVerificationError(`Artifact ${kind} did not declare SQL as source_truth.`, {
|
|
1396
|
+
artifact_kind: kind,
|
|
1397
|
+
verified_current_state: payload ?? null,
|
|
1398
|
+
});
|
|
1399
|
+
}
|
|
1400
|
+
if (payload?.item_id !== implementationItemId) {
|
|
1401
|
+
throw buildVerificationError(`Artifact ${kind} returned item_id ${payload?.item_id ?? 'unknown'} instead of ${implementationItemId}.`, {
|
|
1402
|
+
artifact_kind: kind,
|
|
1403
|
+
verified_current_state: payload ?? null,
|
|
1404
|
+
});
|
|
1405
|
+
}
|
|
1406
|
+
const dataKey = kind === 'artifact_manifest' ? 'artifact_manifest' : 'decision_packet';
|
|
1407
|
+
if (!isPlainObject(payload?.[dataKey])) {
|
|
1408
|
+
throw buildVerificationError(`Artifact ${kind} did not include required ${dataKey} data.`, {
|
|
1409
|
+
artifact_kind: kind,
|
|
1410
|
+
verified_current_state: payload ?? null,
|
|
1411
|
+
});
|
|
1412
|
+
}
|
|
1413
|
+
verifications.push({
|
|
1414
|
+
artifact_kind: kind,
|
|
1415
|
+
http_status: 200,
|
|
1416
|
+
source_truth: payload.source_truth,
|
|
1417
|
+
item_id: payload.item_id,
|
|
1418
|
+
verified_current_state: payload[dataKey],
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
return {
|
|
1423
|
+
itemId: implementationItemId,
|
|
1424
|
+
requiredArtifacts: requiredKinds,
|
|
1425
|
+
verified: true,
|
|
1426
|
+
mutationVerificationEvidence: {
|
|
1427
|
+
mutation_name: 'verifyImplementationItemArtifacts',
|
|
1428
|
+
evidence_label: `implementation-item-artifacts:${implementationItemId}`,
|
|
1429
|
+
mutation_attempted: false,
|
|
1430
|
+
authoritative_read_performed: true,
|
|
1431
|
+
post_condition_verified: true,
|
|
1432
|
+
expected_state: { required_artifacts: requiredKinds },
|
|
1433
|
+
verified_current_state: verifications,
|
|
1434
|
+
},
|
|
1435
|
+
artifacts: verifications,
|
|
1436
|
+
};
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1184
1439
|
// ─── Skills ────────────────────────────────────────────────────────────────
|
|
1185
1440
|
|
|
1186
1441
|
async currentSkillRegistryStatus() {
|