@aifabrix/builder 2.51.0 → 2.52.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.
- 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.api.js +57 -1
- package/lib/api/index.js +24 -7
- package/lib/api/pipeline.api.js +43 -3
- package/lib/api/records-bulk.api.js +28 -3
- package/lib/api/types/evidence.types.js +156 -8
- package/lib/api/work.api.js +283 -0
- package/lib/app/run-container-start.js +4 -0
- package/lib/app/run-deploy-job.js +216 -0
- package/lib/app/run-env-compose.js +32 -0
- package/lib/app/run-env-recovery.js +35 -0
- package/lib/app/run-helpers.js +15 -9
- package/lib/app/run.js +18 -2
- package/lib/channels/add-channel.js +54 -23
- package/lib/channels/approval-guides/chatgpt.js +13 -9
- package/lib/channels/approval-guides/microsoft-copilot.js +7 -8
- package/lib/channels/channel-artifact-generator.js +14 -2
- 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 +25 -1
- 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 +2 -0
- package/lib/cli/infra-guided-footers.js +1 -1
- package/lib/cli/infra-guided-public-url-warn.js +55 -0
- package/lib/cli/infra-guided.js +65 -56
- package/lib/cli/setup-app.help.js +4 -0
- package/lib/cli/setup-app.js +4 -0
- package/lib/cli/setup-app.test-commands.js +59 -28
- package/lib/cli/setup-external-system-help.js +8 -2
- package/lib/cli/setup-external-system.js +62 -12
- package/lib/commands/app-test.js +2 -14
- package/lib/commands/channel-add-setup.js +8 -2
- package/lib/commands/channel.js +16 -5
- package/lib/commands/datasource-load-export-cli.js +6 -1
- package/lib/commands/deploy-gate-failure-details.js +19 -17
- package/lib/commands/deploy-gate-orchestrator.js +3 -2
- package/lib/commands/dev-down.js +2 -6
- package/lib/commands/evidence-kind.js +65 -15
- package/lib/commands/governance-generate-external.js +56 -21
- package/lib/commands/governance-verify-external.js +2 -5
- package/lib/commands/lifecycle-external.js +21 -8
- 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-sync.js +34 -17
- package/lib/commands/role-assistant.js +371 -0
- package/lib/commands/test-governance-external.js +2 -5
- package/lib/commands/up-miso.js +61 -24
- package/lib/commands/upload-data-sync-finalize.js +25 -7
- package/lib/commands/upload-sidecar-followups.js +144 -47
- package/lib/commands/upload.js +43 -6
- 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 +31 -10
- package/lib/commands/verify-operations-external.js +2 -5
- package/lib/commands/verify-operations-step-plan.js +31 -0
- package/lib/commands/verify-trust-command-action.js +2 -5
- package/lib/commands/wizard-core-helpers.js +7 -3
- package/lib/commands/wizard-core.js +78 -9
- package/lib/datasource/bulk-loader-run-batches.js +4 -2
- package/lib/datasource/bulk-loader-service.js +5 -1
- package/lib/datasource/datasource-identifier.js +4 -3
- package/lib/datasource/datasource-validate-summary.js +2 -2
- package/lib/datasource/integration-context.js +3 -3
- package/lib/datasource/resolve-app.js +90 -8
- package/lib/datasource/test-e2e.js +2 -2
- package/lib/datasource/unified-validation-run-body.js +15 -1
- package/lib/datasource/unified-validation-run-resolve.js +2 -2
- package/lib/datasource/validate.js +13 -6
- package/lib/external-system/deploy-helpers.js +39 -1
- package/lib/external-system/deploy.js +25 -11
- 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-system-level.js +21 -3
- package/lib/external-system/test.js +43 -7
- package/lib/generator/builders.js +12 -15
- package/lib/generator/wizard.js +51 -4
- 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/capability-registry.js +20 -1
- package/lib/programmatic/command-options.js +1 -0
- 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/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 +1 -1
- package/lib/programmatic/openapi-schema-descriptions-certify.js +5 -1
- package/lib/programmatic/openapi-schema-descriptions-core.js +1 -1
- 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-spec.js +47 -28
- 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-template-workspace.js +28 -1
- 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/role-assistant/evidence-sync.js +450 -0
- package/lib/role-assistant/github-workflow.js +96 -0
- package/lib/role-assistant/knowledge-markdown-frontmatter.js +75 -0
- package/lib/role-assistant/knowledge-sync.js +485 -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 +221 -0
- package/lib/role-assistant/test-runner-workhub.js +399 -0
- package/lib/role-assistant/test-runner.js +500 -0
- package/lib/schema/application-schema.json +24 -28
- package/lib/schema/deployment-rules.yaml +1 -0
- package/lib/schema/external-datasource.schema.json +38 -4
- package/lib/schema/external-system.schema.json +4 -4
- package/lib/schema/role-assistant-settings.schema.json +108 -0
- package/lib/utils/api.js +52 -27
- package/lib/utils/app-path-resolve.js +72 -24
- package/lib/utils/authentication-shape.js +3 -1
- package/lib/utils/certify-external-workspace.js +40 -0
- package/lib/utils/deployment-validation.js +28 -3
- package/lib/utils/docker-run-ephemeral.js +115 -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 +34 -1
- package/lib/utils/external-system-readiness-compact-display.js +3 -0
- package/lib/utils/health-check-public-warn.js +31 -13
- package/lib/utils/health-check.js +11 -1
- package/lib/utils/help-builder.js +1 -0
- package/lib/utils/paths.js +80 -38
- package/lib/utils/pipeline-upload-auth-retry.js +15 -3
- package/lib/utils/role-assistant-paths.js +160 -0
- package/lib/utils/schema-loader.js +41 -0
- package/lib/utils/schema-resolver.js +36 -4
- package/lib/utils/token-manager.js +38 -29
- package/lib/utils/upload-sync-options.js +7 -4
- package/lib/utils/url-declarative-resolve-load-doc.js +38 -1
- package/lib/validation/external-manifest-validator.js +31 -8
- package/lib/validation/reserved-system-key-prefixes.js +56 -0
- package/lib/validation/validate-external-file.js +8 -1
- package/lib/validation/validate.js +54 -19
- package/package.json +2 -1
- package/scripts/export-builder-api-evidence.js +1 -1
- package/templates/applications/builder-api/application.yaml +3 -3
- package/templates/applications/dataplane/application.yaml +24 -17
- package/templates/applications/dataplane/env.template +2 -2
- package/templates/applications/dataplane/rbac.yaml +24 -10
- package/templates/applications/keycloak/application.yaml +2 -2
- package/templates/applications/keycloak/env.template +5 -2
- package/templates/applications/miso-controller/application.yaml +3 -3
- package/templates/applications/miso-controller/env.template +17 -2
- package/templates/channels/chatgpt/install.template.md.hbs +82 -26
- package/templates/channels/chatgpt/integration/README.md +11 -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/install.template.md.hbs +6 -1
- package/templates/python/docker-compose.hbs +10 -0
- package/templates/typescript/docker-compose.hbs +10 -0
- /package/.cursor/plans/{157.1-security-quality-violations-by-type.csv → Done/157.1-security-quality-violations-by-type.csv} +0 -0
|
@@ -13,11 +13,38 @@ const { getDeploymentAuthMode } = require('../utils/deployment-auth-mode');
|
|
|
13
13
|
const { resolveDataplaneUrl } = require('../utils/dataplane-resolver');
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
17
|
-
* @param {
|
|
16
|
+
* Prefer caller-injected Bearer + dataplane URL (builder-api programmatic path).
|
|
17
|
+
* @param {Object} options
|
|
18
|
+
* @returns {Promise<{ environment: string, controllerUrl: string, dataplaneUrl: string, authConfig: Object }|null>}
|
|
19
|
+
*/
|
|
20
|
+
async function tryResolveInjectedProtectionContext(options) {
|
|
21
|
+
if (!(options.authConfig && options.authConfig.token && options.dataplaneUrl)) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const environment =
|
|
25
|
+
options.env || options.environment || (await resolveEnvironment().catch(() => 'dev'));
|
|
26
|
+
let controllerUrl = options.controllerUrl || '';
|
|
27
|
+
if (!controllerUrl) {
|
|
28
|
+
try {
|
|
29
|
+
controllerUrl = (await resolveControllerUrl()) || '';
|
|
30
|
+
} catch {
|
|
31
|
+
controllerUrl = '';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
environment,
|
|
36
|
+
controllerUrl,
|
|
37
|
+
dataplaneUrl: options.dataplaneUrl,
|
|
38
|
+
authConfig: options.authConfig
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* CLI / device-token resolution when no programmatic inject is present.
|
|
44
|
+
* @param {Object} options
|
|
18
45
|
* @returns {Promise<{ environment: string, controllerUrl: string, dataplaneUrl: string, authConfig: Object }>}
|
|
19
46
|
*/
|
|
20
|
-
async function
|
|
47
|
+
async function resolveCliProtectionDataplaneContext(options) {
|
|
21
48
|
const environment = options.env || (await resolveEnvironment());
|
|
22
49
|
const controllerUrl = await resolveControllerUrl();
|
|
23
50
|
if (!controllerUrl) {
|
|
@@ -37,15 +64,30 @@ async function resolveProtectionDataplaneContext(options = {}) {
|
|
|
37
64
|
}
|
|
38
65
|
|
|
39
66
|
if (!authConfig?.token) {
|
|
40
|
-
throw new Error(
|
|
41
|
-
'Authentication required. Run "aifabrix login" and try again.'
|
|
42
|
-
);
|
|
67
|
+
throw new Error('Authentication required. Run "aifabrix login" and try again.');
|
|
43
68
|
}
|
|
44
69
|
|
|
45
70
|
const dataplaneUrl = await resolveDataplaneUrl(controllerUrl, environment, authConfig);
|
|
46
71
|
return { environment, controllerUrl, dataplaneUrl, authConfig };
|
|
47
72
|
}
|
|
48
73
|
|
|
74
|
+
/**
|
|
75
|
+
* @param {Object} [options]
|
|
76
|
+
* @param {string} [options.env]
|
|
77
|
+
* @param {string} [options.environment]
|
|
78
|
+
* @param {string} [options.dataplaneUrl]
|
|
79
|
+
* @param {Object} [options.authConfig]
|
|
80
|
+
* @param {string} [options.controllerUrl]
|
|
81
|
+
* @returns {Promise<{ environment: string, controllerUrl: string, dataplaneUrl: string, authConfig: Object }>}
|
|
82
|
+
*/
|
|
83
|
+
async function resolveProtectionDataplaneContext(options = {}) {
|
|
84
|
+
const injected = await tryResolveInjectedProtectionContext(options);
|
|
85
|
+
if (injected) {
|
|
86
|
+
return injected;
|
|
87
|
+
}
|
|
88
|
+
return resolveCliProtectionDataplaneContext(options);
|
|
89
|
+
}
|
|
90
|
+
|
|
49
91
|
module.exports = {
|
|
50
92
|
resolveProtectionDataplaneContext
|
|
51
93
|
};
|
package/lib/protection/paths.js
CHANGED
|
@@ -89,10 +89,32 @@ function protectionRootHasManifestFiles(root) {
|
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Prefer builder-api workspace scope when present (concurrent-safe; no process.env).
|
|
94
|
+
* @returns {string|null}
|
|
95
|
+
*/
|
|
96
|
+
function getScopedProtectionRoot() {
|
|
97
|
+
try {
|
|
98
|
+
const { getIntegrationPathScope } = require('../programmatic/integration-path-scope');
|
|
99
|
+
const scope = getIntegrationPathScope();
|
|
100
|
+
if (scope && typeof scope.getProtectionRoot === 'function') {
|
|
101
|
+
return scope.getProtectionRoot();
|
|
102
|
+
}
|
|
103
|
+
} catch {
|
|
104
|
+
// programmatic module optional for CLI-only usage
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
92
109
|
/**
|
|
93
110
|
* @returns {string} Absolute path to protection manifests directory
|
|
94
111
|
*/
|
|
95
112
|
function getProtectionRoot() {
|
|
113
|
+
const scoped = getScopedProtectionRoot();
|
|
114
|
+
if (scoped) {
|
|
115
|
+
return scoped;
|
|
116
|
+
}
|
|
117
|
+
|
|
96
118
|
const explicit = process.env.AIFABRIX_PROTECTION_ROOT;
|
|
97
119
|
if (explicit && typeof explicit === 'string' && explicit.trim()) {
|
|
98
120
|
return path.resolve(explicit.trim());
|
|
@@ -18,9 +18,30 @@ const { assertNoDuplicateDatasourceKeys, listProtectionManifestPaths } = require
|
|
|
18
18
|
const { loadProtectionManifest } = require('./load');
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
|
+
* @param {Object} [options]
|
|
22
|
+
* @param {string} [options.controllerUrl]
|
|
23
|
+
* @param {Object} [options.authConfig]
|
|
21
24
|
* @returns {Promise<{ controllerUrl: string, authConfig: Object }>}
|
|
22
25
|
*/
|
|
23
|
-
async function resolveControllerAuthForDimensions() {
|
|
26
|
+
async function resolveControllerAuthForDimensions(options = {}) {
|
|
27
|
+
if (options.authConfig && options.authConfig.token) {
|
|
28
|
+
let controllerUrl = options.controllerUrl || '';
|
|
29
|
+
if (!controllerUrl) {
|
|
30
|
+
try {
|
|
31
|
+
controllerUrl = (await resolveControllerUrl()) || '';
|
|
32
|
+
} catch {
|
|
33
|
+
controllerUrl = '';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (!controllerUrl) {
|
|
37
|
+
throw new Error('Controller URL is required for dimension probe.');
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
controllerUrl: normalizeControllerUrl(controllerUrl),
|
|
41
|
+
authConfig: options.authConfig
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
24
45
|
const controllerUrl = await resolveControllerUrl();
|
|
25
46
|
if (!controllerUrl) {
|
|
26
47
|
throw new Error('Controller URL is required. Run "aifabrix login" first.');
|
|
@@ -165,7 +165,7 @@ async function runProtectionCreate(datasourceKey, opts, logger) {
|
|
|
165
165
|
const verbose = opts.verbose === true;
|
|
166
166
|
|
|
167
167
|
const dpCtx = await resolveProtectionDataplaneContext(opts);
|
|
168
|
-
const dimCtx = await resolveControllerAuthForDimensions();
|
|
168
|
+
const dimCtx = await resolveControllerAuthForDimensions(opts);
|
|
169
169
|
|
|
170
170
|
logCreateHeader({ environment: dpCtx.environment, type: preset?.type }, dsKey, dimensionKey, logger);
|
|
171
171
|
|
|
@@ -22,7 +22,7 @@ async function syncDatasourceAfterProtectionUpload(dataplaneUrl, authConfig, dat
|
|
|
22
22
|
}
|
|
23
23
|
try {
|
|
24
24
|
const created = unwrapApiData(
|
|
25
|
-
await createSyncJob(dataplaneUrl, key, authConfig, {})
|
|
25
|
+
await createSyncJob(dataplaneUrl, key, authConfig, { syncType: 'bulk' })
|
|
26
26
|
);
|
|
27
27
|
const syncJobId =
|
|
28
28
|
created?.id || created?.syncJobId || created?.data?.id || null;
|
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Role Assistant evidence import/export for dataplane evidence lifecycle.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Sync local role-assistant evidence rows with dataplane evidence APIs
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const fs = require('../internal/fs-real-sync');
|
|
13
|
+
const yaml = require('js-yaml');
|
|
14
|
+
const logger = require('../utils/logger');
|
|
15
|
+
const { formatSuccessLine, formatWarningLine, metadata } = require('../utils/cli-test-layout-chalk');
|
|
16
|
+
const {
|
|
17
|
+
listEvidence,
|
|
18
|
+
loadEvidenceBulk,
|
|
19
|
+
validateEvidenceCandidate,
|
|
20
|
+
certifyEvidenceCandidate,
|
|
21
|
+
activateEvidenceCandidate
|
|
22
|
+
} = require('../api/evidence.api');
|
|
23
|
+
const { resolveExternalSystemPath } = require('../utils/app-path-resolve');
|
|
24
|
+
const { SYSTEM_KIND_DIGITAL_WORKER } = require('./settings');
|
|
25
|
+
|
|
26
|
+
const EVIDENCE_DIR_NAME = 'evidence';
|
|
27
|
+
const EVIDENCE_EXPORT_FILE = 'evidence.json';
|
|
28
|
+
const VALIDATION_FAILED = 'failed';
|
|
29
|
+
const VALIDATION_PASSED = 'passed';
|
|
30
|
+
|
|
31
|
+
const EVIDENCE_MUTABLE_KEYS = [
|
|
32
|
+
'key',
|
|
33
|
+
'resourceType',
|
|
34
|
+
'roleKey',
|
|
35
|
+
'kindKey',
|
|
36
|
+
'kindVersion',
|
|
37
|
+
'aspect',
|
|
38
|
+
'knowledgeType',
|
|
39
|
+
'originalLanguage',
|
|
40
|
+
'structuredContent',
|
|
41
|
+
'title',
|
|
42
|
+
'description',
|
|
43
|
+
'when',
|
|
44
|
+
'expectedBehavior',
|
|
45
|
+
'order',
|
|
46
|
+
'status',
|
|
47
|
+
'generationConfidence',
|
|
48
|
+
'suggestionExplanation',
|
|
49
|
+
'contributionOverrides',
|
|
50
|
+
'approval'
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
/** Read-only projections exported for Builder visibility (ignored on load). */
|
|
54
|
+
const EVIDENCE_EXPORT_PROJECTION_KEYS = [
|
|
55
|
+
'valueProposal',
|
|
56
|
+
'contributionProjection',
|
|
57
|
+
'fingerprint',
|
|
58
|
+
'provenanceSummary',
|
|
59
|
+
'trustProjections'
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
function resolveSystemNode(input) {
|
|
63
|
+
if (!input || typeof input !== 'object') {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
return input.system || input.application || null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function isDigitalWorkerModel(input) {
|
|
70
|
+
const systemNode = resolveSystemNode(input);
|
|
71
|
+
if (!systemNode || typeof systemNode.systemKind !== 'string') {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
return systemNode.systemKind.trim() === SYSTEM_KIND_DIGITAL_WORKER;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function isEvidenceFileName(fileName) {
|
|
78
|
+
const lower = String(fileName || '').toLowerCase();
|
|
79
|
+
return lower.endsWith('.json') || lower.endsWith('.yaml') || lower.endsWith('.yml');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function collectEvidenceFiles(dirPath) {
|
|
83
|
+
if (!fs.existsSync(dirPath)) {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
87
|
+
const files = [];
|
|
88
|
+
for (const entry of entries) {
|
|
89
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
90
|
+
if (entry.isDirectory()) {
|
|
91
|
+
files.push(...collectEvidenceFiles(fullPath));
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (isEvidenceFileName(entry.name)) {
|
|
95
|
+
files.push(fullPath);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return files.sort();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function parseEvidenceFile(filePath) {
|
|
102
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
103
|
+
if (filePath.toLowerCase().endsWith('.json')) {
|
|
104
|
+
return JSON.parse(raw);
|
|
105
|
+
}
|
|
106
|
+
return yaml.load(raw);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function extractEvidenceRows(payload) {
|
|
110
|
+
if (Array.isArray(payload)) {
|
|
111
|
+
return payload;
|
|
112
|
+
}
|
|
113
|
+
if (!payload || typeof payload !== 'object') {
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
if (Array.isArray(payload.data)) {
|
|
117
|
+
return payload.data;
|
|
118
|
+
}
|
|
119
|
+
if (Array.isArray(payload.rows)) {
|
|
120
|
+
return payload.rows;
|
|
121
|
+
}
|
|
122
|
+
if (Array.isArray(payload.evidence)) {
|
|
123
|
+
return payload.evidence;
|
|
124
|
+
}
|
|
125
|
+
return [payload];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function sanitizeEvidenceRow(row, systemKey, options = {}) {
|
|
129
|
+
const includeProjections = options.includeProjections === true;
|
|
130
|
+
const out = {};
|
|
131
|
+
const keys = includeProjections
|
|
132
|
+
? [...EVIDENCE_MUTABLE_KEYS, ...EVIDENCE_EXPORT_PROJECTION_KEYS]
|
|
133
|
+
: EVIDENCE_MUTABLE_KEYS;
|
|
134
|
+
for (const key of keys) {
|
|
135
|
+
if (typeof row[key] !== 'undefined') {
|
|
136
|
+
out[key] = row[key];
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (typeof out.roleKey !== 'string' || !out.roleKey.trim()) {
|
|
140
|
+
out.roleKey = systemKey;
|
|
141
|
+
}
|
|
142
|
+
// Import path: clients cannot load status=active; lifecycle activates after certify.
|
|
143
|
+
if (!includeProjections) {
|
|
144
|
+
out.status = 'candidate';
|
|
145
|
+
}
|
|
146
|
+
if (!out.title || !out.when || !out.expectedBehavior) {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function loadEvidenceRowsFromFolder(evidencePath, systemKey) {
|
|
153
|
+
const files = collectEvidenceFiles(evidencePath);
|
|
154
|
+
const rows = [];
|
|
155
|
+
for (const filePath of files) {
|
|
156
|
+
const parsed = parseEvidenceFile(filePath);
|
|
157
|
+
const extractedRows = extractEvidenceRows(parsed);
|
|
158
|
+
for (const row of extractedRows) {
|
|
159
|
+
if (!row || typeof row !== 'object' || Array.isArray(row)) {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
const normalized = sanitizeEvidenceRow(row, systemKey);
|
|
163
|
+
if (!normalized) {
|
|
164
|
+
const shortName = path.relative(evidencePath, filePath);
|
|
165
|
+
throw new Error(
|
|
166
|
+
`Evidence row in ${shortName} is missing required fields title/when/expectedBehavior.`
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
rows.push(normalized);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return { files, rows };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function resolveEvidenceDirectory(systemKey, pathOptions = {}) {
|
|
176
|
+
const appPath = resolveExternalSystemPath(systemKey, pathOptions);
|
|
177
|
+
return path.join(appPath, EVIDENCE_DIR_NAME);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function normalizeEvidenceRef(row) {
|
|
181
|
+
if (!row || typeof row !== 'object') {
|
|
182
|
+
return '';
|
|
183
|
+
}
|
|
184
|
+
const candidates = [row.id, row.key];
|
|
185
|
+
for (const candidate of candidates) {
|
|
186
|
+
if (typeof candidate === 'string' && candidate.trim()) {
|
|
187
|
+
return candidate.trim();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return '';
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function toMatchKey(row) {
|
|
194
|
+
return `${String(row.title || '').trim()}|${String(row.when || '').trim()}|${String(row.expectedBehavior || '').trim()}`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function dedupeRefs(refs) {
|
|
198
|
+
const out = [];
|
|
199
|
+
const seen = new Set();
|
|
200
|
+
for (const ref of refs) {
|
|
201
|
+
if (!ref || seen.has(ref)) {
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
seen.add(ref);
|
|
205
|
+
out.push(ref);
|
|
206
|
+
}
|
|
207
|
+
return out;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function resolveRefsFromLoadedRows(rows) {
|
|
211
|
+
return dedupeRefs((rows || []).map((row) => normalizeEvidenceRef(row)).filter(Boolean));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function buildRoleEvidenceIndex(rows) {
|
|
215
|
+
const byKey = new Map();
|
|
216
|
+
for (const row of rows || []) {
|
|
217
|
+
if (!row || typeof row !== 'object') {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
const ref = normalizeEvidenceRef(row);
|
|
221
|
+
if (!ref) {
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
const matchKey = toMatchKey(row);
|
|
225
|
+
if (matchKey !== '||' && !byKey.has(matchKey)) {
|
|
226
|
+
byKey.set(matchKey, ref);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return byKey;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async function resolveEvidenceLifecycleRefs(dataplaneUrl, authConfig, systemKey, loadedRows, preparedRows) {
|
|
233
|
+
const refs = resolveRefsFromLoadedRows(loadedRows);
|
|
234
|
+
if (refs.length >= preparedRows.length || preparedRows.length === 0) {
|
|
235
|
+
return refs;
|
|
236
|
+
}
|
|
237
|
+
const roleRows = await listEvidence(dataplaneUrl, authConfig, { roleKey: systemKey });
|
|
238
|
+
const roleByKey = buildRoleEvidenceIndex(roleRows);
|
|
239
|
+
for (const row of preparedRows) {
|
|
240
|
+
const matchRef = roleByKey.get(toMatchKey(row));
|
|
241
|
+
if (matchRef) {
|
|
242
|
+
refs.push(matchRef);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return dedupeRefs(refs);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function validationFailed(validationResponse) {
|
|
249
|
+
const status = String(validationResponse?.status || '').trim().toLowerCase();
|
|
250
|
+
return status === VALIDATION_FAILED;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function isActivatedEvidenceRow(row) {
|
|
254
|
+
const status = String(row?.status || '').trim().toLowerCase();
|
|
255
|
+
return !status || status === 'active';
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async function runEvidenceLifecycleForRef(dataplaneUrl, authConfig, evidenceRef) {
|
|
259
|
+
const validation = await validateEvidenceCandidate(dataplaneUrl, authConfig, evidenceRef);
|
|
260
|
+
if (validationFailed(validation)) {
|
|
261
|
+
const issues = Array.isArray(validation?.issues) ? validation.issues.length : 0;
|
|
262
|
+
throw new Error(`Validation failed for evidence ${evidenceRef} (${issues} issue${issues === 1 ? '' : 's'}).`);
|
|
263
|
+
}
|
|
264
|
+
if (String(validation?.status || '').trim().toLowerCase() !== VALIDATION_PASSED) {
|
|
265
|
+
throw new Error(`Validation status for evidence ${evidenceRef} is "${validation?.status || 'unknown'}".`);
|
|
266
|
+
}
|
|
267
|
+
await certifyEvidenceCandidate(dataplaneUrl, authConfig, evidenceRef, {});
|
|
268
|
+
const activated = await activateEvidenceCandidate(dataplaneUrl, authConfig, evidenceRef);
|
|
269
|
+
if (!isActivatedEvidenceRow(activated)) {
|
|
270
|
+
throw new Error(`Evidence ${evidenceRef} did not become active after activation.`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function buildFailureSummary(failures) {
|
|
275
|
+
const preview = failures.slice(0, 3).join('; ');
|
|
276
|
+
const more = failures.length > 3 ? ` (and ${failures.length - 3} more)` : '';
|
|
277
|
+
return `${preview}${more}`;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function buildEvidenceDryRunSummary(evidencePath, files, rows) {
|
|
281
|
+
return {
|
|
282
|
+
skipped: false,
|
|
283
|
+
dryRun: true,
|
|
284
|
+
evidencePath,
|
|
285
|
+
filesDiscovered: files.length,
|
|
286
|
+
discovered: rows.length
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function buildEvidenceImportSummary(evidencePath, files, rows, loadResult) {
|
|
291
|
+
return {
|
|
292
|
+
skipped: false,
|
|
293
|
+
evidencePath,
|
|
294
|
+
filesDiscovered: files.length,
|
|
295
|
+
discovered: rows.length,
|
|
296
|
+
loaded: Number(loadResult.inserted || 0) + Number(loadResult.updated || 0),
|
|
297
|
+
validated: 0,
|
|
298
|
+
certified: 0,
|
|
299
|
+
productionReady: 0,
|
|
300
|
+
failed: 0
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async function loadEvidenceAndResolveRefs(dataplaneUrl, authConfig, systemKey, rows) {
|
|
305
|
+
const loadResult = await loadEvidenceBulk(dataplaneUrl, authConfig, rows, {});
|
|
306
|
+
const refs = await resolveEvidenceLifecycleRefs(dataplaneUrl, authConfig, systemKey, loadResult.data, rows);
|
|
307
|
+
return { loadResult, refs };
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async function runEvidenceLifecycleBatch(dataplaneUrl, authConfig, refs, summary) {
|
|
311
|
+
const failures = [];
|
|
312
|
+
for (const evidenceRef of refs) {
|
|
313
|
+
try {
|
|
314
|
+
await runEvidenceLifecycleForRef(dataplaneUrl, authConfig, evidenceRef);
|
|
315
|
+
summary.validated += 1;
|
|
316
|
+
summary.certified += 1;
|
|
317
|
+
summary.productionReady += 1;
|
|
318
|
+
} catch (error) {
|
|
319
|
+
summary.failed += 1;
|
|
320
|
+
failures.push(error.message || String(error));
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return failures;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function appendUnresolvedEvidenceFailures(summary, failures, refCount, rowCount) {
|
|
327
|
+
if (refCount >= rowCount) {
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
const unresolved = rowCount - refCount;
|
|
331
|
+
summary.failed += unresolved;
|
|
332
|
+
failures.push(`Could not resolve ${unresolved} evidence row(s) to dataplane ids after load.`);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function throwWhenEvidenceLifecycleFailed(summary, failures) {
|
|
336
|
+
if (summary.failed === 0) {
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
const err = new Error(`Role assistant evidence lifecycle failed: ${buildFailureSummary(failures)}`);
|
|
340
|
+
err.evidenceSummary = summary;
|
|
341
|
+
throw err;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
async function importRoleAssistantEvidence(params) {
|
|
345
|
+
const { systemKey, dataplaneUrl, authConfig, model, pathOptions = {}, dryRun = false } = params;
|
|
346
|
+
if (!isDigitalWorkerModel(model)) {
|
|
347
|
+
return { skipped: true, reason: 'not-role-assistant' };
|
|
348
|
+
}
|
|
349
|
+
const evidencePath = resolveEvidenceDirectory(systemKey, pathOptions);
|
|
350
|
+
const { files, rows } = loadEvidenceRowsFromFolder(evidencePath, systemKey);
|
|
351
|
+
if (rows.length === 0) {
|
|
352
|
+
return { skipped: true, reason: 'no-evidence-files', evidencePath };
|
|
353
|
+
}
|
|
354
|
+
if (dryRun) {
|
|
355
|
+
return buildEvidenceDryRunSummary(evidencePath, files, rows);
|
|
356
|
+
}
|
|
357
|
+
const { loadResult, refs } = await loadEvidenceAndResolveRefs(
|
|
358
|
+
dataplaneUrl,
|
|
359
|
+
authConfig,
|
|
360
|
+
systemKey,
|
|
361
|
+
rows
|
|
362
|
+
);
|
|
363
|
+
const summary = buildEvidenceImportSummary(evidencePath, files, rows, loadResult);
|
|
364
|
+
const failures = await runEvidenceLifecycleBatch(dataplaneUrl, authConfig, refs, summary);
|
|
365
|
+
appendUnresolvedEvidenceFailures(summary, failures, refs.length, rows.length);
|
|
366
|
+
throwWhenEvidenceLifecycleFailed(summary, failures);
|
|
367
|
+
return summary;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function sortEvidenceRows(rows) {
|
|
371
|
+
return [...rows].sort((left, right) => {
|
|
372
|
+
const titleCmp = String(left.title || '').localeCompare(String(right.title || ''));
|
|
373
|
+
if (titleCmp !== 0) {
|
|
374
|
+
return titleCmp;
|
|
375
|
+
}
|
|
376
|
+
return Number(left.order || 0) - Number(right.order || 0);
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
async function exportRoleAssistantEvidence(params) {
|
|
381
|
+
const { systemKey, dataplaneUrl, authConfig, manifest, pathOptions = {} } = params;
|
|
382
|
+
const model = { system: manifest?.application, dataSources: manifest?.dataSources || [] };
|
|
383
|
+
if (!isDigitalWorkerModel(model)) {
|
|
384
|
+
return { skipped: true, reason: 'not-role-assistant' };
|
|
385
|
+
}
|
|
386
|
+
const rows = await listEvidence(dataplaneUrl, authConfig, { roleKey: systemKey });
|
|
387
|
+
const normalized = sortEvidenceRows(
|
|
388
|
+
rows
|
|
389
|
+
.map((row) => sanitizeEvidenceRow(row, systemKey, { includeProjections: true }))
|
|
390
|
+
.filter(Boolean)
|
|
391
|
+
);
|
|
392
|
+
if (normalized.length === 0) {
|
|
393
|
+
return { skipped: true, reason: 'no-evidence-rows' };
|
|
394
|
+
}
|
|
395
|
+
const evidencePath = resolveEvidenceDirectory(systemKey, pathOptions);
|
|
396
|
+
fs.mkdirSync(evidencePath, { recursive: true });
|
|
397
|
+
const outPath = path.join(evidencePath, EVIDENCE_EXPORT_FILE);
|
|
398
|
+
fs.writeFileSync(outPath, `${JSON.stringify(normalized, null, 2)}\n`, 'utf8');
|
|
399
|
+
return {
|
|
400
|
+
skipped: false,
|
|
401
|
+
count: normalized.length,
|
|
402
|
+
evidencePath,
|
|
403
|
+
filePath: outPath
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function logRoleAssistantEvidenceImportSummary(systemKey, result, options = {}) {
|
|
408
|
+
if (result.skipped) {
|
|
409
|
+
if (options.verbose === true) {
|
|
410
|
+
logger.log(metadata(`Role assistant evidence skipped (${systemKey}): ${result.reason}`));
|
|
411
|
+
}
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
if (result.dryRun) {
|
|
415
|
+
logger.log(
|
|
416
|
+
formatWarningLine(
|
|
417
|
+
`Role assistant evidence dry-run: ${result.discovered} row(s) from ${result.filesDiscovered} file(s).`
|
|
418
|
+
)
|
|
419
|
+
);
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
logger.log(
|
|
423
|
+
formatSuccessLine(
|
|
424
|
+
`Role assistant evidence applied: discovered ${result.discovered}, loaded ${result.loaded}, `
|
|
425
|
+
+ `validated ${result.validated}, certified ${result.certified}, production-ready ${result.productionReady}.`
|
|
426
|
+
)
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function logRoleAssistantEvidenceExportSummary(systemKey, result, options = {}) {
|
|
431
|
+
if (result.skipped) {
|
|
432
|
+
if (options.verbose === true) {
|
|
433
|
+
logger.log(metadata(`Role assistant evidence export skipped (${systemKey}): ${result.reason}`));
|
|
434
|
+
}
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
logger.log(
|
|
438
|
+
formatSuccessLine(
|
|
439
|
+
`Role assistant evidence exported (${systemKey}): ${result.count} row(s) to ${result.filePath}.`
|
|
440
|
+
)
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
module.exports = {
|
|
445
|
+
isDigitalWorkerModel,
|
|
446
|
+
importRoleAssistantEvidence,
|
|
447
|
+
exportRoleAssistantEvidence,
|
|
448
|
+
logRoleAssistantEvidenceImportSummary,
|
|
449
|
+
logRoleAssistantEvidenceExportSummary
|
|
450
|
+
};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Role Assistant GitHub workflow scaffolding.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Generates a promotion workflow for role-assistant packages
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const fsSync = require('../internal/fs-real-sync');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const { resolveExternalSystemPath } = require('../utils/app-path-resolve');
|
|
14
|
+
|
|
15
|
+
const WORKFLOW_FILE_NAME = 'role-assistant-promote.yml';
|
|
16
|
+
|
|
17
|
+
function buildRoleAssistantWorkflowContent(systemKey) {
|
|
18
|
+
return `name: Role Assistant Promotion
|
|
19
|
+
|
|
20
|
+
on:
|
|
21
|
+
workflow_dispatch:
|
|
22
|
+
inputs:
|
|
23
|
+
environment:
|
|
24
|
+
description: "Target environment"
|
|
25
|
+
required: true
|
|
26
|
+
type: choice
|
|
27
|
+
options:
|
|
28
|
+
- dev
|
|
29
|
+
- tst
|
|
30
|
+
- pro
|
|
31
|
+
default: dev
|
|
32
|
+
|
|
33
|
+
jobs:
|
|
34
|
+
promote:
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
steps:
|
|
37
|
+
- name: Checkout repository
|
|
38
|
+
uses: actions/checkout@v4
|
|
39
|
+
|
|
40
|
+
- name: Setup Node.js
|
|
41
|
+
uses: actions/setup-node@v4
|
|
42
|
+
with:
|
|
43
|
+
node-version: "20"
|
|
44
|
+
|
|
45
|
+
- name: Install AI Fabrix Builder
|
|
46
|
+
run: npm install -g @aifabrix/builder
|
|
47
|
+
|
|
48
|
+
- name: Login to AI Fabrix
|
|
49
|
+
run: |
|
|
50
|
+
set -e
|
|
51
|
+
aifabrix login \\
|
|
52
|
+
--method credentials \\
|
|
53
|
+
--app ${systemKey} \\
|
|
54
|
+
--client-id \${{ secrets.AIFABRIX_CLIENT_ID }} \\
|
|
55
|
+
--client-secret \${{ secrets.AIFABRIX_CLIENT_SECRET }} \\
|
|
56
|
+
--controller \${{ secrets.AIFABRIX_CONTROLLER_URL }} \\
|
|
57
|
+
--environment \${{ inputs.environment }}
|
|
58
|
+
|
|
59
|
+
- name: Validate role assistant package
|
|
60
|
+
run: |
|
|
61
|
+
set -e
|
|
62
|
+
aifabrix validate ${systemKey}
|
|
63
|
+
|
|
64
|
+
- name: Upload role assistant package
|
|
65
|
+
run: |
|
|
66
|
+
set -e
|
|
67
|
+
aifabrix upload ${systemKey} --env \${{ inputs.environment }}
|
|
68
|
+
|
|
69
|
+
- name: Run role assistant lifecycle test
|
|
70
|
+
run: |
|
|
71
|
+
set -e
|
|
72
|
+
aifabrix role-assistant test ${systemKey} --env \${{ inputs.environment }} --json
|
|
73
|
+
`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function resolveRoleAssistantWorkflowPath(systemKey, options = {}) {
|
|
77
|
+
const rootDir = options.roleAssistantPath || resolveExternalSystemPath(systemKey, options.pathOptions || {});
|
|
78
|
+
return path.join(rootDir, '.github', 'workflows', WORKFLOW_FILE_NAME);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function ensureRoleAssistantGithubWorkflow(systemKey, options = {}) {
|
|
82
|
+
const workflowPath = resolveRoleAssistantWorkflowPath(systemKey, options);
|
|
83
|
+
if (!options.force && fsSync.existsSync(workflowPath)) {
|
|
84
|
+
return { created: false, workflowPath, reason: 'already-exists' };
|
|
85
|
+
}
|
|
86
|
+
fsSync.mkdirSync(path.dirname(workflowPath), { recursive: true });
|
|
87
|
+
fsSync.writeFileSync(workflowPath, buildRoleAssistantWorkflowContent(systemKey), 'utf8');
|
|
88
|
+
return { created: true, workflowPath };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = {
|
|
92
|
+
WORKFLOW_FILE_NAME,
|
|
93
|
+
buildRoleAssistantWorkflowContent,
|
|
94
|
+
resolveRoleAssistantWorkflowPath,
|
|
95
|
+
ensureRoleAssistantGithubWorkflow
|
|
96
|
+
};
|