@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
@@ -33,12 +33,12 @@ function mapIdempotencyKeyMismatch(err) {
33
33
  * Returns -1 if a < b, 0 if equal, 1 if a > b.
34
34
  */
35
35
  function compareSemver(a, b) {
36
- const parseVersion = (v) => v.split('.').map(p => parseInt(p.split('-')[0], 10) || 0);
36
+ const parseVersion = (v) => v.split('.').map(p => parseInt(p.split('-')[0], 10) ?? 0);
37
37
  const aParts = parseVersion(a);
38
38
  const bParts = parseVersion(b);
39
39
  const len = Math.max(aParts.length, bParts.length);
40
40
  for (let i = 0; i < len; i++) {
41
- const diff = (aParts[i] || 0) - (bParts[i] || 0);
41
+ const diff = (aParts[i] ?? 0) - (bParts[i] ?? 0);
42
42
  if (diff !== 0)
43
43
  return diff < 0 ? -1 : 1;
44
44
  }
@@ -67,7 +67,7 @@ class AxonFlow {
67
67
  };
68
68
  }
69
69
  // Set defaults first to determine endpoint
70
- const endpoint = config.endpoint || 'https://staging-eu.getaxonflow.com';
70
+ const endpoint = config.endpoint ?? 'https://staging-eu.getaxonflow.com';
71
71
  // Credentials check: OAuth2-style (clientId/clientSecret)
72
72
  const hasCredentials = !!(config.clientId && config.clientSecret);
73
73
  // Set configuration
@@ -75,19 +75,19 @@ class AxonFlow {
75
75
  clientId: config.clientId,
76
76
  clientSecret: config.clientSecret,
77
77
  endpoint,
78
- mode: config.mode || 'production',
79
- tenant: config.tenant || '',
80
- debug: config.debug || false,
81
- timeout: config.timeout || 30000,
82
- mapTimeout: config.mapTimeout || 120000, // 2 minutes for MAP operations
78
+ mode: config.mode ?? 'production',
79
+ tenant: config.tenant ?? '',
80
+ debug: config.debug ?? false,
81
+ timeout: config.timeout ?? 30000,
82
+ mapTimeout: config.mapTimeout ?? 120000, // 2 minutes for MAP operations
83
83
  retry: {
84
84
  enabled: config.retry?.enabled !== false,
85
- maxAttempts: config.retry?.maxAttempts || 3,
86
- delay: config.retry?.delay || 1000,
85
+ maxAttempts: config.retry?.maxAttempts ?? 3,
86
+ delay: config.retry?.delay ?? 1000,
87
87
  },
88
88
  cache: {
89
89
  enabled: config.cache?.enabled !== false,
90
- ttl: config.cache?.ttl || 60000,
90
+ ttl: config.cache?.ttl ?? 60000,
91
91
  },
92
92
  };
93
93
  // Interceptors removed in v3.0.0 (deprecated wrapOpenAIClient/wrapAnthropicClient)
@@ -124,7 +124,7 @@ class AxonFlow {
124
124
  // clientSecret defaults to empty string for community/no-secret mode.
125
125
  const effectiveClientId = this.getEffectiveClientId();
126
126
  if (effectiveClientId) {
127
- const credentials = Buffer.from(`${effectiveClientId}:${this.config.clientSecret || ''}`).toString('base64');
127
+ const credentials = Buffer.from(`${effectiveClientId}:${this.config.clientSecret ?? ''}`).toString('base64');
128
128
  headers['Authorization'] = `Basic ${credentials}`;
129
129
  }
130
130
  // Include SDK version for version discovery and compatibility checks
@@ -141,6 +141,9 @@ class AxonFlow {
141
141
  * @returns The clientId to use in requests
142
142
  */
143
143
  getEffectiveClientId() {
144
+ // Intentional || (not ??): an empty-string clientId/tenant is treated as
145
+ // "missing" by the SDK contract — see tests/smart-defaults.test.ts.
146
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
144
147
  return this.config.clientId || this.config.tenant || 'community';
145
148
  }
146
149
  /**
@@ -212,7 +215,7 @@ class AxonFlow {
212
215
  // If denied, throw error
213
216
  if (!governanceResponse.allowed) {
214
217
  const violation = governanceResponse.violations?.[0];
215
- throw new Error(`Request blocked by AxonFlow: ${violation?.description || 'Policy violation'}`);
218
+ throw new Error(`Request blocked by AxonFlow: ${violation?.description ?? 'Policy violation'}`);
216
219
  }
217
220
  // Execute the AI call (possibly with modifications)
218
221
  const modifiedCall = governanceResponse.modifiedRequest
@@ -262,6 +265,9 @@ class AxonFlow {
262
265
  const agentRequest = {
263
266
  query: request.aiRequest.prompt,
264
267
  user_token: '',
268
+ // Intentional || (not ??): an empty-string clientId/tenant is treated as
269
+ // "missing" by the SDK contract — see tests/smart-defaults.test.ts.
270
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
265
271
  client_id: this.config.clientId || this.config.tenant,
266
272
  request_type: 'llm_chat',
267
273
  context: {
@@ -289,7 +295,7 @@ class AxonFlow {
289
295
  const agentResponse = await response.json();
290
296
  // Transform Agent API response to SDK format
291
297
  // Extract policy name from policy_info if available
292
- const policyName = agentResponse.policy_info?.policies_evaluated?.[0] || 'agent-policy';
298
+ const policyName = agentResponse.policy_info?.policies_evaluated?.[0] ?? 'agent-policy';
293
299
  return {
294
300
  requestId: request.requestId,
295
301
  allowed: !agentResponse.blocked,
@@ -298,17 +304,17 @@ class AxonFlow {
298
304
  {
299
305
  type: 'security',
300
306
  severity: 'high',
301
- description: agentResponse.block_reason || 'Request blocked by policy',
307
+ description: agentResponse.block_reason ?? 'Request blocked by policy',
302
308
  policy: policyName,
303
309
  action: 'blocked',
304
310
  },
305
311
  ]
306
312
  : [],
307
313
  modifiedRequest: agentResponse.data,
308
- policies: agentResponse.policy_info?.policies_evaluated || [],
314
+ policies: agentResponse.policy_info?.policies_evaluated ?? [],
309
315
  audit: {
310
316
  timestamp: Date.now(),
311
- duration: parseInt(agentResponse.policy_info?.processing_time?.replace('ms', '') || '0'),
317
+ duration: parseInt(agentResponse.policy_info?.processing_time?.replace('ms', '') ?? '0'),
312
318
  tenant: this.config.tenant,
313
319
  },
314
320
  };
@@ -322,7 +328,7 @@ class AxonFlow {
322
328
  if (this.config.debug) {
323
329
  (0, helpers_1.debugLog)('Request processed', {
324
330
  allowed: response.allowed,
325
- violations: response.violations?.length || 0,
331
+ violations: response.violations?.length ?? 0,
326
332
  duration: response.audit.duration,
327
333
  });
328
334
  }
@@ -331,9 +337,10 @@ class AxonFlow {
331
337
  * Check if an error is from AxonFlow (vs the AI provider)
332
338
  */
333
339
  isAxonFlowError(error) {
334
- return (error?.message?.includes('AxonFlow') ||
335
- error?.message?.includes('governance') ||
336
- error?.message?.includes('fetch'));
340
+ const msg = error?.message;
341
+ if (typeof msg !== 'string')
342
+ return false;
343
+ return msg.includes('AxonFlow') || msg.includes('governance') || msg.includes('fetch');
337
344
  }
338
345
  /**
339
346
  * Create a sandbox client for testing
@@ -529,13 +536,16 @@ class AxonFlow {
529
536
  */
530
537
  async proxyLLMCall(options) {
531
538
  // Default to "anonymous" if userToken is empty/undefined (community mode)
532
- const effectiveUserToken = options.userToken || 'anonymous';
539
+ const effectiveUserToken = options.userToken ?? 'anonymous';
533
540
  const agentRequest = {
534
541
  query: options.query,
535
542
  user_token: effectiveUserToken,
543
+ // Intentional || (not ??): an empty-string clientId/tenant is treated as
544
+ // "missing" by the SDK contract — see tests/smart-defaults.test.ts.
545
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
536
546
  client_id: this.config.clientId || this.config.tenant,
537
547
  request_type: options.requestType,
538
- context: options.context || {},
548
+ context: options.context ?? {},
539
549
  };
540
550
  if (options.media && options.media.length > 0) {
541
551
  agentRequest.media = options.media.map(m => ({
@@ -588,7 +598,7 @@ class AxonFlow {
588
598
  try {
589
599
  const errorJson = JSON.parse(errorText);
590
600
  if (errorJson.blocked || errorJson.block_reason) {
591
- throw new errors_1.PolicyViolationError(errorJson.block_reason || 'Request blocked by policy', errorJson.policy_info?.policies_evaluated);
601
+ throw new errors_1.PolicyViolationError(errorJson.block_reason ?? 'Request blocked by policy', errorJson.policy_info?.policies_evaluated);
592
602
  }
593
603
  }
594
604
  catch (e) {
@@ -602,13 +612,11 @@ class AxonFlow {
602
612
  }
603
613
  }
604
614
  // Parse response if not already parsed (from 402 handling)
605
- if (!data) {
606
- data = await response.json();
607
- }
615
+ data ?? (data = await response.json());
608
616
  // Check for policy violation in successful response (some blocked responses return 200)
609
617
  // Note: Don't throw for budget blocks (402 responses) - return with budgetInfo instead
610
618
  if (data.blocked && !data.budget_info) {
611
- throw new errors_1.PolicyViolationError(data.block_reason || 'Request blocked by policy', data.policy_info?.policies_evaluated);
619
+ throw new errors_1.PolicyViolationError(data.block_reason ?? 'Request blocked by policy', data.policy_info?.policies_evaluated);
612
620
  }
613
621
  // Transform snake_case response to camelCase
614
622
  const result = {
@@ -617,18 +625,18 @@ class AxonFlow {
617
625
  result: data.result,
618
626
  planId: data.plan_id,
619
627
  requestId: data.request_id,
620
- metadata: data.metadata || {},
628
+ metadata: data.metadata ?? {},
621
629
  error: data.error,
622
- blocked: data.blocked || false,
630
+ blocked: data.blocked ?? false,
623
631
  blockReason: data.block_reason,
624
632
  };
625
633
  // Parse policy info if present
626
634
  if (data.policy_info) {
627
635
  result.policyInfo = {
628
- policiesEvaluated: data.policy_info.policies_evaluated || [],
629
- staticChecks: data.policy_info.static_checks || [],
630
- processingTime: data.policy_info.processing_time || '',
631
- tenantId: data.policy_info.tenant_id || '',
636
+ policiesEvaluated: data.policy_info.policies_evaluated ?? [],
637
+ staticChecks: data.policy_info.static_checks ?? [],
638
+ processingTime: data.policy_info.processing_time ?? '',
639
+ tenantId: data.policy_info.tenant_id ?? '',
632
640
  codeArtifact: data.policy_info.code_artifact,
633
641
  };
634
642
  }
@@ -637,10 +645,10 @@ class AxonFlow {
637
645
  result.budgetInfo = {
638
646
  budgetId: data.budget_info.budget_id,
639
647
  budgetName: data.budget_info.budget_name,
640
- usedUsd: data.budget_info.used_usd || 0,
641
- limitUsd: data.budget_info.limit_usd || 0,
642
- percentage: data.budget_info.percentage || 0,
643
- exceeded: data.budget_info.exceeded || false,
648
+ usedUsd: data.budget_info.used_usd ?? 0,
649
+ limitUsd: data.budget_info.limit_usd ?? 0,
650
+ percentage: data.budget_info.percentage ?? 0,
651
+ exceeded: data.budget_info.exceeded ?? false,
644
652
  action: data.budget_info.action,
645
653
  };
646
654
  }
@@ -684,7 +692,7 @@ class AxonFlow {
684
692
  async listConnectors() {
685
693
  const response = await this.orchestratorRequest('GET', '/api/v1/connectors');
686
694
  // Handle wrapped response
687
- const connectors = Array.isArray(response) ? response : response.connectors || [];
695
+ const connectors = Array.isArray(response) ? response : (response.connectors ?? []);
688
696
  if (this.config.debug) {
689
697
  (0, helpers_1.debugLog)('Listed connectors', { count: connectors.length });
690
698
  }
@@ -737,11 +745,14 @@ class AxonFlow {
737
745
  const agentRequest = {
738
746
  query,
739
747
  user_token: '',
748
+ // Intentional || (not ??): an empty-string clientId/tenant is treated as
749
+ // "missing" by the SDK contract — see tests/smart-defaults.test.ts.
750
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
740
751
  client_id: this.config.clientId || this.config.tenant,
741
752
  request_type: 'mcp-query',
742
753
  context: {
743
754
  connector: connectorName,
744
- params: params || {},
755
+ params: params ?? {},
745
756
  },
746
757
  };
747
758
  const url = `${this.config.endpoint}/api/request`;
@@ -810,7 +821,7 @@ class AxonFlow {
810
821
  const body = {
811
822
  connector: options.connector,
812
823
  statement: options.statement,
813
- options: options.options || {},
824
+ options: options.options ?? {},
814
825
  };
815
826
  if (this.config.debug) {
816
827
  (0, helpers_1.debugLog)('MCP Query', {
@@ -827,7 +838,7 @@ class AxonFlow {
827
838
  const responseData = await response.json();
828
839
  // Handle policy blocks (403 responses)
829
840
  if (!response.ok) {
830
- throw new errors_1.ConnectorError(responseData.error || `MCP query failed: ${response.status} ${response.statusText}`, options.connector, 'mcpQuery');
841
+ throw new errors_1.ConnectorError(responseData.error ?? `MCP query failed: ${response.status} ${response.statusText}`, options.connector, 'mcpQuery');
831
842
  }
832
843
  if (this.config.debug) {
833
844
  (0, helpers_1.debugLog)('MCP Query result', {
@@ -893,7 +904,7 @@ class AxonFlow {
893
904
  if (options.parameters) {
894
905
  body.parameters = options.parameters;
895
906
  }
896
- body.operation = options.operation || 'execute';
907
+ body.operation = options.operation ?? 'execute';
897
908
  if (this.config.debug) {
898
909
  (0, helpers_1.debugLog)('MCP Check Input', {
899
910
  connectorType: options.connectorType,
@@ -909,7 +920,7 @@ class AxonFlow {
909
920
  const responseData = await response.json();
910
921
  // 403 means policy blocked — this is a valid check response, not an error
911
922
  if (!response.ok && response.status !== 403) {
912
- throw new errors_1.ConnectorError(responseData.error || 'MCP check-input failed', options.connectorType, 'check-input');
923
+ throw new errors_1.ConnectorError(responseData.error ?? 'MCP check-input failed', options.connectorType, 'check-input');
913
924
  }
914
925
  if (this.config.debug) {
915
926
  (0, helpers_1.debugLog)('MCP Check Input result', {
@@ -981,7 +992,7 @@ class AxonFlow {
981
992
  const responseData = await response.json();
982
993
  // 403 means policy blocked — this is a valid check response, not an error
983
994
  if (!response.ok && response.status !== 403) {
984
- throw new errors_1.ConnectorError(responseData.error || 'MCP check-output failed', options.connectorType, 'check-output');
995
+ throw new errors_1.ConnectorError(responseData.error ?? 'MCP check-output failed', options.connectorType, 'check-output');
985
996
  }
986
997
  if (this.config.debug) {
987
998
  (0, helpers_1.debugLog)('MCP Check Output result', {
@@ -1027,7 +1038,13 @@ class AxonFlow {
1027
1038
  }
1028
1039
  const agentRequest = {
1029
1040
  query,
1041
+ // Intentional || (not ??): an empty-string clientId/tenant is treated as
1042
+ // "missing" by the SDK contract — see tests/smart-defaults.test.ts.
1043
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
1030
1044
  user_token: userToken || this.config.clientId || this.config.tenant,
1045
+ // Intentional || (not ??): an empty-string clientId/tenant is treated as
1046
+ // "missing" by the SDK contract — see tests/smart-defaults.test.ts.
1047
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
1031
1048
  client_id: this.config.clientId || this.config.tenant,
1032
1049
  request_type: 'multi-agent-plan',
1033
1050
  context,
@@ -1053,18 +1070,24 @@ class AxonFlow {
1053
1070
  throw new errors_1.PlanExecutionError(`Plan generation failed: ${agentResponse.error}`, undefined, 'generation');
1054
1071
  }
1055
1072
  // plan_id can be at top level or inside data
1056
- const planId = agentResponse.plan_id || agentResponse.data?.plan_id;
1073
+ const planId = agentResponse.plan_id ?? agentResponse.data?.plan_id;
1057
1074
  if (this.config.debug) {
1058
1075
  (0, helpers_1.debugLog)('Plan generated', { planId });
1059
1076
  }
1060
1077
  return {
1061
1078
  planId,
1062
- status: agentResponse.data?.status || 'pending',
1063
- steps: agentResponse.data?.steps || [],
1064
- domain: agentResponse.data?.domain || domain || 'generic',
1065
- complexity: agentResponse.data?.complexity || 0,
1066
- parallel: agentResponse.data?.parallel || false,
1067
- metadata: agentResponse.metadata || {},
1079
+ status: agentResponse.data?.status ?? 'pending',
1080
+ steps: agentResponse.data?.steps ?? [],
1081
+ domain: agentResponse.data?.domain ?? domain ?? 'generic',
1082
+ complexity: agentResponse.data?.complexity ?? 0,
1083
+ parallel: agentResponse.data?.parallel ?? false,
1084
+ metadata: agentResponse.metadata ?? {},
1085
+ success: agentResponse.success,
1086
+ version: agentResponse.version ?? agentResponse.data?.version,
1087
+ result: agentResponse.result ?? agentResponse.data?.result,
1088
+ error: agentResponse.error,
1089
+ workflow_execution_id: agentResponse.workflow_execution_id ?? agentResponse.data?.workflow_execution_id,
1090
+ policy_info: agentResponse.policy_info ?? agentResponse.data?.policy_info,
1068
1091
  };
1069
1092
  }
1070
1093
  /**
@@ -1075,7 +1098,13 @@ class AxonFlow {
1075
1098
  async executePlan(planId, userToken) {
1076
1099
  const agentRequest = {
1077
1100
  query: '',
1101
+ // Intentional || (not ??): an empty-string clientId/tenant is treated as
1102
+ // "missing" by the SDK contract — see tests/smart-defaults.test.ts.
1103
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
1078
1104
  user_token: userToken || this.config.clientId || this.config.tenant,
1105
+ // Intentional || (not ??): an empty-string clientId/tenant is treated as
1106
+ // "missing" by the SDK contract — see tests/smart-defaults.test.ts.
1107
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
1079
1108
  client_id: this.config.clientId || this.config.tenant,
1080
1109
  request_type: 'execute-plan',
1081
1110
  context: { plan_id: planId },
@@ -1107,7 +1136,7 @@ class AxonFlow {
1107
1136
  if (data.error && !error)
1108
1137
  error = data.error;
1109
1138
  // Throw on nested failure (e.g., cancelled plan execution)
1110
- throw new errors_1.PlanExecutionError(error || 'Plan execution failed', planId, 'execution');
1139
+ throw new errors_1.PlanExecutionError(error ?? 'Plan execution failed', planId, 'execution');
1111
1140
  }
1112
1141
  if (!result && data?.result)
1113
1142
  result = data.result;
@@ -1189,8 +1218,13 @@ class AxonFlow {
1189
1218
  (0, helpers_1.debugLog)('Plan cancelled', { planId, status: data.status });
1190
1219
  }
1191
1220
  return {
1192
- planId: data.plan_id || planId,
1221
+ planId: data.plan_id ?? planId,
1193
1222
  status: data.status,
1223
+ // `success` is the canonical wire boolean. The deprecated
1224
+ // `message` slot is also kept populated for back-compat
1225
+ // readers; on a current server it will always be undefined
1226
+ // because the wire never emits it.
1227
+ success: data.success,
1194
1228
  message: data.message,
1195
1229
  };
1196
1230
  }
@@ -1215,6 +1249,9 @@ class AxonFlow {
1215
1249
  if (request.domain) {
1216
1250
  body.domain = request.domain;
1217
1251
  }
1252
+ if (request.metadata !== undefined) {
1253
+ body.metadata = request.metadata;
1254
+ }
1218
1255
  const response = await fetch(url, {
1219
1256
  method: 'PUT',
1220
1257
  headers,
@@ -1234,7 +1271,7 @@ class AxonFlow {
1234
1271
  (0, helpers_1.debugLog)('Plan updated', { planId, version: data.version });
1235
1272
  }
1236
1273
  return {
1237
- planId: data.plan_id || planId,
1274
+ planId: data.plan_id ?? planId,
1238
1275
  version: data.version,
1239
1276
  status: data.status,
1240
1277
  success: data.success ?? true,
@@ -1259,7 +1296,7 @@ class AxonFlow {
1259
1296
  throw new errors_1.PlanExecutionError(`Get plan versions failed: ${response.status} ${response.statusText} - ${errorText}`, planId, 'versions');
1260
1297
  }
1261
1298
  const data = await response.json();
1262
- const versions = (data.versions || []).map((v) => ({
1299
+ const versions = (data.versions ?? []).map((v) => ({
1263
1300
  version: v.version,
1264
1301
  changedAt: v.changed_at,
1265
1302
  changedBy: v.changed_by,
@@ -1267,7 +1304,7 @@ class AxonFlow {
1267
1304
  changeSummary: v.change_summary,
1268
1305
  }));
1269
1306
  return {
1270
- planId: data.plan_id || planId,
1307
+ planId: data.plan_id ?? planId,
1271
1308
  versions,
1272
1309
  };
1273
1310
  }
@@ -1297,9 +1334,13 @@ class AxonFlow {
1297
1334
  (0, helpers_1.debugLog)('Plan resumed', { planId, approved: data.approved });
1298
1335
  }
1299
1336
  return {
1300
- planId: data.plan_id || planId,
1337
+ planId: data.plan_id ?? planId,
1301
1338
  status: data.status,
1339
+ // `result` is the canonical wire-aggregated outcome on resume.
1340
+ result: data.result,
1302
1341
  approved: data.approved,
1342
+ // `message` kept populated for the back-compat alias; legacy
1343
+ // path read this slot historically.
1303
1344
  message: data.message,
1304
1345
  };
1305
1346
  }
@@ -1362,8 +1403,8 @@ class AxonFlow {
1362
1403
  user_token: options.userToken,
1363
1404
  client_id: clientId,
1364
1405
  query: options.query,
1365
- data_sources: options.dataSources || [],
1366
- context: options.context || {},
1406
+ data_sources: options.dataSources ?? [],
1407
+ context: options.context ?? {},
1367
1408
  };
1368
1409
  const headers = {
1369
1410
  'Content-Type': 'application/json',
@@ -1394,9 +1435,9 @@ class AxonFlow {
1394
1435
  const result = {
1395
1436
  contextId: data.context_id,
1396
1437
  approved: data.approved,
1397
- requiresRedaction: data.requires_redaction || false,
1398
- approvedData: data.approved_data || {},
1399
- policies: data.policies || [],
1438
+ requiresRedaction: data.requires_redaction ?? false,
1439
+ approvedData: data.approved_data ?? {},
1440
+ policies: data.policies ?? [],
1400
1441
  expiresAt,
1401
1442
  blockReason: data.block_reason,
1402
1443
  };
@@ -1455,7 +1496,7 @@ class AxonFlow {
1455
1496
  total_tokens: options.tokenUsage.totalTokens,
1456
1497
  },
1457
1498
  latency_ms: options.latencyMs,
1458
- metadata: options.metadata || {},
1499
+ metadata: options.metadata ?? {},
1459
1500
  };
1460
1501
  const headers = {
1461
1502
  'Content-Type': 'application/json',
@@ -1574,7 +1615,7 @@ class AxonFlow {
1574
1615
  const response = await this.orchestratorRequest('GET', '/api/v1/circuit-breaker/status');
1575
1616
  const data = response.data;
1576
1617
  return {
1577
- activeCircuits: (data.active_circuits || []).map(c => ({
1618
+ activeCircuits: (data.active_circuits ?? []).map(c => ({
1578
1619
  id: c.id,
1579
1620
  scope: c.scope,
1580
1621
  scopeId: c.scope_id,
@@ -1617,7 +1658,7 @@ class AxonFlow {
1617
1658
  const response = await this.orchestratorRequest('GET', path);
1618
1659
  const data = response.data;
1619
1660
  return {
1620
- history: (data.history || []).map(h => ({
1661
+ history: (data.history ?? []).map(h => ({
1621
1662
  id: h.id,
1622
1663
  orgId: h.org_id,
1623
1664
  scope: h.scope,
@@ -1873,10 +1914,10 @@ class AxonFlow {
1873
1914
  * forward compatibility per ADR-043.
1874
1915
  */
1875
1916
  parseDecisionExplanation(raw) {
1876
- const r = raw || {};
1877
- const rawMatches = r.policy_matches || [];
1917
+ const r = raw ?? {};
1918
+ const rawMatches = r.policy_matches ?? [];
1878
1919
  const policyMatches = rawMatches.map(m => ({
1879
- policyId: m.policy_id || '',
1920
+ policyId: m.policy_id ?? '',
1880
1921
  policyName: m.policy_name,
1881
1922
  action: m.action,
1882
1923
  riskLevel: m.risk_level,
@@ -1885,18 +1926,18 @@ class AxonFlow {
1885
1926
  }));
1886
1927
  const rawRules = r.matched_rules;
1887
1928
  const matchedRules = rawRules?.map(m => ({
1888
- policyId: m.policy_id || '',
1929
+ policyId: m.policy_id ?? '',
1889
1930
  ruleId: m.rule_id,
1890
1931
  ruleText: m.rule_text,
1891
1932
  matchedOn: m.matched_on,
1892
1933
  }));
1893
1934
  return {
1894
- decisionId: r.decision_id || '',
1935
+ decisionId: r.decision_id ?? '',
1895
1936
  timestamp: r.timestamp ? new Date(r.timestamp) : new Date(),
1896
1937
  policyMatches,
1897
1938
  matchedRules,
1898
- decision: r.decision || '',
1899
- reason: r.reason || '',
1939
+ decision: r.decision ?? '',
1940
+ reason: r.reason ?? '',
1900
1941
  riskLevel: r.risk_level,
1901
1942
  overrideAvailable: r.override_available ?? false,
1902
1943
  overrideExistingId: r.override_existing_id,
@@ -1943,7 +1984,7 @@ class AxonFlow {
1943
1984
  };
1944
1985
  }
1945
1986
  const data = response;
1946
- const entries = (data.entries || []).map(e => this.parseAuditLogEntry(e));
1987
+ const entries = (data.entries ?? []).map(e => this.parseAuditLogEntry(e));
1947
1988
  return {
1948
1989
  entries,
1949
1990
  total: data.total ?? entries.length,
@@ -1997,7 +2038,7 @@ class AxonFlow {
1997
2038
  };
1998
2039
  }
1999
2040
  const data = response;
2000
- const entries = (data.entries || []).map(e => this.parseAuditLogEntry(e));
2041
+ const entries = (data.entries ?? []).map(e => this.parseAuditLogEntry(e));
2001
2042
  return {
2002
2043
  entries,
2003
2044
  total: data.total ?? entries.length,
@@ -2117,7 +2158,7 @@ class AxonFlow {
2117
2158
  }
2118
2159
  // Backend returns { policies: [], pagination: {} }, extract the array
2119
2160
  const response = await this.policyRequest('GET', path);
2120
- return response.policies || [];
2161
+ return response.policies ?? [];
2121
2162
  }
2122
2163
  /**
2123
2164
  * Get a specific static policy by ID.
@@ -2168,12 +2209,19 @@ class AxonFlow {
2168
2209
  severity: policy.severity,
2169
2210
  enabled: policy.enabled,
2170
2211
  action: policy.action,
2171
- tier: policy.tier || 'tenant',
2212
+ tier: policy.tier ?? 'tenant',
2172
2213
  };
2173
2214
  // Add organization_id for organization tier policies
2174
2215
  if (policy.organizationId) {
2175
2216
  requestBody.organization_id = policy.organizationId;
2176
2217
  }
2218
+ // Wire-canonical fields surfaced in the v6 alignment sweep.
2219
+ if (policy.priority !== undefined) {
2220
+ requestBody.priority = policy.priority;
2221
+ }
2222
+ if (policy.tags !== undefined) {
2223
+ requestBody.tags = policy.tags;
2224
+ }
2177
2225
  return this.policyRequest('POST', '/api/v1/static-policies', requestBody);
2178
2226
  }
2179
2227
  /**
@@ -2262,7 +2310,7 @@ class AxonFlow {
2262
2310
  }
2263
2311
  // Backend returns { static: [], dynamic: [], ... }, extract the static array
2264
2312
  const response = await this.policyRequest('GET', path);
2265
- return response.static || [];
2313
+ return response.static ?? [];
2266
2314
  }
2267
2315
  /**
2268
2316
  * Test a regex pattern against sample inputs.
@@ -2307,12 +2355,21 @@ class AxonFlow {
2307
2355
  (0, helpers_1.debugLog)('Getting static policy versions', { id });
2308
2356
  }
2309
2357
  const response = await this.policyRequest('GET', `/api/v1/static-policies/${id}/versions`);
2310
- // Transform snake_case API response to camelCase
2358
+ // Transform snake_case API response to camelCase, populating
2359
+ // both the wire-canonical fields (`id`, `policy_id`,
2360
+ // `change_summary`, `snapshot`) and the legacy aliases (kept for
2361
+ // back-compat; will read undefined on a current server).
2311
2362
  return response.versions.map(v => ({
2363
+ id: v.id,
2364
+ policy_id: v.policy_id,
2312
2365
  version: v.version,
2313
2366
  changedBy: v.changed_by,
2314
2367
  changedAt: v.changed_at,
2315
2368
  changeType: v.change_type,
2369
+ change_summary: v.change_summary,
2370
+ snapshot: v.snapshot,
2371
+ // @deprecated aliases — only populated if a legacy server
2372
+ // still emits them; current servers don't.
2316
2373
  changeDescription: v.change_description,
2317
2374
  previousValues: v.previous_values,
2318
2375
  newValues: v.new_values,
@@ -2381,7 +2438,7 @@ class AxonFlow {
2381
2438
  (0, helpers_1.debugLog)('Listing policy overrides');
2382
2439
  }
2383
2440
  const response = await this.policyRequest('GET', '/api/v1/static-policies/overrides');
2384
- return response.overrides || [];
2441
+ return response.overrides ?? [];
2385
2442
  }
2386
2443
  // ============================================================================
2387
2444
  // Dynamic Policy Methods
@@ -2428,7 +2485,7 @@ class AxonFlow {
2428
2485
  // API returns {"policies": [...]} wrapper via Agent proxy
2429
2486
  const response = await this.orchestratorRequest('GET', path);
2430
2487
  // Handle both wrapped and unwrapped responses for compatibility
2431
- return Array.isArray(response) ? response : response.policies || [];
2488
+ return Array.isArray(response) ? response : (response.policies ?? []);
2432
2489
  }
2433
2490
  /**
2434
2491
  * Get a specific dynamic policy by ID.
@@ -2483,7 +2540,7 @@ class AxonFlow {
2483
2540
  requestBody.priority = policy.priority;
2484
2541
  if (policy.enabled !== undefined)
2485
2542
  requestBody.enabled = policy.enabled;
2486
- requestBody.tier = policy.tier || 'tenant';
2543
+ requestBody.tier = policy.tier ?? 'tenant';
2487
2544
  if (policy.organizationId) {
2488
2545
  requestBody.organization_id = policy.organizationId;
2489
2546
  }
@@ -2577,7 +2634,7 @@ class AxonFlow {
2577
2634
  // API returns {"policies": [...]} wrapper via Agent proxy
2578
2635
  const response = await this.orchestratorRequest('GET', path);
2579
2636
  // Handle both wrapped and unwrapped responses for compatibility
2580
- return Array.isArray(response) ? response : response.policies || [];
2637
+ return Array.isArray(response) ? response : (response.policies ?? []);
2581
2638
  }
2582
2639
  // ============================================================================
2583
2640
  // Portal Authentication Methods (Enterprise)
@@ -2906,7 +2963,7 @@ class AxonFlow {
2906
2963
  const response = await this.portalRequest('GET', path);
2907
2964
  // Transform snake_case response to camelCase
2908
2965
  return {
2909
- prs: (response.prs || []).map(pr => ({
2966
+ prs: (response.prs ?? []).map(pr => ({
2910
2967
  id: pr.id,
2911
2968
  prNumber: pr.pr_number,
2912
2969
  prUrl: pr.pr_url,
@@ -3112,7 +3169,7 @@ class AxonFlow {
3112
3169
  }
3113
3170
  const response = await this.portalRequest('GET', path);
3114
3171
  return {
3115
- records: (response.records || []).map(r => ({
3172
+ records: (response.records ?? []).map(r => ({
3116
3173
  id: r.id,
3117
3174
  prNumber: r.pr_number,
3118
3175
  prUrl: r.pr_url,
@@ -3288,7 +3345,7 @@ class AxonFlow {
3288
3345
  }
3289
3346
  const response = await this.orchestratorRequest('GET', path);
3290
3347
  return {
3291
- executions: (response.executions || []).map(e => ({
3348
+ executions: (response.executions ?? []).map(e => ({
3292
3349
  requestId: e.request_id,
3293
3350
  workflowName: e.workflow_name,
3294
3351
  status: e.status,
@@ -3372,6 +3429,7 @@ class AxonFlow {
3372
3429
  approvalRequired: s.approval_required,
3373
3430
  approvedBy: s.approved_by,
3374
3431
  approvedAt: s.approved_at,
3432
+ retryCount: s.retry_count,
3375
3433
  })),
3376
3434
  };
3377
3435
  }
@@ -3415,6 +3473,7 @@ class AxonFlow {
3415
3473
  approvalRequired: s.approval_required,
3416
3474
  approvedBy: s.approved_by,
3417
3475
  approvedAt: s.approved_at,
3476
+ retryCount: s.retry_count,
3418
3477
  }));
3419
3478
  }
3420
3479
  /**
@@ -3553,8 +3612,8 @@ class AxonFlow {
3553
3612
  const path = `/api/v1/budgets${queryString ? `?${queryString}` : ''}`;
3554
3613
  const response = await this.orchestratorRequest('GET', path);
3555
3614
  return {
3556
- budgets: (response.budgets || []).map(b => this.mapBudgetResponse(b)),
3557
- total: response.total || 0,
3615
+ budgets: (response.budgets ?? []).map(b => this.mapBudgetResponse(b)),
3616
+ total: response.total ?? 0,
3558
3617
  };
3559
3618
  }
3560
3619
  /**
@@ -3598,13 +3657,13 @@ class AxonFlow {
3598
3657
  const response = await this.orchestratorRequest('GET', `/api/v1/budgets/${budgetId}/status`);
3599
3658
  return {
3600
3659
  budget: this.mapBudgetResponse(response.budget),
3601
- usedUsd: response.used_usd || 0,
3602
- remainingUsd: response.remaining_usd || 0,
3603
- percentage: response.percentage || 0,
3604
- isExceeded: response.is_exceeded || false,
3605
- isBlocked: response.is_blocked || false,
3606
- periodStart: response.period_start || '',
3607
- periodEnd: response.period_end || '',
3660
+ usedUsd: response.used_usd ?? 0,
3661
+ remainingUsd: response.remaining_usd ?? 0,
3662
+ percentage: response.percentage ?? 0,
3663
+ isExceeded: response.is_exceeded ?? false,
3664
+ isBlocked: response.is_blocked ?? false,
3665
+ periodStart: response.period_start ?? '',
3666
+ periodEnd: response.period_end ?? '',
3608
3667
  };
3609
3668
  }
3610
3669
  /**
@@ -3615,19 +3674,20 @@ class AxonFlow {
3615
3674
  */
3616
3675
  async getBudgetAlerts(budgetId) {
3617
3676
  const response = await this.orchestratorRequest('GET', `/api/v1/budgets/${budgetId}/alerts`);
3618
- const alerts = (response.alerts || []).map((a) => ({
3619
- id: a.id || '',
3620
- budgetId: a.budget_id || '',
3621
- alertType: a.alert_type || '',
3622
- threshold: a.threshold || 0,
3623
- percentageReached: a.percentage_reached || 0,
3624
- amountUsd: a.amount_usd || 0,
3625
- message: a.message || '',
3626
- createdAt: a.created_at || '',
3677
+ const alerts = (response.alerts ?? []).map((a) => ({
3678
+ id: a.id ?? '',
3679
+ budgetId: a.budget_id ?? '',
3680
+ alertType: a.alert_type ?? '',
3681
+ threshold: a.threshold ?? 0,
3682
+ percentageReached: a.percentage_reached ?? 0,
3683
+ amountUsd: a.amount_usd ?? 0,
3684
+ message: a.message ?? '',
3685
+ createdAt: a.created_at ?? '',
3686
+ acknowledged: a.acknowledged,
3627
3687
  }));
3628
3688
  return {
3629
3689
  alerts,
3630
- count: response.count || 0,
3690
+ count: response.count ?? 0,
3631
3691
  };
3632
3692
  }
3633
3693
  /**
@@ -3650,11 +3710,11 @@ class AxonFlow {
3650
3710
  body.user_id = request.userId;
3651
3711
  const response = await this.orchestratorRequest('POST', '/api/v1/budgets/check', body);
3652
3712
  return {
3653
- allowed: response.allowed || false,
3713
+ allowed: response.allowed ?? false,
3654
3714
  action: response.action,
3655
3715
  message: response.message,
3656
3716
  budgets: response.budgets
3657
- ? (response.budgets || []).map(b => this.mapBudgetResponse(b))
3717
+ ? (response.budgets ?? []).map(b => this.mapBudgetResponse(b))
3658
3718
  : undefined,
3659
3719
  };
3660
3720
  }
@@ -3671,14 +3731,14 @@ class AxonFlow {
3671
3731
  const path = period ? `/api/v1/usage?period=${period}` : '/api/v1/usage';
3672
3732
  const response = await this.orchestratorRequest('GET', path);
3673
3733
  return {
3674
- totalCostUsd: response.total_cost_usd || 0,
3675
- totalRequests: response.total_requests || 0,
3676
- totalTokensIn: response.total_tokens_in || 0,
3677
- totalTokensOut: response.total_tokens_out || 0,
3678
- averageCostPerRequest: response.average_cost_per_request || 0,
3679
- period: response.period || '',
3680
- periodStart: response.period_start || '',
3681
- periodEnd: response.period_end || '',
3734
+ totalCostUsd: response.total_cost_usd ?? 0,
3735
+ totalRequests: response.total_requests ?? 0,
3736
+ totalTokensIn: response.total_tokens_in ?? 0,
3737
+ totalTokensOut: response.total_tokens_out ?? 0,
3738
+ averageCostPerRequest: response.average_cost_per_request ?? 0,
3739
+ period: response.period ?? '',
3740
+ periodStart: response.period_start ?? '',
3741
+ periodEnd: response.period_end ?? '',
3682
3742
  };
3683
3743
  }
3684
3744
  /**
@@ -3694,21 +3754,22 @@ class AxonFlow {
3694
3754
  if (period)
3695
3755
  params.set('period', period);
3696
3756
  const response = await this.orchestratorRequest('GET', `/api/v1/usage/breakdown?${params.toString()}`);
3697
- const items = (response.items || []).map((i) => ({
3698
- groupValue: i.group_value || '',
3699
- costUsd: i.cost_usd || 0,
3700
- percentage: i.percentage || 0,
3701
- requestCount: i.request_count || 0,
3702
- tokensIn: i.tokens_in || 0,
3703
- tokensOut: i.tokens_out || 0,
3757
+ const items = (response.items ?? []).map((i) => ({
3758
+ groupBy: i.group_by,
3759
+ groupValue: i.group_value ?? '',
3760
+ costUsd: i.cost_usd ?? 0,
3761
+ percentage: i.percentage ?? 0,
3762
+ requestCount: i.request_count ?? 0,
3763
+ tokensIn: i.tokens_in ?? 0,
3764
+ tokensOut: i.tokens_out ?? 0,
3704
3765
  }));
3705
3766
  return {
3706
- groupBy: response.group_by || '',
3707
- totalCostUsd: response.total_cost_usd || 0,
3767
+ groupBy: response.group_by ?? '',
3768
+ totalCostUsd: response.total_cost_usd ?? 0,
3708
3769
  items,
3709
- period: response.period || '',
3710
- periodStart: response.period_start || '',
3711
- periodEnd: response.period_end || '',
3770
+ period: response.period ?? '',
3771
+ periodStart: response.period_start ?? '',
3772
+ periodEnd: response.period_end ?? '',
3712
3773
  };
3713
3774
  }
3714
3775
  /**
@@ -3730,21 +3791,33 @@ class AxonFlow {
3730
3791
  const queryString = params.toString();
3731
3792
  const path = `/api/v1/usage/records${queryString ? `?${queryString}` : ''}`;
3732
3793
  const response = await this.orchestratorRequest('GET', path);
3733
- const records = (response.records || []).map((r) => ({
3734
- id: r.id || '',
3735
- provider: r.provider || '',
3736
- model: r.model || '',
3737
- tokensIn: r.tokens_in || 0,
3738
- tokensOut: r.tokens_out || 0,
3739
- costUsd: r.cost_usd || 0,
3794
+ const records = (response.records ?? []).map((r) => ({
3795
+ id: r.id ?? '',
3796
+ provider: r.provider ?? '',
3797
+ model: r.model ?? '',
3798
+ tokensIn: r.tokens_in ?? 0,
3799
+ tokensOut: r.tokens_out ?? 0,
3800
+ costUsd: r.cost_usd ?? 0,
3740
3801
  requestId: r.request_id,
3741
3802
  orgId: r.org_id,
3742
3803
  agentId: r.agent_id,
3804
+ // Wire-canonical fields surfaced in the v6 alignment sweep.
3805
+ // The `timestamp` legacy field below has always read undefined
3806
+ // because the server emits `created_at`; both kept populated
3807
+ // so existing readers of `timestamp` see no behavior change.
3808
+ created_at: r.created_at,
3809
+ success: r.success,
3810
+ error_message: r.error_message,
3811
+ latency_ms: r.latency_ms,
3812
+ team_id: r.team_id,
3813
+ tenant_id: r.tenant_id,
3814
+ user_id: r.user_id,
3815
+ workflow_id: r.workflow_id,
3743
3816
  timestamp: r.timestamp,
3744
3817
  }));
3745
3818
  return {
3746
3819
  records,
3747
- total: response.total || 0,
3820
+ total: response.total ?? 0,
3748
3821
  };
3749
3822
  }
3750
3823
  // ========================================
@@ -3772,7 +3845,7 @@ class AxonFlow {
3772
3845
  const pricing = this.mapPricingResponse(response);
3773
3846
  return { pricing: [pricing] };
3774
3847
  }
3775
- const pricingList = (response.pricing || []).map(p => this.mapPricingResponse(p));
3848
+ const pricingList = (response.pricing ?? []).map(p => this.mapPricingResponse(p));
3776
3849
  return { pricing: pricingList };
3777
3850
  }
3778
3851
  // ========================================
@@ -3780,15 +3853,17 @@ class AxonFlow {
3780
3853
  // ========================================
3781
3854
  mapBudgetResponse(response) {
3782
3855
  return {
3783
- id: response.id || '',
3784
- name: response.name || '',
3785
- scope: response.scope || '',
3786
- limitUsd: response.limit_usd || 0,
3787
- period: response.period || '',
3788
- onExceed: response.on_exceed || '',
3789
- alertThresholds: response.alert_thresholds || [],
3856
+ id: response.id ?? '',
3857
+ name: response.name ?? '',
3858
+ scope: response.scope ?? '',
3859
+ limitUsd: response.limit_usd ?? 0,
3860
+ period: response.period ?? '',
3861
+ onExceed: response.on_exceed ?? '',
3862
+ alertThresholds: response.alert_thresholds ?? [],
3790
3863
  enabled: response.enabled ?? true,
3791
3864
  scopeId: response.scope_id,
3865
+ tenant_id: response.tenant_id,
3866
+ org_id: response.org_id,
3792
3867
  createdAt: response.created_at,
3793
3868
  updatedAt: response.updated_at,
3794
3869
  };
@@ -3796,11 +3871,11 @@ class AxonFlow {
3796
3871
  mapPricingResponse(response) {
3797
3872
  const pricingData = response.pricing;
3798
3873
  return {
3799
- provider: response.provider || '',
3800
- model: response.model || '',
3874
+ provider: response.provider ?? '',
3875
+ model: response.model ?? '',
3801
3876
  pricing: {
3802
- inputPer1k: pricingData?.input_per_1k || 0,
3803
- outputPer1k: pricingData?.output_per_1k || 0,
3877
+ inputPer1k: pricingData?.input_per_1k ?? 0,
3878
+ outputPer1k: pricingData?.output_per_1k ?? 0,
3804
3879
  },
3805
3880
  };
3806
3881
  }
@@ -3997,7 +4072,7 @@ class AxonFlow {
3997
4072
  throw new errors_1.ConfigurationError('Step ID is required');
3998
4073
  }
3999
4074
  try {
4000
- await this.orchestratorRequest('POST', `/api/v1/workflows/${workflowId}/steps/${stepId}/complete`, request || {});
4075
+ await this.orchestratorRequest('POST', `/api/v1/workflows/${workflowId}/steps/${stepId}/complete`, request ?? {});
4001
4076
  }
4002
4077
  catch (err) {
4003
4078
  const idem = mapIdempotencyKeyMismatch(err);
@@ -4116,26 +4191,37 @@ class AxonFlow {
4116
4191
  /**
4117
4192
  * Approve a workflow step that requires human approval.
4118
4193
  *
4119
- * Call this to approve a step that was gated with a 'require_approval' decision.
4194
+ * The server requires `comment` with a minimum of 10 characters it's the
4195
+ * audit-trail justification that every approval carries into the workflow
4196
+ * history. Callers should always supply a meaningful comment.
4120
4197
  *
4121
4198
  * @param workflowId - ID of the workflow
4122
4199
  * @param stepId - ID of the step to approve
4200
+ * @param comment - Audit justification for the approval (min 10 chars)
4123
4201
  * @returns Approval response with status
4124
4202
  *
4125
4203
  * @example
4126
4204
  * ```typescript
4127
- * const result = await client.approveStep('wf_123', 'step_456');
4205
+ * const result = await client.approveStep(
4206
+ * 'wf_123',
4207
+ * 'step_456',
4208
+ * 'Approved after full audit review'
4209
+ * );
4128
4210
  * console.log(`Step ${result.step_id} status: ${result.status}`);
4129
4211
  * ```
4130
4212
  */
4131
- async approveStep(workflowId, stepId) {
4213
+ async approveStep(workflowId, stepId, comment) {
4132
4214
  if (!workflowId) {
4133
4215
  throw new errors_1.ConfigurationError('Workflow ID is required');
4134
4216
  }
4135
4217
  if (!stepId) {
4136
4218
  throw new errors_1.ConfigurationError('Step ID is required');
4137
4219
  }
4138
- return this.orchestratorRequest('POST', `/api/v1/workflow-control/${workflowId}/steps/${stepId}/approve`, {});
4220
+ const body = {};
4221
+ if (comment) {
4222
+ body.comment = comment;
4223
+ }
4224
+ return this.orchestratorRequest('POST', `/api/v1/workflows/${workflowId}/steps/${stepId}/approve`, body);
4139
4225
  }
4140
4226
  /**
4141
4227
  * Reject a workflow step that requires human approval.
@@ -4164,21 +4250,26 @@ class AxonFlow {
4164
4250
  if (reason) {
4165
4251
  body.reason = reason;
4166
4252
  }
4167
- return this.orchestratorRequest('POST', `/api/v1/workflow-control/${workflowId}/steps/${stepId}/reject`, body);
4253
+ return this.orchestratorRequest('POST', `/api/v1/workflows/${workflowId}/steps/${stepId}/reject`, body);
4168
4254
  }
4169
4255
  /**
4170
- * Get pending approvals for workflow steps.
4256
+ * Get pending approvals for workflow steps — the WCP-plane listing.
4257
+ *
4258
+ * Lists steps that are waiting for human approval across all planes for
4259
+ * the caller's tenant. Use {@link getPendingPlanApprovals} for the
4260
+ * MAP-plane listing (scopes to MAP-backed workflows and populates
4261
+ * `plan_id` on every entry).
4171
4262
  *
4172
- * Lists all steps that are waiting for human approval across all workflows.
4263
+ * Available on Evaluation+ licenses.
4173
4264
  *
4174
4265
  * @param options - Optional filtering options
4175
- * @returns List of pending approvals with total count
4266
+ * @returns List of pending approvals with count
4176
4267
  *
4177
4268
  * @example
4178
4269
  * ```typescript
4179
4270
  * const pending = await client.getPendingApprovals({ limit: 10 });
4180
- * console.log(`${pending.total} approvals pending`);
4181
- * for (const approval of pending.approvals) {
4271
+ * console.log(`${pending.count} approvals pending`);
4272
+ * for (const approval of pending.pending_approvals) {
4182
4273
  * console.log(`${approval.workflow_name} / ${approval.step_name}`);
4183
4274
  * }
4184
4275
  * ```
@@ -4190,8 +4281,44 @@ class AxonFlow {
4190
4281
  }
4191
4282
  const queryString = params.toString();
4192
4283
  const path = queryString
4193
- ? `/api/v1/workflow-control/pending-approvals?${queryString}`
4194
- : '/api/v1/workflow-control/pending-approvals';
4284
+ ? `/api/v1/workflows/approvals/pending?${queryString}`
4285
+ : '/api/v1/workflows/approvals/pending';
4286
+ return this.orchestratorRequest('GET', path);
4287
+ }
4288
+ /**
4289
+ * List pending approvals for MAP-backed workflows — the MAP-plane
4290
+ * counterpart of {@link getPendingApprovals}. Every entry has `plan_id`
4291
+ * populated. Pass `options.plan_id` to scope the listing to a single plan.
4292
+ *
4293
+ * Requires an Evaluation or Enterprise license (same tier gate as the
4294
+ * MAP step approve/reject endpoints).
4295
+ *
4296
+ * @param options - Optional filtering options (`limit`, `plan_id`)
4297
+ * @returns List of MAP-plane pending approvals with count
4298
+ *
4299
+ * @example
4300
+ * ```typescript
4301
+ * const pending = await client.getPendingPlanApprovals({
4302
+ * plan_id: 'plan-abc123',
4303
+ * limit: 10,
4304
+ * });
4305
+ * for (const approval of pending.pending_approvals) {
4306
+ * console.log(`Plan ${approval.plan_id} step ${approval.step_id} awaiting approval`);
4307
+ * }
4308
+ * ```
4309
+ */
4310
+ async getPendingPlanApprovals(options) {
4311
+ const params = new URLSearchParams();
4312
+ if (options?.limit !== undefined) {
4313
+ params.set('limit', options.limit.toString());
4314
+ }
4315
+ if (options?.plan_id) {
4316
+ params.set('plan_id', options.plan_id);
4317
+ }
4318
+ const queryString = params.toString();
4319
+ const path = queryString
4320
+ ? `/api/v1/plans/approvals/pending?${queryString}`
4321
+ : '/api/v1/plans/approvals/pending';
4195
4322
  return this.orchestratorRequest('GET', path);
4196
4323
  }
4197
4324
  // =============================================================================
@@ -4231,7 +4358,7 @@ class AxonFlow {
4231
4358
  (0, helpers_1.debugLog)('Plan rolled back', { planId, version: data.version });
4232
4359
  }
4233
4360
  return {
4234
- planId: data.plan_id || planId,
4361
+ planId: data.plan_id ?? planId,
4235
4362
  version: data.version,
4236
4363
  previousVersion: data.previous_version,
4237
4364
  status: data.status,
@@ -4497,7 +4624,7 @@ class AxonFlow {
4497
4624
  throw new errors_1.APIError(response.status, response.statusText, errorText);
4498
4625
  }
4499
4626
  const data = await response.json();
4500
- return (data || []).map((s) => this.mapSystemResponse(s));
4627
+ return (data ?? []).map((s) => this.mapSystemResponse(s));
4501
4628
  }
4502
4629
  async masfeatActivateSystem(systemId) {
4503
4630
  // Use PUT to update status - the /activate endpoint doesn't exist
@@ -4552,8 +4679,8 @@ class AxonFlow {
4552
4679
  highMaterialityCount: data.high_materiality_count ?? data.high_materiality ?? 0,
4553
4680
  mediumMaterialityCount: data.medium_materiality_count ?? data.medium_materiality ?? 0,
4554
4681
  lowMaterialityCount: data.low_materiality_count ?? data.low_materiality ?? 0,
4555
- byUseCase: data.by_use_case || {},
4556
- byStatus: data.by_status || {},
4682
+ byUseCase: data.by_use_case ?? {},
4683
+ byStatus: data.by_status ?? {},
4557
4684
  };
4558
4685
  }
4559
4686
  // Assessment Methods
@@ -4561,7 +4688,7 @@ class AxonFlow {
4561
4688
  const url = `${this.config.endpoint}/api/v1/masfeat/assessments`;
4562
4689
  const body = {
4563
4690
  system_id: request.systemId,
4564
- assessment_type: request.assessmentType || 'periodic',
4691
+ assessment_type: request.assessmentType ?? 'periodic',
4565
4692
  assessors: request.assessors,
4566
4693
  };
4567
4694
  if (request.assessmentDate)
@@ -4700,7 +4827,7 @@ class AxonFlow {
4700
4827
  throw new errors_1.APIError(response.status, response.statusText, errorText);
4701
4828
  }
4702
4829
  const data = await response.json();
4703
- return (data || []).map((a) => this.mapAssessmentResponse(a));
4830
+ return (data ?? []).map((a) => this.mapAssessmentResponse(a));
4704
4831
  }
4705
4832
  async masfeatSubmitAssessment(assessmentId) {
4706
4833
  const url = `${this.config.endpoint}/api/v1/masfeat/assessments/${assessmentId}/submit`;
@@ -4916,20 +5043,20 @@ class AxonFlow {
4916
5043
  if (data && typeof data === 'object' && 'history' in data) {
4917
5044
  data = data.history;
4918
5045
  }
4919
- return (data || []).map((e) => ({
5046
+ return (data ?? []).map((e) => ({
4920
5047
  id: e.id,
4921
5048
  killSwitchId: e.kill_switch_id,
4922
5049
  // Handle both API formats: event_type (SDK expected) vs action (API actual)
4923
- eventType: e.event_type || e.action,
5050
+ eventType: e.event_type ?? e.action,
4924
5051
  // Build eventData from additional fields if not present
4925
- eventData: e.event_data ||
4926
- (e.previous_status || e.new_status || e.reason
5052
+ eventData: e.event_data ??
5053
+ ((e.previous_status ?? e.new_status ?? e.reason)
4927
5054
  ? { previousStatus: e.previous_status, newStatus: e.new_status, reason: e.reason }
4928
5055
  : undefined),
4929
5056
  // Handle both API formats: created_by vs performed_by
4930
- createdBy: e.created_by || e.performed_by,
5057
+ createdBy: e.created_by ?? e.performed_by,
4931
5058
  // Handle both API formats: created_at vs performed_at
4932
- createdAt: new Date(e.created_at || e.performed_at),
5059
+ createdAt: new Date(e.created_at ?? e.performed_at),
4933
5060
  }));
4934
5061
  }
4935
5062
  // Helper methods for MAS FEAT
@@ -4943,7 +5070,7 @@ class AxonFlow {
4943
5070
  useCase: data.use_case,
4944
5071
  ownerTeam: data.owner_team,
4945
5072
  technicalOwner: data.technical_owner,
4946
- businessOwner: data.business_owner || data.owner_email,
5073
+ businessOwner: data.business_owner ?? data.owner_email,
4947
5074
  customerImpact: data.customer_impact ?? data.risk_rating_impact,
4948
5075
  modelComplexity: data.model_complexity ?? data.risk_rating_complexity,
4949
5076
  humanReliance: data.human_reliance ?? data.risk_rating_reliance,
@@ -4964,6 +5091,7 @@ class AxonFlow {
4964
5091
  description: data.description,
4965
5092
  status: data.status,
4966
5093
  remediation: data.remediation,
5094
+ article: data.article,
4967
5095
  dueDate: data.due_date ? new Date(data.due_date) : undefined,
4968
5096
  };
4969
5097
  }
@@ -5011,7 +5139,7 @@ class AxonFlow {
5011
5139
  autoTriggerEnabled: data.auto_trigger_enabled,
5012
5140
  triggeredAt: data.triggered_at ? new Date(data.triggered_at) : undefined,
5013
5141
  triggeredBy: data.triggered_by,
5014
- triggeredReason: data.triggered_reason || data.trigger_reason,
5142
+ triggeredReason: data.triggered_reason ?? data.trigger_reason,
5015
5143
  restoredAt: data.restored_at ? new Date(data.restored_at) : undefined,
5016
5144
  restoredBy: data.restored_by,
5017
5145
  createdAt: new Date(data.created_at),
@@ -5184,7 +5312,7 @@ class AxonFlow {
5184
5312
  }
5185
5313
  const response = await this.orchestratorRequest('GET', path);
5186
5314
  return {
5187
- items: response.data || [],
5315
+ items: response.data ?? [],
5188
5316
  total: response.meta?.total ?? 0,
5189
5317
  has_more: (response.meta?.offset ?? 0) + (response.data?.length ?? 0) < (response.meta?.total ?? 0),
5190
5318
  };
@@ -5489,7 +5617,7 @@ class AxonFlow {
5489
5617
  // Process complete SSE events (separated by double newline)
5490
5618
  const events = buffer.split('\n\n');
5491
5619
  // Keep the last (potentially incomplete) chunk in the buffer
5492
- buffer = events.pop() || '';
5620
+ buffer = events.pop() ?? '';
5493
5621
  for (const event of events) {
5494
5622
  const trimmed = event.trim();
5495
5623
  if (!trimmed) {