@aifabrix/builder 2.44.6 → 2.45.5

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 (257) hide show
  1. package/.cursor/rules/cli-layout.mdc +7 -3
  2. package/.cursor/rules/docs-rules.mdc +3 -1
  3. package/integration/hubspot-test/test-artifacts/wizard-hubspot-credential-real.yaml +1 -1
  4. package/integration/hubspot-test/test-artifacts/wizard-hubspot-env-vars.yaml +1 -1
  5. package/integration/hubspot-test/wizard-hubspot-e2e.yaml +1 -1
  6. package/jest.projects.js +100 -1
  7. package/lib/api/agent-metadata-validation.api.js +93 -0
  8. package/lib/api/audit-api-helpers.js +68 -0
  9. package/lib/api/audit.api.js +119 -0
  10. package/lib/api/auth-cache.api.js +44 -0
  11. package/lib/api/certificates.api.js +4 -4
  12. package/lib/api/credential.api.js +2 -2
  13. package/lib/api/dataplane-health.api.js +137 -0
  14. package/lib/api/dataplane-sync.api.js +27 -0
  15. package/lib/api/datasources-core.api.js +21 -18
  16. package/lib/api/datasources-extended.api.js +22 -22
  17. package/lib/api/dimensions.api.js +3 -0
  18. package/lib/api/external-systems.api.js +13 -13
  19. package/lib/api/governance-scenarios.api.js +39 -0
  20. package/lib/api/groups.api.js +79 -0
  21. package/lib/api/index.js +45 -2
  22. package/lib/api/pipeline.api.js +4 -4
  23. package/lib/api/protection.api.js +157 -0
  24. package/lib/api/records-bulk.api.js +30 -0
  25. package/lib/api/records-search-parse.js +49 -0
  26. package/lib/api/records-search.api.js +29 -0
  27. package/lib/api/types/agent-metadata-validation.types.js +67 -0
  28. package/lib/api/types/audit.types.js +72 -0
  29. package/lib/api/types/dataplane-health.types.js +27 -0
  30. package/lib/api/types/dimensions.types.js +2 -0
  31. package/lib/api/types/governance-scenarios.types.js +39 -0
  32. package/lib/api/types/protection.types.js +69 -0
  33. package/lib/api/types/records-bulk.types.js +35 -0
  34. package/lib/api/types/records-search.types.js +28 -0
  35. package/lib/api/user-groups.api.js +50 -0
  36. package/lib/api/users.api.js +95 -0
  37. package/lib/api/validation-run.api.js +3 -3
  38. package/lib/api/validation-runner.js +3 -3
  39. package/lib/api/wizard-platform.api.js +3 -3
  40. package/lib/api/wizard.api.js +21 -21
  41. package/lib/app/deploy.js +34 -19
  42. package/lib/app/helpers.js +3 -3
  43. package/lib/app/index.js +3 -3
  44. package/lib/app/register.js +7 -6
  45. package/lib/app/restart-display.js +52 -21
  46. package/lib/app/rotate-secret.js +7 -6
  47. package/lib/app/run-helpers.js +15 -8
  48. package/lib/app/run.js +57 -9
  49. package/lib/app/show-display.js +7 -0
  50. package/lib/app/show.js +87 -5
  51. package/lib/build/index.js +9 -5
  52. package/lib/cli/index.js +4 -2
  53. package/lib/cli/infra-guided.js +62 -28
  54. package/lib/cli/installation-log-command.js +73 -0
  55. package/lib/cli/setup-app.help.js +50 -2
  56. package/lib/cli/setup-app.js +44 -25
  57. package/lib/cli/setup-app.test-commands.js +80 -21
  58. package/lib/cli/setup-auth.js +124 -52
  59. package/lib/cli/setup-external-system.js +37 -3
  60. package/lib/cli/setup-infra-up-dataplane-action.js +111 -0
  61. package/lib/cli/setup-infra-up-platform-action.js +131 -0
  62. package/lib/cli/setup-infra.js +60 -119
  63. package/lib/cli/setup-platform.js +1 -1
  64. package/lib/cli/setup-utility-help-after.js +45 -0
  65. package/lib/cli/setup-utility-resolve.js +132 -0
  66. package/lib/cli/setup-utility.js +148 -116
  67. package/lib/commands/app-logs.js +81 -33
  68. package/lib/commands/auth-config.js +116 -18
  69. package/lib/commands/auth-status-dataplane-version.js +231 -0
  70. package/lib/commands/auth-status-display.js +94 -0
  71. package/lib/commands/auth-status.js +70 -6
  72. package/lib/commands/credential-push.js +10 -5
  73. package/lib/commands/datasource-load-export-cli.js +178 -0
  74. package/lib/commands/datasource-test-trust-cli.js +114 -0
  75. package/lib/commands/datasource-unified-test-cli.js +25 -17
  76. package/lib/commands/datasource-unified-test-cli.options.js +18 -3
  77. package/lib/commands/datasource-unified-test-e2e-cli-helpers.js +5 -5
  78. package/lib/commands/datasource-verify-audit-cli.js +96 -0
  79. package/lib/commands/datasource.js +68 -1
  80. package/lib/commands/dimension-value.js +43 -13
  81. package/lib/commands/dimension.js +38 -7
  82. package/lib/commands/identity-apply-sync.js +131 -0
  83. package/lib/commands/identity-group-cli.js +146 -0
  84. package/lib/commands/identity-membership-role-cache.js +175 -0
  85. package/lib/commands/identity-shared.js +94 -0
  86. package/lib/commands/identity-user-cli.js +158 -0
  87. package/lib/commands/identity.js +43 -0
  88. package/lib/commands/login.js +46 -16
  89. package/lib/commands/protection-cli-leaves.js +286 -0
  90. package/lib/commands/protection.js +50 -0
  91. package/lib/commands/repair.js +13 -3
  92. package/lib/commands/setup-modes.js +19 -6
  93. package/lib/commands/setup-prompts.js +41 -8
  94. package/lib/commands/setup.js +114 -9
  95. package/lib/commands/teardown.js +54 -5
  96. package/lib/commands/test-e2e-external.js +40 -9
  97. package/lib/commands/test-governance-cli-options.js +47 -0
  98. package/lib/commands/test-governance-command-action.js +55 -0
  99. package/lib/commands/test-governance-external.js +88 -0
  100. package/lib/commands/test-trust-cli-options.js +32 -0
  101. package/lib/commands/test-trust-command-action.js +118 -0
  102. package/lib/commands/test-trust-external.js +127 -0
  103. package/lib/commands/up-common.js +48 -14
  104. package/lib/commands/up-dataplane.js +21 -18
  105. package/lib/commands/up-miso.js +12 -8
  106. package/lib/commands/upload.js +38 -15
  107. package/lib/commands/wizard-core.js +3 -0
  108. package/lib/core/audit-logger.js +1 -34
  109. package/lib/core/config-admin-email.js +56 -0
  110. package/lib/core/config-device-dataplane.js +185 -0
  111. package/lib/core/config-normalize.js +60 -0
  112. package/lib/core/config-registered-controller-urls.js +54 -0
  113. package/lib/core/config.js +33 -50
  114. package/lib/core/secrets-ensure-infra.js +1 -1
  115. package/lib/core/secrets-env-content.js +86 -90
  116. package/lib/core/secrets-env-declarative-expand.js +170 -0
  117. package/lib/core/secrets-env-write.js +2 -0
  118. package/lib/core/secrets-load.js +106 -102
  119. package/lib/datasource/agent-trust-debug-log.js +45 -0
  120. package/lib/datasource/agent-trust-fetch.js +115 -0
  121. package/lib/datasource/agent-trust-map.js +101 -0
  122. package/lib/datasource/agent-trust-mock.js +44 -0
  123. package/lib/datasource/agent-trust-run.js +93 -0
  124. package/lib/datasource/agent-trust-timeout.js +25 -0
  125. package/lib/datasource/audit-evidence-display.js +80 -0
  126. package/lib/datasource/audit-evidence-extract-debug.js +103 -0
  127. package/lib/datasource/audit-evidence-extract.js +177 -0
  128. package/lib/datasource/audit-evidence-matrix-rows.js +158 -0
  129. package/lib/datasource/audit-evidence-operation-aliases.js +63 -0
  130. package/lib/datasource/audit-evidence-poll.js +109 -0
  131. package/lib/datasource/audit-evidence-run.js +177 -0
  132. package/lib/datasource/audit-evidence-verify-helpers.js +167 -0
  133. package/lib/datasource/audit-evidence-verify-matrix.js +177 -0
  134. package/lib/datasource/audit-evidence-verify.js +108 -0
  135. package/lib/datasource/bulk-loader-run-batches.js +88 -0
  136. package/lib/datasource/bulk-loader-service-retry.js +52 -0
  137. package/lib/datasource/bulk-loader-service.js +127 -0
  138. package/lib/datasource/capability/run-capability-dimension.js +2 -1
  139. package/lib/datasource/datasource-exporter-resolve.js +82 -0
  140. package/lib/datasource/datasource-exporter-service.js +144 -0
  141. package/lib/datasource/datasource-identifier.js +148 -0
  142. package/lib/datasource/datasource-load-export-context.js +92 -0
  143. package/lib/datasource/integration-context.js +6 -4
  144. package/lib/datasource/local-data-paths.js +145 -0
  145. package/lib/datasource/log-cleaner.js +241 -0
  146. package/lib/datasource/log-viewer.js +111 -6
  147. package/lib/datasource/record-file-parser.js +85 -0
  148. package/lib/datasource/record-mapper.js +129 -0
  149. package/lib/datasource/resolve-app.js +74 -55
  150. package/lib/datasource/test-e2e.js +26 -2
  151. package/lib/datasource/unified-validation-run-resolve.js +45 -12
  152. package/lib/datasource/unified-validation-run.js +43 -17
  153. package/lib/datasource/validate.js +37 -20
  154. package/lib/external-system/deploy.js +5 -1
  155. package/lib/external-system/test-system-level.js +25 -4
  156. package/lib/external-system/test.js +3 -1
  157. package/lib/governance/governance-pack-loader.js +70 -0
  158. package/lib/governance/governance-run-display.js +119 -0
  159. package/lib/identity/identity-apply-core.js +108 -0
  160. package/lib/identity/identity-apply-phases.js +244 -0
  161. package/lib/identity/identity-apply-service.js +77 -0
  162. package/lib/identity/identity-csv-parser.js +114 -0
  163. package/lib/internal/fs-real-sync.js +29 -1
  164. package/lib/internal/node-fs.js +2 -0
  165. package/lib/protection/auth-context.js +45 -0
  166. package/lib/protection/convert-batch.js +90 -0
  167. package/lib/protection/load.js +41 -0
  168. package/lib/protection/paths.js +158 -0
  169. package/lib/protection/preflight-datasource-ready.js +46 -0
  170. package/lib/protection/protection-create-probe-lines.js +142 -0
  171. package/lib/protection/protection-create-probes.js +95 -0
  172. package/lib/protection/protection-create-scaffold.js +281 -0
  173. package/lib/protection/protection-display-helpers.js +445 -0
  174. package/lib/protection/protection-display.js +215 -0
  175. package/lib/protection/protection-preset-registry.js +97 -0
  176. package/lib/protection/report-exit.js +62 -0
  177. package/lib/protection/resolve.js +202 -0
  178. package/lib/protection/run-commands-validate.js +130 -0
  179. package/lib/protection/run-commands.js +216 -0
  180. package/lib/protection/run-protection-create-helpers.js +134 -0
  181. package/lib/protection/run-protection-create-probes-flow.js +43 -0
  182. package/lib/protection/run-protection-create.js +191 -0
  183. package/lib/protection/scope.js +22 -0
  184. package/lib/protection/sync-after-upload.js +64 -0
  185. package/lib/protection/upload-batch.js +127 -0
  186. package/lib/protection/validate-batch.js +113 -0
  187. package/lib/protection/validate-local.js +65 -0
  188. package/lib/resolvers/dimension-file.js +29 -4
  189. package/lib/schema/application-schema.json +4 -0
  190. package/lib/schema/external-datasource.schema.json +47 -13
  191. package/lib/schema/flag-map-validation-run.json +2 -2
  192. package/lib/schema/infra.parameter.yaml +10 -0
  193. package/lib/schema/protection.schema.json +374 -0
  194. package/lib/schema/type/credential-field-registry.json +636 -0
  195. package/lib/schema/wizard-config.schema.json +1 -1
  196. package/lib/utils/agent-trust-run-display.js +231 -0
  197. package/lib/utils/agent-trust-run-exit.js +59 -0
  198. package/lib/utils/agent-trust-wait-ui.js +94 -0
  199. package/lib/utils/app-config-resolver.js +24 -1
  200. package/lib/utils/applications-config-defaults.js +206 -0
  201. package/lib/utils/auth-config-validator.js +2 -12
  202. package/lib/utils/bash-secret-env.js +1 -1
  203. package/lib/utils/cli-exit-codes.js +30 -0
  204. package/lib/utils/cli-sync-options.js +33 -0
  205. package/lib/utils/compose-generate-docker-compose.js +111 -6
  206. package/lib/utils/compose-generator.js +17 -8
  207. package/lib/utils/controller-url.js +50 -7
  208. package/lib/utils/credential-push-ui.js +90 -0
  209. package/lib/utils/dataplane-cli-version-gate.js +202 -0
  210. package/lib/utils/dataplane-cli-version-help.js +74 -0
  211. package/lib/utils/datasource-export-display.js +137 -0
  212. package/lib/utils/datasource-load-display.js +174 -0
  213. package/lib/utils/datasource-load-export-ui.js +120 -0
  214. package/lib/utils/datasource-test-run-capability-scope.js +3 -3
  215. package/lib/utils/datasource-test-run-display.js +3 -3
  216. package/lib/utils/datasource-test-run-tty-log.js +5 -1
  217. package/lib/utils/env-copy.js +99 -14
  218. package/lib/utils/env-template.js +5 -1
  219. package/lib/utils/health-check-url.js +18 -15
  220. package/lib/utils/health-check.js +7 -5
  221. package/lib/utils/help-builder.js +30 -24
  222. package/lib/utils/infra-optional-service-flags.js +69 -0
  223. package/lib/utils/installation-log-core.js +282 -0
  224. package/lib/utils/installation-log-record.js +237 -0
  225. package/lib/utils/installation-log.js +123 -0
  226. package/lib/utils/log-redaction.js +105 -0
  227. package/lib/utils/manifest-location.js +164 -0
  228. package/lib/utils/manifest-source-emit.js +184 -0
  229. package/lib/utils/paths.js +238 -89
  230. package/lib/utils/remote-secrets-loader.js +7 -1
  231. package/lib/utils/run-cli-flags.js +29 -0
  232. package/lib/utils/secrets-canonical.js +10 -3
  233. package/lib/utils/secrets-path.js +3 -4
  234. package/lib/utils/secrets-utils.js +20 -10
  235. package/lib/utils/semver-compare.js +127 -0
  236. package/lib/utils/system-builder-root.js +10 -2
  237. package/lib/utils/upload-pipeline-ui.js +91 -0
  238. package/lib/utils/url-declarative-public-base.js +80 -12
  239. package/lib/utils/url-declarative-resolve-build-urls.js +238 -0
  240. package/lib/utils/url-declarative-resolve-build.js +24 -393
  241. package/lib/utils/url-declarative-resolve-expand-token.js +189 -0
  242. package/lib/utils/url-declarative-resolve-load-doc.js +12 -3
  243. package/lib/utils/url-declarative-resolve-surface-state.js +102 -0
  244. package/lib/utils/url-declarative-resolve.js +47 -7
  245. package/lib/utils/url-declarative-runtime-base-path.js +21 -1
  246. package/lib/utils/urls-local-registry-scan.js +103 -0
  247. package/lib/utils/urls-local-registry.js +161 -90
  248. package/lib/utils/validation-poll-ui.js +14 -7
  249. package/lib/validation/wizard-config-validator.js +58 -19
  250. package/package.json +3 -1
  251. package/templates/applications/dataplane/application.yaml +4 -0
  252. package/templates/applications/dataplane/env.template +13 -1
  253. package/templates/applications/dataplane/rbac.yaml +4 -0
  254. package/templates/applications/miso-controller/application.yaml +2 -0
  255. package/templates/applications/miso-controller/env.template +27 -29
  256. package/templates/external-system/README.md.hbs +56 -8
  257. package/.npmrc.token +0 -1
@@ -1,7 +1,9 @@
1
1
  /**
2
- * Secrets loading cascade (user file, aifabrix-secrets file or remote API, builder YAML, defaults).
2
+ * Secrets loading: primary user file plus configured `aifabrix-secrets` (shared YAML or remote API).
3
3
  *
4
- * @fileoverview Split from secrets.js for module size limits
4
+ * @fileoverview Default `kv://` resolution uses only `~/.aifabrix/secrets.local.yaml` and `aifabrix-secrets`
5
+ * from config — not cwd-ancestor `.aifabrix/secrets.local.yaml`, not `builder/secrets.local.yaml`,
6
+ * and not `~/.aifabrix/secrets.yaml`.
5
7
  * @author AI Fabrix Team
6
8
  * @version 1.0.0
7
9
  */
@@ -13,27 +15,75 @@ const path = require('path');
13
15
  const config = require('./config');
14
16
  const { readYamlAtPath, applyCanonicalSecretsOverride } = require('../utils/secrets-canonical');
15
17
  const { ensureSecureFilePermissions } = require('../utils/secure-file-permissions');
18
+ const { resolveSecretsPath } = require('../utils/secrets-path');
16
19
  const {
17
- resolveSecretsPath
18
- } = require('../utils/secrets-path');
19
- const {
20
- loadUserSecrets,
21
20
  loadPrimaryUserSecrets,
22
21
  loadDefaultSecrets,
23
22
  ensurePrimaryUserSecretsFileExists
24
23
  } = require('../utils/secrets-utils');
25
- const { decryptSecret, isEncrypted } = require('../utils/secrets-encryption');
26
24
  const pathsUtil = require('../utils/paths');
27
- const { collectAncestorAifabrixSecretsLocalYamlPaths } = require('../utils/secrets-ancestor-paths');
25
+ const { decryptSecret, isEncrypted } = require('../utils/secrets-encryption');
26
+ const logger = require('../utils/logger');
27
+ const { isSecretKeyAllowedEmpty } = require('./secrets-ensure-infra');
28
+
29
+ /** Dedupe optional decrypt warnings across repeated `loadSecrets` (and duplicate module instances). */
30
+ function optionalDecryptWarnSeen(key) {
31
+ const g = globalThis;
32
+ if (!g.__aifabrixOptionalDecryptWarnOnce) {
33
+ g.__aifabrixOptionalDecryptWarnOnce = new Set();
34
+ }
35
+ const set = g.__aifabrixOptionalDecryptWarnOnce;
36
+ if (set.has(key)) return true;
37
+ set.add(key);
38
+ return false;
39
+ }
40
+
41
+ /**
42
+ * @param {string} key
43
+ * @param {unknown} value
44
+ * @param {string} encryptionKey
45
+ * @param {Record<string, string>} decryptedSecrets
46
+ * @param {string} [sourceHint] - File path or API label for decrypt errors / warnings
47
+ */
48
+ function mergeDecryptedEntry(key, value, encryptionKey, decryptedSecrets, sourceHint) {
49
+ if (!isEncrypted(value)) {
50
+ decryptedSecrets[key] = value;
51
+ return;
52
+ }
53
+ try {
54
+ decryptedSecrets[key] = decryptSecret(value, encryptionKey);
55
+ } catch (error) {
56
+ const msg = error && error.message ? error.message : String(error);
57
+ if (!isSecretKeyAllowedEmpty(key)) {
58
+ const where =
59
+ sourceHint && String(sourceHint).trim().length > 0
60
+ ? ` (encrypted value loaded from: ${sourceHint})`
61
+ : ' (encrypted value source not recorded; check ~/.aifabrix/secrets.local.yaml and aifabrix-secrets)';
62
+ throw new Error(`Failed to decrypt secret '${key}'${where}: ${msg}`);
63
+ }
64
+ if (!optionalDecryptWarnSeen(key)) {
65
+ const where =
66
+ sourceHint && String(sourceHint).trim().length > 0
67
+ ? ` Encrypted value loaded from: ${sourceHint}.`
68
+ : '';
69
+ logger.warn(
70
+ `Optional secret '${key}' could not be decrypted (${msg}). Treating as empty.${where} ` +
71
+ 'Remove the stale key from shared or local secrets, or fix secrets-encryption alignment.'
72
+ );
73
+ }
74
+ decryptedSecrets[key] = '';
75
+ }
76
+ }
28
77
 
29
78
  /**
30
79
  * Decrypts encrypted values in secrets object
31
80
  *
32
81
  * @async
33
82
  * @param {Object} secrets - Secrets object with potentially encrypted values
83
+ * @param {{ keySources?: Record<string, string>, defaultSourceLabel?: string }} [options] - Per-key file/API path for errors
34
84
  * @returns {Promise<Object>} Secrets object with decrypted values
35
85
  */
36
- async function decryptSecretsObject(secrets) {
86
+ async function decryptSecretsObject(secrets, options) {
37
87
  if (!secrets || typeof secrets !== 'object') {
38
88
  return secrets;
39
89
  }
@@ -49,17 +99,13 @@ async function decryptSecretsObject(secrets) {
49
99
  return secrets;
50
100
  }
51
101
 
102
+ const keySources = options && options.keySources ? options.keySources : null;
103
+ const defaultLabel = options && options.defaultSourceLabel ? options.defaultSourceLabel : '';
104
+
52
105
  const decryptedSecrets = {};
53
106
  for (const [key, value] of Object.entries(secrets)) {
54
- if (isEncrypted(value)) {
55
- try {
56
- decryptedSecrets[key] = decryptSecret(value, encryptionKey);
57
- } catch (error) {
58
- throw new Error(`Failed to decrypt secret '${key}': ${error.message}`);
59
- }
60
- } else {
61
- decryptedSecrets[key] = value;
62
- }
107
+ const sourceHint = (keySources && keySources[key]) || defaultLabel || '';
108
+ mergeDecryptedEntry(key, value, encryptionKey, decryptedSecrets, sourceHint);
63
109
  }
64
110
 
65
111
  return decryptedSecrets;
@@ -67,8 +113,9 @@ async function decryptSecretsObject(secrets) {
67
113
 
68
114
  /**
69
115
  * Merges config file secrets into user secrets (user wins). Returns null if path missing or config empty.
116
+ * @param {Record<string, string>} [keySources] - Mutated: path for keys filled from this file
70
117
  */
71
- function mergeUserWithConfigFile(userSecrets, resolvedConfigPath) {
118
+ function mergeUserWithConfigFile(userSecrets, resolvedConfigPath, keySources) {
72
119
  if (!fs.existsSync(resolvedConfigPath)) {
73
120
  return null;
74
121
  }
@@ -86,6 +133,9 @@ function mergeUserWithConfigFile(userSecrets, resolvedConfigPath) {
86
133
  for (const key of Object.keys(configSecrets)) {
87
134
  if (!(key in merged) || merged[key] === undefined || merged[key] === null || merged[key] === '') {
88
135
  merged[key] = configSecrets[key];
136
+ if (keySources) {
137
+ keySources[key] = resolvedConfigPath;
138
+ }
89
139
  }
90
140
  }
91
141
  return merged;
@@ -99,122 +149,74 @@ function createMergeHelpers(userSecrets) {
99
149
  };
100
150
  }
101
151
 
102
- async function mergeFromConfiguredSecretsPath(configSecretsPath, userSecrets, helpers) {
152
+ async function mergeFromConfiguredSecretsPath(configSecretsPath, userSecrets, helpers, keySources) {
103
153
  const remoteDevAuth = require('../utils/remote-dev-auth');
104
154
  const effectiveShared = await remoteDevAuth.resolveSharedSecretsEndpoint(configSecretsPath);
105
155
 
106
156
  if (remoteDevAuth.isRemoteSecretsUrl(effectiveShared)) {
107
157
  const { loadRemoteSharedSecrets, mergeUserWithRemoteSecrets } = require('../utils/remote-secrets-loader');
108
158
  const remoteSecrets = await loadRemoteSharedSecrets();
109
- const merged = mergeUserWithRemoteSecrets(userSecrets, remoteSecrets);
159
+ const remoteLabel = `shared secrets API (${effectiveShared})`;
160
+ const merged = mergeUserWithRemoteSecrets(userSecrets, remoteSecrets, keySources, remoteLabel);
110
161
  return helpers.hasKeys(merged) ? merged : helpers.userOrNull();
111
162
  }
112
163
 
113
164
  const resolvedConfigPath = path.isAbsolute(configSecretsPath)
114
165
  ? configSecretsPath
115
166
  : path.resolve(process.cwd(), configSecretsPath);
116
- const merged = mergeUserWithConfigFile(userSecrets, resolvedConfigPath);
167
+ const merged = mergeUserWithConfigFile(userSecrets, resolvedConfigPath, keySources);
117
168
  return merged !== null ? merged : helpers.userOrNull();
118
169
  }
119
170
 
120
171
  async function loadMergedConfigAndUserSecrets() {
121
172
  const userSecrets = loadPrimaryUserSecrets();
122
173
  const helpers = createMergeHelpers(userSecrets);
174
+ const userPath = pathsUtil.getPrimaryUserSecretsLocalPath();
175
+ /** @type {Record<string, string>} */
176
+ const keySources = {};
177
+ for (const k of Object.keys(userSecrets || {})) {
178
+ keySources[k] = userPath;
179
+ }
123
180
 
124
181
  try {
125
182
  const configSecretsPath = await config.getSecretsPath();
126
183
  if (!configSecretsPath) {
127
- return helpers.userOrNull();
184
+ return { merged: helpers.userOrNull(), keySources };
128
185
  }
129
- return await mergeFromConfiguredSecretsPath(configSecretsPath, userSecrets, helpers);
186
+ const merged = await mergeFromConfiguredSecretsPath(configSecretsPath, userSecrets, helpers, keySources);
187
+ return { merged, keySources };
130
188
  } catch (error) {
131
189
  if (error.message && error.message.startsWith('Failed to load secrets file')) {
132
190
  throw error;
133
191
  }
134
- return null;
135
- }
136
- }
137
-
138
- function collectBuilderSecretsYamlPaths() {
139
- const projectRoot = pathsUtil.getProjectRoot();
140
- const candidates = [];
141
- if (projectRoot) {
142
- candidates.push(path.join(projectRoot, 'builder', 'secrets.local.yaml'));
143
- }
144
- try {
145
- const alt = path.join(pathsUtil.getBuilderRoot(), 'secrets.local.yaml');
146
- if (!candidates.length || path.resolve(candidates[0]) !== path.resolve(alt)) {
147
- candidates.push(alt);
148
- }
149
- } catch {
150
- /* ignore */
151
- }
152
- return candidates;
153
- }
154
-
155
- function mergeBuilderSecretsLocalFiles(merged) {
156
- try {
157
- const seen = new Set();
158
- let out = merged;
159
- for (const builderPath of collectBuilderSecretsYamlPaths()) {
160
- if (!builderPath || seen.has(path.resolve(builderPath))) {
161
- continue;
162
- }
163
- seen.add(path.resolve(builderPath));
164
- if (fs.existsSync(builderPath)) {
165
- ensureSecureFilePermissions(builderPath);
166
- const builderSecrets = mergeUserWithConfigFile(out || {}, builderPath);
167
- if (builderSecrets) out = builderSecrets;
168
- }
169
- }
170
- return out;
171
- } catch {
172
- return merged;
173
- }
174
- }
175
-
176
- /**
177
- * Merge each `cwd/.aifabrix/secrets.local.yaml` found walking up to filesystem root.
178
- * Later files only fill keys missing or empty on the merged object (same as shared merge).
179
- * Skips the primary user file path when it appears on the walk (already loaded in merge chain).
180
- *
181
- * @param {Object|null|undefined} merged - Current secrets map
182
- * @returns {Object}
183
- */
184
- function mergeAncestorAifabrixSecretsLocalFiles(merged) {
185
- try {
186
- const primaryUser = path.resolve(pathsUtil.getPrimaryUserSecretsLocalPath());
187
- let out = merged && typeof merged === 'object' ? merged : {};
188
- const ancestorPaths = collectAncestorAifabrixSecretsLocalYamlPaths(process.cwd(), fs.existsSync);
189
- for (const secretsPath of ancestorPaths) {
190
- if (path.resolve(secretsPath) === primaryUser) {
191
- continue;
192
- }
193
- ensureSecureFilePermissions(secretsPath);
194
- const next = mergeUserWithConfigFile(out, secretsPath);
195
- if (next) out = next;
196
- }
197
- return out;
198
- } catch {
199
- return merged && typeof merged === 'object' ? merged : {};
192
+ return { merged: null, keySources };
200
193
  }
201
194
  }
202
195
 
203
196
  async function loadSecretsWithFallbacks() {
204
- let merged = await loadMergedConfigAndUserSecrets();
197
+ const mergedResult = await loadMergedConfigAndUserSecrets();
198
+ let merged = mergedResult.merged;
199
+ const keySources = mergedResult.keySources;
205
200
  if (!merged || Object.keys(merged).length === 0) {
206
201
  merged = loadPrimaryUserSecrets();
207
- if (Object.keys(merged).length === 0) {
208
- merged = loadUserSecrets();
202
+ const userPath = pathsUtil.getPrimaryUserSecretsLocalPath();
203
+ for (const k of Object.keys(merged || {})) {
204
+ keySources[k] = userPath;
209
205
  }
210
- merged = await applyCanonicalSecretsOverride(merged);
206
+ merged = await applyCanonicalSecretsOverride(merged || {}, keySources);
211
207
  }
212
- merged = mergeAncestorAifabrixSecretsLocalFiles(merged);
213
- merged = mergeBuilderSecretsLocalFiles(merged);
214
- if (Object.keys(merged).length === 0) {
208
+ const configuredShared = await config.getSecretsPath();
209
+ if ((!merged || Object.keys(merged).length === 0) && !configuredShared) {
215
210
  merged = loadDefaultSecrets();
211
+ const defaultPath = path.join(pathsUtil.getAifabrixHome(), 'secrets.yaml');
212
+ for (const k of Object.keys(merged || {})) {
213
+ keySources[k] = defaultPath;
214
+ }
216
215
  }
217
- return merged;
216
+ if (!merged || Object.keys(merged).length === 0) {
217
+ return { merged: {}, keySources };
218
+ }
219
+ return { merged, keySources };
218
220
  }
219
221
 
220
222
  /**
@@ -232,14 +234,16 @@ async function loadSecrets(secretsPath, _appName) {
232
234
  if (!explicitSecrets || typeof explicitSecrets !== 'object') {
233
235
  throw new Error(`Invalid secrets file format: ${resolvedPath}`);
234
236
  }
235
- return await decryptSecretsObject(explicitSecrets);
237
+ return await decryptSecretsObject(explicitSecrets, { defaultSourceLabel: resolvedPath });
236
238
  }
237
- let mergedSecrets = await loadSecretsWithFallbacks();
239
+ let { merged: mergedSecrets, keySources } = await loadSecretsWithFallbacks();
238
240
  if (!mergedSecrets || Object.keys(mergedSecrets).length === 0) {
239
241
  ensurePrimaryUserSecretsFileExists();
240
- mergedSecrets = await loadSecretsWithFallbacks();
242
+ const again = await loadSecretsWithFallbacks();
243
+ mergedSecrets = again.merged;
244
+ keySources = again.keySources;
241
245
  }
242
- return await decryptSecretsObject(mergedSecrets || {});
246
+ return await decryptSecretsObject(mergedSecrets || {}, { keySources });
243
247
  }
244
248
 
245
249
  module.exports = {
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @fileoverview Debug log writer for agent trust CLI runs (plan 143).
3
+ */
4
+
5
+ 'use strict';
6
+
7
+ const chalk = require('chalk');
8
+ const path = require('path');
9
+ const logger = require('../utils/logger');
10
+ const { getIntegrationPath } = require('../utils/paths');
11
+ const { writeTestLog } = require('../utils/test-log-writer');
12
+
13
+ /**
14
+ * @async
15
+ * @param {string} appKey - Integration folder key
16
+ * @param {string} datasourceKey
17
+ * @param {Object} payload
18
+ * @returns {Promise<string|null>} Log path or null when skipped
19
+ */
20
+ async function writeTrustDebugLog(appKey, datasourceKey, payload) {
21
+ const logPath = await writeTestLog(
22
+ appKey,
23
+ payload,
24
+ `test-trust-${datasourceKey}`,
25
+ path.dirname(getIntegrationPath(appKey))
26
+ );
27
+ return logPath;
28
+ }
29
+
30
+ /**
31
+ * @async
32
+ * @param {string} appKey
33
+ * @param {string} datasourceKey
34
+ * @param {Object} payload
35
+ */
36
+ async function writeTrustDebugLogAndPrint(appKey, datasourceKey, payload) {
37
+ const logPath = await writeTrustDebugLog(appKey, datasourceKey, payload);
38
+ logger.log(chalk.gray(` Debug log: ${logPath}`));
39
+ return logPath;
40
+ }
41
+
42
+ module.exports = {
43
+ writeTrustDebugLog,
44
+ writeTrustDebugLogAndPrint
45
+ };
@@ -0,0 +1,115 @@
1
+ /**
2
+ * @fileoverview Dataplane fetch paths for agent trust runs (POST validate / GET latest).
3
+ */
4
+
5
+ 'use strict';
6
+
7
+ const {
8
+ runAgentMetadataValidation,
9
+ getLatestAgentMetadataValidation
10
+ } = require('../api/agent-metadata-validation.api');
11
+ const { parseTrustTimeoutMs } = require('./agent-trust-timeout');
12
+ const { isAgentTrustMockEnabled, buildMockTrustRun } = require('./agent-trust-mock');
13
+ const { mapRunResponseToTrustRun, mapLatestResultToTrustRun } = require('./agent-trust-map');
14
+ const {
15
+ runWithAgentTrustWaitSpinner,
16
+ logAgentTrustWaitHintOnce
17
+ } = require('../utils/agent-trust-wait-ui');
18
+
19
+ function unwrapApiData(response) {
20
+ if (response && response.data !== undefined) return response.data;
21
+ return response;
22
+ }
23
+
24
+ /**
25
+ * @param {Object} options
26
+ * @returns {boolean}
27
+ */
28
+ function shouldPreferLatestRead(options = {}) {
29
+ if (options.revalidate === true) return false;
30
+ return options.summary === true;
31
+ }
32
+
33
+ /**
34
+ * @param {Error} err
35
+ * @returns {boolean}
36
+ */
37
+ function isLatestNotFoundError(err) {
38
+ const code = err && (err.statusCode || err.status);
39
+ if (code === 404) return true;
40
+ const msg = err && err.message ? String(err.message) : '';
41
+ return /no agent metadata validation/i.test(msg);
42
+ }
43
+
44
+ /**
45
+ * @async
46
+ * @param {Object} params
47
+ * @returns {Promise<Object>}
48
+ */
49
+ async function postValidateTrustRun(params) {
50
+ const { dataplaneUrl, datasourceKey, systemKey, authConfig, body, options } = params;
51
+ const timeoutMs = parseTrustTimeoutMs(options);
52
+ logAgentTrustWaitHintOnce(datasourceKey, timeoutMs);
53
+ const raw = await runWithAgentTrustWaitSpinner(
54
+ () =>
55
+ runAgentMetadataValidation(dataplaneUrl, datasourceKey, authConfig, body, { timeoutMs }),
56
+ { datasourceKey, timeoutMs }
57
+ );
58
+ return mapRunResponseToTrustRun({
59
+ datasourceKey,
60
+ systemKey,
61
+ runResponse: unwrapApiData(raw)
62
+ });
63
+ }
64
+
65
+ /**
66
+ * @async
67
+ * @param {Object} params
68
+ * @returns {Promise<Object>}
69
+ */
70
+ async function getLatestTrustRun(params) {
71
+ const { dataplaneUrl, datasourceKey, systemKey, authConfig, options } = params;
72
+ const timeoutMs = parseTrustTimeoutMs(options);
73
+ const raw = await getLatestAgentMetadataValidation(dataplaneUrl, datasourceKey, authConfig, {
74
+ timeoutMs
75
+ });
76
+ return mapLatestResultToTrustRun({
77
+ datasourceKey,
78
+ systemKey,
79
+ latestResult: unwrapApiData(raw)
80
+ });
81
+ }
82
+
83
+ /**
84
+ * @async
85
+ * @param {Object} params
86
+ * @returns {Promise<Object>}
87
+ */
88
+ async function fetchTrustRunFromDataplane(params) {
89
+ const { datasourceKey, systemKey, dataplaneUrl, authConfig, options } = params;
90
+ if (isAgentTrustMockEnabled()) {
91
+ return buildMockTrustRun(datasourceKey, systemKey);
92
+ }
93
+
94
+ const body = { forceRevalidate: options.revalidate === true };
95
+ if (options.peerDatasourceConfigs && typeof options.peerDatasourceConfigs === 'object') {
96
+ body.peerDatasourceConfigs = options.peerDatasourceConfigs;
97
+ }
98
+
99
+ if (!shouldPreferLatestRead(options)) {
100
+ return postValidateTrustRun({ dataplaneUrl, datasourceKey, systemKey, authConfig, body, options });
101
+ }
102
+
103
+ try {
104
+ return await getLatestTrustRun({ dataplaneUrl, datasourceKey, systemKey, authConfig, options });
105
+ } catch (err) {
106
+ if (!isLatestNotFoundError(err)) throw err;
107
+ return postValidateTrustRun({ dataplaneUrl, datasourceKey, systemKey, authConfig, body, options });
108
+ }
109
+ }
110
+
111
+ module.exports = {
112
+ shouldPreferLatestRead,
113
+ fetchTrustRunFromDataplane,
114
+ isLatestNotFoundError
115
+ };
@@ -0,0 +1,101 @@
1
+ /**
2
+ * @fileoverview Map dataplane agent metadata validation API payloads to CLI trust runs.
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ /**
10
+ * @param {string} trustDecision
11
+ * @returns {'ok'|'warn'|'fail'}
12
+ */
13
+ function trustDecisionToRowStatus(trustDecision) {
14
+ const td = String(trustDecision || '').trim();
15
+ if (td === 'trusted') return 'ok';
16
+ if (td === 'usableWithWarnings') return 'warn';
17
+ return 'fail';
18
+ }
19
+
20
+ /**
21
+ * @param {Object} result - AgentMetadataValidationResult from dataplane
22
+ * @returns {string[]}
23
+ */
24
+ function highLevelWarningsFromResult(result) {
25
+ if (!result || !Array.isArray(result.findings)) return [];
26
+ return result.findings
27
+ .filter(f => f && (f.severity === 'warning' || f.severity === 'error'))
28
+ .slice(0, 8)
29
+ .map(f => {
30
+ const code = f.code ? String(f.code) : 'FINDING';
31
+ const msg = f.message ? String(f.message) : '';
32
+ return msg ? `${code} — ${msg}` : code;
33
+ });
34
+ }
35
+
36
+ /**
37
+ * @param {Object} params
38
+ * @param {string} params.datasourceKey
39
+ * @param {string} params.systemKey
40
+ * @param {Object} params.runResponse - POST run response body
41
+ * @returns {Object} AgentTrustRun-shaped object for CLI display/exit
42
+ */
43
+ function mapRunResponseToTrustRun({ datasourceKey, systemKey, runResponse }) {
44
+ const body = runResponse && typeof runResponse === 'object' ? runResponse : {};
45
+ const result = body.result && typeof body.result === 'object' ? body.result : {};
46
+ const trustDecision = result.trustDecision || 'pending';
47
+ return {
48
+ datasourceKey,
49
+ systemKey,
50
+ status: trustDecisionToRowStatus(trustDecision),
51
+ trustDecision,
52
+ validationStatus: result.status || 'pending',
53
+ confidence: typeof result.confidence === 'number' ? result.confidence : 0,
54
+ summary: result.summary || '',
55
+ validatedAt: result.validatedAt || '',
56
+ inputHash: result.inputHash || '',
57
+ contractVersion: result.contractVersion || '',
58
+ cacheHit: body.reused === true,
59
+ highLevelWarnings: highLevelWarningsFromResult(result),
60
+ findings: Array.isArray(result.findings) ? result.findings : [],
61
+ observedBusinessModel: result.observedBusinessModel || null,
62
+ runId: body.id || null
63
+ };
64
+ }
65
+
66
+ /**
67
+ * @param {Object} params
68
+ * @param {string} params.datasourceKey
69
+ * @param {string} params.systemKey
70
+ * @param {Object} params.latestResult - GET /agent-validation body
71
+ * @returns {Object}
72
+ */
73
+ function mapLatestResultToTrustRun({ datasourceKey, systemKey, latestResult }) {
74
+ const result = latestResult && typeof latestResult === 'object' ? latestResult : {};
75
+ const trustDecision = result.trustDecision || 'pending';
76
+ return {
77
+ datasourceKey,
78
+ systemKey,
79
+ status: trustDecisionToRowStatus(trustDecision),
80
+ trustDecision,
81
+ validationStatus: result.status || 'pending',
82
+ confidence: typeof result.confidence === 'number' ? result.confidence : 0,
83
+ summary: result.summary || '',
84
+ validatedAt: result.validatedAt || '',
85
+ inputHash: result.inputHash || '',
86
+ contractVersion: result.contractVersion || '',
87
+ cacheHit: true,
88
+ highLevelWarnings: highLevelWarningsFromResult(result),
89
+ findings: Array.isArray(result.findings) ? result.findings : [],
90
+ observedBusinessModel: result.observedBusinessModel || null,
91
+ runId: null,
92
+ readLatest: true
93
+ };
94
+ }
95
+
96
+ module.exports = {
97
+ trustDecisionToRowStatus,
98
+ highLevelWarningsFromResult,
99
+ mapRunResponseToTrustRun,
100
+ mapLatestResultToTrustRun
101
+ };
@@ -0,0 +1,44 @@
1
+ /**
2
+ * @fileoverview Dev-only mock trust runs when AIFABRIX_AGENT_TRUST_MOCK=1 (plan 143).
3
+ * Not documented in user-facing CLI docs; for local CLI development without dataplane 404.5.
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ /**
9
+ * @returns {boolean}
10
+ */
11
+ function isAgentTrustMockEnabled() {
12
+ return String(process.env.AIFABRIX_AGENT_TRUST_MOCK || '').trim() === '1';
13
+ }
14
+
15
+ /**
16
+ * @param {string} datasourceKey
17
+ * @param {string} systemKey
18
+ * @returns {Object}
19
+ */
20
+ function buildMockTrustRun(datasourceKey, systemKey) {
21
+ return {
22
+ datasourceKey,
23
+ systemKey,
24
+ status: 'warn',
25
+ trustDecision: 'usableWithWarnings',
26
+ validationStatus: 'warning',
27
+ confidence: 0.72,
28
+ summary: 'Mock trust run (AIFABRIX_AGENT_TRUST_MOCK=1)',
29
+ validatedAt: new Date().toISOString(),
30
+ inputHash: 'mock-input-hash',
31
+ contractVersion: '1.0.0',
32
+ cacheHit: false,
33
+ highLevelWarnings: ['MOCK_TRUST — set AIFABRIX_AGENT_TRUST_MOCK=0 for live dataplane validation'],
34
+ findings: [],
35
+ observedBusinessModel: null,
36
+ runId: 'mock-run-id',
37
+ mock: true
38
+ };
39
+ }
40
+
41
+ module.exports = {
42
+ isAgentTrustMockEnabled,
43
+ buildMockTrustRun
44
+ };