@axonflow/sdk 5.5.0 → 6.0.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.
Files changed (71) hide show
  1. package/dist/cjs/adapters/governed-tool.js +4 -4
  2. package/dist/cjs/adapters/langgraph.js +9 -9
  3. package/dist/cjs/client.d.ts +43 -8
  4. package/dist/cjs/client.d.ts.map +1 -1
  5. package/dist/cjs/client.js +304 -176
  6. package/dist/cjs/client.js.map +1 -1
  7. package/dist/cjs/errors.js +1 -1
  8. package/dist/cjs/index.d.ts +2 -2
  9. package/dist/cjs/index.d.ts.map +1 -1
  10. package/dist/cjs/index.js.map +1 -1
  11. package/dist/cjs/telemetry.d.ts.map +1 -1
  12. package/dist/cjs/telemetry.js +57 -8
  13. package/dist/cjs/telemetry.js.map +1 -1
  14. package/dist/cjs/types/connector.d.ts +33 -5
  15. package/dist/cjs/types/connector.d.ts.map +1 -1
  16. package/dist/cjs/types/connector.js.map +1 -1
  17. package/dist/cjs/types/cost-controls.d.ts +29 -0
  18. package/dist/cjs/types/cost-controls.d.ts.map +1 -1
  19. package/dist/cjs/types/execution-replay.d.ts +2 -0
  20. package/dist/cjs/types/execution-replay.d.ts.map +1 -1
  21. package/dist/cjs/types/masfeat.d.ts +2 -0
  22. package/dist/cjs/types/masfeat.d.ts.map +1 -1
  23. package/dist/cjs/types/planning.d.ts +78 -8
  24. package/dist/cjs/types/planning.d.ts.map +1 -1
  25. package/dist/cjs/types/policies.d.ts +92 -14
  26. package/dist/cjs/types/policies.d.ts.map +1 -1
  27. package/dist/cjs/types/policy.d.ts +58 -4
  28. package/dist/cjs/types/policy.d.ts.map +1 -1
  29. package/dist/cjs/types/proxy.d.ts +22 -4
  30. package/dist/cjs/types/proxy.d.ts.map +1 -1
  31. package/dist/cjs/types/workflows.d.ts +142 -17
  32. package/dist/cjs/types/workflows.d.ts.map +1 -1
  33. package/dist/cjs/types/workflows.js.map +1 -1
  34. package/dist/cjs/version.d.ts +1 -1
  35. package/dist/cjs/version.js +1 -1
  36. package/dist/esm/adapters/governed-tool.js +4 -4
  37. package/dist/esm/adapters/langgraph.js +9 -9
  38. package/dist/esm/client.d.ts +43 -8
  39. package/dist/esm/client.d.ts.map +1 -1
  40. package/dist/esm/client.js +304 -176
  41. package/dist/esm/client.js.map +1 -1
  42. package/dist/esm/errors.js +1 -1
  43. package/dist/esm/index.d.ts +2 -2
  44. package/dist/esm/index.d.ts.map +1 -1
  45. package/dist/esm/index.js.map +1 -1
  46. package/dist/esm/telemetry.d.ts.map +1 -1
  47. package/dist/esm/telemetry.js +57 -8
  48. package/dist/esm/telemetry.js.map +1 -1
  49. package/dist/esm/types/connector.d.ts +33 -5
  50. package/dist/esm/types/connector.d.ts.map +1 -1
  51. package/dist/esm/types/connector.js.map +1 -1
  52. package/dist/esm/types/cost-controls.d.ts +29 -0
  53. package/dist/esm/types/cost-controls.d.ts.map +1 -1
  54. package/dist/esm/types/execution-replay.d.ts +2 -0
  55. package/dist/esm/types/execution-replay.d.ts.map +1 -1
  56. package/dist/esm/types/masfeat.d.ts +2 -0
  57. package/dist/esm/types/masfeat.d.ts.map +1 -1
  58. package/dist/esm/types/planning.d.ts +78 -8
  59. package/dist/esm/types/planning.d.ts.map +1 -1
  60. package/dist/esm/types/policies.d.ts +92 -14
  61. package/dist/esm/types/policies.d.ts.map +1 -1
  62. package/dist/esm/types/policy.d.ts +58 -4
  63. package/dist/esm/types/policy.d.ts.map +1 -1
  64. package/dist/esm/types/proxy.d.ts +22 -4
  65. package/dist/esm/types/proxy.d.ts.map +1 -1
  66. package/dist/esm/types/workflows.d.ts +142 -17
  67. package/dist/esm/types/workflows.d.ts.map +1 -1
  68. package/dist/esm/types/workflows.js.map +1 -1
  69. package/dist/esm/version.d.ts +1 -1
  70. package/dist/esm/version.js +1 -1
  71. package/package.json +3 -2
@@ -30,12 +30,12 @@ function mapIdempotencyKeyMismatch(err) {
30
30
  * Returns -1 if a < b, 0 if equal, 1 if a > b.
31
31
  */
32
32
  function compareSemver(a, b) {
33
- const parseVersion = (v) => v.split('.').map(p => parseInt(p.split('-')[0], 10) || 0);
33
+ const parseVersion = (v) => v.split('.').map(p => parseInt(p.split('-')[0], 10) ?? 0);
34
34
  const aParts = parseVersion(a);
35
35
  const bParts = parseVersion(b);
36
36
  const len = Math.max(aParts.length, bParts.length);
37
37
  for (let i = 0; i < len; i++) {
38
- const diff = (aParts[i] || 0) - (bParts[i] || 0);
38
+ const diff = (aParts[i] ?? 0) - (bParts[i] ?? 0);
39
39
  if (diff !== 0)
40
40
  return diff < 0 ? -1 : 1;
41
41
  }
@@ -64,7 +64,7 @@ export class AxonFlow {
64
64
  };
65
65
  }
66
66
  // Set defaults first to determine endpoint
67
- const endpoint = config.endpoint || 'https://staging-eu.getaxonflow.com';
67
+ const endpoint = config.endpoint ?? 'https://staging-eu.getaxonflow.com';
68
68
  // Credentials check: OAuth2-style (clientId/clientSecret)
69
69
  const hasCredentials = !!(config.clientId && config.clientSecret);
70
70
  // Set configuration
@@ -72,19 +72,19 @@ export class AxonFlow {
72
72
  clientId: config.clientId,
73
73
  clientSecret: config.clientSecret,
74
74
  endpoint,
75
- mode: config.mode || 'production',
76
- tenant: config.tenant || '',
77
- debug: config.debug || false,
78
- timeout: config.timeout || 30000,
79
- mapTimeout: config.mapTimeout || 120000, // 2 minutes for MAP operations
75
+ mode: config.mode ?? 'production',
76
+ tenant: config.tenant ?? '',
77
+ debug: config.debug ?? false,
78
+ timeout: config.timeout ?? 30000,
79
+ mapTimeout: config.mapTimeout ?? 120000, // 2 minutes for MAP operations
80
80
  retry: {
81
81
  enabled: config.retry?.enabled !== false,
82
- maxAttempts: config.retry?.maxAttempts || 3,
83
- delay: config.retry?.delay || 1000,
82
+ maxAttempts: config.retry?.maxAttempts ?? 3,
83
+ delay: config.retry?.delay ?? 1000,
84
84
  },
85
85
  cache: {
86
86
  enabled: config.cache?.enabled !== false,
87
- ttl: config.cache?.ttl || 60000,
87
+ ttl: config.cache?.ttl ?? 60000,
88
88
  },
89
89
  };
90
90
  // Interceptors removed in v3.0.0 (deprecated wrapOpenAIClient/wrapAnthropicClient)
@@ -121,7 +121,7 @@ export class AxonFlow {
121
121
  // clientSecret defaults to empty string for community/no-secret mode.
122
122
  const effectiveClientId = this.getEffectiveClientId();
123
123
  if (effectiveClientId) {
124
- const credentials = Buffer.from(`${effectiveClientId}:${this.config.clientSecret || ''}`).toString('base64');
124
+ const credentials = Buffer.from(`${effectiveClientId}:${this.config.clientSecret ?? ''}`).toString('base64');
125
125
  headers['Authorization'] = `Basic ${credentials}`;
126
126
  }
127
127
  // Include SDK version for version discovery and compatibility checks
@@ -138,6 +138,9 @@ export class AxonFlow {
138
138
  * @returns The clientId to use in requests
139
139
  */
140
140
  getEffectiveClientId() {
141
+ // Intentional || (not ??): an empty-string clientId/tenant is treated as
142
+ // "missing" by the SDK contract — see tests/smart-defaults.test.ts.
143
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
141
144
  return this.config.clientId || this.config.tenant || 'community';
142
145
  }
143
146
  /**
@@ -209,7 +212,7 @@ export class AxonFlow {
209
212
  // If denied, throw error
210
213
  if (!governanceResponse.allowed) {
211
214
  const violation = governanceResponse.violations?.[0];
212
- throw new Error(`Request blocked by AxonFlow: ${violation?.description || 'Policy violation'}`);
215
+ throw new Error(`Request blocked by AxonFlow: ${violation?.description ?? 'Policy violation'}`);
213
216
  }
214
217
  // Execute the AI call (possibly with modifications)
215
218
  const modifiedCall = governanceResponse.modifiedRequest
@@ -259,6 +262,9 @@ export class AxonFlow {
259
262
  const agentRequest = {
260
263
  query: request.aiRequest.prompt,
261
264
  user_token: '',
265
+ // Intentional || (not ??): an empty-string clientId/tenant is treated as
266
+ // "missing" by the SDK contract — see tests/smart-defaults.test.ts.
267
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
262
268
  client_id: this.config.clientId || this.config.tenant,
263
269
  request_type: 'llm_chat',
264
270
  context: {
@@ -286,7 +292,7 @@ export class AxonFlow {
286
292
  const agentResponse = await response.json();
287
293
  // Transform Agent API response to SDK format
288
294
  // Extract policy name from policy_info if available
289
- const policyName = agentResponse.policy_info?.policies_evaluated?.[0] || 'agent-policy';
295
+ const policyName = agentResponse.policy_info?.policies_evaluated?.[0] ?? 'agent-policy';
290
296
  return {
291
297
  requestId: request.requestId,
292
298
  allowed: !agentResponse.blocked,
@@ -295,17 +301,17 @@ export class AxonFlow {
295
301
  {
296
302
  type: 'security',
297
303
  severity: 'high',
298
- description: agentResponse.block_reason || 'Request blocked by policy',
304
+ description: agentResponse.block_reason ?? 'Request blocked by policy',
299
305
  policy: policyName,
300
306
  action: 'blocked',
301
307
  },
302
308
  ]
303
309
  : [],
304
310
  modifiedRequest: agentResponse.data,
305
- policies: agentResponse.policy_info?.policies_evaluated || [],
311
+ policies: agentResponse.policy_info?.policies_evaluated ?? [],
306
312
  audit: {
307
313
  timestamp: Date.now(),
308
- duration: parseInt(agentResponse.policy_info?.processing_time?.replace('ms', '') || '0'),
314
+ duration: parseInt(agentResponse.policy_info?.processing_time?.replace('ms', '') ?? '0'),
309
315
  tenant: this.config.tenant,
310
316
  },
311
317
  };
@@ -319,7 +325,7 @@ export class AxonFlow {
319
325
  if (this.config.debug) {
320
326
  debugLog('Request processed', {
321
327
  allowed: response.allowed,
322
- violations: response.violations?.length || 0,
328
+ violations: response.violations?.length ?? 0,
323
329
  duration: response.audit.duration,
324
330
  });
325
331
  }
@@ -328,9 +334,10 @@ export class AxonFlow {
328
334
  * Check if an error is from AxonFlow (vs the AI provider)
329
335
  */
330
336
  isAxonFlowError(error) {
331
- return (error?.message?.includes('AxonFlow') ||
332
- error?.message?.includes('governance') ||
333
- error?.message?.includes('fetch'));
337
+ const msg = error?.message;
338
+ if (typeof msg !== 'string')
339
+ return false;
340
+ return msg.includes('AxonFlow') || msg.includes('governance') || msg.includes('fetch');
334
341
  }
335
342
  /**
336
343
  * Create a sandbox client for testing
@@ -526,13 +533,16 @@ export class AxonFlow {
526
533
  */
527
534
  async proxyLLMCall(options) {
528
535
  // Default to "anonymous" if userToken is empty/undefined (community mode)
529
- const effectiveUserToken = options.userToken || 'anonymous';
536
+ const effectiveUserToken = options.userToken ?? 'anonymous';
530
537
  const agentRequest = {
531
538
  query: options.query,
532
539
  user_token: effectiveUserToken,
540
+ // Intentional || (not ??): an empty-string clientId/tenant is treated as
541
+ // "missing" by the SDK contract — see tests/smart-defaults.test.ts.
542
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
533
543
  client_id: this.config.clientId || this.config.tenant,
534
544
  request_type: options.requestType,
535
- context: options.context || {},
545
+ context: options.context ?? {},
536
546
  };
537
547
  if (options.media && options.media.length > 0) {
538
548
  agentRequest.media = options.media.map(m => ({
@@ -585,7 +595,7 @@ export class AxonFlow {
585
595
  try {
586
596
  const errorJson = JSON.parse(errorText);
587
597
  if (errorJson.blocked || errorJson.block_reason) {
588
- throw new PolicyViolationError(errorJson.block_reason || 'Request blocked by policy', errorJson.policy_info?.policies_evaluated);
598
+ throw new PolicyViolationError(errorJson.block_reason ?? 'Request blocked by policy', errorJson.policy_info?.policies_evaluated);
589
599
  }
590
600
  }
591
601
  catch (e) {
@@ -599,13 +609,11 @@ export class AxonFlow {
599
609
  }
600
610
  }
601
611
  // Parse response if not already parsed (from 402 handling)
602
- if (!data) {
603
- data = await response.json();
604
- }
612
+ data ?? (data = await response.json());
605
613
  // Check for policy violation in successful response (some blocked responses return 200)
606
614
  // Note: Don't throw for budget blocks (402 responses) - return with budgetInfo instead
607
615
  if (data.blocked && !data.budget_info) {
608
- throw new PolicyViolationError(data.block_reason || 'Request blocked by policy', data.policy_info?.policies_evaluated);
616
+ throw new PolicyViolationError(data.block_reason ?? 'Request blocked by policy', data.policy_info?.policies_evaluated);
609
617
  }
610
618
  // Transform snake_case response to camelCase
611
619
  const result = {
@@ -614,18 +622,18 @@ export class AxonFlow {
614
622
  result: data.result,
615
623
  planId: data.plan_id,
616
624
  requestId: data.request_id,
617
- metadata: data.metadata || {},
625
+ metadata: data.metadata ?? {},
618
626
  error: data.error,
619
- blocked: data.blocked || false,
627
+ blocked: data.blocked ?? false,
620
628
  blockReason: data.block_reason,
621
629
  };
622
630
  // Parse policy info if present
623
631
  if (data.policy_info) {
624
632
  result.policyInfo = {
625
- policiesEvaluated: data.policy_info.policies_evaluated || [],
626
- staticChecks: data.policy_info.static_checks || [],
627
- processingTime: data.policy_info.processing_time || '',
628
- tenantId: data.policy_info.tenant_id || '',
633
+ policiesEvaluated: data.policy_info.policies_evaluated ?? [],
634
+ staticChecks: data.policy_info.static_checks ?? [],
635
+ processingTime: data.policy_info.processing_time ?? '',
636
+ tenantId: data.policy_info.tenant_id ?? '',
629
637
  codeArtifact: data.policy_info.code_artifact,
630
638
  };
631
639
  }
@@ -634,10 +642,10 @@ export class AxonFlow {
634
642
  result.budgetInfo = {
635
643
  budgetId: data.budget_info.budget_id,
636
644
  budgetName: data.budget_info.budget_name,
637
- usedUsd: data.budget_info.used_usd || 0,
638
- limitUsd: data.budget_info.limit_usd || 0,
639
- percentage: data.budget_info.percentage || 0,
640
- exceeded: data.budget_info.exceeded || false,
645
+ usedUsd: data.budget_info.used_usd ?? 0,
646
+ limitUsd: data.budget_info.limit_usd ?? 0,
647
+ percentage: data.budget_info.percentage ?? 0,
648
+ exceeded: data.budget_info.exceeded ?? false,
641
649
  action: data.budget_info.action,
642
650
  };
643
651
  }
@@ -681,7 +689,7 @@ export class AxonFlow {
681
689
  async listConnectors() {
682
690
  const response = await this.orchestratorRequest('GET', '/api/v1/connectors');
683
691
  // Handle wrapped response
684
- const connectors = Array.isArray(response) ? response : response.connectors || [];
692
+ const connectors = Array.isArray(response) ? response : (response.connectors ?? []);
685
693
  if (this.config.debug) {
686
694
  debugLog('Listed connectors', { count: connectors.length });
687
695
  }
@@ -734,11 +742,14 @@ export class AxonFlow {
734
742
  const agentRequest = {
735
743
  query,
736
744
  user_token: '',
745
+ // Intentional || (not ??): an empty-string clientId/tenant is treated as
746
+ // "missing" by the SDK contract — see tests/smart-defaults.test.ts.
747
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
737
748
  client_id: this.config.clientId || this.config.tenant,
738
749
  request_type: 'mcp-query',
739
750
  context: {
740
751
  connector: connectorName,
741
- params: params || {},
752
+ params: params ?? {},
742
753
  },
743
754
  };
744
755
  const url = `${this.config.endpoint}/api/request`;
@@ -807,7 +818,7 @@ export class AxonFlow {
807
818
  const body = {
808
819
  connector: options.connector,
809
820
  statement: options.statement,
810
- options: options.options || {},
821
+ options: options.options ?? {},
811
822
  };
812
823
  if (this.config.debug) {
813
824
  debugLog('MCP Query', {
@@ -824,7 +835,7 @@ export class AxonFlow {
824
835
  const responseData = await response.json();
825
836
  // Handle policy blocks (403 responses)
826
837
  if (!response.ok) {
827
- throw new ConnectorError(responseData.error || `MCP query failed: ${response.status} ${response.statusText}`, options.connector, 'mcpQuery');
838
+ throw new ConnectorError(responseData.error ?? `MCP query failed: ${response.status} ${response.statusText}`, options.connector, 'mcpQuery');
828
839
  }
829
840
  if (this.config.debug) {
830
841
  debugLog('MCP Query result', {
@@ -890,7 +901,7 @@ export class AxonFlow {
890
901
  if (options.parameters) {
891
902
  body.parameters = options.parameters;
892
903
  }
893
- body.operation = options.operation || 'execute';
904
+ body.operation = options.operation ?? 'execute';
894
905
  if (this.config.debug) {
895
906
  debugLog('MCP Check Input', {
896
907
  connectorType: options.connectorType,
@@ -906,7 +917,7 @@ export class AxonFlow {
906
917
  const responseData = await response.json();
907
918
  // 403 means policy blocked — this is a valid check response, not an error
908
919
  if (!response.ok && response.status !== 403) {
909
- throw new ConnectorError(responseData.error || 'MCP check-input failed', options.connectorType, 'check-input');
920
+ throw new ConnectorError(responseData.error ?? 'MCP check-input failed', options.connectorType, 'check-input');
910
921
  }
911
922
  if (this.config.debug) {
912
923
  debugLog('MCP Check Input result', {
@@ -978,7 +989,7 @@ export class AxonFlow {
978
989
  const responseData = await response.json();
979
990
  // 403 means policy blocked — this is a valid check response, not an error
980
991
  if (!response.ok && response.status !== 403) {
981
- throw new ConnectorError(responseData.error || 'MCP check-output failed', options.connectorType, 'check-output');
992
+ throw new ConnectorError(responseData.error ?? 'MCP check-output failed', options.connectorType, 'check-output');
982
993
  }
983
994
  if (this.config.debug) {
984
995
  debugLog('MCP Check Output result', {
@@ -1024,7 +1035,13 @@ export class AxonFlow {
1024
1035
  }
1025
1036
  const agentRequest = {
1026
1037
  query,
1038
+ // Intentional || (not ??): an empty-string clientId/tenant is treated as
1039
+ // "missing" by the SDK contract — see tests/smart-defaults.test.ts.
1040
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
1027
1041
  user_token: userToken || this.config.clientId || this.config.tenant,
1042
+ // Intentional || (not ??): an empty-string clientId/tenant is treated as
1043
+ // "missing" by the SDK contract — see tests/smart-defaults.test.ts.
1044
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
1028
1045
  client_id: this.config.clientId || this.config.tenant,
1029
1046
  request_type: 'multi-agent-plan',
1030
1047
  context,
@@ -1050,18 +1067,24 @@ export class AxonFlow {
1050
1067
  throw new PlanExecutionError(`Plan generation failed: ${agentResponse.error}`, undefined, 'generation');
1051
1068
  }
1052
1069
  // plan_id can be at top level or inside data
1053
- const planId = agentResponse.plan_id || agentResponse.data?.plan_id;
1070
+ const planId = agentResponse.plan_id ?? agentResponse.data?.plan_id;
1054
1071
  if (this.config.debug) {
1055
1072
  debugLog('Plan generated', { planId });
1056
1073
  }
1057
1074
  return {
1058
1075
  planId,
1059
- status: agentResponse.data?.status || 'pending',
1060
- steps: agentResponse.data?.steps || [],
1061
- domain: agentResponse.data?.domain || domain || 'generic',
1062
- complexity: agentResponse.data?.complexity || 0,
1063
- parallel: agentResponse.data?.parallel || false,
1064
- metadata: agentResponse.metadata || {},
1076
+ status: agentResponse.data?.status ?? 'pending',
1077
+ steps: agentResponse.data?.steps ?? [],
1078
+ domain: agentResponse.data?.domain ?? domain ?? 'generic',
1079
+ complexity: agentResponse.data?.complexity ?? 0,
1080
+ parallel: agentResponse.data?.parallel ?? false,
1081
+ metadata: agentResponse.metadata ?? {},
1082
+ success: agentResponse.success,
1083
+ version: agentResponse.version ?? agentResponse.data?.version,
1084
+ result: agentResponse.result ?? agentResponse.data?.result,
1085
+ error: agentResponse.error,
1086
+ workflow_execution_id: agentResponse.workflow_execution_id ?? agentResponse.data?.workflow_execution_id,
1087
+ policy_info: agentResponse.policy_info ?? agentResponse.data?.policy_info,
1065
1088
  };
1066
1089
  }
1067
1090
  /**
@@ -1072,7 +1095,13 @@ export class AxonFlow {
1072
1095
  async executePlan(planId, userToken) {
1073
1096
  const agentRequest = {
1074
1097
  query: '',
1098
+ // Intentional || (not ??): an empty-string clientId/tenant is treated as
1099
+ // "missing" by the SDK contract — see tests/smart-defaults.test.ts.
1100
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
1075
1101
  user_token: userToken || this.config.clientId || this.config.tenant,
1102
+ // Intentional || (not ??): an empty-string clientId/tenant is treated as
1103
+ // "missing" by the SDK contract — see tests/smart-defaults.test.ts.
1104
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
1076
1105
  client_id: this.config.clientId || this.config.tenant,
1077
1106
  request_type: 'execute-plan',
1078
1107
  context: { plan_id: planId },
@@ -1104,7 +1133,7 @@ export class AxonFlow {
1104
1133
  if (data.error && !error)
1105
1134
  error = data.error;
1106
1135
  // Throw on nested failure (e.g., cancelled plan execution)
1107
- throw new PlanExecutionError(error || 'Plan execution failed', planId, 'execution');
1136
+ throw new PlanExecutionError(error ?? 'Plan execution failed', planId, 'execution');
1108
1137
  }
1109
1138
  if (!result && data?.result)
1110
1139
  result = data.result;
@@ -1186,8 +1215,13 @@ export class AxonFlow {
1186
1215
  debugLog('Plan cancelled', { planId, status: data.status });
1187
1216
  }
1188
1217
  return {
1189
- planId: data.plan_id || planId,
1218
+ planId: data.plan_id ?? planId,
1190
1219
  status: data.status,
1220
+ // `success` is the canonical wire boolean. The deprecated
1221
+ // `message` slot is also kept populated for back-compat
1222
+ // readers; on a current server it will always be undefined
1223
+ // because the wire never emits it.
1224
+ success: data.success,
1191
1225
  message: data.message,
1192
1226
  };
1193
1227
  }
@@ -1212,6 +1246,9 @@ export class AxonFlow {
1212
1246
  if (request.domain) {
1213
1247
  body.domain = request.domain;
1214
1248
  }
1249
+ if (request.metadata !== undefined) {
1250
+ body.metadata = request.metadata;
1251
+ }
1215
1252
  const response = await fetch(url, {
1216
1253
  method: 'PUT',
1217
1254
  headers,
@@ -1231,7 +1268,7 @@ export class AxonFlow {
1231
1268
  debugLog('Plan updated', { planId, version: data.version });
1232
1269
  }
1233
1270
  return {
1234
- planId: data.plan_id || planId,
1271
+ planId: data.plan_id ?? planId,
1235
1272
  version: data.version,
1236
1273
  status: data.status,
1237
1274
  success: data.success ?? true,
@@ -1256,7 +1293,7 @@ export class AxonFlow {
1256
1293
  throw new PlanExecutionError(`Get plan versions failed: ${response.status} ${response.statusText} - ${errorText}`, planId, 'versions');
1257
1294
  }
1258
1295
  const data = await response.json();
1259
- const versions = (data.versions || []).map((v) => ({
1296
+ const versions = (data.versions ?? []).map((v) => ({
1260
1297
  version: v.version,
1261
1298
  changedAt: v.changed_at,
1262
1299
  changedBy: v.changed_by,
@@ -1264,7 +1301,7 @@ export class AxonFlow {
1264
1301
  changeSummary: v.change_summary,
1265
1302
  }));
1266
1303
  return {
1267
- planId: data.plan_id || planId,
1304
+ planId: data.plan_id ?? planId,
1268
1305
  versions,
1269
1306
  };
1270
1307
  }
@@ -1294,9 +1331,13 @@ export class AxonFlow {
1294
1331
  debugLog('Plan resumed', { planId, approved: data.approved });
1295
1332
  }
1296
1333
  return {
1297
- planId: data.plan_id || planId,
1334
+ planId: data.plan_id ?? planId,
1298
1335
  status: data.status,
1336
+ // `result` is the canonical wire-aggregated outcome on resume.
1337
+ result: data.result,
1299
1338
  approved: data.approved,
1339
+ // `message` kept populated for the back-compat alias; legacy
1340
+ // path read this slot historically.
1300
1341
  message: data.message,
1301
1342
  };
1302
1343
  }
@@ -1359,8 +1400,8 @@ export class AxonFlow {
1359
1400
  user_token: options.userToken,
1360
1401
  client_id: clientId,
1361
1402
  query: options.query,
1362
- data_sources: options.dataSources || [],
1363
- context: options.context || {},
1403
+ data_sources: options.dataSources ?? [],
1404
+ context: options.context ?? {},
1364
1405
  };
1365
1406
  const headers = {
1366
1407
  'Content-Type': 'application/json',
@@ -1391,9 +1432,9 @@ export class AxonFlow {
1391
1432
  const result = {
1392
1433
  contextId: data.context_id,
1393
1434
  approved: data.approved,
1394
- requiresRedaction: data.requires_redaction || false,
1395
- approvedData: data.approved_data || {},
1396
- policies: data.policies || [],
1435
+ requiresRedaction: data.requires_redaction ?? false,
1436
+ approvedData: data.approved_data ?? {},
1437
+ policies: data.policies ?? [],
1397
1438
  expiresAt,
1398
1439
  blockReason: data.block_reason,
1399
1440
  };
@@ -1452,7 +1493,7 @@ export class AxonFlow {
1452
1493
  total_tokens: options.tokenUsage.totalTokens,
1453
1494
  },
1454
1495
  latency_ms: options.latencyMs,
1455
- metadata: options.metadata || {},
1496
+ metadata: options.metadata ?? {},
1456
1497
  };
1457
1498
  const headers = {
1458
1499
  'Content-Type': 'application/json',
@@ -1571,7 +1612,7 @@ export class AxonFlow {
1571
1612
  const response = await this.orchestratorRequest('GET', '/api/v1/circuit-breaker/status');
1572
1613
  const data = response.data;
1573
1614
  return {
1574
- activeCircuits: (data.active_circuits || []).map(c => ({
1615
+ activeCircuits: (data.active_circuits ?? []).map(c => ({
1575
1616
  id: c.id,
1576
1617
  scope: c.scope,
1577
1618
  scopeId: c.scope_id,
@@ -1614,7 +1655,7 @@ export class AxonFlow {
1614
1655
  const response = await this.orchestratorRequest('GET', path);
1615
1656
  const data = response.data;
1616
1657
  return {
1617
- history: (data.history || []).map(h => ({
1658
+ history: (data.history ?? []).map(h => ({
1618
1659
  id: h.id,
1619
1660
  orgId: h.org_id,
1620
1661
  scope: h.scope,
@@ -1870,10 +1911,10 @@ export class AxonFlow {
1870
1911
  * forward compatibility per ADR-043.
1871
1912
  */
1872
1913
  parseDecisionExplanation(raw) {
1873
- const r = raw || {};
1874
- const rawMatches = r.policy_matches || [];
1914
+ const r = raw ?? {};
1915
+ const rawMatches = r.policy_matches ?? [];
1875
1916
  const policyMatches = rawMatches.map(m => ({
1876
- policyId: m.policy_id || '',
1917
+ policyId: m.policy_id ?? '',
1877
1918
  policyName: m.policy_name,
1878
1919
  action: m.action,
1879
1920
  riskLevel: m.risk_level,
@@ -1882,18 +1923,18 @@ export class AxonFlow {
1882
1923
  }));
1883
1924
  const rawRules = r.matched_rules;
1884
1925
  const matchedRules = rawRules?.map(m => ({
1885
- policyId: m.policy_id || '',
1926
+ policyId: m.policy_id ?? '',
1886
1927
  ruleId: m.rule_id,
1887
1928
  ruleText: m.rule_text,
1888
1929
  matchedOn: m.matched_on,
1889
1930
  }));
1890
1931
  return {
1891
- decisionId: r.decision_id || '',
1932
+ decisionId: r.decision_id ?? '',
1892
1933
  timestamp: r.timestamp ? new Date(r.timestamp) : new Date(),
1893
1934
  policyMatches,
1894
1935
  matchedRules,
1895
- decision: r.decision || '',
1896
- reason: r.reason || '',
1936
+ decision: r.decision ?? '',
1937
+ reason: r.reason ?? '',
1897
1938
  riskLevel: r.risk_level,
1898
1939
  overrideAvailable: r.override_available ?? false,
1899
1940
  overrideExistingId: r.override_existing_id,
@@ -1940,7 +1981,7 @@ export class AxonFlow {
1940
1981
  };
1941
1982
  }
1942
1983
  const data = response;
1943
- const entries = (data.entries || []).map(e => this.parseAuditLogEntry(e));
1984
+ const entries = (data.entries ?? []).map(e => this.parseAuditLogEntry(e));
1944
1985
  return {
1945
1986
  entries,
1946
1987
  total: data.total ?? entries.length,
@@ -1994,7 +2035,7 @@ export class AxonFlow {
1994
2035
  };
1995
2036
  }
1996
2037
  const data = response;
1997
- const entries = (data.entries || []).map(e => this.parseAuditLogEntry(e));
2038
+ const entries = (data.entries ?? []).map(e => this.parseAuditLogEntry(e));
1998
2039
  return {
1999
2040
  entries,
2000
2041
  total: data.total ?? entries.length,
@@ -2114,7 +2155,7 @@ export class AxonFlow {
2114
2155
  }
2115
2156
  // Backend returns { policies: [], pagination: {} }, extract the array
2116
2157
  const response = await this.policyRequest('GET', path);
2117
- return response.policies || [];
2158
+ return response.policies ?? [];
2118
2159
  }
2119
2160
  /**
2120
2161
  * Get a specific static policy by ID.
@@ -2165,12 +2206,19 @@ export class AxonFlow {
2165
2206
  severity: policy.severity,
2166
2207
  enabled: policy.enabled,
2167
2208
  action: policy.action,
2168
- tier: policy.tier || 'tenant',
2209
+ tier: policy.tier ?? 'tenant',
2169
2210
  };
2170
2211
  // Add organization_id for organization tier policies
2171
2212
  if (policy.organizationId) {
2172
2213
  requestBody.organization_id = policy.organizationId;
2173
2214
  }
2215
+ // Wire-canonical fields surfaced in the v6 alignment sweep.
2216
+ if (policy.priority !== undefined) {
2217
+ requestBody.priority = policy.priority;
2218
+ }
2219
+ if (policy.tags !== undefined) {
2220
+ requestBody.tags = policy.tags;
2221
+ }
2174
2222
  return this.policyRequest('POST', '/api/v1/static-policies', requestBody);
2175
2223
  }
2176
2224
  /**
@@ -2259,7 +2307,7 @@ export class AxonFlow {
2259
2307
  }
2260
2308
  // Backend returns { static: [], dynamic: [], ... }, extract the static array
2261
2309
  const response = await this.policyRequest('GET', path);
2262
- return response.static || [];
2310
+ return response.static ?? [];
2263
2311
  }
2264
2312
  /**
2265
2313
  * Test a regex pattern against sample inputs.
@@ -2304,12 +2352,21 @@ export class AxonFlow {
2304
2352
  debugLog('Getting static policy versions', { id });
2305
2353
  }
2306
2354
  const response = await this.policyRequest('GET', `/api/v1/static-policies/${id}/versions`);
2307
- // Transform snake_case API response to camelCase
2355
+ // Transform snake_case API response to camelCase, populating
2356
+ // both the wire-canonical fields (`id`, `policy_id`,
2357
+ // `change_summary`, `snapshot`) and the legacy aliases (kept for
2358
+ // back-compat; will read undefined on a current server).
2308
2359
  return response.versions.map(v => ({
2360
+ id: v.id,
2361
+ policy_id: v.policy_id,
2309
2362
  version: v.version,
2310
2363
  changedBy: v.changed_by,
2311
2364
  changedAt: v.changed_at,
2312
2365
  changeType: v.change_type,
2366
+ change_summary: v.change_summary,
2367
+ snapshot: v.snapshot,
2368
+ // @deprecated aliases — only populated if a legacy server
2369
+ // still emits them; current servers don't.
2313
2370
  changeDescription: v.change_description,
2314
2371
  previousValues: v.previous_values,
2315
2372
  newValues: v.new_values,
@@ -2378,7 +2435,7 @@ export class AxonFlow {
2378
2435
  debugLog('Listing policy overrides');
2379
2436
  }
2380
2437
  const response = await this.policyRequest('GET', '/api/v1/static-policies/overrides');
2381
- return response.overrides || [];
2438
+ return response.overrides ?? [];
2382
2439
  }
2383
2440
  // ============================================================================
2384
2441
  // Dynamic Policy Methods
@@ -2425,7 +2482,7 @@ export class AxonFlow {
2425
2482
  // API returns {"policies": [...]} wrapper via Agent proxy
2426
2483
  const response = await this.orchestratorRequest('GET', path);
2427
2484
  // Handle both wrapped and unwrapped responses for compatibility
2428
- return Array.isArray(response) ? response : response.policies || [];
2485
+ return Array.isArray(response) ? response : (response.policies ?? []);
2429
2486
  }
2430
2487
  /**
2431
2488
  * Get a specific dynamic policy by ID.
@@ -2480,7 +2537,7 @@ export class AxonFlow {
2480
2537
  requestBody.priority = policy.priority;
2481
2538
  if (policy.enabled !== undefined)
2482
2539
  requestBody.enabled = policy.enabled;
2483
- requestBody.tier = policy.tier || 'tenant';
2540
+ requestBody.tier = policy.tier ?? 'tenant';
2484
2541
  if (policy.organizationId) {
2485
2542
  requestBody.organization_id = policy.organizationId;
2486
2543
  }
@@ -2574,7 +2631,7 @@ export class AxonFlow {
2574
2631
  // API returns {"policies": [...]} wrapper via Agent proxy
2575
2632
  const response = await this.orchestratorRequest('GET', path);
2576
2633
  // Handle both wrapped and unwrapped responses for compatibility
2577
- return Array.isArray(response) ? response : response.policies || [];
2634
+ return Array.isArray(response) ? response : (response.policies ?? []);
2578
2635
  }
2579
2636
  // ============================================================================
2580
2637
  // Portal Authentication Methods (Enterprise)
@@ -2903,7 +2960,7 @@ export class AxonFlow {
2903
2960
  const response = await this.portalRequest('GET', path);
2904
2961
  // Transform snake_case response to camelCase
2905
2962
  return {
2906
- prs: (response.prs || []).map(pr => ({
2963
+ prs: (response.prs ?? []).map(pr => ({
2907
2964
  id: pr.id,
2908
2965
  prNumber: pr.pr_number,
2909
2966
  prUrl: pr.pr_url,
@@ -3109,7 +3166,7 @@ export class AxonFlow {
3109
3166
  }
3110
3167
  const response = await this.portalRequest('GET', path);
3111
3168
  return {
3112
- records: (response.records || []).map(r => ({
3169
+ records: (response.records ?? []).map(r => ({
3113
3170
  id: r.id,
3114
3171
  prNumber: r.pr_number,
3115
3172
  prUrl: r.pr_url,
@@ -3285,7 +3342,7 @@ export class AxonFlow {
3285
3342
  }
3286
3343
  const response = await this.orchestratorRequest('GET', path);
3287
3344
  return {
3288
- executions: (response.executions || []).map(e => ({
3345
+ executions: (response.executions ?? []).map(e => ({
3289
3346
  requestId: e.request_id,
3290
3347
  workflowName: e.workflow_name,
3291
3348
  status: e.status,
@@ -3369,6 +3426,7 @@ export class AxonFlow {
3369
3426
  approvalRequired: s.approval_required,
3370
3427
  approvedBy: s.approved_by,
3371
3428
  approvedAt: s.approved_at,
3429
+ retryCount: s.retry_count,
3372
3430
  })),
3373
3431
  };
3374
3432
  }
@@ -3412,6 +3470,7 @@ export class AxonFlow {
3412
3470
  approvalRequired: s.approval_required,
3413
3471
  approvedBy: s.approved_by,
3414
3472
  approvedAt: s.approved_at,
3473
+ retryCount: s.retry_count,
3415
3474
  }));
3416
3475
  }
3417
3476
  /**
@@ -3550,8 +3609,8 @@ export class AxonFlow {
3550
3609
  const path = `/api/v1/budgets${queryString ? `?${queryString}` : ''}`;
3551
3610
  const response = await this.orchestratorRequest('GET', path);
3552
3611
  return {
3553
- budgets: (response.budgets || []).map(b => this.mapBudgetResponse(b)),
3554
- total: response.total || 0,
3612
+ budgets: (response.budgets ?? []).map(b => this.mapBudgetResponse(b)),
3613
+ total: response.total ?? 0,
3555
3614
  };
3556
3615
  }
3557
3616
  /**
@@ -3595,13 +3654,13 @@ export class AxonFlow {
3595
3654
  const response = await this.orchestratorRequest('GET', `/api/v1/budgets/${budgetId}/status`);
3596
3655
  return {
3597
3656
  budget: this.mapBudgetResponse(response.budget),
3598
- usedUsd: response.used_usd || 0,
3599
- remainingUsd: response.remaining_usd || 0,
3600
- percentage: response.percentage || 0,
3601
- isExceeded: response.is_exceeded || false,
3602
- isBlocked: response.is_blocked || false,
3603
- periodStart: response.period_start || '',
3604
- periodEnd: response.period_end || '',
3657
+ usedUsd: response.used_usd ?? 0,
3658
+ remainingUsd: response.remaining_usd ?? 0,
3659
+ percentage: response.percentage ?? 0,
3660
+ isExceeded: response.is_exceeded ?? false,
3661
+ isBlocked: response.is_blocked ?? false,
3662
+ periodStart: response.period_start ?? '',
3663
+ periodEnd: response.period_end ?? '',
3605
3664
  };
3606
3665
  }
3607
3666
  /**
@@ -3612,19 +3671,20 @@ export class AxonFlow {
3612
3671
  */
3613
3672
  async getBudgetAlerts(budgetId) {
3614
3673
  const response = await this.orchestratorRequest('GET', `/api/v1/budgets/${budgetId}/alerts`);
3615
- const alerts = (response.alerts || []).map((a) => ({
3616
- id: a.id || '',
3617
- budgetId: a.budget_id || '',
3618
- alertType: a.alert_type || '',
3619
- threshold: a.threshold || 0,
3620
- percentageReached: a.percentage_reached || 0,
3621
- amountUsd: a.amount_usd || 0,
3622
- message: a.message || '',
3623
- createdAt: a.created_at || '',
3674
+ const alerts = (response.alerts ?? []).map((a) => ({
3675
+ id: a.id ?? '',
3676
+ budgetId: a.budget_id ?? '',
3677
+ alertType: a.alert_type ?? '',
3678
+ threshold: a.threshold ?? 0,
3679
+ percentageReached: a.percentage_reached ?? 0,
3680
+ amountUsd: a.amount_usd ?? 0,
3681
+ message: a.message ?? '',
3682
+ createdAt: a.created_at ?? '',
3683
+ acknowledged: a.acknowledged,
3624
3684
  }));
3625
3685
  return {
3626
3686
  alerts,
3627
- count: response.count || 0,
3687
+ count: response.count ?? 0,
3628
3688
  };
3629
3689
  }
3630
3690
  /**
@@ -3647,11 +3707,11 @@ export class AxonFlow {
3647
3707
  body.user_id = request.userId;
3648
3708
  const response = await this.orchestratorRequest('POST', '/api/v1/budgets/check', body);
3649
3709
  return {
3650
- allowed: response.allowed || false,
3710
+ allowed: response.allowed ?? false,
3651
3711
  action: response.action,
3652
3712
  message: response.message,
3653
3713
  budgets: response.budgets
3654
- ? (response.budgets || []).map(b => this.mapBudgetResponse(b))
3714
+ ? (response.budgets ?? []).map(b => this.mapBudgetResponse(b))
3655
3715
  : undefined,
3656
3716
  };
3657
3717
  }
@@ -3668,14 +3728,14 @@ export class AxonFlow {
3668
3728
  const path = period ? `/api/v1/usage?period=${period}` : '/api/v1/usage';
3669
3729
  const response = await this.orchestratorRequest('GET', path);
3670
3730
  return {
3671
- totalCostUsd: response.total_cost_usd || 0,
3672
- totalRequests: response.total_requests || 0,
3673
- totalTokensIn: response.total_tokens_in || 0,
3674
- totalTokensOut: response.total_tokens_out || 0,
3675
- averageCostPerRequest: response.average_cost_per_request || 0,
3676
- period: response.period || '',
3677
- periodStart: response.period_start || '',
3678
- periodEnd: response.period_end || '',
3731
+ totalCostUsd: response.total_cost_usd ?? 0,
3732
+ totalRequests: response.total_requests ?? 0,
3733
+ totalTokensIn: response.total_tokens_in ?? 0,
3734
+ totalTokensOut: response.total_tokens_out ?? 0,
3735
+ averageCostPerRequest: response.average_cost_per_request ?? 0,
3736
+ period: response.period ?? '',
3737
+ periodStart: response.period_start ?? '',
3738
+ periodEnd: response.period_end ?? '',
3679
3739
  };
3680
3740
  }
3681
3741
  /**
@@ -3691,21 +3751,22 @@ export class AxonFlow {
3691
3751
  if (period)
3692
3752
  params.set('period', period);
3693
3753
  const response = await this.orchestratorRequest('GET', `/api/v1/usage/breakdown?${params.toString()}`);
3694
- const items = (response.items || []).map((i) => ({
3695
- groupValue: i.group_value || '',
3696
- costUsd: i.cost_usd || 0,
3697
- percentage: i.percentage || 0,
3698
- requestCount: i.request_count || 0,
3699
- tokensIn: i.tokens_in || 0,
3700
- tokensOut: i.tokens_out || 0,
3754
+ const items = (response.items ?? []).map((i) => ({
3755
+ groupBy: i.group_by,
3756
+ groupValue: i.group_value ?? '',
3757
+ costUsd: i.cost_usd ?? 0,
3758
+ percentage: i.percentage ?? 0,
3759
+ requestCount: i.request_count ?? 0,
3760
+ tokensIn: i.tokens_in ?? 0,
3761
+ tokensOut: i.tokens_out ?? 0,
3701
3762
  }));
3702
3763
  return {
3703
- groupBy: response.group_by || '',
3704
- totalCostUsd: response.total_cost_usd || 0,
3764
+ groupBy: response.group_by ?? '',
3765
+ totalCostUsd: response.total_cost_usd ?? 0,
3705
3766
  items,
3706
- period: response.period || '',
3707
- periodStart: response.period_start || '',
3708
- periodEnd: response.period_end || '',
3767
+ period: response.period ?? '',
3768
+ periodStart: response.period_start ?? '',
3769
+ periodEnd: response.period_end ?? '',
3709
3770
  };
3710
3771
  }
3711
3772
  /**
@@ -3727,21 +3788,33 @@ export class AxonFlow {
3727
3788
  const queryString = params.toString();
3728
3789
  const path = `/api/v1/usage/records${queryString ? `?${queryString}` : ''}`;
3729
3790
  const response = await this.orchestratorRequest('GET', path);
3730
- const records = (response.records || []).map((r) => ({
3731
- id: r.id || '',
3732
- provider: r.provider || '',
3733
- model: r.model || '',
3734
- tokensIn: r.tokens_in || 0,
3735
- tokensOut: r.tokens_out || 0,
3736
- costUsd: r.cost_usd || 0,
3791
+ const records = (response.records ?? []).map((r) => ({
3792
+ id: r.id ?? '',
3793
+ provider: r.provider ?? '',
3794
+ model: r.model ?? '',
3795
+ tokensIn: r.tokens_in ?? 0,
3796
+ tokensOut: r.tokens_out ?? 0,
3797
+ costUsd: r.cost_usd ?? 0,
3737
3798
  requestId: r.request_id,
3738
3799
  orgId: r.org_id,
3739
3800
  agentId: r.agent_id,
3801
+ // Wire-canonical fields surfaced in the v6 alignment sweep.
3802
+ // The `timestamp` legacy field below has always read undefined
3803
+ // because the server emits `created_at`; both kept populated
3804
+ // so existing readers of `timestamp` see no behavior change.
3805
+ created_at: r.created_at,
3806
+ success: r.success,
3807
+ error_message: r.error_message,
3808
+ latency_ms: r.latency_ms,
3809
+ team_id: r.team_id,
3810
+ tenant_id: r.tenant_id,
3811
+ user_id: r.user_id,
3812
+ workflow_id: r.workflow_id,
3740
3813
  timestamp: r.timestamp,
3741
3814
  }));
3742
3815
  return {
3743
3816
  records,
3744
- total: response.total || 0,
3817
+ total: response.total ?? 0,
3745
3818
  };
3746
3819
  }
3747
3820
  // ========================================
@@ -3769,7 +3842,7 @@ export class AxonFlow {
3769
3842
  const pricing = this.mapPricingResponse(response);
3770
3843
  return { pricing: [pricing] };
3771
3844
  }
3772
- const pricingList = (response.pricing || []).map(p => this.mapPricingResponse(p));
3845
+ const pricingList = (response.pricing ?? []).map(p => this.mapPricingResponse(p));
3773
3846
  return { pricing: pricingList };
3774
3847
  }
3775
3848
  // ========================================
@@ -3777,15 +3850,17 @@ export class AxonFlow {
3777
3850
  // ========================================
3778
3851
  mapBudgetResponse(response) {
3779
3852
  return {
3780
- id: response.id || '',
3781
- name: response.name || '',
3782
- scope: response.scope || '',
3783
- limitUsd: response.limit_usd || 0,
3784
- period: response.period || '',
3785
- onExceed: response.on_exceed || '',
3786
- alertThresholds: response.alert_thresholds || [],
3853
+ id: response.id ?? '',
3854
+ name: response.name ?? '',
3855
+ scope: response.scope ?? '',
3856
+ limitUsd: response.limit_usd ?? 0,
3857
+ period: response.period ?? '',
3858
+ onExceed: response.on_exceed ?? '',
3859
+ alertThresholds: response.alert_thresholds ?? [],
3787
3860
  enabled: response.enabled ?? true,
3788
3861
  scopeId: response.scope_id,
3862
+ tenant_id: response.tenant_id,
3863
+ org_id: response.org_id,
3789
3864
  createdAt: response.created_at,
3790
3865
  updatedAt: response.updated_at,
3791
3866
  };
@@ -3793,11 +3868,11 @@ export class AxonFlow {
3793
3868
  mapPricingResponse(response) {
3794
3869
  const pricingData = response.pricing;
3795
3870
  return {
3796
- provider: response.provider || '',
3797
- model: response.model || '',
3871
+ provider: response.provider ?? '',
3872
+ model: response.model ?? '',
3798
3873
  pricing: {
3799
- inputPer1k: pricingData?.input_per_1k || 0,
3800
- outputPer1k: pricingData?.output_per_1k || 0,
3874
+ inputPer1k: pricingData?.input_per_1k ?? 0,
3875
+ outputPer1k: pricingData?.output_per_1k ?? 0,
3801
3876
  },
3802
3877
  };
3803
3878
  }
@@ -3994,7 +4069,7 @@ export class AxonFlow {
3994
4069
  throw new ConfigurationError('Step ID is required');
3995
4070
  }
3996
4071
  try {
3997
- await this.orchestratorRequest('POST', `/api/v1/workflows/${workflowId}/steps/${stepId}/complete`, request || {});
4072
+ await this.orchestratorRequest('POST', `/api/v1/workflows/${workflowId}/steps/${stepId}/complete`, request ?? {});
3998
4073
  }
3999
4074
  catch (err) {
4000
4075
  const idem = mapIdempotencyKeyMismatch(err);
@@ -4113,26 +4188,37 @@ export class AxonFlow {
4113
4188
  /**
4114
4189
  * Approve a workflow step that requires human approval.
4115
4190
  *
4116
- * Call this to approve a step that was gated with a 'require_approval' decision.
4191
+ * The server requires `comment` with a minimum of 10 characters it's the
4192
+ * audit-trail justification that every approval carries into the workflow
4193
+ * history. Callers should always supply a meaningful comment.
4117
4194
  *
4118
4195
  * @param workflowId - ID of the workflow
4119
4196
  * @param stepId - ID of the step to approve
4197
+ * @param comment - Audit justification for the approval (min 10 chars)
4120
4198
  * @returns Approval response with status
4121
4199
  *
4122
4200
  * @example
4123
4201
  * ```typescript
4124
- * const result = await client.approveStep('wf_123', 'step_456');
4202
+ * const result = await client.approveStep(
4203
+ * 'wf_123',
4204
+ * 'step_456',
4205
+ * 'Approved after full audit review'
4206
+ * );
4125
4207
  * console.log(`Step ${result.step_id} status: ${result.status}`);
4126
4208
  * ```
4127
4209
  */
4128
- async approveStep(workflowId, stepId) {
4210
+ async approveStep(workflowId, stepId, comment) {
4129
4211
  if (!workflowId) {
4130
4212
  throw new ConfigurationError('Workflow ID is required');
4131
4213
  }
4132
4214
  if (!stepId) {
4133
4215
  throw new ConfigurationError('Step ID is required');
4134
4216
  }
4135
- return this.orchestratorRequest('POST', `/api/v1/workflow-control/${workflowId}/steps/${stepId}/approve`, {});
4217
+ const body = {};
4218
+ if (comment) {
4219
+ body.comment = comment;
4220
+ }
4221
+ return this.orchestratorRequest('POST', `/api/v1/workflows/${workflowId}/steps/${stepId}/approve`, body);
4136
4222
  }
4137
4223
  /**
4138
4224
  * Reject a workflow step that requires human approval.
@@ -4161,21 +4247,26 @@ export class AxonFlow {
4161
4247
  if (reason) {
4162
4248
  body.reason = reason;
4163
4249
  }
4164
- return this.orchestratorRequest('POST', `/api/v1/workflow-control/${workflowId}/steps/${stepId}/reject`, body);
4250
+ return this.orchestratorRequest('POST', `/api/v1/workflows/${workflowId}/steps/${stepId}/reject`, body);
4165
4251
  }
4166
4252
  /**
4167
- * Get pending approvals for workflow steps.
4253
+ * Get pending approvals for workflow steps — the WCP-plane listing.
4254
+ *
4255
+ * Lists steps that are waiting for human approval across all planes for
4256
+ * the caller's tenant. Use {@link getPendingPlanApprovals} for the
4257
+ * MAP-plane listing (scopes to MAP-backed workflows and populates
4258
+ * `plan_id` on every entry).
4168
4259
  *
4169
- * Lists all steps that are waiting for human approval across all workflows.
4260
+ * Available on Evaluation+ licenses.
4170
4261
  *
4171
4262
  * @param options - Optional filtering options
4172
- * @returns List of pending approvals with total count
4263
+ * @returns List of pending approvals with count
4173
4264
  *
4174
4265
  * @example
4175
4266
  * ```typescript
4176
4267
  * const pending = await client.getPendingApprovals({ limit: 10 });
4177
- * console.log(`${pending.total} approvals pending`);
4178
- * for (const approval of pending.approvals) {
4268
+ * console.log(`${pending.count} approvals pending`);
4269
+ * for (const approval of pending.pending_approvals) {
4179
4270
  * console.log(`${approval.workflow_name} / ${approval.step_name}`);
4180
4271
  * }
4181
4272
  * ```
@@ -4187,8 +4278,44 @@ export class AxonFlow {
4187
4278
  }
4188
4279
  const queryString = params.toString();
4189
4280
  const path = queryString
4190
- ? `/api/v1/workflow-control/pending-approvals?${queryString}`
4191
- : '/api/v1/workflow-control/pending-approvals';
4281
+ ? `/api/v1/workflows/approvals/pending?${queryString}`
4282
+ : '/api/v1/workflows/approvals/pending';
4283
+ return this.orchestratorRequest('GET', path);
4284
+ }
4285
+ /**
4286
+ * List pending approvals for MAP-backed workflows — the MAP-plane
4287
+ * counterpart of {@link getPendingApprovals}. Every entry has `plan_id`
4288
+ * populated. Pass `options.plan_id` to scope the listing to a single plan.
4289
+ *
4290
+ * Requires an Evaluation or Enterprise license (same tier gate as the
4291
+ * MAP step approve/reject endpoints).
4292
+ *
4293
+ * @param options - Optional filtering options (`limit`, `plan_id`)
4294
+ * @returns List of MAP-plane pending approvals with count
4295
+ *
4296
+ * @example
4297
+ * ```typescript
4298
+ * const pending = await client.getPendingPlanApprovals({
4299
+ * plan_id: 'plan-abc123',
4300
+ * limit: 10,
4301
+ * });
4302
+ * for (const approval of pending.pending_approvals) {
4303
+ * console.log(`Plan ${approval.plan_id} step ${approval.step_id} awaiting approval`);
4304
+ * }
4305
+ * ```
4306
+ */
4307
+ async getPendingPlanApprovals(options) {
4308
+ const params = new URLSearchParams();
4309
+ if (options?.limit !== undefined) {
4310
+ params.set('limit', options.limit.toString());
4311
+ }
4312
+ if (options?.plan_id) {
4313
+ params.set('plan_id', options.plan_id);
4314
+ }
4315
+ const queryString = params.toString();
4316
+ const path = queryString
4317
+ ? `/api/v1/plans/approvals/pending?${queryString}`
4318
+ : '/api/v1/plans/approvals/pending';
4192
4319
  return this.orchestratorRequest('GET', path);
4193
4320
  }
4194
4321
  // =============================================================================
@@ -4228,7 +4355,7 @@ export class AxonFlow {
4228
4355
  debugLog('Plan rolled back', { planId, version: data.version });
4229
4356
  }
4230
4357
  return {
4231
- planId: data.plan_id || planId,
4358
+ planId: data.plan_id ?? planId,
4232
4359
  version: data.version,
4233
4360
  previousVersion: data.previous_version,
4234
4361
  status: data.status,
@@ -4494,7 +4621,7 @@ export class AxonFlow {
4494
4621
  throw new APIError(response.status, response.statusText, errorText);
4495
4622
  }
4496
4623
  const data = await response.json();
4497
- return (data || []).map((s) => this.mapSystemResponse(s));
4624
+ return (data ?? []).map((s) => this.mapSystemResponse(s));
4498
4625
  }
4499
4626
  async masfeatActivateSystem(systemId) {
4500
4627
  // Use PUT to update status - the /activate endpoint doesn't exist
@@ -4549,8 +4676,8 @@ export class AxonFlow {
4549
4676
  highMaterialityCount: data.high_materiality_count ?? data.high_materiality ?? 0,
4550
4677
  mediumMaterialityCount: data.medium_materiality_count ?? data.medium_materiality ?? 0,
4551
4678
  lowMaterialityCount: data.low_materiality_count ?? data.low_materiality ?? 0,
4552
- byUseCase: data.by_use_case || {},
4553
- byStatus: data.by_status || {},
4679
+ byUseCase: data.by_use_case ?? {},
4680
+ byStatus: data.by_status ?? {},
4554
4681
  };
4555
4682
  }
4556
4683
  // Assessment Methods
@@ -4558,7 +4685,7 @@ export class AxonFlow {
4558
4685
  const url = `${this.config.endpoint}/api/v1/masfeat/assessments`;
4559
4686
  const body = {
4560
4687
  system_id: request.systemId,
4561
- assessment_type: request.assessmentType || 'periodic',
4688
+ assessment_type: request.assessmentType ?? 'periodic',
4562
4689
  assessors: request.assessors,
4563
4690
  };
4564
4691
  if (request.assessmentDate)
@@ -4697,7 +4824,7 @@ export class AxonFlow {
4697
4824
  throw new APIError(response.status, response.statusText, errorText);
4698
4825
  }
4699
4826
  const data = await response.json();
4700
- return (data || []).map((a) => this.mapAssessmentResponse(a));
4827
+ return (data ?? []).map((a) => this.mapAssessmentResponse(a));
4701
4828
  }
4702
4829
  async masfeatSubmitAssessment(assessmentId) {
4703
4830
  const url = `${this.config.endpoint}/api/v1/masfeat/assessments/${assessmentId}/submit`;
@@ -4913,20 +5040,20 @@ export class AxonFlow {
4913
5040
  if (data && typeof data === 'object' && 'history' in data) {
4914
5041
  data = data.history;
4915
5042
  }
4916
- return (data || []).map((e) => ({
5043
+ return (data ?? []).map((e) => ({
4917
5044
  id: e.id,
4918
5045
  killSwitchId: e.kill_switch_id,
4919
5046
  // Handle both API formats: event_type (SDK expected) vs action (API actual)
4920
- eventType: e.event_type || e.action,
5047
+ eventType: e.event_type ?? e.action,
4921
5048
  // Build eventData from additional fields if not present
4922
- eventData: e.event_data ||
4923
- (e.previous_status || e.new_status || e.reason
5049
+ eventData: e.event_data ??
5050
+ ((e.previous_status ?? e.new_status ?? e.reason)
4924
5051
  ? { previousStatus: e.previous_status, newStatus: e.new_status, reason: e.reason }
4925
5052
  : undefined),
4926
5053
  // Handle both API formats: created_by vs performed_by
4927
- createdBy: e.created_by || e.performed_by,
5054
+ createdBy: e.created_by ?? e.performed_by,
4928
5055
  // Handle both API formats: created_at vs performed_at
4929
- createdAt: new Date(e.created_at || e.performed_at),
5056
+ createdAt: new Date(e.created_at ?? e.performed_at),
4930
5057
  }));
4931
5058
  }
4932
5059
  // Helper methods for MAS FEAT
@@ -4940,7 +5067,7 @@ export class AxonFlow {
4940
5067
  useCase: data.use_case,
4941
5068
  ownerTeam: data.owner_team,
4942
5069
  technicalOwner: data.technical_owner,
4943
- businessOwner: data.business_owner || data.owner_email,
5070
+ businessOwner: data.business_owner ?? data.owner_email,
4944
5071
  customerImpact: data.customer_impact ?? data.risk_rating_impact,
4945
5072
  modelComplexity: data.model_complexity ?? data.risk_rating_complexity,
4946
5073
  humanReliance: data.human_reliance ?? data.risk_rating_reliance,
@@ -4961,6 +5088,7 @@ export class AxonFlow {
4961
5088
  description: data.description,
4962
5089
  status: data.status,
4963
5090
  remediation: data.remediation,
5091
+ article: data.article,
4964
5092
  dueDate: data.due_date ? new Date(data.due_date) : undefined,
4965
5093
  };
4966
5094
  }
@@ -5008,7 +5136,7 @@ export class AxonFlow {
5008
5136
  autoTriggerEnabled: data.auto_trigger_enabled,
5009
5137
  triggeredAt: data.triggered_at ? new Date(data.triggered_at) : undefined,
5010
5138
  triggeredBy: data.triggered_by,
5011
- triggeredReason: data.triggered_reason || data.trigger_reason,
5139
+ triggeredReason: data.triggered_reason ?? data.trigger_reason,
5012
5140
  restoredAt: data.restored_at ? new Date(data.restored_at) : undefined,
5013
5141
  restoredBy: data.restored_by,
5014
5142
  createdAt: new Date(data.created_at),
@@ -5181,7 +5309,7 @@ export class AxonFlow {
5181
5309
  }
5182
5310
  const response = await this.orchestratorRequest('GET', path);
5183
5311
  return {
5184
- items: response.data || [],
5312
+ items: response.data ?? [],
5185
5313
  total: response.meta?.total ?? 0,
5186
5314
  has_more: (response.meta?.offset ?? 0) + (response.data?.length ?? 0) < (response.meta?.total ?? 0),
5187
5315
  };
@@ -5486,7 +5614,7 @@ export class AxonFlow {
5486
5614
  // Process complete SSE events (separated by double newline)
5487
5615
  const events = buffer.split('\n\n');
5488
5616
  // Keep the last (potentially incomplete) chunk in the buffer
5489
- buffer = events.pop() || '';
5617
+ buffer = events.pop() ?? '';
5490
5618
  for (const event of events) {
5491
5619
  const trimmed = event.trim();
5492
5620
  if (!trimmed) {