@bpmsoftwaresolutions/ai-engine-client 1.1.15 → 1.1.17

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +101 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bpmsoftwaresolutions/ai-engine-client",
3
- "version": "1.1.15",
3
+ "version": "1.1.17",
4
4
  "description": "Thin npm client for the AI Engine operator and retrieval APIs",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
package/src/index.js CHANGED
@@ -1,4 +1,18 @@
1
1
  const DEFAULT_TIMEOUT_MS = 30000;
2
+ export const AI_ENGINE_CLIENT_VERSION = '1.1.17';
3
+ export const GOVERNED_MUTATION_REQUIRED_CAPABILITIES = [
4
+ 'executeVerifiedMutation',
5
+ 'post_mutation_verification',
6
+ 'acceptance_check_read',
7
+ 'artifact_manifest_read',
8
+ 'decision_packet_read',
9
+ 'claim_signoff',
10
+ ];
11
+ export const AI_ENGINE_CLIENT_CAPABILITIES = [
12
+ ...GOVERNED_MUTATION_REQUIRED_CAPABILITIES,
13
+ 'execution_eligibility_read',
14
+ 'workflow_resume_context_read',
15
+ ];
2
16
 
3
17
  export const LOGA_CONTRACT = 'ai-engine-ui/v1';
4
18
  export const LOGA_INTERACTION_CONTRACT = 'loga-choreography/v1';
@@ -124,6 +138,13 @@ function buildVerificationError(message, details = {}) {
124
138
  return error;
125
139
  }
126
140
 
141
+ function buildEligibilityError(message, details = {}) {
142
+ const error = new Error(message);
143
+ error.code = 'EXECUTION_ELIGIBILITY_FAILED';
144
+ error.details = details;
145
+ return error;
146
+ }
147
+
127
148
  export class AIEngineClient {
128
149
  constructor({ baseUrl, accessToken, tokenProvider, apiKey, clientId, actorId, fetchImpl, timeoutMs } = {}) {
129
150
  if (!baseUrl) throw new Error('baseUrl is required.');
@@ -135,6 +156,8 @@ export class AIEngineClient {
135
156
  this.actorId = actorId || 'sdk:npm-ai-engine-client';
136
157
  this.fetchImpl = fetchImpl || globalThis.fetch;
137
158
  this.timeoutMs = timeoutMs || DEFAULT_TIMEOUT_MS;
159
+ this.clientVersion = AI_ENGINE_CLIENT_VERSION;
160
+ this.clientCapabilities = [...AI_ENGINE_CLIENT_CAPABILITIES];
138
161
  if (typeof this.fetchImpl !== 'function') {
139
162
  throw new Error('A fetch implementation is required. Use Node 18+ or supply fetchImpl.');
140
163
  }
@@ -399,6 +422,17 @@ export class AIEngineClient {
399
422
  return this._request('/api/governance/claims/claim-work-item', { method: 'POST', body });
400
423
  }
401
424
 
425
+ async getExecutionEligibility(body = {}) {
426
+ return this._request('/api/governance/execution-eligibility', {
427
+ method: 'POST',
428
+ body: {
429
+ required_capabilities: GOVERNED_MUTATION_REQUIRED_CAPABILITIES,
430
+ verification_required: true,
431
+ ...body,
432
+ },
433
+ });
434
+ }
435
+
402
436
  async signoffClaim(claimId, body = {}) {
403
437
  if (!claimId) throw new Error('claimId is required.');
404
438
  return this._request(`/api/governance/claims/${encodeURIComponent(claimId)}/signoff`, {
@@ -1164,9 +1198,46 @@ export class AIEngineClient {
1164
1198
  }
1165
1199
 
1166
1200
  async updateImplementationItemStatus(implementationItemId, body) {
1167
- return this._request(`/api/governed-implementation/items/${implementationItemId}/status`, {
1201
+ await this._assertExecutionEligibility({
1202
+ ...(body || {}),
1203
+ claimed_item_id: body?.claimed_item_id || implementationItemId,
1204
+ requested_mutation_surface: body?.requested_mutation_surface || 'implementation_packet_item',
1205
+ verification_required: true,
1206
+ });
1207
+ const result = await this._request(`/api/governed-implementation/items/${implementationItemId}/status`, {
1168
1208
  method: 'PATCH', body,
1169
1209
  });
1210
+ const expectedStatus = cleanText(body?.status);
1211
+ const authoritativeItem = isPlainObject(result?.implementation_item) ? result.implementation_item : null;
1212
+ const actualStatus = cleanText(authoritativeItem?.status);
1213
+ if (expectedStatus && !authoritativeItem) {
1214
+ throw buildVerificationError(
1215
+ `updateImplementationItemStatus did not return authoritative item state for ${implementationItemId}.`,
1216
+ {
1217
+ mutation_name: 'updateImplementationItemStatus',
1218
+ mutation_attempted: true,
1219
+ authoritative_read_performed: false,
1220
+ post_condition_verified: false,
1221
+ expected_state: { status: expectedStatus },
1222
+ mutation_result: result ?? null,
1223
+ },
1224
+ );
1225
+ }
1226
+ if (expectedStatus && actualStatus !== expectedStatus) {
1227
+ throw buildVerificationError(
1228
+ `updateImplementationItemStatus returned ${actualStatus ?? 'unknown'} instead of ${expectedStatus}.`,
1229
+ {
1230
+ mutation_name: 'updateImplementationItemStatus',
1231
+ mutation_attempted: true,
1232
+ authoritative_read_performed: true,
1233
+ post_condition_verified: false,
1234
+ expected_state: { status: expectedStatus },
1235
+ verified_current_state: authoritativeItem ?? result ?? null,
1236
+ mutation_result: result ?? null,
1237
+ },
1238
+ );
1239
+ }
1240
+ return result;
1170
1241
  }
1171
1242
 
1172
1243
  async addImplementationItemEvidence(implementationItemId, body) {
@@ -1186,6 +1257,12 @@ export class AIEngineClient {
1186
1257
  }
1187
1258
 
1188
1259
  async updateAcceptanceCheckStatus(implementationItemId, acceptanceCheckId, body) {
1260
+ await this._assertExecutionEligibility({
1261
+ ...(body || {}),
1262
+ claimed_item_id: body?.claimed_item_id || implementationItemId,
1263
+ requested_mutation_surface: body?.requested_mutation_surface || 'implementation_acceptance_check',
1264
+ verification_required: true,
1265
+ });
1189
1266
  return this._request(
1190
1267
  `/api/governed-implementation/items/${implementationItemId}/acceptance-checks/${acceptanceCheckId}/status`,
1191
1268
  { method: 'PATCH', body }
@@ -1316,15 +1393,17 @@ export class AIEngineClient {
1316
1393
  }
1317
1394
 
1318
1395
  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
1396
  return this.executeVerifiedMutation({
1324
1397
  mutationName: 'updateImplementationItemStatus',
1325
1398
  evidenceLabel: `implementation-item-status:${implementationItemId}`,
1326
1399
  mutationFn: () => this.updateImplementationItemStatus(implementationItemId, { ...body, status }),
1327
- verificationFn: async () => {
1400
+ verificationFn: async (mutationResult) => {
1401
+ const workflowId = cleanText(body.workflowId)
1402
+ || cleanText(body.workflow_id)
1403
+ || cleanText(mutationResult?.workflow_id);
1404
+ if (!workflowId) {
1405
+ throw new Error('workflowId is required to verify implementation item status.');
1406
+ }
1328
1407
  const roadmap = await this.getWorkflowImplementationRoadmap(workflowId);
1329
1408
  const items = Array.isArray(roadmap?.items) ? roadmap.items : [];
1330
1409
  const matchedItem = items.find((item) => item?.implementation_item_id === implementationItemId) || null;
@@ -2002,6 +2081,8 @@ export class AIEngineClient {
2002
2081
  const resolvedHeaders = {
2003
2082
  accept: accept || 'application/json',
2004
2083
  'x-actor-id': this.actorId,
2084
+ 'x-ai-engine-client-version': this.clientVersion,
2085
+ 'x-ai-engine-client-capabilities': this.clientCapabilities.join(','),
2005
2086
  ...(isJsonBody(body) ? { 'content-type': 'application/json' } : {}),
2006
2087
  ...(token ? { authorization: `Bearer ${token}` } : {}),
2007
2088
  ...(!token && this.clientId ? { 'x-client-id': this.clientId } : {}),
@@ -2014,6 +2095,20 @@ export class AIEngineClient {
2014
2095
  return resolvedHeaders;
2015
2096
  }
2016
2097
 
2098
+ async _assertExecutionEligibility(body = {}) {
2099
+ const eligibility = await this.getExecutionEligibility(body);
2100
+ if (eligibility?.execution_eligible && eligibility?.mutation_allowed) {
2101
+ return eligibility;
2102
+ }
2103
+ throw buildEligibilityError(
2104
+ `Execution eligibility gate blocked mutation${cleanText(body?.requested_mutation_surface) ? ` on ${cleanText(body.requested_mutation_surface)}` : ''}.`,
2105
+ {
2106
+ required_gate: eligibility?.required_gate || 'execution_eligibility.required_gate',
2107
+ execution_eligibility: eligibility ?? null,
2108
+ },
2109
+ );
2110
+ }
2111
+
2017
2112
  async _request(path, { method = 'GET', query, headers, body } = {}) {
2018
2113
  const url = appendQuery(`${this.baseUrl}${path}`, query);
2019
2114
  const controller = new AbortController();