@aifabrix/builder 2.50.0 → 2.51.1
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.
- package/.cursor/plans/Done/157.1-security-quality-violations-by-type.csv +374 -0
- package/.cursor/rules/assistant-driven-integration-build.mdc +40 -0
- package/integration/hubspot-test/create-hubspot.js +1 -16
- package/jest.isolated-projects.js +77 -1
- package/jest.projects.js +5 -426
- package/lib/api/assistants.api.js +67 -0
- package/lib/api/datasources-extended.api.js +6 -6
- package/lib/api/evidence-authoring-helpers.js +267 -0
- package/lib/api/evidence-authoring.api.js +337 -0
- package/lib/api/evidence-kinds.api.js +128 -0
- package/lib/api/evidence.api.js +57 -1
- package/lib/api/index.js +24 -7
- package/lib/api/pipeline.api.js +47 -3
- package/lib/api/records-bulk.api.js +28 -3
- package/lib/api/resource-types.api.js +4 -4
- package/lib/api/roles.api.js +121 -0
- package/lib/api/types/evidence-kinds.types.js +42 -0
- package/lib/api/types/evidence.types.js +156 -8
- package/lib/api/types/roles.types.js +36 -0
- package/lib/api/work.api.js +283 -0
- package/lib/app/certification-verify-rows.js +1 -1
- package/lib/app/run-deploy-job.js +108 -0
- package/lib/app/run-helpers.js +10 -3
- package/lib/app/run.js +8 -0
- package/lib/app/show.js +2 -2
- package/lib/certification/merge-certification-from-artifact.js +1 -1
- package/lib/channels/add-channel.js +54 -23
- package/lib/channels/approval-guides/chatgpt.js +8 -7
- package/lib/channels/approval-guides/microsoft-copilot.js +7 -8
- package/lib/channels/channel-artifact-generator.js +16 -3
- package/lib/channels/channel-credential.js +3 -1
- package/lib/channels/channel-probe-evidence.js +79 -0
- package/lib/channels/channel-probe.js +265 -13
- package/lib/channels/channel-runtime-resolver.js +120 -29
- package/lib/channels/channel-status-display.js +44 -0
- package/lib/channels/channel-targets.js +7 -0
- package/lib/channels/chatgpt-runtime-contract.js +29 -0
- package/lib/channels/microsoft-copilot-package.js +280 -0
- package/lib/channels/runtime-tool-catalog.js +165 -0
- package/lib/cli/index.js +6 -0
- package/lib/cli/setup-app.js +26 -5
- package/lib/cli/setup-dev.js +23 -7
- package/lib/cli/setup-external-system-help.js +8 -2
- package/lib/cli/setup-external-system.js +12 -0
- package/lib/cli/setup-utility-help-after.js +12 -0
- package/lib/cli/setup-utility-validate.js +41 -2
- package/lib/commands/app-test.js +2 -14
- package/lib/commands/datasource-load-export-cli.js +6 -1
- package/lib/commands/deploy-gate-failure-details.js +23 -0
- package/lib/commands/deploy-gate-orchestrator.js +64 -18
- package/lib/commands/dev-cli-handlers.js +6 -3
- package/lib/commands/dev-down.js +2 -6
- package/lib/commands/dev-init.js +7 -109
- package/lib/commands/dev-refresh.js +197 -0
- package/lib/commands/evidence-kind.js +478 -0
- package/lib/commands/governance-command-action.js +4 -2
- package/lib/commands/governance-generate-external.js +56 -21
- package/lib/commands/lifecycle-external.js +10 -4
- package/lib/commands/repair-datasource-apply.js +10 -1
- package/lib/commands/repair-datasource-run.js +4 -2
- package/lib/commands/repair-datasource-test-payload.js +80 -19
- package/lib/commands/repair-datasource-webhooks-sync-bridge.js +42 -3
- package/lib/commands/repair-internal.js +9 -1
- package/lib/commands/repair-openapi-export.js +1 -1
- package/lib/commands/repair-openapi-sync.js +43 -18
- package/lib/commands/resource-type.js +101 -18
- package/lib/commands/role-assistant.js +313 -0
- package/lib/commands/role.js +452 -0
- package/lib/commands/setup.js +1 -1
- package/lib/commands/upload-data-sync-finalize.js +5 -4
- package/lib/commands/upload-sidecar-followups.js +144 -47
- package/lib/commands/upload.js +43 -6
- package/lib/commands/validate-json.js +230 -0
- package/lib/commands/verify-governance-combined-external.js +73 -9
- package/lib/commands/verify-governance-db.js +80 -25
- package/lib/commands/verify-governance-external.js +29 -5
- package/lib/commands/verify-operations-step-plan.js +78 -40
- package/lib/commands/wizard-core-helpers.js +7 -3
- package/lib/commands/wizard-core.js +78 -9
- package/lib/core/audit-logger.js +1 -1
- package/lib/core/secrets-load.js +1 -3
- package/lib/datasource/bulk-loader-run-batches.js +4 -2
- package/lib/datasource/bulk-loader-service.js +5 -1
- package/lib/datasource/capability/relate-operations.js +1 -1
- package/lib/datasource/datasource-validate-summary.js +2 -2
- package/lib/datasource/validate.js +13 -6
- package/lib/external-system/deploy-helpers.js +35 -1
- package/lib/external-system/deploy.js +23 -10
- package/lib/external-system/download-helpers.js +111 -2
- package/lib/external-system/download-role-assistant.js +55 -0
- package/lib/external-system/download.js +45 -26
- package/lib/external-system/test-helpers.js +1 -1
- package/lib/external-system/test.js +39 -3
- package/lib/generator/builders.js +3 -0
- package/lib/generator/helpers.js +13 -5
- package/lib/generator/split-variables.js +9 -3
- package/lib/generator/wizard.js +51 -4
- package/lib/infrastructure/helpers-docker-check.js +6 -0
- package/lib/infrastructure/helpers.js +1 -1
- package/lib/lifecycle/access-control-abac.js +121 -0
- package/lib/lifecycle/lifecycle-governance-options.js +4 -1
- package/lib/lifecycle/product-model.js +3 -2
- package/lib/lifecycle/report-display.js +14 -2
- package/lib/programmatic/auth-config.js +3 -2
- package/lib/programmatic/bearer-auth.js +1 -1
- package/lib/programmatic/capability-registry.js +20 -1
- package/lib/programmatic/command-options.js +1 -0
- package/lib/programmatic/enterprise-mcp-client.js +18 -2
- package/lib/programmatic/index.js +4 -0
- package/lib/programmatic/manifest-patch-section.js +48 -9
- package/lib/programmatic/manifest-section-registry-data.js +3 -2
- package/lib/programmatic/manifest-section-registry.js +18 -1
- package/lib/programmatic/openapi-apply-result-schemas.js +44 -0
- package/lib/programmatic/openapi-component-schemas.js +16 -1
- package/lib/programmatic/openapi-description-enricher.js +3 -1
- package/lib/programmatic/openapi-descriptions.js +55 -5
- package/lib/programmatic/openapi-evidence-schemas.js +161 -0
- package/lib/programmatic/openapi-manifest-schemas.js +6 -2
- package/lib/programmatic/openapi-operation-bindings.js +99 -0
- package/lib/programmatic/openapi-request-schemas.js +36 -1
- package/lib/programmatic/openapi-result-schemas.js +29 -12
- package/lib/programmatic/openapi-schema-descriptions-certify.js +5 -1
- package/lib/programmatic/openapi-schema-descriptions-core.js +6 -3
- package/lib/programmatic/openapi-schema-descriptions-evidence.js +156 -0
- package/lib/programmatic/openapi-schema-descriptions-manifest.js +4 -3
- package/lib/programmatic/openapi-schema-descriptions-operations.js +70 -0
- package/lib/programmatic/openapi-schema-helpers.js +16 -1
- package/lib/programmatic/openapi-spec.js +48 -29
- package/lib/programmatic/orchestration-openapi-meta.js +5 -0
- package/lib/programmatic/request-timeout.js +3 -0
- package/lib/programmatic/route-handler-map.js +89 -2
- package/lib/programmatic/run-certify.js +15 -2
- package/lib/programmatic/run-deploy.js +74 -0
- package/lib/programmatic/run-evidence-authoring.js +80 -0
- package/lib/programmatic/run-protection.js +124 -0
- package/lib/programmatic/run-repository-emcp.js +11 -3
- package/lib/programmatic/run-template-workspace.js +42 -2
- package/lib/programmatic/workspace-context.js +6 -0
- package/lib/protection/auth-context.js +48 -6
- package/lib/protection/paths.js +22 -0
- package/lib/protection/run-protection-create-helpers.js +22 -1
- package/lib/protection/run-protection-create.js +1 -1
- package/lib/protection/sync-after-upload.js +1 -1
- package/lib/resolvers/evidence-kind-file.js +230 -0
- package/lib/resolvers/resource-type-file.js +36 -1
- package/lib/resolvers/role-file.js +227 -0
- package/lib/role-assistant/evidence-sync.js +450 -0
- package/lib/role-assistant/github-workflow.js +96 -0
- package/lib/role-assistant/knowledge-sync.js +465 -0
- package/lib/role-assistant/settings.js +126 -0
- package/lib/role-assistant/test-cases-evaluate.js +170 -0
- package/lib/role-assistant/test-cases-honesty.js +184 -0
- package/lib/role-assistant/test-cases.js +364 -0
- package/lib/role-assistant/test-runner-workhub-answers.js +203 -0
- package/lib/role-assistant/test-runner-workhub.js +385 -0
- package/lib/role-assistant/test-runner.js +500 -0
- package/lib/schema/application-schema.json +36 -2
- package/lib/schema/deployment-rules.yaml +1 -0
- package/lib/schema/external-datasource.schema.json +121 -4
- package/lib/schema/external-system.schema.json +20 -6
- package/lib/schema/infra.parameter.yaml +45 -1
- package/lib/schema/role-assistant-settings.schema.json +108 -0
- package/lib/schema/type/credential-field-registry.json +19 -3
- package/lib/utils/api.js +52 -21
- package/lib/utils/app-path-resolve.js +72 -24
- package/lib/utils/app-register-config.js +4 -3
- package/lib/utils/authentication-shape.js +3 -1
- package/lib/utils/cli-sync-options.js +59 -3
- package/lib/utils/cli-utils.js +11 -3
- package/lib/utils/deployment-validation.js +28 -3
- package/lib/utils/dev-ca-install.js +5 -6
- package/lib/utils/dev-config.js +2 -2
- package/lib/utils/dev-list-sort.js +145 -0
- package/lib/utils/dev-platform-tls.js +0 -63
- package/lib/utils/docker-build.js +46 -27
- package/lib/utils/docker-daemon-tls-ca.js +1 -1
- package/lib/utils/docker-exec.js +91 -6
- package/lib/utils/docker-run-ephemeral.js +55 -0
- package/lib/utils/enterprise-knowledge-paths.js +29 -12
- package/lib/utils/env-copy-keycloak.js +196 -0
- package/lib/utils/env-copy.js +35 -4
- package/lib/utils/error-formatters/error-parser.js +0 -3
- package/lib/utils/external-env-template.js +1 -1
- package/lib/utils/external-system-readiness-compact-blocks.js +28 -1
- package/lib/utils/external-system-readiness-compact-display.js +3 -0
- package/lib/utils/health-check-request.js +2 -3
- package/lib/utils/help-builder.js +4 -0
- package/lib/utils/image-name.js +22 -4
- package/lib/utils/logger.js +37 -4
- package/lib/utils/manifest-source-emit.js +1 -1
- package/lib/utils/paths.js +98 -26
- package/lib/utils/pipeline-upload-auth-retry.js +19 -3
- package/lib/utils/postgres-platform-bootstrap.js +24 -17
- package/lib/utils/postgres-wipe.js +5 -2
- package/lib/utils/remote-dev-auth.js +39 -4
- package/lib/utils/resolve-docker-image-ref.js +36 -6
- package/lib/utils/role-assistant-paths.js +98 -0
- package/lib/utils/schema-loader.js +41 -0
- package/lib/utils/schema-resolver.js +36 -4
- package/lib/utils/token-manager.js +47 -22
- package/lib/utils/upload-sync-options.js +17 -3
- package/lib/utils/url-declarative-resolve-load-doc.js +38 -1
- package/lib/validation/external-manifest-validator.js +31 -8
- package/lib/validation/openapi-contract-surface-validator.js +4 -0
- package/lib/validation/validate-external-file.js +8 -1
- package/lib/validation/validator.js +6 -5
- package/package.json +35 -12
- package/scripts/export-builder-api-evidence.js +3 -3
- package/scripts/lib/plan-actor.js +2 -2
- package/scripts/lib/plan-meta.js +1 -1
- package/scripts/lib/plan-parse.js +1 -1
- package/scripts/lib/plan-prevalidate-gates.js +12 -1
- package/scripts/lib/plan-run-report.js +13 -2
- package/scripts/pnpm/lib/resolve-aifabrix-bin.mjs +10 -10
- package/templates/applications/dataplane/application.yaml +47 -17
- package/templates/applications/dataplane/env.template +28 -9
- package/templates/applications/dataplane/rbac.yaml +78 -2
- package/templates/applications/miso-controller/application.yaml +10 -1
- package/templates/applications/miso-controller/env.template +13 -8
- package/templates/applications/miso-controller/rbac.yaml +72 -0
- package/templates/channels/chatgpt/install.template.md.hbs +42 -24
- package/templates/channels/chatgpt/integration/README.md +10 -7
- package/templates/channels/chatgpt/integration/application.json +2 -2
- package/templates/channels/chatgpt/integration/chatgpt-system.json +3 -3
- package/templates/channels/chatgpt/integration/env.template +1 -1
- package/templates/channels/chatgpt/manifest.template.json.hbs +36 -5
- package/templates/channels/microsoft-copilot/ai-plugin.template.json.hbs +28 -0
- package/templates/channels/microsoft-copilot/declarative-agent.template.json.hbs +22 -0
- package/templates/channels/microsoft-copilot/install.template.md.hbs +32 -22
- package/templates/channels/microsoft-copilot/integration/README.md +9 -7
- package/templates/channels/microsoft-copilot/integration/application.json +2 -2
- package/templates/channels/microsoft-copilot/integration/env.template +1 -1
- package/templates/channels/microsoft-copilot/integration/microsoft-copilot-system.json +7 -6
- package/templates/channels/microsoft-copilot/manifest.template.json.hbs +34 -6
- package/templates/channels/slack/integration/slack-system.json +60 -2
- package/templates/python/requirements.txt +1 -1
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evidence kind catalog file helpers for create/import/export.
|
|
3
|
+
*
|
|
4
|
+
* File JSON uses `domain`; Controller API uses `domainKey`.
|
|
5
|
+
*
|
|
6
|
+
* @fileoverview Evidence kind catalog file parsing and API mapping
|
|
7
|
+
* @author AI Fabrix Team
|
|
8
|
+
* @version 1.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const { existsSync, readFileSync, writeFileSync } = require('../internal/fs-real-sync');
|
|
15
|
+
|
|
16
|
+
const DOMAIN_KEY_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
17
|
+
const SEVERITIES = new Set(['low', 'medium', 'high', 'critical']);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {string} filePath
|
|
21
|
+
* @returns {object|Array}
|
|
22
|
+
*/
|
|
23
|
+
function loadEvidenceKindJson(filePath) {
|
|
24
|
+
const p = path.resolve(String(filePath || '').trim());
|
|
25
|
+
if (!p) {
|
|
26
|
+
throw new Error('--file is required');
|
|
27
|
+
}
|
|
28
|
+
if (!existsSync(p)) {
|
|
29
|
+
throw new Error(`File not found: ${p}`);
|
|
30
|
+
}
|
|
31
|
+
let parsed;
|
|
32
|
+
try {
|
|
33
|
+
parsed = JSON.parse(readFileSync(p, 'utf8'));
|
|
34
|
+
} catch (e) {
|
|
35
|
+
throw new Error(`Invalid JSON in ${p}: ${e.message}`);
|
|
36
|
+
}
|
|
37
|
+
if (parsed === null || typeof parsed !== 'object') {
|
|
38
|
+
throw new Error(`Evidence kind file must be a JSON object or array: ${p}`);
|
|
39
|
+
}
|
|
40
|
+
return parsed;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @param {object} entry
|
|
45
|
+
* @param {number} index
|
|
46
|
+
* @returns {string}
|
|
47
|
+
*/
|
|
48
|
+
function requireDomainValue(entry, index) {
|
|
49
|
+
const raw = entry.domain !== undefined ? entry.domain : entry.domainKey;
|
|
50
|
+
const value = typeof raw === 'string' ? raw.trim().toLowerCase() : '';
|
|
51
|
+
if (!value) {
|
|
52
|
+
throw new Error(`evidenceKinds[${index}].domain is required`);
|
|
53
|
+
}
|
|
54
|
+
if (!DOMAIN_KEY_PATTERN.test(value)) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
'domain must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens'
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
return value;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Map baseline/file evidence kind row to Controller create payload (domain → domainKey).
|
|
64
|
+
* @param {object} row
|
|
65
|
+
* @param {number} [index]
|
|
66
|
+
* @returns {Object}
|
|
67
|
+
*/
|
|
68
|
+
function mapEvidenceKindFileToApiPayload(row, index = 0) {
|
|
69
|
+
if (!row || typeof row !== 'object') {
|
|
70
|
+
throw new Error(`evidenceKinds[${index}] must be a JSON object`);
|
|
71
|
+
}
|
|
72
|
+
const key = String(row.key || '').trim();
|
|
73
|
+
if (!key) {
|
|
74
|
+
throw new Error(`evidenceKinds[${index}].key is required`);
|
|
75
|
+
}
|
|
76
|
+
const displayName = String(row.displayName || '').trim();
|
|
77
|
+
if (!displayName) {
|
|
78
|
+
throw new Error(`evidenceKinds[${index}].displayName is required`);
|
|
79
|
+
}
|
|
80
|
+
const purpose = String(row.purpose || '').trim();
|
|
81
|
+
if (!purpose) {
|
|
82
|
+
throw new Error(`evidenceKinds[${index}].purpose is required`);
|
|
83
|
+
}
|
|
84
|
+
const domainKey = requireDomainValue(row, index);
|
|
85
|
+
const payload = { key, displayName, purpose, domainKey };
|
|
86
|
+
copyOptionalFields(payload, row, [
|
|
87
|
+
'description',
|
|
88
|
+
'expectedUse',
|
|
89
|
+
'requiresApproval',
|
|
90
|
+
'isActive',
|
|
91
|
+
'sortOrder',
|
|
92
|
+
'source'
|
|
93
|
+
]);
|
|
94
|
+
assignOptionalSeverity(payload, row, index);
|
|
95
|
+
if (row.metadata && typeof row.metadata === 'object') {
|
|
96
|
+
payload.metadata = row.metadata;
|
|
97
|
+
}
|
|
98
|
+
return payload;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* @param {Object} payload
|
|
103
|
+
* @param {object} row
|
|
104
|
+
* @param {number} index
|
|
105
|
+
* @returns {void}
|
|
106
|
+
*/
|
|
107
|
+
function assignOptionalSeverity(payload, row, index) {
|
|
108
|
+
if (row.defaultSeverity === undefined) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const severity = String(row.defaultSeverity).trim();
|
|
112
|
+
if (!SEVERITIES.has(severity)) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
`evidenceKinds[${index}].defaultSeverity must be one of: ${[...SEVERITIES].join(', ')}`
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
payload.defaultSeverity = severity;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* @param {Object} target
|
|
122
|
+
* @param {object} source
|
|
123
|
+
* @param {string[]} fields
|
|
124
|
+
* @returns {void}
|
|
125
|
+
*/
|
|
126
|
+
function copyOptionalFields(target, source, fields) {
|
|
127
|
+
for (const field of fields) {
|
|
128
|
+
if (source[field] !== undefined) {
|
|
129
|
+
target[field] = source[field];
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Map Controller evidence kind record to baseline-shaped JSON (domainKey → domain).
|
|
136
|
+
* @param {object} record
|
|
137
|
+
* @returns {Object}
|
|
138
|
+
*/
|
|
139
|
+
function mapEvidenceKindApiToFileRow(record) {
|
|
140
|
+
const row = {
|
|
141
|
+
key: record.key,
|
|
142
|
+
displayName: record.displayName,
|
|
143
|
+
domain: record.domainKey || record.domain,
|
|
144
|
+
purpose: record.purpose
|
|
145
|
+
};
|
|
146
|
+
if (record.description) row.description = record.description;
|
|
147
|
+
if (record.expectedUse) row.expectedUse = record.expectedUse;
|
|
148
|
+
if (record.defaultSeverity) row.defaultSeverity = record.defaultSeverity;
|
|
149
|
+
if (record.requiresApproval !== undefined) row.requiresApproval = record.requiresApproval;
|
|
150
|
+
if (record.sortOrder !== undefined) row.sortOrder = record.sortOrder;
|
|
151
|
+
if (record.metadata && typeof record.metadata === 'object') {
|
|
152
|
+
row.metadata = record.metadata;
|
|
153
|
+
}
|
|
154
|
+
return row;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* @param {object} parsed
|
|
159
|
+
* @returns {void}
|
|
160
|
+
*/
|
|
161
|
+
function rejectBaselineCatalogShape(parsed) {
|
|
162
|
+
if (
|
|
163
|
+
parsed &&
|
|
164
|
+
typeof parsed === 'object' &&
|
|
165
|
+
Array.isArray(parsed.evidenceKinds) &&
|
|
166
|
+
!parsed.key
|
|
167
|
+
) {
|
|
168
|
+
throw new Error(
|
|
169
|
+
'File is a baseline catalog (evidenceKinds[]). ' +
|
|
170
|
+
'Use `aifabrix evidence-kind import --file <path>` to seed all entries, ' +
|
|
171
|
+
'or pass a single evidence kind object with key and displayName.'
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* @param {string} filePath
|
|
178
|
+
* @returns {Object}
|
|
179
|
+
*/
|
|
180
|
+
function readEvidenceKindCreateFile(filePath) {
|
|
181
|
+
const parsed = loadEvidenceKindJson(filePath);
|
|
182
|
+
rejectBaselineCatalogShape(parsed);
|
|
183
|
+
return mapEvidenceKindFileToApiPayload(parsed, 0);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* @param {string} filePath
|
|
188
|
+
* @returns {Object[]}
|
|
189
|
+
*/
|
|
190
|
+
function readEvidenceKindCatalogFile(filePath) {
|
|
191
|
+
const parsed = loadEvidenceKindJson(filePath);
|
|
192
|
+
let rows;
|
|
193
|
+
if (Array.isArray(parsed)) {
|
|
194
|
+
rows = parsed;
|
|
195
|
+
} else if (Array.isArray(parsed.evidenceKinds)) {
|
|
196
|
+
rows = parsed.evidenceKinds;
|
|
197
|
+
} else {
|
|
198
|
+
throw new Error(
|
|
199
|
+
'Import file must be a baseline catalog with evidenceKinds[] or a JSON array of evidence kinds.'
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
if (rows.length === 0) {
|
|
203
|
+
throw new Error('Import file contains no evidence kinds');
|
|
204
|
+
}
|
|
205
|
+
return rows.map((row, index) => mapEvidenceKindFileToApiPayload(row, index));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* @param {string} filePath
|
|
210
|
+
* @param {Object[]} rows
|
|
211
|
+
* @param {string} [version]
|
|
212
|
+
* @returns {void}
|
|
213
|
+
*/
|
|
214
|
+
function writeEvidenceKindCatalogFile(filePath, rows, version = '1.0.0') {
|
|
215
|
+
const p = path.resolve(String(filePath || '').trim());
|
|
216
|
+
if (!p) {
|
|
217
|
+
throw new Error('--file is required');
|
|
218
|
+
}
|
|
219
|
+
const catalog = { version, evidenceKinds: rows };
|
|
220
|
+
writeFileSync(p, `${JSON.stringify(catalog, null, 2)}\n`, 'utf8');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
module.exports = {
|
|
224
|
+
mapEvidenceKindFileToApiPayload,
|
|
225
|
+
mapEvidenceKindApiToFileRow,
|
|
226
|
+
readEvidenceKindCreateFile,
|
|
227
|
+
readEvidenceKindCatalogFile,
|
|
228
|
+
writeEvidenceKindCatalogFile,
|
|
229
|
+
SEVERITIES
|
|
230
|
+
};
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
'use strict';
|
|
10
10
|
|
|
11
11
|
const path = require('path');
|
|
12
|
-
const { existsSync, readFileSync } = require('../internal/fs-real-sync');
|
|
12
|
+
const { existsSync, readFileSync, writeFileSync } = require('../internal/fs-real-sync');
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* @typedef {Object} ResourceTypeCreateInput
|
|
@@ -172,9 +172,44 @@ function readResourceTypeCatalogFile(filePath) {
|
|
|
172
172
|
return rows.map((row, index) => normalizeResourceTypeCreateRow(row, index));
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
+
/**
|
|
176
|
+
* Map Controller resource type record to baseline-shaped JSON.
|
|
177
|
+
* @param {object} record
|
|
178
|
+
* @returns {Object}
|
|
179
|
+
*/
|
|
180
|
+
function mapResourceTypeApiToFileRow(record) {
|
|
181
|
+
const row = {
|
|
182
|
+
key: record.key,
|
|
183
|
+
displayName: record.displayName,
|
|
184
|
+
domain: record.domain
|
|
185
|
+
};
|
|
186
|
+
if (record.description) row.description = record.description;
|
|
187
|
+
if (record.category) row.category = record.category;
|
|
188
|
+
if (record.defaultRiskLevel) row.defaultRiskLevel = record.defaultRiskLevel;
|
|
189
|
+
if (record.status) row.status = record.status;
|
|
190
|
+
return row;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* @param {string} filePath
|
|
195
|
+
* @param {Object[]} rows - baseline-shaped resource type rows
|
|
196
|
+
* @param {string} [version]
|
|
197
|
+
* @returns {void}
|
|
198
|
+
*/
|
|
199
|
+
function writeResourceTypeCatalogFile(filePath, rows, version = '1.0.0') {
|
|
200
|
+
const p = path.resolve(String(filePath || '').trim());
|
|
201
|
+
if (!p) {
|
|
202
|
+
throw new Error('--file is required');
|
|
203
|
+
}
|
|
204
|
+
const catalog = { version, resourceTypes: rows };
|
|
205
|
+
writeFileSync(p, `${JSON.stringify(catalog, null, 2)}\n`, 'utf8');
|
|
206
|
+
}
|
|
207
|
+
|
|
175
208
|
module.exports = {
|
|
176
209
|
readResourceTypeCreateFile,
|
|
177
210
|
readResourceTypeCatalogFile,
|
|
211
|
+
mapResourceTypeApiToFileRow,
|
|
212
|
+
writeResourceTypeCatalogFile,
|
|
178
213
|
CATEGORIES,
|
|
179
214
|
RISK_LEVELS,
|
|
180
215
|
STATUSES
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Role catalog file helpers for create/import/export.
|
|
3
|
+
*
|
|
4
|
+
* File JSON uses `domain`; Controller API uses `domainKey`.
|
|
5
|
+
* Compiler fields nest under `metadata` on the API payload.
|
|
6
|
+
*
|
|
7
|
+
* @fileoverview Role catalog file parsing and API mapping
|
|
8
|
+
* @author AI Fabrix Team
|
|
9
|
+
* @version 1.0.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const { existsSync, readFileSync, writeFileSync } = require('../internal/fs-real-sync');
|
|
16
|
+
|
|
17
|
+
/** Compiler fields that live top-level in baseline JSON and under metadata in the API. */
|
|
18
|
+
const ROLE_COMPILER_FIELDS = [
|
|
19
|
+
'defaultResourceType',
|
|
20
|
+
'primaryResourceTypes',
|
|
21
|
+
'defaultCapability',
|
|
22
|
+
'defaultCapabilities',
|
|
23
|
+
'defaultKnowledgeProfiles',
|
|
24
|
+
'defaultEvidenceKinds',
|
|
25
|
+
'defaultKnowledgeKeys',
|
|
26
|
+
'defaultKnowledgeGenerationProfiles'
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
const DOMAIN_KEY_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {string} filePath
|
|
33
|
+
* @returns {object|Array}
|
|
34
|
+
*/
|
|
35
|
+
function loadRoleJson(filePath) {
|
|
36
|
+
const p = path.resolve(String(filePath || '').trim());
|
|
37
|
+
if (!p) {
|
|
38
|
+
throw new Error('--file is required');
|
|
39
|
+
}
|
|
40
|
+
if (!existsSync(p)) {
|
|
41
|
+
throw new Error(`File not found: ${p}`);
|
|
42
|
+
}
|
|
43
|
+
let parsed;
|
|
44
|
+
try {
|
|
45
|
+
parsed = JSON.parse(readFileSync(p, 'utf8'));
|
|
46
|
+
} catch (e) {
|
|
47
|
+
throw new Error(`Invalid JSON in ${p}: ${e.message}`);
|
|
48
|
+
}
|
|
49
|
+
if (parsed === null || typeof parsed !== 'object') {
|
|
50
|
+
throw new Error(`Role file must be a JSON object or array: ${p}`);
|
|
51
|
+
}
|
|
52
|
+
return parsed;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param {object} entry
|
|
57
|
+
* @param {number} index
|
|
58
|
+
* @returns {string}
|
|
59
|
+
*/
|
|
60
|
+
function requireDomainValue(entry, index) {
|
|
61
|
+
const raw = entry.domain !== undefined ? entry.domain : entry.domainKey;
|
|
62
|
+
const value = typeof raw === 'string' ? raw.trim().toLowerCase() : '';
|
|
63
|
+
if (!value) {
|
|
64
|
+
throw new Error(`roles[${index}].domain is required`);
|
|
65
|
+
}
|
|
66
|
+
if (!DOMAIN_KEY_PATTERN.test(value)) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
'domain must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens'
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Map baseline/file role row to Controller create payload (domain → domainKey; compiler → metadata).
|
|
76
|
+
* @param {object} row
|
|
77
|
+
* @param {number} [index]
|
|
78
|
+
* @returns {Object}
|
|
79
|
+
*/
|
|
80
|
+
function mapRoleFileToApiPayload(row, index = 0) {
|
|
81
|
+
if (!row || typeof row !== 'object') {
|
|
82
|
+
throw new Error(`roles[${index}] must be a JSON object`);
|
|
83
|
+
}
|
|
84
|
+
const key = String(row.key || '').trim();
|
|
85
|
+
if (!key) {
|
|
86
|
+
throw new Error(`roles[${index}].key is required`);
|
|
87
|
+
}
|
|
88
|
+
const displayName = String(row.displayName || '').trim();
|
|
89
|
+
if (!displayName) {
|
|
90
|
+
throw new Error(`roles[${index}].displayName is required`);
|
|
91
|
+
}
|
|
92
|
+
const domainKey = requireDomainValue(row, index);
|
|
93
|
+
const metadata = collectRoleCompilerMetadata(row);
|
|
94
|
+
const payload = { key, displayName, domainKey };
|
|
95
|
+
copyOptionalFields(payload, row, ['description', 'purpose', 'businessOutcome', 'isActive', 'source']);
|
|
96
|
+
if (Object.keys(metadata).length > 0) {
|
|
97
|
+
payload.metadata = metadata;
|
|
98
|
+
}
|
|
99
|
+
return payload;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* @param {object} row
|
|
104
|
+
* @returns {Object}
|
|
105
|
+
*/
|
|
106
|
+
function collectRoleCompilerMetadata(row) {
|
|
107
|
+
const metadata = {
|
|
108
|
+
...(row.metadata && typeof row.metadata === 'object' ? row.metadata : {})
|
|
109
|
+
};
|
|
110
|
+
for (const field of ROLE_COMPILER_FIELDS) {
|
|
111
|
+
if (row[field] !== undefined) {
|
|
112
|
+
metadata[field] = row[field];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return metadata;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* @param {Object} target
|
|
120
|
+
* @param {object} source
|
|
121
|
+
* @param {string[]} fields
|
|
122
|
+
* @returns {void}
|
|
123
|
+
*/
|
|
124
|
+
function copyOptionalFields(target, source, fields) {
|
|
125
|
+
for (const field of fields) {
|
|
126
|
+
if (source[field] !== undefined) {
|
|
127
|
+
target[field] = source[field];
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Map Controller role record to baseline-shaped JSON (domainKey → domain; flatten compiler metadata).
|
|
134
|
+
* @param {object} record
|
|
135
|
+
* @returns {Object}
|
|
136
|
+
*/
|
|
137
|
+
function mapRoleApiToFileRow(record) {
|
|
138
|
+
const row = {
|
|
139
|
+
key: record.key,
|
|
140
|
+
displayName: record.displayName,
|
|
141
|
+
domain: record.domainKey || record.domain
|
|
142
|
+
};
|
|
143
|
+
if (record.description) row.description = record.description;
|
|
144
|
+
if (record.purpose) row.purpose = record.purpose;
|
|
145
|
+
if (record.businessOutcome) row.businessOutcome = record.businessOutcome;
|
|
146
|
+
const meta = record.metadata && typeof record.metadata === 'object' ? { ...record.metadata } : {};
|
|
147
|
+
for (const field of ROLE_COMPILER_FIELDS) {
|
|
148
|
+
if (meta[field] !== undefined) {
|
|
149
|
+
row[field] = meta[field];
|
|
150
|
+
delete meta[field];
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (Object.keys(meta).length > 0) {
|
|
154
|
+
row.metadata = meta;
|
|
155
|
+
}
|
|
156
|
+
return row;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* @param {object} parsed
|
|
161
|
+
* @returns {void}
|
|
162
|
+
*/
|
|
163
|
+
function rejectBaselineCatalogShape(parsed) {
|
|
164
|
+
if (parsed && typeof parsed === 'object' && Array.isArray(parsed.roles) && !parsed.key) {
|
|
165
|
+
throw new Error(
|
|
166
|
+
'File is a baseline catalog (roles[]). ' +
|
|
167
|
+
'Use `aifabrix role import --file <path>` to seed all entries, ' +
|
|
168
|
+
'or pass a single role object with key and displayName.'
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* @param {string} filePath
|
|
175
|
+
* @returns {Object}
|
|
176
|
+
*/
|
|
177
|
+
function readRoleCreateFile(filePath) {
|
|
178
|
+
const parsed = loadRoleJson(filePath);
|
|
179
|
+
rejectBaselineCatalogShape(parsed);
|
|
180
|
+
return mapRoleFileToApiPayload(parsed, 0);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* @param {string} filePath
|
|
185
|
+
* @returns {Object[]}
|
|
186
|
+
*/
|
|
187
|
+
function readRoleCatalogFile(filePath) {
|
|
188
|
+
const parsed = loadRoleJson(filePath);
|
|
189
|
+
let rows;
|
|
190
|
+
if (Array.isArray(parsed)) {
|
|
191
|
+
rows = parsed;
|
|
192
|
+
} else if (Array.isArray(parsed.roles)) {
|
|
193
|
+
rows = parsed.roles;
|
|
194
|
+
} else {
|
|
195
|
+
throw new Error(
|
|
196
|
+
'Import file must be a baseline catalog with roles[] or a JSON array of roles.'
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
if (rows.length === 0) {
|
|
200
|
+
throw new Error('Import file contains no roles');
|
|
201
|
+
}
|
|
202
|
+
return rows.map((row, index) => mapRoleFileToApiPayload(row, index));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* @param {string} filePath
|
|
207
|
+
* @param {Object[]} rows - baseline-shaped role rows
|
|
208
|
+
* @param {string} [version]
|
|
209
|
+
* @returns {void}
|
|
210
|
+
*/
|
|
211
|
+
function writeRoleCatalogFile(filePath, rows, version = '1.0.0') {
|
|
212
|
+
const p = path.resolve(String(filePath || '').trim());
|
|
213
|
+
if (!p) {
|
|
214
|
+
throw new Error('--file is required');
|
|
215
|
+
}
|
|
216
|
+
const catalog = { version, roles: rows };
|
|
217
|
+
writeFileSync(p, `${JSON.stringify(catalog, null, 2)}\n`, 'utf8');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
module.exports = {
|
|
221
|
+
ROLE_COMPILER_FIELDS,
|
|
222
|
+
mapRoleFileToApiPayload,
|
|
223
|
+
mapRoleApiToFileRow,
|
|
224
|
+
readRoleCreateFile,
|
|
225
|
+
readRoleCatalogFile,
|
|
226
|
+
writeRoleCatalogFile
|
|
227
|
+
};
|