@aifabrix/builder 2.50.0 → 2.51.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 (103) hide show
  1. package/.cursor/plans/157.1-security-quality-violations-by-type.csv +374 -0
  2. package/.cursor/rules/assistant-driven-integration-build.mdc +40 -0
  3. package/integration/hubspot-test/create-hubspot.js +1 -16
  4. package/lib/api/evidence-kinds.api.js +128 -0
  5. package/lib/api/pipeline.api.js +5 -1
  6. package/lib/api/resource-types.api.js +4 -4
  7. package/lib/api/roles.api.js +121 -0
  8. package/lib/api/types/evidence-kinds.types.js +42 -0
  9. package/lib/api/types/roles.types.js +36 -0
  10. package/lib/app/certification-verify-rows.js +1 -1
  11. package/lib/app/show.js +2 -2
  12. package/lib/certification/merge-certification-from-artifact.js +1 -1
  13. package/lib/channels/channel-artifact-generator.js +2 -1
  14. package/lib/cli/index.js +4 -0
  15. package/lib/cli/setup-app.js +22 -5
  16. package/lib/cli/setup-dev.js +23 -7
  17. package/lib/cli/setup-utility-help-after.js +12 -0
  18. package/lib/cli/setup-utility-validate.js +41 -2
  19. package/lib/commands/deploy-gate-failure-details.js +21 -0
  20. package/lib/commands/deploy-gate-orchestrator.js +64 -18
  21. package/lib/commands/dev-cli-handlers.js +6 -3
  22. package/lib/commands/dev-init.js +7 -109
  23. package/lib/commands/dev-refresh.js +197 -0
  24. package/lib/commands/evidence-kind.js +428 -0
  25. package/lib/commands/governance-command-action.js +4 -2
  26. package/lib/commands/repair-openapi-export.js +1 -1
  27. package/lib/commands/repair-openapi-sync.js +14 -6
  28. package/lib/commands/resource-type.js +101 -18
  29. package/lib/commands/role.js +452 -0
  30. package/lib/commands/setup.js +1 -1
  31. package/lib/commands/validate-json.js +230 -0
  32. package/lib/commands/verify-operations-step-plan.js +78 -40
  33. package/lib/core/audit-logger.js +1 -1
  34. package/lib/core/secrets-load.js +1 -3
  35. package/lib/datasource/capability/relate-operations.js +1 -1
  36. package/lib/generator/helpers.js +13 -5
  37. package/lib/generator/split-variables.js +9 -3
  38. package/lib/infrastructure/helpers-docker-check.js +6 -0
  39. package/lib/infrastructure/helpers.js +1 -1
  40. package/lib/programmatic/auth-config.js +3 -2
  41. package/lib/programmatic/bearer-auth.js +1 -1
  42. package/lib/programmatic/enterprise-mcp-client.js +18 -2
  43. package/lib/programmatic/manifest-section-registry.js +18 -1
  44. package/lib/programmatic/openapi-result-schemas.js +28 -11
  45. package/lib/programmatic/openapi-schema-descriptions-core.js +5 -2
  46. package/lib/programmatic/openapi-schema-helpers.js +16 -1
  47. package/lib/programmatic/openapi-spec.js +1 -1
  48. package/lib/programmatic/run-repository-emcp.js +11 -3
  49. package/lib/programmatic/run-template-workspace.js +14 -1
  50. package/lib/resolvers/evidence-kind-file.js +230 -0
  51. package/lib/resolvers/resource-type-file.js +36 -1
  52. package/lib/resolvers/role-file.js +227 -0
  53. package/lib/schema/application-schema.json +22 -2
  54. package/lib/schema/external-datasource.schema.json +85 -2
  55. package/lib/schema/external-system.schema.json +16 -2
  56. package/lib/schema/infra.parameter.yaml +25 -1
  57. package/lib/schema/type/credential-field-registry.json +19 -3
  58. package/lib/utils/api.js +8 -2
  59. package/lib/utils/app-register-config.js +4 -3
  60. package/lib/utils/cli-sync-options.js +59 -3
  61. package/lib/utils/cli-utils.js +11 -3
  62. package/lib/utils/dev-ca-install.js +5 -6
  63. package/lib/utils/dev-config.js +2 -2
  64. package/lib/utils/dev-list-sort.js +145 -0
  65. package/lib/utils/dev-platform-tls.js +0 -63
  66. package/lib/utils/docker-build.js +46 -27
  67. package/lib/utils/docker-daemon-tls-ca.js +1 -1
  68. package/lib/utils/docker-exec.js +91 -6
  69. package/lib/utils/env-copy.js +1 -3
  70. package/lib/utils/error-formatters/error-parser.js +0 -3
  71. package/lib/utils/external-env-template.js +1 -1
  72. package/lib/utils/external-system-readiness-compact-blocks.js +28 -1
  73. package/lib/utils/health-check-request.js +2 -3
  74. package/lib/utils/help-builder.js +3 -0
  75. package/lib/utils/image-name.js +22 -4
  76. package/lib/utils/logger.js +37 -4
  77. package/lib/utils/manifest-source-emit.js +1 -1
  78. package/lib/utils/paths.js +32 -4
  79. package/lib/utils/pipeline-upload-auth-retry.js +4 -0
  80. package/lib/utils/postgres-platform-bootstrap.js +24 -17
  81. package/lib/utils/postgres-wipe.js +5 -2
  82. package/lib/utils/remote-dev-auth.js +39 -4
  83. package/lib/utils/resolve-docker-image-ref.js +36 -6
  84. package/lib/utils/token-manager.js +16 -0
  85. package/lib/utils/upload-sync-options.js +17 -3
  86. package/lib/validation/openapi-contract-surface-validator.js +4 -0
  87. package/lib/validation/validator.js +6 -5
  88. package/package.json +34 -12
  89. package/scripts/export-builder-api-evidence.js +2 -2
  90. package/scripts/lib/plan-actor.js +2 -2
  91. package/scripts/lib/plan-meta.js +1 -1
  92. package/scripts/lib/plan-parse.js +1 -1
  93. package/scripts/lib/plan-prevalidate-gates.js +12 -1
  94. package/scripts/lib/plan-run-report.js +13 -2
  95. package/scripts/pnpm/lib/resolve-aifabrix-bin.mjs +10 -10
  96. package/templates/applications/dataplane/application.yaml +26 -4
  97. package/templates/applications/dataplane/env.template +28 -9
  98. package/templates/applications/dataplane/rbac.yaml +64 -2
  99. package/templates/applications/miso-controller/application.yaml +10 -1
  100. package/templates/applications/miso-controller/env.template +10 -7
  101. package/templates/applications/miso-controller/rbac.yaml +72 -0
  102. package/templates/channels/slack/integration/slack-system.json +60 -2
  103. package/templates/python/requirements.txt +1 -1
@@ -0,0 +1,428 @@
1
+ /**
2
+ * Evidence kind catalog commands (Controller).
3
+ *
4
+ * Commands:
5
+ * - aifabrix evidence-kind create|import|export|get|list
6
+ *
7
+ * Distinct from `aifabrix evidence` (Evidence Fabrix v2 rows).
8
+ *
9
+ * @fileoverview Evidence kind catalog CLI commands
10
+ * @author AI Fabrix Team
11
+ * @version 1.0.0
12
+ */
13
+
14
+ 'use strict';
15
+
16
+ const chalk = require('chalk');
17
+ const logger = require('../utils/logger');
18
+ const { resolveControllerUrl } = require('../utils/controller-url');
19
+ const { normalizeControllerUrl, resolveEnvironment } = require('../core/config');
20
+ const { getOrRefreshDeviceToken } = require('../utils/token-manager');
21
+ const {
22
+ readEvidenceKindCreateFile,
23
+ readEvidenceKindCatalogFile,
24
+ writeEvidenceKindCatalogFile,
25
+ mapEvidenceKindFileToApiPayload,
26
+ mapEvidenceKindApiToFileRow
27
+ } = require('../resolvers/evidence-kind-file');
28
+ const {
29
+ formatBlockingError,
30
+ formatSuccessLine,
31
+ infoLine,
32
+ headerKeyValue
33
+ } = require('../utils/cli-layout-chalk');
34
+ const {
35
+ listEvidenceKinds,
36
+ getEvidenceKind,
37
+ createEvidenceKindIdempotent
38
+ } = require('../api/evidence-kinds.api');
39
+
40
+ const EVIDENCE_KIND_KEY_PATTERN = /^[a-z][a-z0-9_]*$/;
41
+
42
+ const EVIDENCE_KIND_HELP_AFTER = `
43
+
44
+ Examples:
45
+ $ aifabrix evidence-kind create --key obligation --display-name "Obligation" --domain governance --purpose "Track commitments"
46
+ $ aifabrix evidence-kind get obligation
47
+ $ aifabrix evidence-kind list --search risk
48
+
49
+ Per-command help: aifabrix evidence-kind <command> --help
50
+ `;
51
+
52
+ const EVIDENCE_KIND_IMPORT_HELP_AFTER = `
53
+
54
+ Examples:
55
+ $ aifabrix evidence-kind import --file ./baseline-evidence-kinds.json
56
+ `;
57
+
58
+ const EVIDENCE_KIND_EXPORT_HELP_AFTER = `
59
+
60
+ Examples:
61
+ $ aifabrix evidence-kind export --file ./evidence-kinds-export.json
62
+ `;
63
+
64
+ const EVIDENCE_KIND_CREATE_HELP_AFTER = `
65
+
66
+ Examples:
67
+ $ aifabrix evidence-kind create --key obligation --display-name "Obligation" --domain governance --purpose "Track commitments"
68
+ $ aifabrix evidence-kind create --key risk --display-name "Risk" --domain governance --purpose "..." --default-severity high
69
+ $ aifabrix evidence-kind create --file ./obligation.json
70
+ `;
71
+
72
+ const EVIDENCE_KIND_GET_HELP_AFTER = `
73
+
74
+ Examples:
75
+ $ aifabrix evidence-kind get obligation
76
+ $ aifabrix evidence-kind get clx1234567890abcdef
77
+ `;
78
+
79
+ const EVIDENCE_KIND_LIST_HELP_AFTER = `
80
+
81
+ Examples:
82
+ $ aifabrix evidence-kind list
83
+ $ aifabrix evidence-kind list --page 1 --page-size 50
84
+ $ aifabrix evidence-kind list --search risk
85
+ `;
86
+
87
+ /**
88
+ * @param {string} raw
89
+ * @returns {string}
90
+ */
91
+ function requireEvidenceKindKey(raw) {
92
+ const key = String(raw || '').trim();
93
+ if (!key) {
94
+ throw new Error('Evidence kind key is required (--key <key> or --file <path>).');
95
+ }
96
+ if (!EVIDENCE_KIND_KEY_PATTERN.test(key)) {
97
+ throw new Error(
98
+ 'Evidence kind key must start with a lowercase letter and contain only lowercase letters, numbers, and underscores.'
99
+ );
100
+ }
101
+ return key;
102
+ }
103
+
104
+ /**
105
+ * @param {Object} _options
106
+ * @returns {Promise<{ controllerUrl: string, authConfig: Object }>}
107
+ */
108
+ async function resolveControllerAndAuth(_options) {
109
+ const controllerUrl = await resolveControllerUrl();
110
+ if (!controllerUrl) {
111
+ throw new Error('Controller URL is required. Run "aifabrix login" first.');
112
+ }
113
+ const normalized = normalizeControllerUrl(controllerUrl);
114
+ const deviceToken = await getOrRefreshDeviceToken(normalized);
115
+ if (!deviceToken || !deviceToken.token) {
116
+ throw new Error(
117
+ `Not authenticated for controller: ${controllerUrl}. ` +
118
+ 'Run "aifabrix login" and try again.'
119
+ );
120
+ }
121
+ return {
122
+ controllerUrl: deviceToken.controller || normalized,
123
+ authConfig: { type: 'bearer', token: deviceToken.token }
124
+ };
125
+ }
126
+
127
+ async function resolveHeaderContext() {
128
+ const env = await resolveEnvironment();
129
+ const controllerUrl = await resolveControllerUrl();
130
+ const normalized = normalizeControllerUrl(controllerUrl);
131
+ return { environment: env || 'dev', controllerUrl: normalized };
132
+ }
133
+
134
+ /**
135
+ * @param {Object} response
136
+ * @returns {any}
137
+ */
138
+ function unwrapControllerData(response) {
139
+ return response?.data?.data ?? response?.data ?? response;
140
+ }
141
+
142
+ /**
143
+ * Build create payload from flags and optional --file (maps domain → domainKey).
144
+ * @param {Object} options
145
+ * @returns {Object}
146
+ */
147
+ function buildCreatePayload(options) {
148
+ let base = {};
149
+ if (options.file) {
150
+ base = readEvidenceKindCreateFile(options.file);
151
+ }
152
+ const merged = { ...base };
153
+ if (options.key) merged.key = options.key;
154
+ if (options.displayName) merged.displayName = options.displayName;
155
+ if (options.description !== undefined) merged.description = options.description;
156
+ if (options.purpose !== undefined) merged.purpose = options.purpose;
157
+ if (options.expectedUse !== undefined) merged.expectedUse = options.expectedUse;
158
+ if (options.defaultSeverity) merged.defaultSeverity = options.defaultSeverity;
159
+ if (options.domain) {
160
+ merged.domain = options.domain;
161
+ delete merged.domainKey;
162
+ }
163
+ if (options.file && !options.domain && base.domainKey) {
164
+ return { ...base, key: requireEvidenceKindKey(merged.key || base.key) };
165
+ }
166
+ const payload = mapEvidenceKindFileToApiPayload(
167
+ {
168
+ ...merged,
169
+ domain: merged.domain || merged.domainKey
170
+ },
171
+ 0
172
+ );
173
+ payload.key = requireEvidenceKindKey(payload.key);
174
+ return payload;
175
+ }
176
+
177
+ /**
178
+ * @param {Object[]} rows
179
+ * @param {string} controllerUrl
180
+ * @param {Object} authConfig
181
+ * @returns {Promise<{ created: number, existed: number, total: number }>}
182
+ */
183
+ async function importEvidenceKindRows(rows, controllerUrl, authConfig) {
184
+ let created = 0;
185
+ let existed = 0;
186
+ for (const payload of rows) {
187
+ payload.key = requireEvidenceKindKey(payload.key);
188
+ const out = await createEvidenceKindIdempotent(controllerUrl, authConfig, payload);
189
+ if (out.created) {
190
+ created += 1;
191
+ continue;
192
+ }
193
+ existed += 1;
194
+ }
195
+ return { created, existed, total: rows.length };
196
+ }
197
+
198
+ /**
199
+ * @param {string} controllerUrl
200
+ * @param {Object} authConfig
201
+ * @returns {Promise<Object[]>}
202
+ */
203
+ async function fetchAllEvidenceKinds(controllerUrl, authConfig) {
204
+ const all = [];
205
+ let page = 1;
206
+ const pageSize = 100;
207
+ for (;;) {
208
+ const res = await listEvidenceKinds(controllerUrl, authConfig, { page, pageSize });
209
+ const payload = unwrapControllerData(res);
210
+ const items = payload?.data ?? payload ?? [];
211
+ if (!Array.isArray(items) || items.length === 0) {
212
+ break;
213
+ }
214
+ all.push(...items);
215
+ if (items.length < pageSize) {
216
+ break;
217
+ }
218
+ page += 1;
219
+ }
220
+ return all;
221
+ }
222
+
223
+ function setupEvidenceKindImportCommand(cmd) {
224
+ cmd
225
+ .command('import')
226
+ .description('Import evidence kinds from baseline JSON (idempotent)')
227
+ .addHelpText('after', EVIDENCE_KIND_IMPORT_HELP_AFTER)
228
+ .requiredOption('--file <path>', 'Baseline catalog (evidenceKinds[]) or JSON array')
229
+ .action(async(options) => {
230
+ try {
231
+ const { controllerUrl, authConfig } = await resolveControllerAndAuth(options);
232
+ const rows = readEvidenceKindCatalogFile(options.file);
233
+ const summary = await importEvidenceKindRows(rows, controllerUrl, authConfig);
234
+ logger.log(
235
+ formatSuccessLine(
236
+ `Evidence kinds imported: ${summary.total} total ` +
237
+ `(${summary.created} created, ${summary.existed} already existed)`
238
+ )
239
+ );
240
+ } catch (e) {
241
+ logger.error(formatBlockingError(e.message));
242
+ process.exit(1);
243
+ }
244
+ });
245
+ }
246
+
247
+ function setupEvidenceKindExportCommand(cmd) {
248
+ cmd
249
+ .command('export')
250
+ .description('Export evidence kinds to baseline-shaped JSON')
251
+ .addHelpText('after', EVIDENCE_KIND_EXPORT_HELP_AFTER)
252
+ .requiredOption('--file <path>', 'Output path for baseline catalog JSON')
253
+ .action(async(options) => {
254
+ try {
255
+ const { controllerUrl, authConfig } = await resolveControllerAndAuth(options);
256
+ const records = await fetchAllEvidenceKinds(controllerUrl, authConfig);
257
+ const rows = records.map(mapEvidenceKindApiToFileRow);
258
+ writeEvidenceKindCatalogFile(options.file, rows);
259
+ logger.log(
260
+ formatSuccessLine(`Evidence kinds exported: ${rows.length} written to ${options.file}`)
261
+ );
262
+ } catch (e) {
263
+ logger.error(formatBlockingError(e.message));
264
+ process.exit(1);
265
+ }
266
+ });
267
+ }
268
+
269
+ function setupEvidenceKindCreateCommand(cmd) {
270
+ cmd
271
+ .command('create')
272
+ .description('Create evidence kind (idempotent; success if it already exists)')
273
+ .addHelpText('after', EVIDENCE_KIND_CREATE_HELP_AFTER)
274
+ .option('--file <path>', 'Read evidence kind create payload from JSON file')
275
+ .option('--key <key>', 'Catalog key (lowercase letters, digits, underscores)')
276
+ .option('--display-name <name>', 'Display name')
277
+ .option('--domain <domain>', 'Enterprise domain key (e.g. governance, sales)')
278
+ .option('--purpose <text>', 'Evidence kind purpose (required)')
279
+ .option('--expected-use <text>', 'Expected use guidance')
280
+ .option('--default-severity <level>', 'low | medium | high | critical')
281
+ .option('--description <text>', 'Description')
282
+ .action(async(options) => {
283
+ try {
284
+ const { controllerUrl, authConfig } = await resolveControllerAndAuth(options);
285
+ const payload = buildCreatePayload(options);
286
+ const out = await createEvidenceKindIdempotent(controllerUrl, authConfig, payload);
287
+ const row = unwrapControllerData(out.response);
288
+ const domain = row.domainKey || payload.domainKey || '—';
289
+ if (out.created) {
290
+ logger.log(formatSuccessLine(`Evidence kind created: ${row.key} (domain: ${domain})`));
291
+ return;
292
+ }
293
+ logger.log(formatSuccessLine(`Evidence kind exists: ${row.key} (domain: ${domain})`));
294
+ logger.log(infoLine('No changes were needed.'));
295
+ } catch (e) {
296
+ logger.error(formatBlockingError(e.message));
297
+ process.exit(1);
298
+ }
299
+ });
300
+ }
301
+
302
+ function setupEvidenceKindGetCommand(cmd) {
303
+ cmd
304
+ .command('get <evidenceKindIdOrKey>')
305
+ .description('Get evidence kind by id or key')
306
+ .addHelpText('after', EVIDENCE_KIND_GET_HELP_AFTER)
307
+ .action(async(evidenceKindIdOrKey, options) => {
308
+ try {
309
+ const { environment, controllerUrl: headerControllerUrl } = await resolveHeaderContext();
310
+ const { controllerUrl, authConfig } = await resolveControllerAndAuth(options);
311
+ const res = await getEvidenceKind(controllerUrl, authConfig, String(evidenceKindIdOrKey).trim());
312
+ const row = unwrapControllerData(res);
313
+ logger.log(
314
+ chalk.bold(
315
+ `\n📎 Evidence kind in ${environment} environment (${headerControllerUrl}):\n`
316
+ )
317
+ );
318
+ logger.log(headerKeyValue('Key:', row.key || '—'));
319
+ logger.log(headerKeyValue('Display:', row.displayName || '—'));
320
+ logger.log(headerKeyValue('Domain:', row.domainKey || '—'));
321
+ logger.log(headerKeyValue('Purpose:', row.purpose || '—'));
322
+ logger.log(headerKeyValue('Severity:', row.defaultSeverity || '—'));
323
+ logger.log(headerKeyValue('Active:', row.isActive === false ? 'false' : 'true'));
324
+ if (row.expectedUse) logger.log(headerKeyValue('Expected use:', row.expectedUse));
325
+ if (row.description) logger.log(headerKeyValue('Description:', row.description));
326
+ logger.log('');
327
+ } catch (e) {
328
+ logger.error(formatBlockingError(e.message));
329
+ process.exit(1);
330
+ }
331
+ });
332
+ }
333
+
334
+ const EVIDENCE_KIND_LIST_COLUMNS = { key: 24, display: 24, domain: 14, severity: 10, active: 8 };
335
+
336
+ function logEvidenceKindListHeader(columns) {
337
+ const keyCol = 'Key'.padEnd(columns.key);
338
+ const displayCol = 'Display'.padEnd(columns.display);
339
+ const domainCol = 'Domain'.padEnd(columns.domain);
340
+ const severityCol = 'Severity'.padEnd(columns.severity);
341
+ const activeCol = 'Active'.padEnd(columns.active);
342
+ logger.log(chalk.gray(`${keyCol}${displayCol}${domainCol}${severityCol}${activeCol}`));
343
+ logger.log(
344
+ chalk.gray(
345
+ '-'.repeat(
346
+ columns.key + columns.display + columns.domain + columns.severity + columns.active
347
+ )
348
+ )
349
+ );
350
+ }
351
+
352
+ function logEvidenceKindListRows(items, columns) {
353
+ items.forEach((d) => {
354
+ const key = (d?.key ?? '—').toString().padEnd(columns.key);
355
+ const displayName = (d?.displayName ?? '—').toString().padEnd(columns.display);
356
+ const domain = (d?.domainKey ?? '—').toString().padEnd(columns.domain);
357
+ const severity = (d?.defaultSeverity ?? '—').toString().padEnd(columns.severity);
358
+ const active = (d?.isActive === false ? 'false' : 'true').padEnd(columns.active);
359
+ logger.log(`${key}${displayName}${domain}${severity}${active}`);
360
+ });
361
+ }
362
+
363
+ function displayEvidenceKindList(items, environment, controllerUrl) {
364
+ logger.log(
365
+ chalk.bold(`\n📎 Evidence kinds in ${environment} environment (${controllerUrl}):\n`)
366
+ );
367
+ if (!Array.isArray(items) || items.length === 0) {
368
+ logger.log(chalk.gray(' No evidence kinds found.\n'));
369
+ return;
370
+ }
371
+ logEvidenceKindListHeader(EVIDENCE_KIND_LIST_COLUMNS);
372
+ logEvidenceKindListRows(items, EVIDENCE_KIND_LIST_COLUMNS);
373
+ logger.log('');
374
+ }
375
+
376
+ function setupEvidenceKindListCommand(cmd) {
377
+ cmd
378
+ .command('list')
379
+ .description('List evidence kinds')
380
+ .addHelpText('after', EVIDENCE_KIND_LIST_HELP_AFTER)
381
+ .option('--page <n>', 'Page', (v) => parseInt(v, 10))
382
+ .option('--page-size <n>', 'Page size', (v) => parseInt(v, 10))
383
+ .option('--search <text>', 'Search by key/displayName/description')
384
+ .option('--domain-key <domain>', 'Filter by domain key')
385
+ .action(async(options) => {
386
+ try {
387
+ const { environment, controllerUrl: headerControllerUrl } = await resolveHeaderContext();
388
+ const { controllerUrl, authConfig } = await resolveControllerAndAuth(options);
389
+ const res = await listEvidenceKinds(controllerUrl, authConfig, {
390
+ page: options.page,
391
+ pageSize: options.pageSize,
392
+ search: options.search,
393
+ domainKey: options.domainKey
394
+ });
395
+ const payload = unwrapControllerData(res);
396
+ const items = payload?.data ?? payload ?? [];
397
+ displayEvidenceKindList(items, environment, headerControllerUrl);
398
+ } catch (e) {
399
+ logger.error(formatBlockingError(e.message));
400
+ process.exit(1);
401
+ }
402
+ });
403
+ }
404
+
405
+ /**
406
+ * @param {import('commander').Command} program
407
+ */
408
+ function setupEvidenceKindCommands(program) {
409
+ const cmd = program
410
+ .command('evidence-kind')
411
+ .description('Manage the Controller Evidence Kind Catalog')
412
+ .addHelpText('after', EVIDENCE_KIND_HELP_AFTER);
413
+ setupEvidenceKindCreateCommand(cmd);
414
+ setupEvidenceKindImportCommand(cmd);
415
+ setupEvidenceKindExportCommand(cmd);
416
+ setupEvidenceKindGetCommand(cmd);
417
+ setupEvidenceKindListCommand(cmd);
418
+ }
419
+
420
+ module.exports = {
421
+ setupEvidenceKindCommands,
422
+ requireEvidenceKindKey,
423
+ buildCreatePayload,
424
+ unwrapControllerData,
425
+ importEvidenceKindRows,
426
+ mapEvidenceKindFileToApiPayload,
427
+ mapEvidenceKindApiToFileRow
428
+ };
@@ -14,6 +14,7 @@ const { displayVerifyGovernanceTTY } = require('../lifecycle/report-display');
14
14
  const { exitCodeFromVerdict } = require('../lifecycle/exit-codes');
15
15
  const { buildJsonEnvelope } = require('../lifecycle/json-envelope');
16
16
  const { formatSuccessLine } = require('../utils/cli-test-layout-chalk');
17
+ const { cliOptsSkipPublish } = require('../utils/cli-sync-options');
17
18
 
18
19
  /**
19
20
  * Commander repeatable-option collector. Splits comma-separated values so both
@@ -31,11 +32,12 @@ function collectString(value, previous) {
31
32
  }
32
33
 
33
34
  function normalizeGovernanceOptions(options, cmd) {
34
- const parent = cmd && cmd.parent ? cmd.parent.opts() : {};
35
+ const parentCmd = cmd && cmd.parent ? cmd.parent : null;
36
+ const parent = parentCmd ? parentCmd.opts() : {};
35
37
  return {
36
38
  env: options.env || parent.env || 'dev',
37
39
  verbose: options.verbose === true || parent.verbose === true,
38
- noPublish: options.noPublish === true || parent.noPublish === true,
40
+ noPublish: cliOptsSkipPublish(options, cmd) || cliOptsSkipPublish(parent, parentCmd),
39
41
  force: options.force === true || parent.force === true,
40
42
  json: options.json === true || parent.json === true,
41
43
  pack: options.pack,
@@ -30,7 +30,7 @@ function documentKeyToLocalOpenApiPath(appPath, systemKey, documentKey) {
30
30
  }
31
31
 
32
32
  /** @type {Set<string>} */
33
- const PROGRAMMATIC_ORCHESTRATION_SYSTEM_KEYS = new Set(['aifabrix-builder-api']);
33
+ const PROGRAMMATIC_ORCHESTRATION_SYSTEM_KEYS = new Set(['builder-api']);
34
34
 
35
35
  /**
36
36
  * @param {string} systemKey
@@ -97,7 +97,7 @@ function extractOpenApiFileListItems(res) {
97
97
  const data = res && res.data;
98
98
  if (data && Array.isArray(data.data)) return data.data;
99
99
  if (data && Array.isArray(data.items)) return data.items;
100
- if (res && Array.isArray(res.items)) return res.items;
100
+ if (Array.isArray(res.items)) return res.items;
101
101
  return [];
102
102
  }
103
103
 
@@ -140,13 +140,15 @@ async function syncOpenApiFilesForMcp(opts) {
140
140
  authConfig: opts.authConfig
141
141
  });
142
142
  const existingKeys = await getExistingOpenApiKeys(dataplaneUrl, systemKey, authConfig);
143
- const uploadedKeys = new Set(existingKeys);
143
+ const uploadedThisRun = new Set();
144
144
  let uploaded = 0;
145
145
  let skipped = 0;
146
+ const forceUpload = opts.force === true;
146
147
 
147
148
  for (const fileName of datasourceFiles || []) {
148
149
  const documentKey = readDatasourceDocumentKey(appPath, fileName);
149
- if (!documentKey || uploadedKeys.has(documentKey)) continue;
150
+ if (!documentKey || uploadedThisRun.has(documentKey)) continue;
151
+ if (!forceUpload && existingKeys.has(documentKey)) continue;
150
152
 
151
153
  const localPath = documentKeyToLocalOpenApiPath(appPath, systemKey, documentKey);
152
154
  if (!(await fileExistsAsync(localPath))) {
@@ -155,7 +157,7 @@ async function syncOpenApiFilesForMcp(opts) {
155
157
  }
156
158
 
157
159
  await uploadOneOpenApiFile(dataplaneUrl, authConfig, systemKey, localPath, documentKey);
158
- uploadedKeys.add(documentKey);
160
+ uploadedThisRun.add(documentKey);
159
161
  uploaded += 1;
160
162
  }
161
163
 
@@ -177,7 +179,11 @@ async function maybeSyncOpenApiFilesForMcp(opts) {
177
179
  const r = await syncOpenApiFilesForMcp(opts);
178
180
  const lines = [];
179
181
  if (r.uploaded > 0) {
180
- lines.push(`Uploaded ${r.uploaded} OpenAPI file(s) for MCP (keyed by openapi.documentKey)`);
182
+ lines.push(
183
+ opts.force
184
+ ? `Uploaded ${r.uploaded} OpenAPI file(s) for MCP (force replace by openapi.documentKey)`
185
+ : `Uploaded ${r.uploaded} OpenAPI file(s) for MCP (keyed by openapi.documentKey)`
186
+ );
181
187
  }
182
188
  if (r.skipped > 0) {
183
189
  lines.push(
@@ -215,7 +221,9 @@ async function maybeRunOpenApiSyncForMcp({
215
221
  dryRun,
216
222
  appPath,
217
223
  systemKey,
218
- datasourceFiles
224
+ datasourceFiles,
225
+ // --api re-exports local OpenAPI; always replace SpecFile rows that already exist.
226
+ force: true
219
227
  });
220
228
  openapiLines.forEach((l) => changes.push(l));
221
229
  return exportLines.length > 0 || openapiLines.length > 0;