@aifabrix/builder 2.45.0 → 2.46.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/.cursor/rules/cli-layout.mdc +2 -0
- package/.cursor/rules/cli-product-neutral.mdc +43 -0
- package/.cursor/rules/docs-rules.mdc +3 -1
- package/.cursor/rules/project-rules.mdc +1 -0
- package/README.md +43 -17
- package/integration/hubspot-test/wizard-hubspot-e2e.yaml +1 -1
- package/jest.config.default.js +2 -1
- package/jest.config.isolated.js +2 -1
- package/jest.global-live-fabrix-hooks.js +36 -0
- package/jest.isolated-projects.js +130 -0
- package/jest.projects.js +69 -121
- package/lib/api/agent-metadata-validation.api.js +93 -0
- package/lib/api/api-response-errors.js +124 -0
- package/lib/api/audit-api-helpers.js +68 -0
- package/lib/api/audit.api.js +119 -0
- package/lib/api/auth-cache.api.js +44 -0
- package/lib/api/certificates.api.js +4 -4
- package/lib/api/credential.api.js +2 -2
- package/lib/api/dataplane-health.api.js +137 -0
- package/lib/api/dataplane-sync.api.js +27 -0
- package/lib/api/datasources-core.api.js +21 -18
- package/lib/api/datasources-extended.api.js +22 -22
- package/lib/api/dimensions.api.js +3 -0
- package/lib/api/external-systems.api.js +13 -13
- package/lib/api/governance-scenario-pack.api.js +159 -0
- package/lib/api/governance-scenarios.api.js +39 -0
- package/lib/api/groups.api.js +79 -0
- package/lib/api/index.js +88 -5
- package/lib/api/lifecycle.api.js +77 -0
- package/lib/api/pipeline.api.js +7 -5
- package/lib/api/protection.api.js +157 -0
- package/lib/api/records-bulk.api.js +43 -0
- package/lib/api/records-search-parse.js +49 -0
- package/lib/api/records-search.api.js +29 -0
- package/lib/api/types/agent-metadata-validation.types.js +67 -0
- package/lib/api/types/audit.types.js +72 -0
- package/lib/api/types/dataplane-health.types.js +27 -0
- package/lib/api/types/dimensions.types.js +2 -0
- package/lib/api/types/governance-scenarios.types.js +39 -0
- package/lib/api/types/protection.types.js +69 -0
- package/lib/api/types/records-bulk.types.js +35 -0
- package/lib/api/types/records-search.types.js +28 -0
- package/lib/api/user-groups.api.js +50 -0
- package/lib/api/users.api.js +95 -0
- package/lib/api/validation-run.api.js +3 -3
- package/lib/api/validation-runner.js +5 -3
- package/lib/api/wizard-platform.api.js +3 -3
- package/lib/api/wizard.api.js +21 -21
- package/lib/app/deploy-config.js +4 -1
- package/lib/app/deploy.js +34 -19
- package/lib/app/down.js +20 -1
- package/lib/app/push.js +4 -5
- package/lib/app/register.js +7 -4
- package/lib/app/run-env-compose.js +84 -30
- package/lib/build/index.js +5 -0
- package/lib/certification/sync-system-certification.js +13 -6
- package/lib/cli/index.js +8 -2
- package/lib/cli/infra-guided-auth.js +109 -0
- package/lib/cli/infra-guided.js +91 -69
- package/lib/cli/setup-app.help.js +19 -2
- package/lib/cli/setup-app.js +58 -23
- package/lib/cli/setup-app.test-commands.js +28 -21
- package/lib/cli/setup-auth.js +55 -3
- package/lib/cli/setup-certification-commands.js +147 -0
- package/lib/cli/setup-certification.help.js +141 -0
- package/lib/cli/setup-environment.js +19 -2
- package/lib/cli/setup-external-system-help.js +62 -0
- package/lib/cli/setup-external-system.js +62 -6
- package/lib/cli/setup-infra.js +9 -1
- package/lib/cli/setup-platform.js +6 -1
- package/lib/cli/setup-secrets.js +33 -1
- package/lib/cli/setup-utility-help-after.js +65 -0
- package/lib/cli/setup-utility-repair.js +198 -0
- package/lib/cli/setup-utility-resolve.js +42 -6
- package/lib/cli/setup-utility-validate.js +197 -0
- package/lib/cli/setup-utility.js +41 -258
- package/lib/commands/auth-status-client-tokens.js +214 -0
- package/lib/commands/auth-status-dataplane-version.js +269 -0
- package/lib/commands/auth-status-display.js +94 -0
- package/lib/commands/auth-status.js +111 -53
- package/lib/commands/credential-push.js +10 -5
- package/lib/commands/datasource-load-export-cli.js +178 -0
- package/lib/commands/datasource-test-trust-cli.js +118 -0
- package/lib/commands/datasource-unified-test-cli.js +31 -20
- package/lib/commands/datasource-unified-test-cli.options.js +22 -3
- package/lib/commands/datasource-unified-test-e2e-cli-helpers.js +5 -5
- package/lib/commands/datasource-verify-audit-cli.js +96 -0
- package/lib/commands/datasource.js +68 -1
- package/lib/commands/dev-show-display.js +8 -1
- package/lib/commands/dimension-value.js +43 -13
- package/lib/commands/dimension.js +38 -7
- package/lib/commands/governance-cli-shared.js +94 -0
- package/lib/commands/governance-command-action.js +132 -0
- package/lib/commands/governance-download-external.js +34 -0
- package/lib/commands/governance-generate-external.js +71 -0
- package/lib/commands/governance-upload-external.js +36 -0
- package/lib/commands/governance-verify-external.js +64 -0
- package/lib/commands/governance.js +144 -0
- package/lib/commands/identity-apply-sync.js +131 -0
- package/lib/commands/identity-group-cli.js +146 -0
- package/lib/commands/identity-membership-role-cache.js +175 -0
- package/lib/commands/identity-shared.js +94 -0
- package/lib/commands/identity-user-cli.js +158 -0
- package/lib/commands/identity.js +43 -0
- package/lib/commands/lifecycle-command-action.js +123 -0
- package/lib/commands/lifecycle-external.js +268 -0
- package/lib/commands/login.js +56 -17
- package/lib/commands/protection-cli-leaves.js +286 -0
- package/lib/commands/protection.js +50 -0
- package/lib/commands/repair-auth-apply.js +127 -0
- package/lib/commands/repair-changed-files.js +41 -0
- package/lib/commands/repair-datasource-keys.js +4 -1
- package/lib/commands/repair-datasource-run.js +59 -0
- package/lib/commands/repair-datasource-test-payload.js +259 -0
- package/lib/commands/repair-datasource.js +1 -51
- package/lib/commands/repair-env-template.js +64 -13
- package/lib/commands/repair-external-run.js +216 -0
- package/lib/commands/repair-openapi-sync.js +23 -0
- package/lib/commands/repair-persist.js +4 -0
- package/lib/commands/repair-rbac.js +52 -1
- package/lib/commands/repair-steps.js +145 -0
- package/lib/commands/repair.js +29 -286
- package/lib/commands/secrets-get.js +184 -0
- package/lib/commands/secrets-remove-all.js +3 -1
- package/lib/commands/setup-image-refresh.js +146 -0
- package/lib/commands/setup-modes.js +158 -162
- package/lib/commands/setup-platform-auth.js +76 -0
- package/lib/commands/setup-prompts-admin.js +244 -0
- package/lib/commands/setup-prompts-platform-mode.js +67 -0
- package/lib/commands/setup-prompts-validators.js +36 -0
- package/lib/commands/setup-prompts.js +24 -76
- package/lib/commands/setup-run-context.js +45 -0
- package/lib/commands/setup-spinners.js +39 -0
- package/lib/commands/setup.js +27 -5
- package/lib/commands/teardown.js +39 -0
- package/lib/commands/test-e2e-external.js +61 -14
- package/lib/commands/test-governance-cli-options.js +47 -0
- package/lib/commands/test-governance-external.js +67 -0
- package/lib/commands/test-trust-cli-options.js +32 -0
- package/lib/commands/test-trust-external.js +126 -0
- package/lib/commands/up-common.js +25 -13
- package/lib/commands/up-dataplane.js +50 -13
- package/lib/commands/upload-publish-context.js +75 -0
- package/lib/commands/upload-sidecar-followups.js +89 -0
- package/lib/commands/upload.js +108 -105
- package/lib/commands/verify-governance-combined-external.js +61 -0
- package/lib/commands/verify-governance-command-action.js +64 -0
- package/lib/commands/verify-governance-db.js +102 -0
- package/lib/commands/verify-governance-external.js +77 -0
- package/lib/commands/verify-governance-result-builders.js +71 -0
- package/lib/commands/verify-operations-command-action.js +68 -0
- package/lib/commands/verify-operations-external.js +268 -0
- package/lib/commands/verify-operations-persist.js +75 -0
- package/lib/commands/verify-operations-step-plan.js +68 -0
- package/lib/commands/verify-operations-steps.js +111 -0
- package/lib/commands/verify-trust-command-action.js +70 -0
- package/lib/commands/verify-trust-external.js +67 -0
- package/lib/commands/wizard-config-normalizer.js +43 -23
- package/lib/commands/wizard-core.js +5 -0
- package/lib/core/admin-password-generator.js +38 -0
- package/lib/core/admin-secrets-bundle.js +111 -0
- package/lib/core/admin-secrets-env-overlay.js +62 -0
- package/lib/core/admin-secrets.js +32 -4
- package/lib/core/config-attach-extensions.js +3 -0
- package/lib/core/config-device-dataplane.js +185 -0
- package/lib/core/config-platform-controller.js +44 -0
- package/lib/core/config-setup-installation-profile.js +42 -0
- package/lib/core/config-setup-platform-mode.js +46 -0
- package/lib/core/config.js +46 -36
- package/lib/core/secrets-admin-env.js +6 -2
- package/lib/core/secrets-ensure.js +4 -0
- package/lib/core/secrets-env-content.js +54 -48
- package/lib/core/secrets-env-write.js +10 -1
- package/lib/core/secrets-infra-placeholder-sync.js +2 -0
- package/lib/core/setup-installation-profile.js +43 -0
- package/lib/core/setup-platform-mode.js +55 -0
- package/lib/datasource/agent-trust-debug-log.js +45 -0
- package/lib/datasource/agent-trust-fetch.js +115 -0
- package/lib/datasource/agent-trust-map.js +101 -0
- package/lib/datasource/agent-trust-mock.js +44 -0
- package/lib/datasource/agent-trust-run.js +95 -0
- package/lib/datasource/agent-trust-timeout.js +25 -0
- package/lib/datasource/audit-evidence-display.js +80 -0
- package/lib/datasource/audit-evidence-extract-debug.js +115 -0
- package/lib/datasource/audit-evidence-extract.js +180 -0
- package/lib/datasource/audit-evidence-matrix-rows.js +158 -0
- package/lib/datasource/audit-evidence-operation-aliases.js +63 -0
- package/lib/datasource/audit-evidence-poll.js +109 -0
- package/lib/datasource/audit-evidence-run.js +177 -0
- package/lib/datasource/audit-evidence-verify-helpers.js +167 -0
- package/lib/datasource/audit-evidence-verify-matrix.js +205 -0
- package/lib/datasource/audit-evidence-verify.js +108 -0
- package/lib/datasource/bulk-loader-run-batches.js +100 -0
- package/lib/datasource/bulk-loader-service-retry.js +52 -0
- package/lib/datasource/bulk-loader-service.js +127 -0
- package/lib/datasource/capability/run-capability-dimension.js +2 -1
- package/lib/datasource/datasource-exporter-resolve.js +82 -0
- package/lib/datasource/datasource-exporter-service.js +144 -0
- package/lib/datasource/datasource-identifier.js +148 -0
- package/lib/datasource/datasource-load-export-context.js +95 -0
- package/lib/datasource/integration-context.js +6 -4
- package/lib/datasource/local-data-paths.js +145 -0
- package/lib/datasource/log-cleaner.js +241 -0
- package/lib/datasource/log-viewer.js +111 -6
- package/lib/datasource/record-file-parser.js +85 -0
- package/lib/datasource/record-mapper.js +129 -0
- package/lib/datasource/resolve-app.js +74 -55
- package/lib/datasource/test-e2e.js +29 -2
- package/lib/datasource/unified-validation-run-body.js +3 -1
- package/lib/datasource/unified-validation-run-resolve.js +45 -12
- package/lib/datasource/unified-validation-run.js +47 -18
- package/lib/datasource/validate.js +37 -20
- package/lib/deployment/environment-poll-status.js +138 -0
- package/lib/deployment/environment.js +42 -34
- package/lib/external-system/download.js +21 -20
- package/lib/external-system/generator.js +5 -1
- package/lib/external-system/integration-auth-context.js +45 -0
- package/lib/external-system/test-auth.js +12 -5
- package/lib/external-system/test-system-level.js +25 -4
- package/lib/external-system/test.js +9 -7
- package/lib/generator/external-controller-manifest.js +15 -0
- package/lib/governance/governance-cli-errors.js +96 -0
- package/lib/governance/governance-download-display.js +38 -0
- package/lib/governance/governance-pack-loader.js +70 -0
- package/lib/governance/governance-run-display.js +309 -0
- package/lib/identity/identity-apply-core.js +108 -0
- package/lib/identity/identity-apply-phases.js +244 -0
- package/lib/identity/identity-apply-service.js +77 -0
- package/lib/identity/identity-csv-parser.js +114 -0
- package/lib/infrastructure/admin-secrets-apply.js +84 -0
- package/lib/infrastructure/admin-secrets-ensure.js +130 -0
- package/lib/infrastructure/admin-secrets-resolve.js +95 -0
- package/lib/infrastructure/admin-secrets-sync.js +91 -0
- package/lib/infrastructure/helpers.js +23 -129
- package/lib/infrastructure/index.js +5 -6
- package/lib/infrastructure/orphan-infra-docker-teardown.js +11 -5
- package/lib/internal/fs-real-sync.js +29 -1
- package/lib/lifecycle/certification-progress.js +231 -0
- package/lib/lifecycle/dataplane-ui-url.js +145 -0
- package/lib/lifecycle/exit-codes.js +24 -0
- package/lib/lifecycle/json-envelope.js +25 -0
- package/lib/lifecycle/lifecycle-governance-options.js +51 -0
- package/lib/lifecycle/logger-quiet.js +29 -0
- package/lib/lifecycle/open-api-published.js +59 -0
- package/lib/lifecycle/operations-readiness-metrics.js +101 -0
- package/lib/lifecycle/product-model.js +196 -0
- package/lib/lifecycle/report-display.js +471 -0
- package/lib/lifecycle/scenario-upload.js +191 -0
- package/lib/lifecycle/verify-step-progress.js +87 -0
- package/lib/parameters/infra-kv-discovery.js +27 -2
- package/lib/protection/auth-context.js +51 -0
- package/lib/protection/convert-batch.js +90 -0
- package/lib/protection/load.js +41 -0
- package/lib/protection/paths.js +158 -0
- package/lib/protection/preflight-datasource-ready.js +46 -0
- package/lib/protection/protection-create-probe-lines.js +142 -0
- package/lib/protection/protection-create-probes.js +95 -0
- package/lib/protection/protection-create-scaffold.js +281 -0
- package/lib/protection/protection-display-helpers.js +445 -0
- package/lib/protection/protection-display.js +215 -0
- package/lib/protection/protection-preset-registry.js +97 -0
- package/lib/protection/report-exit.js +62 -0
- package/lib/protection/resolve.js +202 -0
- package/lib/protection/run-commands-validate.js +130 -0
- package/lib/protection/run-commands.js +216 -0
- package/lib/protection/run-protection-create-helpers.js +134 -0
- package/lib/protection/run-protection-create-probes-flow.js +43 -0
- package/lib/protection/run-protection-create.js +191 -0
- package/lib/protection/scope.js +22 -0
- package/lib/protection/sync-after-upload.js +64 -0
- package/lib/protection/upload-batch.js +127 -0
- package/lib/protection/validate-batch.js +113 -0
- package/lib/protection/validate-local.js +65 -0
- package/lib/resolvers/dimension-file.js +29 -4
- package/lib/schema/application-schema.json +1 -1
- package/lib/schema/datasource-test-run.schema.json +89 -6
- package/lib/schema/external-datasource.schema.json +2636 -2588
- package/lib/schema/external-system.schema.json +1089 -761
- package/lib/schema/flag-map-validation-run.json +2 -2
- package/lib/schema/infra.parameter.yaml +45 -0
- package/lib/schema/protection.schema.json +374 -0
- package/lib/schema/type/credential-field-registry.json +642 -0
- package/lib/schema/wizard-config.schema.json +1 -1
- package/lib/utils/agent-trust-run-display.js +231 -0
- package/lib/utils/agent-trust-run-exit.js +59 -0
- package/lib/utils/agent-trust-wait-ui.js +94 -0
- package/lib/utils/aifabrix-test-runtime-guard.js +125 -0
- package/lib/utils/api.js +55 -31
- package/lib/utils/app-relative-path-env.js +112 -0
- package/lib/utils/cli-exit-codes.js +30 -0
- package/lib/utils/cli-secrets-error-format.js +83 -21
- package/lib/utils/cli-sync-options.js +33 -0
- package/lib/utils/cli-utils.js +16 -4
- package/lib/utils/compose-generate-docker-compose.js +4 -0
- package/lib/utils/compose-traefik-ingress-base.js +16 -1
- package/lib/utils/config-secrets-path.js +46 -0
- package/lib/utils/controller-url.js +88 -1
- package/lib/utils/credential-push-ui.js +90 -0
- package/lib/utils/credential-secrets-env.js +137 -9
- package/lib/utils/dataplane-cli-version-gate.js +202 -0
- package/lib/utils/dataplane-cli-version-help.js +74 -0
- package/lib/utils/dataplane-resolver.js +129 -2
- package/lib/utils/datasource-export-display.js +137 -0
- package/lib/utils/datasource-load-display.js +174 -0
- package/lib/utils/datasource-load-export-ui.js +120 -0
- package/lib/utils/datasource-test-run-capability-scope.js +3 -3
- package/lib/utils/datasource-test-run-display.js +3 -3
- package/lib/utils/datasource-test-run-tty-log.js +5 -1
- package/lib/utils/declarative-url-ports.js +3 -3
- package/lib/utils/deployment-auth-mode.js +41 -0
- package/lib/utils/developer-volume-id.js +33 -0
- package/lib/utils/env-copy.js +14 -9
- package/lib/utils/env-kv-orphan-lines.js +43 -0
- package/lib/utils/error-formatters/network-errors.js +13 -4
- package/lib/utils/external-env-template.js +1 -0
- package/lib/utils/external-system-readiness-core.js +10 -0
- package/lib/utils/external-system-readiness-display.js +80 -23
- package/lib/utils/health-check.js +4 -1
- package/lib/utils/help-builder.js +35 -26
- package/lib/utils/keycloak-realm-ready.js +101 -0
- package/lib/utils/load-cip-capacity-display-config.js +61 -15
- package/lib/utils/manifest-source-emit.js +24 -0
- package/lib/utils/pipeline-upload-auth-retry.js +82 -0
- package/lib/utils/platform-controller-url.js +156 -0
- package/lib/utils/postgres-platform-bootstrap.js +199 -0
- package/lib/utils/postgres-wipe.js +9 -2
- package/lib/utils/resolve-env-mode.js +47 -0
- package/lib/utils/secrets-generator.js +31 -25
- package/lib/utils/secrets-missing-error.js +164 -0
- package/lib/utils/semver-compare.js +127 -0
- package/lib/utils/service-url-label.js +81 -0
- package/lib/utils/setup-quiet-output.js +25 -0
- package/lib/utils/template-helpers.js +15 -0
- package/lib/utils/token-manager-client-credentials.js +201 -0
- package/lib/utils/token-manager-messages.js +8 -0
- package/lib/utils/token-manager-refresh.js +12 -2
- package/lib/utils/token-manager.js +86 -105
- package/lib/utils/upload-pipeline-ui.js +98 -0
- package/lib/utils/upload-sync-options.js +42 -0
- package/lib/utils/url-declarative-public-base.js +21 -3
- package/lib/utils/url-declarative-vdir-inactive-env.js +14 -4
- package/lib/utils/validation-poll-ui.js +22 -7
- package/lib/utils/validation-run-stage.js +45 -0
- package/lib/validation/wizard-config-validator.js +58 -19
- package/package.json +1 -1
- package/templates/applications/dataplane/application.yaml +2 -1
- package/templates/applications/dataplane/env.template +36 -1
- package/templates/applications/dataplane/rbac.yaml +4 -0
- package/templates/applications/miso-controller/env.template +16 -0
- package/templates/external-system/README.md.hbs +116 -11
- package/templates/python/docker-compose.hbs +19 -18
- package/templates/typescript/docker-compose.hbs +18 -17
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview TTY display for agent metadata trust runs (plan 143).
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
const logger = require('./logger');
|
|
11
|
+
const { sectionTitle, headerKeyValue, metadata: metaGray } = require('./cli-test-layout-chalk');
|
|
12
|
+
const { statusGlyph } = require('./datasource-test-run-display');
|
|
13
|
+
const { trustDecisionToRowStatus } = require('../datasource/agent-trust-map');
|
|
14
|
+
|
|
15
|
+
const SEP = '────────────────────────────────';
|
|
16
|
+
|
|
17
|
+
/** @type {{ datasource: number, trust: number, conf: number, status: number, cache: number }} */
|
|
18
|
+
const TRUST_TABLE_COLS = {
|
|
19
|
+
datasource: 30,
|
|
20
|
+
trust: 24,
|
|
21
|
+
conf: 6,
|
|
22
|
+
status: 8,
|
|
23
|
+
cache: 8
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function trustGlyph(trustDecision) {
|
|
27
|
+
return statusGlyph(trustDecisionToRowStatus(trustDecision));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function publishGateLine(trustDecision, strict) {
|
|
31
|
+
if (trustDecision === 'notTrusted') return chalk.red('blocked (trustDecision notTrusted)');
|
|
32
|
+
if (strict) {
|
|
33
|
+
return trustDecision === 'trusted'
|
|
34
|
+
? chalk.green('allowed (strict: trusted)')
|
|
35
|
+
: chalk.yellow('blocked (strict requires trusted)');
|
|
36
|
+
}
|
|
37
|
+
return chalk.green('allowed (warning-only); use --strict to require trusted');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function logTrustHeader(run, env) {
|
|
41
|
+
logger.log(SEP);
|
|
42
|
+
logger.log(sectionTitle(`Semantic trust — ${run.datasourceKey}`));
|
|
43
|
+
logger.log(headerKeyValue('Environment:', env));
|
|
44
|
+
logger.log(headerKeyValue('System:', run.systemKey || '—'));
|
|
45
|
+
logger.log(SEP);
|
|
46
|
+
logger.log(metaGray(' Layer checked: business metadata (404.5 agent validation)'));
|
|
47
|
+
logger.log(metaGray(' Not checked here: vendor connectivity, sync, record counts (use test-e2e)'));
|
|
48
|
+
logger.log('');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function logTrustSummaryBlock(run) {
|
|
52
|
+
const confPct = Math.round((run.confidence || 0) * 100);
|
|
53
|
+
logger.log(
|
|
54
|
+
headerKeyValue(
|
|
55
|
+
'Trust:',
|
|
56
|
+
`${trustGlyph(run.trustDecision)} ${run.trustDecision} · confidence ${confPct}% · ${run.validationStatus}`
|
|
57
|
+
)
|
|
58
|
+
);
|
|
59
|
+
if (!run.validatedAt) return;
|
|
60
|
+
const hashShort = run.inputHash ? `${String(run.inputHash).slice(0, 8)}…` : '—';
|
|
61
|
+
logger.log(
|
|
62
|
+
metaGray(
|
|
63
|
+
` Validated: ${run.validatedAt} · inputHash ${hashShort} · cache: ${run.cacheHit ? 'hit' : 'miss'}`
|
|
64
|
+
)
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function logTrustWarningsAndFindings(run, opts) {
|
|
69
|
+
if (run.summary) {
|
|
70
|
+
logger.log('');
|
|
71
|
+
logger.log(chalk.white(' Summary'));
|
|
72
|
+
logger.log(` ${run.summary}`);
|
|
73
|
+
}
|
|
74
|
+
const warnings = Array.isArray(run.highLevelWarnings) ? run.highLevelWarnings : [];
|
|
75
|
+
const findings = Array.isArray(run.findings) ? run.findings : [];
|
|
76
|
+
const showVerboseFindings = opts.verbose === true && findings.length > 0;
|
|
77
|
+
if (warnings.length && !showVerboseFindings) {
|
|
78
|
+
logger.log('');
|
|
79
|
+
logger.log(chalk.yellow(' Warnings (high level)'));
|
|
80
|
+
warnings.forEach(w => logger.log(` • ${w}`));
|
|
81
|
+
}
|
|
82
|
+
if (!showVerboseFindings) return;
|
|
83
|
+
logger.log('');
|
|
84
|
+
logger.log(chalk.white(' Findings (verbose, max 10)'));
|
|
85
|
+
findings.slice(0, 10).forEach(f => {
|
|
86
|
+
logger.log(` • [${f.severity || 'info'}] ${f.code || 'FINDING'}: ${f.message || ''}`);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function logTrustFooter(run, opts) {
|
|
91
|
+
logger.log('');
|
|
92
|
+
logger.log(metaGray(' ── Integration reality (informative) ──'));
|
|
93
|
+
logger.log(metaGray(` Local manifest: ${opts.noSync ? 'not uploaded (--no-sync)' : 'uploaded before run'}`));
|
|
94
|
+
logger.log(metaGray(` Publish gate: ${publishGateLine(run.trustDecision, opts.strict === true)}`));
|
|
95
|
+
logger.log(SEP);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function displayAgentTrustRunTTY(run, opts = {}) {
|
|
99
|
+
if (!run || opts.json === true) return;
|
|
100
|
+
const env = opts.environment ? String(opts.environment) : 'dev';
|
|
101
|
+
logTrustHeader(run, env);
|
|
102
|
+
logTrustSummaryBlock(run);
|
|
103
|
+
logTrustWarningsAndFindings(run, opts);
|
|
104
|
+
logTrustFooter(run, opts);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function trustCacheLabel(trustRun) {
|
|
108
|
+
if (!trustRun || typeof trustRun !== 'object') return '—';
|
|
109
|
+
if (trustRun.readLatest === true) return 'latest';
|
|
110
|
+
if (trustRun.cacheHit === true) return 'hit';
|
|
111
|
+
if (trustRun.cacheHit === false) return 'miss';
|
|
112
|
+
return '—';
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @param {boolean} showCache
|
|
117
|
+
* @returns {number}
|
|
118
|
+
*/
|
|
119
|
+
function trustTableLineWidth(showCache) {
|
|
120
|
+
const { datasource, trust, conf, status, cache } = TRUST_TABLE_COLS;
|
|
121
|
+
let w = 2 + datasource + 1 + trust + 1 + conf + 1 + status;
|
|
122
|
+
if (showCache) w += 1 + cache;
|
|
123
|
+
return w;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* @param {string} key
|
|
128
|
+
* @returns {string}
|
|
129
|
+
*/
|
|
130
|
+
function formatTrustTableDatasourceCol(key) {
|
|
131
|
+
return String(key || '').padEnd(TRUST_TABLE_COLS.datasource).slice(0, TRUST_TABLE_COLS.datasource);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* @param {Object|null|undefined} tr
|
|
136
|
+
* @returns {string}
|
|
137
|
+
*/
|
|
138
|
+
function formatTrustTableTrustCol(tr) {
|
|
139
|
+
const w = TRUST_TABLE_COLS.trust;
|
|
140
|
+
if (!tr || !tr.trustDecision) return '—'.padEnd(w);
|
|
141
|
+
const decision = String(tr.trustDecision);
|
|
142
|
+
const cell = `${trustGlyph(decision)} ${decision}`;
|
|
143
|
+
return cell.length > w ? cell.slice(0, w) : cell.padEnd(w);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* @param {Object|null|undefined} tr
|
|
148
|
+
* @returns {string}
|
|
149
|
+
*/
|
|
150
|
+
function formatTrustTableConfCol(tr) {
|
|
151
|
+
const w = TRUST_TABLE_COLS.conf;
|
|
152
|
+
if (!tr || typeof tr.confidence !== 'number') return '—'.padStart(w);
|
|
153
|
+
return `${Math.round(tr.confidence * 100)}%`.padStart(w);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* @param {Object|null|undefined} tr
|
|
158
|
+
* @param {Object} row
|
|
159
|
+
* @returns {string}
|
|
160
|
+
*/
|
|
161
|
+
function formatTrustTableStatusCol(tr, row) {
|
|
162
|
+
const w = TRUST_TABLE_COLS.status;
|
|
163
|
+
const raw = tr ? String(tr.validationStatus || '—') : row.error || 'fail';
|
|
164
|
+
return raw.padEnd(w).slice(0, w);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* @param {boolean} showCache
|
|
169
|
+
*/
|
|
170
|
+
function logSystemTrustTableHeader(showCache) {
|
|
171
|
+
const { datasource, trust, conf, status, cache } = TRUST_TABLE_COLS;
|
|
172
|
+
let header = ` ${'Datasource'.padEnd(datasource)} ${'Trust'.padEnd(trust)} ${'Conf.'.padStart(conf)} ${'Status'.padEnd(status)}`;
|
|
173
|
+
if (showCache) header += ` ${'Cache'.padEnd(cache)}`;
|
|
174
|
+
logger.log(chalk.white(header));
|
|
175
|
+
logger.log(chalk.gray(` ${'─'.repeat(trustTableLineWidth(showCache))}`));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* @param {Object} row
|
|
180
|
+
* @param {boolean} showCache
|
|
181
|
+
* @returns {string}
|
|
182
|
+
*/
|
|
183
|
+
function formatTrustTableRow(row, showCache) {
|
|
184
|
+
const tr = row.trustRun;
|
|
185
|
+
let line = ` ${formatTrustTableDatasourceCol(row.key)} ${formatTrustTableTrustCol(tr)} ${formatTrustTableConfCol(tr)} ${formatTrustTableStatusCol(tr, row)}`;
|
|
186
|
+
if (showCache) {
|
|
187
|
+
line += ` ${trustCacheLabel(tr).padEnd(TRUST_TABLE_COLS.cache).slice(0, TRUST_TABLE_COLS.cache)}`;
|
|
188
|
+
}
|
|
189
|
+
return line;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function logSystemTrustTable(list, opts = {}) {
|
|
193
|
+
const showCache = opts.showCache === true;
|
|
194
|
+
logSystemTrustTableHeader(showCache);
|
|
195
|
+
for (const row of list) {
|
|
196
|
+
logger.log(formatTrustTableRow(row, showCache));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function displaySystemTrustRollupTTY(systemKey, rows, opts = {}) {
|
|
201
|
+
if (opts.json === true) return;
|
|
202
|
+
const list = Array.isArray(rows) ? rows : [];
|
|
203
|
+
logger.log(SEP);
|
|
204
|
+
logger.log(sectionTitle(`Semantic trust — system ${systemKey} (${list.length} datasources)`));
|
|
205
|
+
const { deriveSystemStatus } = require('./external-system-system-test-tty');
|
|
206
|
+
const worst = deriveSystemStatus(
|
|
207
|
+
list.map(r => ({
|
|
208
|
+
success: r.success !== false,
|
|
209
|
+
datasourceTestRun: {
|
|
210
|
+
status: r.trustRun ? trustDecisionToRowStatus(r.trustRun.trustDecision) : 'fail'
|
|
211
|
+
}
|
|
212
|
+
}))
|
|
213
|
+
);
|
|
214
|
+
logger.log(
|
|
215
|
+
headerKeyValue('System trust:', `${statusGlyph(worst)} ${worst} (worst-of rollup)`)
|
|
216
|
+
);
|
|
217
|
+
logger.log('');
|
|
218
|
+
logSystemTrustTable(list, opts);
|
|
219
|
+
logger.log('');
|
|
220
|
+
logger.log(metaGray(' ── What this run proved ──'));
|
|
221
|
+
logger.log(metaGray(` ✔ Semantic metadata reviewed for ${list.length} datasource(s) on dataplane`));
|
|
222
|
+
logger.log(metaGray(' ✖ Did not call external APIs or run CIP (not E2E)'));
|
|
223
|
+
logger.log(SEP);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
module.exports = {
|
|
227
|
+
displayAgentTrustRunTTY,
|
|
228
|
+
displaySystemTrustRollupTTY,
|
|
229
|
+
formatTrustTableRow,
|
|
230
|
+
TRUST_TABLE_COLS
|
|
231
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Exit codes for agent metadata trust CLI runs (plan 143).
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const { trustDecisionToRowStatus } = require('../datasource/agent-trust-map');
|
|
10
|
+
const { deriveSystemStatus } = require('./external-system-system-test-tty');
|
|
11
|
+
const { computeExitCodeFromDatasourceTestRun } = require('./datasource-test-run-exit');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {Object|null|undefined} run
|
|
15
|
+
* @param {Object} [opts]
|
|
16
|
+
* @param {boolean} [opts.warningsAsErrors]
|
|
17
|
+
* @param {boolean} [opts.strict]
|
|
18
|
+
* @returns {number}
|
|
19
|
+
*/
|
|
20
|
+
function computeExitCodeFromTrustRun(run, opts = {}) {
|
|
21
|
+
if (!run || typeof run !== 'object') return 3;
|
|
22
|
+
const td = run.trustDecision;
|
|
23
|
+
if (run.validationStatus === 'failed' || td === 'notTrusted') return 1;
|
|
24
|
+
if (opts.strict === true && td !== 'trusted') return 1;
|
|
25
|
+
if (opts.warningsAsErrors === true && td === 'usableWithWarnings') return 1;
|
|
26
|
+
if (td === 'trusted' || td === 'usableWithWarnings' || td === 'pending') return 0;
|
|
27
|
+
return 3;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {Array<{ success?: boolean, trustRun?: Object|null }>} rows
|
|
32
|
+
* @param {Object} [opts]
|
|
33
|
+
* @returns {number}
|
|
34
|
+
*/
|
|
35
|
+
function computeSystemExitCodeFromTrustRows(rows, opts = {}) {
|
|
36
|
+
const list = Array.isArray(rows) ? rows : [];
|
|
37
|
+
let worst = 0;
|
|
38
|
+
for (const row of list) {
|
|
39
|
+
const code = computeExitCodeFromTrustRun(row && row.trustRun, opts);
|
|
40
|
+
if (code === 3) return 3;
|
|
41
|
+
if (code > worst) worst = code;
|
|
42
|
+
}
|
|
43
|
+
if (worst > 0) return worst;
|
|
44
|
+
const synthetic = list.map(r => {
|
|
45
|
+
const tr = r && r.trustRun;
|
|
46
|
+
const st = tr ? trustDecisionToRowStatus(tr.trustDecision) : 'fail';
|
|
47
|
+
return { success: st !== 'fail', datasourceTestRun: { status: st } };
|
|
48
|
+
});
|
|
49
|
+
const rollup = deriveSystemStatus(synthetic);
|
|
50
|
+
return computeExitCodeFromDatasourceTestRun(
|
|
51
|
+
{ status: rollup },
|
|
52
|
+
{ warningsAsErrors: opts.warningsAsErrors === true }
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = {
|
|
57
|
+
computeExitCodeFromTrustRun,
|
|
58
|
+
computeSystemExitCodeFromTrustRows
|
|
59
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TTY ora spinner for slow agent metadata validation POST (non-cached path).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Agent trust wait presentation (aligned with validation-poll-ui)
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const chalk = require('chalk');
|
|
12
|
+
const logger = require('./logger');
|
|
13
|
+
|
|
14
|
+
function shouldUseAgentTrustWaitSpinner() {
|
|
15
|
+
return Boolean(process && process.stdout && process.stdout.isTTY);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {string} [datasourceKey]
|
|
20
|
+
* @param {number} deadlineMs
|
|
21
|
+
* @returns {string}
|
|
22
|
+
*/
|
|
23
|
+
function buildAgentTrustWaitSpinnerText(datasourceKey, deadlineMs) {
|
|
24
|
+
const remainingSec = Math.max(0, Math.ceil((deadlineMs - Date.now()) / 1000));
|
|
25
|
+
const key =
|
|
26
|
+
datasourceKey && String(datasourceKey).trim() ? String(datasourceKey).trim() : 'datasource';
|
|
27
|
+
return `Waiting for semantic trust validation… ${key} (~${remainingSec}s left)`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Run a blocking validate POST behind a TTY spinner (skipped when not a TTY).
|
|
32
|
+
*
|
|
33
|
+
* @async
|
|
34
|
+
* @param {Function} work - Zero-arg async function performing the HTTP call
|
|
35
|
+
* @param {Object} opts
|
|
36
|
+
* @param {string} [opts.datasourceKey]
|
|
37
|
+
* @param {number} opts.timeoutMs
|
|
38
|
+
* @returns {Promise<*>}
|
|
39
|
+
*/
|
|
40
|
+
async function runWithAgentTrustWaitSpinner(work, opts = {}) {
|
|
41
|
+
const timeoutMs =
|
|
42
|
+
Number.isFinite(opts.timeoutMs) && opts.timeoutMs > 0 ? opts.timeoutMs : 120000;
|
|
43
|
+
const deadlineMs = Date.now() + timeoutMs;
|
|
44
|
+
const datasourceKey = opts.datasourceKey;
|
|
45
|
+
|
|
46
|
+
if (!shouldUseAgentTrustWaitSpinner()) {
|
|
47
|
+
return work();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const ora = require('ora');
|
|
51
|
+
const spinner = ora({
|
|
52
|
+
text: buildAgentTrustWaitSpinnerText(datasourceKey, deadlineMs),
|
|
53
|
+
spinner: 'dots'
|
|
54
|
+
}).start();
|
|
55
|
+
|
|
56
|
+
const tick = setInterval(() => {
|
|
57
|
+
spinner.text = buildAgentTrustWaitSpinnerText(datasourceKey, deadlineMs);
|
|
58
|
+
}, 1000);
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
return await work();
|
|
62
|
+
} finally {
|
|
63
|
+
clearInterval(tick);
|
|
64
|
+
spinner.stop();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const NON_TTY_LOG_MS = 5000;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Optional one-line hint on non-TTY before a slow validate (throttled by caller usage).
|
|
72
|
+
*
|
|
73
|
+
* @param {string} [datasourceKey]
|
|
74
|
+
* @param {number} timeoutMs
|
|
75
|
+
*/
|
|
76
|
+
function logAgentTrustWaitHintOnce(datasourceKey, timeoutMs) {
|
|
77
|
+
if (shouldUseAgentTrustWaitSpinner()) return;
|
|
78
|
+
const budgetSec = Math.max(1, Math.ceil(timeoutMs / 1000));
|
|
79
|
+
const key =
|
|
80
|
+
datasourceKey && String(datasourceKey).trim() ? String(datasourceKey).trim() : 'datasource';
|
|
81
|
+
logger.log(
|
|
82
|
+
chalk.gray(
|
|
83
|
+
`Waiting for semantic trust validation… ${key} (may take up to ~${budgetSec}s; not cached)`
|
|
84
|
+
)
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
module.exports = {
|
|
89
|
+
shouldUseAgentTrustWaitSpinner,
|
|
90
|
+
buildAgentTrustWaitSpinnerText,
|
|
91
|
+
runWithAgentTrustWaitSpinner,
|
|
92
|
+
logAgentTrustWaitHintOnce,
|
|
93
|
+
NON_TTY_LOG_MS
|
|
94
|
+
};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Refuse writes to live ~/.aifabrix paths during Jest runs.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Test-runtime write guard for secrets and admin-secrets files
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const os = require('os');
|
|
14
|
+
|
|
15
|
+
const ALLOW_ENV = 'ALLOW_AIFABRIX_TEST_WRITE_REAL_CONFIG';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @returns {boolean}
|
|
19
|
+
*/
|
|
20
|
+
function isJestTestProcess() {
|
|
21
|
+
return Boolean(process.env.JEST_WORKER_ID) || process.env.NODE_ENV === 'test';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Legacy mock Fabrix dir used across unit tests (not the operator tree when homedir differs). */
|
|
25
|
+
const JEST_MOCK_FABRIX_ROOT = path.join('/home', 'test', '.aifabrix');
|
|
26
|
+
|
|
27
|
+
/** Additional protected trees in cloud / monorepo dev (see plan 185 Phase 2.5). */
|
|
28
|
+
const EXTRA_PROTECTED_FABRIX_ROOTS = ['/workspace/.aifabrix'];
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Resolve symlinks so `~/.aifabrix` → `/workspace/.aifabrix` matches both paths.
|
|
32
|
+
* @param {string} p
|
|
33
|
+
* @returns {string}
|
|
34
|
+
*/
|
|
35
|
+
function resolveFabrixPathKey(p) {
|
|
36
|
+
const resolved = path.resolve(p);
|
|
37
|
+
try {
|
|
38
|
+
if (fs.existsSync(resolved)) {
|
|
39
|
+
return fs.realpathSync(resolved);
|
|
40
|
+
}
|
|
41
|
+
} catch {
|
|
42
|
+
// ignore
|
|
43
|
+
}
|
|
44
|
+
return resolved;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @returns {string[]} Absolute live config directory paths (deduped by realpath).
|
|
49
|
+
*/
|
|
50
|
+
function getLiveFabrixConfigDirCandidates() {
|
|
51
|
+
const keys = new Set();
|
|
52
|
+
const dirs = [];
|
|
53
|
+
const add = (dir) => {
|
|
54
|
+
if (!dir || typeof dir !== 'string') return;
|
|
55
|
+
const key = resolveFabrixPathKey(dir);
|
|
56
|
+
if (keys.has(key)) return;
|
|
57
|
+
keys.add(key);
|
|
58
|
+
dirs.push(key);
|
|
59
|
+
};
|
|
60
|
+
add(path.join(path.resolve(os.homedir()), '.aifabrix'));
|
|
61
|
+
for (const root of EXTRA_PROTECTED_FABRIX_ROOTS) {
|
|
62
|
+
add(root);
|
|
63
|
+
}
|
|
64
|
+
return dirs;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* True when the path is the operator's live ~/.aifabrix tree (not the legacy /home/test mock).
|
|
69
|
+
* @param {string} filePath
|
|
70
|
+
* @returns {boolean}
|
|
71
|
+
*/
|
|
72
|
+
function isLiveFabrixConfigPath(filePath) {
|
|
73
|
+
if (!filePath || typeof filePath !== 'string') return false;
|
|
74
|
+
const normalized = resolveFabrixPathKey(filePath);
|
|
75
|
+
if (
|
|
76
|
+
normalized === JEST_MOCK_FABRIX_ROOT ||
|
|
77
|
+
normalized.startsWith(`${JEST_MOCK_FABRIX_ROOT}${path.sep}`)
|
|
78
|
+
) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
for (const liveRoot of getLiveFabrixConfigDirCandidates()) {
|
|
82
|
+
if (normalized === liveRoot || normalized.startsWith(`${liveRoot}${path.sep}`)) {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* During Jest, writes are allowed everywhere except the live ~/.aifabrix tree.
|
|
91
|
+
* @param {string} filePath
|
|
92
|
+
* @returns {boolean}
|
|
93
|
+
*/
|
|
94
|
+
function isAllowedTestWritePath(filePath) {
|
|
95
|
+
if (!filePath || typeof filePath !== 'string') return true;
|
|
96
|
+
if (process.env[ALLOW_ENV] === '1' || process.env[ALLOW_ENV] === 'true') {
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
if (!isJestTestProcess()) return true;
|
|
100
|
+
return !isLiveFabrixConfigPath(filePath);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* @param {string} filePath
|
|
105
|
+
* @throws {Error} When Jest would write outside sandbox
|
|
106
|
+
*/
|
|
107
|
+
function assertWritableSecretsPathForTests(filePath) {
|
|
108
|
+
if (!isJestTestProcess()) return;
|
|
109
|
+
if (isAllowedTestWritePath(filePath)) return;
|
|
110
|
+
throw new Error(
|
|
111
|
+
`Refusing to write secrets to live config path during tests: ${filePath}. ` +
|
|
112
|
+
'Use the Jest sandbox (default) or set ALLOW_AIFABRIX_TEST_WRITE_REAL_CONFIG=1.'
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
module.exports = {
|
|
117
|
+
ALLOW_ENV,
|
|
118
|
+
EXTRA_PROTECTED_FABRIX_ROOTS,
|
|
119
|
+
isJestTestProcess,
|
|
120
|
+
resolveFabrixPathKey,
|
|
121
|
+
getLiveFabrixConfigDirCandidates,
|
|
122
|
+
isLiveFabrixConfigPath,
|
|
123
|
+
isAllowedTestWritePath,
|
|
124
|
+
assertWritableSecretsPathForTests
|
|
125
|
+
};
|
package/lib/utils/api.js
CHANGED
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
* @version 2.0.0
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
const { parseErrorResponse } = require('./api-error-handler');
|
|
13
|
+
const { parseErrorResponse, formatNetworkError } = require('./api-error-handler');
|
|
14
|
+
const { inferServiceLabelFromUrl } = require('./service-url-label');
|
|
14
15
|
const auditLogger = require('../core/audit-logger');
|
|
15
16
|
|
|
16
17
|
/** Default timeout for HTTP requests (ms). Prevents hanging when the controller is unreachable. 30s allows Azure Web App cold start to complete. */
|
|
@@ -172,7 +173,7 @@ async function handleSuccessResponse(response, url, options, duration) {
|
|
|
172
173
|
* @throws {Error} If URL is empty, null, undefined, whitespace-only, or malformed
|
|
173
174
|
*/
|
|
174
175
|
function validateUrl(url, urlType = 'URL') {
|
|
175
|
-
if (
|
|
176
|
+
if (url === null || url === undefined || typeof url !== 'string') {
|
|
176
177
|
throw new Error(`${urlType} is required and must be a string (received: ${JSON.stringify(url)})`);
|
|
177
178
|
}
|
|
178
179
|
const trimmedUrl = url.trim();
|
|
@@ -248,20 +249,18 @@ async function handleNetworkError(error, url, options, duration) {
|
|
|
248
249
|
const flat = flattenNetworkErrorDetail(error);
|
|
249
250
|
const errorMessage = withUrlContextForNetworkError(flat || error.message, url);
|
|
250
251
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
// Extract controller URL from full URL for error data
|
|
254
|
-
let controllerUrl = null;
|
|
252
|
+
let serviceBaseUrl = null;
|
|
255
253
|
const endpointUrl = url;
|
|
256
254
|
if (url && typeof url === 'string' && url.trim()) {
|
|
257
255
|
try {
|
|
258
256
|
const urlObj = new URL(url);
|
|
259
|
-
|
|
257
|
+
serviceBaseUrl = `${urlObj.protocol}//${urlObj.host}`;
|
|
260
258
|
} catch {
|
|
261
|
-
|
|
262
|
-
controllerUrl = null;
|
|
259
|
+
serviceBaseUrl = null;
|
|
263
260
|
}
|
|
264
261
|
}
|
|
262
|
+
const serviceLabel = inferServiceLabelFromUrl(endpointUrl);
|
|
263
|
+
const parsedError = parseErrorResponse(errorMessage, 0, true);
|
|
265
264
|
|
|
266
265
|
await logApiPerformance({
|
|
267
266
|
url,
|
|
@@ -276,24 +275,61 @@ async function handleNetworkError(error, url, options, duration) {
|
|
|
276
275
|
}
|
|
277
276
|
});
|
|
278
277
|
|
|
279
|
-
// Include both controller URL and full endpoint URL in error data
|
|
280
278
|
const errorData = {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
279
|
+
controllerUrl: serviceBaseUrl,
|
|
280
|
+
endpointUrl: endpointUrl,
|
|
281
|
+
serviceLabel
|
|
284
282
|
};
|
|
283
|
+
const formattedError = formatNetworkError(errorMessage, errorData);
|
|
285
284
|
|
|
286
285
|
return {
|
|
287
286
|
success: false,
|
|
288
|
-
error:
|
|
287
|
+
error: errorMessage,
|
|
289
288
|
errorData: errorData,
|
|
290
|
-
errorType:
|
|
291
|
-
formattedError
|
|
289
|
+
errorType: 'network',
|
|
290
|
+
formattedError,
|
|
292
291
|
network: true,
|
|
293
292
|
originalError: error
|
|
294
293
|
};
|
|
295
294
|
}
|
|
296
295
|
|
|
296
|
+
/**
|
|
297
|
+
* @param {Object} options - Fetch options passed to makeApiCall
|
|
298
|
+
* @returns {{ fetchOptions: Object, singleRequestTimeoutMs: number }}
|
|
299
|
+
*/
|
|
300
|
+
function prepareFetchOptionsForRequest(options) {
|
|
301
|
+
const fetchOptions = { ...options };
|
|
302
|
+
const singleRequestTimeoutMs = resolveSingleRequestTimeoutMs(fetchOptions);
|
|
303
|
+
if (!fetchOptions.signal) {
|
|
304
|
+
fetchOptions.signal = AbortSignal.timeout(singleRequestTimeoutMs);
|
|
305
|
+
}
|
|
306
|
+
delete fetchOptions.timeoutMs;
|
|
307
|
+
delete fetchOptions.requestTimeoutMs;
|
|
308
|
+
return { fetchOptions, singleRequestTimeoutMs };
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* @param {Error} err
|
|
313
|
+
* @param {string} url
|
|
314
|
+
* @param {number} singleRequestTimeoutMs
|
|
315
|
+
* @returns {Error}
|
|
316
|
+
*/
|
|
317
|
+
function normalizeFetchNetworkError(err, url, singleRequestTimeoutMs) {
|
|
318
|
+
if (err?.name !== 'AbortError') {
|
|
319
|
+
return err;
|
|
320
|
+
}
|
|
321
|
+
const serviceLabel = inferServiceLabelFromUrl(url);
|
|
322
|
+
const serviceName =
|
|
323
|
+
serviceLabel === 'dataplane'
|
|
324
|
+
? 'dataplane'
|
|
325
|
+
: serviceLabel === 'controller'
|
|
326
|
+
? 'controller'
|
|
327
|
+
: 'service';
|
|
328
|
+
return new Error(
|
|
329
|
+
`Request timed out after ${Math.round(singleRequestTimeoutMs / 1000)} seconds. The ${serviceName} may be unreachable. Check the URL and network.`
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
297
333
|
/**
|
|
298
334
|
* Make an API call with proper error handling
|
|
299
335
|
* Uses a 30s timeout by default. Pass ``options.timeoutMs`` or ``options.requestTimeoutMs`` for
|
|
@@ -304,22 +340,14 @@ async function handleNetworkError(error, url, options, duration) {
|
|
|
304
340
|
* @returns {Promise<Object>} Response object with success flag
|
|
305
341
|
*/
|
|
306
342
|
async function makeApiCall(url, options = {}) {
|
|
307
|
-
// Validate URL before attempting request
|
|
308
343
|
try {
|
|
309
344
|
validateUrl(url, 'API endpoint URL');
|
|
310
345
|
} catch (error) {
|
|
311
|
-
|
|
312
|
-
return await handleNetworkError(error, url || '', options, duration);
|
|
346
|
+
return await handleNetworkError(error, url || '', options, 0);
|
|
313
347
|
}
|
|
314
348
|
|
|
315
349
|
const startTime = Date.now();
|
|
316
|
-
const fetchOptions =
|
|
317
|
-
const singleRequestTimeoutMs = resolveSingleRequestTimeoutMs(fetchOptions);
|
|
318
|
-
if (!fetchOptions.signal) {
|
|
319
|
-
fetchOptions.signal = AbortSignal.timeout(singleRequestTimeoutMs);
|
|
320
|
-
}
|
|
321
|
-
delete fetchOptions.timeoutMs;
|
|
322
|
-
delete fetchOptions.requestTimeoutMs;
|
|
350
|
+
const { fetchOptions, singleRequestTimeoutMs } = prepareFetchOptionsForRequest(options);
|
|
323
351
|
|
|
324
352
|
try {
|
|
325
353
|
const response = await fetch(url, fetchOptions);
|
|
@@ -332,11 +360,7 @@ async function makeApiCall(url, options = {}) {
|
|
|
332
360
|
return await handleSuccessResponse(response, url, options, duration);
|
|
333
361
|
} catch (err) {
|
|
334
362
|
const duration = Date.now() - startTime;
|
|
335
|
-
const error = err
|
|
336
|
-
? new Error(
|
|
337
|
-
`Request timed out after ${Math.round(singleRequestTimeoutMs / 1000)} seconds. The controller may be unreachable. Check the URL and network.`
|
|
338
|
-
)
|
|
339
|
-
: err;
|
|
363
|
+
const error = normalizeFetchNetworkError(err, url, singleRequestTimeoutMs);
|
|
340
364
|
return await handleNetworkError(error, url, options, duration);
|
|
341
365
|
}
|
|
342
366
|
}
|