@aifabrix/builder 2.61.2 → 2.63.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/lib/api/applications.api.js +22 -2
- package/lib/api/bootstrap-snapshot.api.js +115 -0
- package/lib/api/onboarding-status.api.js +117 -0
- package/lib/app/deploy.js +14 -5
- package/lib/app/register.js +24 -2
- package/lib/app/run-env-compose.js +44 -8
- package/lib/app/run-env-recovery.js +1 -0
- package/lib/app/run-helpers.js +1 -1
- package/lib/app/run.js +6 -4
- package/lib/build/standard-docker-build.js +4 -0
- package/lib/channels/channel-artifact-generator.js +13 -3
- package/lib/cli/infra-guided-footers.js +10 -8
- package/lib/cli/infra-guided.js +60 -12
- package/lib/cli/setup-infra-up-platform-action.js +14 -1
- package/lib/cli/setup-platform.js +20 -3
- package/lib/commands/auth-config.js +2 -0
- package/lib/commands/login.js +4 -0
- package/lib/commands/repair-datasource-apply.js +3 -1
- package/lib/commands/repair-datasource-legacy.js +94 -0
- package/lib/commands/setup-image-refresh.js +26 -0
- package/lib/commands/setup-modes.js +9 -2
- package/lib/commands/setup-onboarding-readiness.js +83 -0
- package/lib/commands/setup-prompts-platform-mode.js +34 -0
- package/lib/commands/setup.js +35 -10
- package/lib/commands/up-builder-api.js +24 -8
- package/lib/commands/up-common.js +16 -1
- package/lib/commands/up-dataplane-bootstrap.js +149 -16
- package/lib/commands/up-dataplane-credentials.js +21 -0
- package/lib/commands/up-dataplane.js +26 -34
- package/lib/commands/up-integration-server.js +112 -0
- package/lib/commands/wizard-config-normalizer.js +5 -1
- package/lib/core/admin-secrets-env-overlay.js +8 -3
- package/lib/core/environment-access-policy.js +121 -0
- package/lib/core/environment-mode-policy.js +177 -0
- package/lib/core/local-env-overrides.js +149 -0
- package/lib/core/secrets-ensure.js +4 -2
- package/lib/core/secrets-env-content.js +12 -9
- package/lib/core/secrets-env-write.js +1 -1
- package/lib/datasource/capability/capability-manifest.js +1 -0
- package/lib/deployment/installation/index.js +14 -1
- package/lib/deployment/installation/infra-catalog.js +3 -0
- package/lib/deployment/installation/local-environment-stage.js +285 -0
- package/lib/deployment/installation/local-installation.js +76 -0
- package/lib/deployment/installation/stage-input.js +5 -1
- package/lib/generator/builders.js +3 -1
- package/lib/generator/index.js +32 -9
- package/lib/generator/split-variables.js +2 -2
- package/lib/parameters/database-adoption.js +125 -0
- package/lib/parameters/physical-database-naming.js +136 -0
- package/lib/parameters/physical-secret-name.js +194 -0
- package/lib/parameters/rsa-secret-values.js +165 -0
- package/lib/programmatic/run-channel-install-artifacts.js +28 -2
- package/lib/programmatic/run-channel-package.js +3 -1
- package/lib/programmatic/workspace-context.js +37 -2
- package/lib/schema/infra.parameter.yaml +61 -27
- package/lib/schema/infrastructure-schema.json +213 -120
- package/lib/utils/compose-generate-docker-compose.js +2 -2
- package/lib/utils/compose-generator.js +4 -7
- package/lib/utils/compose-miso-env.js +2 -2
- package/lib/utils/compose-traefik-ingress-base.js +3 -4
- package/lib/utils/dataplane-dimension-abac-setting.js +120 -0
- package/lib/utils/declarative-url-ports.js +3 -0
- package/lib/utils/env-environment-file-paths.js +105 -0
- package/lib/utils/environment-scoped-resources.js +48 -14
- package/lib/utils/log-redaction.js +4 -1
- package/lib/utils/paths-system-builder-keys.js +7 -1
- package/lib/utils/paths.js +7 -2
- package/lib/utils/postgres-platform-bootstrap.js +35 -1
- package/lib/utils/redis-env-scope.js +21 -1
- package/lib/utils/registry-auth-sources.js +154 -0
- package/lib/utils/registry-credentials.js +131 -14
- package/lib/utils/secrets-generator.js +30 -21
- package/lib/utils/secrets-helpers.js +5 -7
- package/lib/utils/secrets-kv-scope.js +127 -28
- package/lib/utils/secrets-materialize-local.js +9 -0
- package/lib/utils/secrets-missing-error.js +35 -1
- package/lib/utils/token-manager.js +14 -8
- package/lib/utils/url-public-path-prefix.js +6 -10
- package/package.json +4 -2
- package/templates/README.md +6 -0
- package/templates/applications/builder-api/application.yaml +1 -1
- package/templates/applications/dataplane/application.yaml +2 -2
- package/templates/applications/dataplane/env.template +1 -1
- package/templates/applications/integration-server/application.yaml +91 -0
- package/templates/applications/integration-server/env.template +215 -0
- package/templates/applications/mirrored-templates.json +52 -0
- package/templates/applications/miso-controller/application.yaml +43 -1
- package/templates/applications/miso-controller/env.template +45 -39
- package/templates/marketplace/createUiDefinition.json +52 -4
- package/templates/marketplace/main.json +85 -7
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Env-scoped kv:// resolution helpers (prefixed keys + in-memory aliases).
|
|
3
3
|
*
|
|
4
|
+
* Two regimes share this module:
|
|
5
|
+
*
|
|
6
|
+
* - `dev` and `miso` are **lenient** (plan 117). A `<env>-<key>` secret wins when present,
|
|
7
|
+
* and an absent one falls back to the unprefixed key. Existing installations are written
|
|
8
|
+
* that way, so removing the fallback would break every one of them.
|
|
9
|
+
* - `tst` and `pro` are **strict** (plan 210.0). Only `<env>-<key>` is read. An absent key
|
|
10
|
+
* is absent — it never falls back to the unprefixed value and it is never generated.
|
|
11
|
+
*
|
|
12
|
+
* The strict rule is the whole point: these environments share one Postgres, one Redis and
|
|
13
|
+
* one secrets file with `dev`. A fallback there would hand production whatever `dev` happens
|
|
14
|
+
* to hold, which is worse than failing, because it looks like it worked. Strictness is
|
|
15
|
+
* therefore deliberately independent of `useEnvironmentScopedResources` and of the
|
|
16
|
+
* per-application `environmentScopedResources` flag: an installation that forgot to set
|
|
17
|
+
* either must not silently resolve `pro` against `dev`'s secrets.
|
|
18
|
+
*
|
|
4
19
|
* @fileoverview Split from secrets-helpers for file size limits
|
|
5
20
|
* @author AI Fabrix Team
|
|
6
21
|
* @version 1.0.0
|
|
@@ -8,6 +23,25 @@
|
|
|
8
23
|
|
|
9
24
|
'use strict';
|
|
10
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Environments where the `<env>-` prefix is mandatory rather than preferred.
|
|
28
|
+
*
|
|
29
|
+
* Not the same list as `physical-database-naming.PHYSICAL_NAME_ENVIRONMENTS`: that one is the
|
|
30
|
+
* Postgres naming vocabulary and includes `miso` and `dev`, which resolve leniently here.
|
|
31
|
+
*/
|
|
32
|
+
const STRICT_SCOPED_ENVIRONMENTS = Object.freeze(['tst', 'pro']);
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {string} envKey - Run environment key
|
|
36
|
+
* @returns {boolean} True when the prefix is mandatory for this environment
|
|
37
|
+
*/
|
|
38
|
+
function isStrictScopedEnvironment(envKey) {
|
|
39
|
+
if (!envKey || typeof envKey !== 'string') {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
return STRICT_SCOPED_ENVIRONMENTS.includes(envKey.trim().toLowerCase());
|
|
43
|
+
}
|
|
44
|
+
|
|
11
45
|
/**
|
|
12
46
|
* Treat undefined/null and whitespace-only strings as no value so prefixed slots
|
|
13
47
|
* cannot shadow unprefixed secrets with empty placeholders (plan 117 scoped KV).
|
|
@@ -25,9 +59,80 @@ function isKvSlotEmpty(v) {
|
|
|
25
59
|
return false;
|
|
26
60
|
}
|
|
27
61
|
|
|
62
|
+
/**
|
|
63
|
+
* The secret key a strict environment actually requires.
|
|
64
|
+
*
|
|
65
|
+
* **Not simply `<env>-<key>`.** Miso's naming rule has three cases, and only the middle one
|
|
66
|
+
* is a bare environment prefix:
|
|
67
|
+
*
|
|
68
|
+
* ```text
|
|
69
|
+
* system <key> unprefixed, deliberately
|
|
70
|
+
* environment-scoped pro-<key>
|
|
71
|
+
* application-scoped pro-<app>-<key>
|
|
72
|
+
* ```
|
|
73
|
+
*
|
|
74
|
+
* They are not distinguishable from the key alone, so the name comes from the catalog entry's
|
|
75
|
+
* own vault-name pattern rather than being built here. A database password is therefore
|
|
76
|
+
* `pro-dataplane-databases-0-passwordKeyVault` — the application segment before the key — and
|
|
77
|
+
* `keycloak-*` and `acr-*` keep the names the Marketplace template writes.
|
|
78
|
+
*
|
|
79
|
+
* @param {string} envKey - Run environment key
|
|
80
|
+
* @param {string} pathStr - Unprefixed kv path
|
|
81
|
+
* @returns {string}
|
|
82
|
+
*/
|
|
83
|
+
function strictScopedKeyName(envKey, pathStr) {
|
|
84
|
+
const { resolveStrictSecretName } = require('../parameters/physical-secret-name');
|
|
85
|
+
return resolveStrictSecretName(pathStr, String(envKey).toLowerCase());
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Alias every unprefixed key to its `<env>-` name so a lenient lookup finds it.
|
|
90
|
+
*
|
|
91
|
+
* Never called for a strict environment: aliasing there would reintroduce the fallback
|
|
92
|
+
* this module exists to remove.
|
|
93
|
+
*
|
|
94
|
+
* @param {Object} secrets
|
|
95
|
+
* @param {string} envKey
|
|
96
|
+
* @returns {Object}
|
|
97
|
+
*/
|
|
98
|
+
function mergeSecretsWithPrefixedCopies(secrets, envKey) {
|
|
99
|
+
if (!secrets || typeof secrets !== 'object' || !envKey) {
|
|
100
|
+
return secrets;
|
|
101
|
+
}
|
|
102
|
+
const prefix = `${String(envKey).toLowerCase()}-`;
|
|
103
|
+
const merged = { ...secrets };
|
|
104
|
+
for (const [k, v] of Object.entries(secrets)) {
|
|
105
|
+
if (typeof k !== 'string' || !k || k.startsWith(prefix)) continue;
|
|
106
|
+
const pk = prefix + k;
|
|
107
|
+
if (isKvSlotEmpty(merged[pk]) && !isKvSlotEmpty(v)) {
|
|
108
|
+
merged[pk] = v;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return merged;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Resolve the scoped-kv regime and the secrets map to read through.
|
|
116
|
+
*
|
|
117
|
+
* One helper so every caller applies the same rule; a caller that recomputed `effective`
|
|
118
|
+
* itself could leave a strict environment resolving leniently.
|
|
119
|
+
*
|
|
120
|
+
* @param {Object} secrets - Merged secrets map
|
|
121
|
+
* @param {{ effective?: boolean, envKey?: string }|null} scopedKv
|
|
122
|
+
* @returns {{ secretsMap: Object, envKey: string|undefined, effective: boolean, strict: boolean }}
|
|
123
|
+
*/
|
|
124
|
+
function prepareScopedKv(secrets, scopedKv) {
|
|
125
|
+
const envKey = scopedKv && scopedKv.envKey ? scopedKv.envKey : undefined;
|
|
126
|
+
const strict = isStrictScopedEnvironment(envKey);
|
|
127
|
+
const effective = strict || Boolean(scopedKv && scopedKv.effective && envKey);
|
|
128
|
+
const secretsMap =
|
|
129
|
+
effective && !strict ? mergeSecretsWithPrefixedCopies(secrets, envKey) : secrets;
|
|
130
|
+
return { secretsMap, envKey, effective, strict };
|
|
131
|
+
}
|
|
132
|
+
|
|
28
133
|
/**
|
|
29
134
|
* @param {Function} getValueByPath - From secrets-helpers
|
|
30
|
-
* @returns {
|
|
135
|
+
* @returns {Object} Scoped kv helpers bound to that lookup
|
|
31
136
|
*/
|
|
32
137
|
function createScopedKvHelpers(getValueByPath) {
|
|
33
138
|
/**
|
|
@@ -38,40 +143,34 @@ function createScopedKvHelpers(getValueByPath) {
|
|
|
38
143
|
* @returns {*}
|
|
39
144
|
*/
|
|
40
145
|
function getValueByPathWithEnvScope(secrets, pathStr, envKey, effective) {
|
|
41
|
-
|
|
146
|
+
const strict = isStrictScopedEnvironment(envKey);
|
|
147
|
+
if (!strict && (!effective || !envKey)) {
|
|
42
148
|
return getValueByPath(secrets, pathStr);
|
|
43
149
|
}
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
150
|
+
const fromPrefixed = getValueByPath(secrets, strictScopedKeyName(envKey, pathStr));
|
|
151
|
+
if (strict) {
|
|
152
|
+
// No fallback. Undefined rather than the empty value so the caller's
|
|
153
|
+
// allowed-empty rule still decides whether absence is an error.
|
|
154
|
+
return isKvSlotEmpty(fromPrefixed) ? undefined : fromPrefixed;
|
|
155
|
+
}
|
|
47
156
|
if (!isKvSlotEmpty(fromPrefixed)) {
|
|
48
157
|
return fromPrefixed;
|
|
49
158
|
}
|
|
50
159
|
return getValueByPath(secrets, pathStr);
|
|
51
160
|
}
|
|
52
161
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
function mergeSecretsWithPrefixedCopies(secrets, envKey) {
|
|
59
|
-
if (!secrets || typeof secrets !== 'object' || !envKey) {
|
|
60
|
-
return secrets;
|
|
61
|
-
}
|
|
62
|
-
const prefix = `${String(envKey).toLowerCase()}-`;
|
|
63
|
-
const merged = { ...secrets };
|
|
64
|
-
for (const [k, v] of Object.entries(secrets)) {
|
|
65
|
-
if (typeof k !== 'string' || !k || k.startsWith(prefix)) continue;
|
|
66
|
-
const pk = prefix + k;
|
|
67
|
-
if (isKvSlotEmpty(merged[pk]) && !isKvSlotEmpty(v)) {
|
|
68
|
-
merged[pk] = v;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return merged;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return { getValueByPathWithEnvScope, mergeSecretsWithPrefixedCopies };
|
|
162
|
+
return {
|
|
163
|
+
getValueByPathWithEnvScope,
|
|
164
|
+
mergeSecretsWithPrefixedCopies,
|
|
165
|
+
prepareScopedKv
|
|
166
|
+
};
|
|
75
167
|
}
|
|
76
168
|
|
|
77
|
-
module.exports = {
|
|
169
|
+
module.exports = {
|
|
170
|
+
STRICT_SCOPED_ENVIRONMENTS,
|
|
171
|
+
isStrictScopedEnvironment,
|
|
172
|
+
strictScopedKeyName,
|
|
173
|
+
mergeSecretsWithPrefixedCopies,
|
|
174
|
+
prepareScopedKv,
|
|
175
|
+
createScopedKvHelpers
|
|
176
|
+
};
|
|
@@ -17,6 +17,7 @@ const secretsUtils = require('./secrets-utils');
|
|
|
17
17
|
const { loadConfiguredSharedSecretsStore } = require('./remote-secrets-loader');
|
|
18
18
|
const { decryptSecretsObject } = require('../core/secrets-load');
|
|
19
19
|
const { collectUniqueKvPathStrings } = require('./secrets-kv-refs');
|
|
20
|
+
const { isStrictScopedEnvironment } = require('./secrets-kv-scope');
|
|
20
21
|
const {
|
|
21
22
|
resolveKvRefValue,
|
|
22
23
|
mergeSecretsWithPrefixedCopies,
|
|
@@ -98,6 +99,14 @@ async function materializeResolvedKvSecretsToUserLocal(templateContent, mergedSe
|
|
|
98
99
|
return [];
|
|
99
100
|
}
|
|
100
101
|
|
|
102
|
+
// This writes back under the UNPREFIXED key. In a strict environment that would mint an
|
|
103
|
+
// unprefixed copy of a tst or pro secret which dev would then resolve, quietly undoing the
|
|
104
|
+
// isolation. Nothing is lost by skipping: a strict environment resolves only `<env>-<key>`,
|
|
105
|
+
// so every value it resolved was already stored under the name it will be read by.
|
|
106
|
+
if (isStrictScopedEnvironment(scopedKv && scopedKv.envKey)) {
|
|
107
|
+
return [];
|
|
108
|
+
}
|
|
109
|
+
|
|
101
110
|
try {
|
|
102
111
|
const { effective, secretsMap, localMap } = buildScopedMaps(mergedSecrets, scopedKv);
|
|
103
112
|
const sharedScoped = await resolveSharedScopedMapForMaterialize(scopedKv, effective);
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
const { formatMissingSecretsFileInfo } = require('./secrets-helpers');
|
|
12
12
|
const { isCommentOrEmptyLine } = require('./secrets-kv-refs');
|
|
13
|
+
const { isStrictScopedEnvironment, strictScopedKeyName } = require('./secrets-kv-scope');
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* @param {string} kvRef - e.g. kv://hubspot-demo/clientSecret
|
|
@@ -117,6 +118,35 @@ function buildKvNamespaceAlternateHints(missing, secrets, appName) {
|
|
|
117
118
|
return lines;
|
|
118
119
|
}
|
|
119
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Name the key a strict environment actually needs.
|
|
123
|
+
*
|
|
124
|
+
* The template references `kv://<key>`, but `tst` and `pro` only read `<env>-<key>`, so an
|
|
125
|
+
* error quoting the template reference alone would send someone to create the one name that
|
|
126
|
+
* will not be used. Both are printed: the reference so the template line is findable, and
|
|
127
|
+
* the required key so `aifabrix secret set` gets the right argument.
|
|
128
|
+
*
|
|
129
|
+
* @param {string[]} missing - kv:// refs
|
|
130
|
+
* @param {{ envKey?: string }|null} [scopedKv]
|
|
131
|
+
* @returns {string[]}
|
|
132
|
+
*/
|
|
133
|
+
function buildStrictScopedKeyHints(missing, scopedKv) {
|
|
134
|
+
const envKey = scopedKv && scopedKv.envKey ? scopedKv.envKey : null;
|
|
135
|
+
if (!envKey || !isStrictScopedEnvironment(envKey)) {
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
const env = String(envKey).toLowerCase();
|
|
139
|
+
const lines = missing.map(
|
|
140
|
+
(ref) => `Required-key: ${strictScopedKeyName(env, kvRefToSecretSetKey(ref))}`
|
|
141
|
+
);
|
|
142
|
+
lines.push(
|
|
143
|
+
`Scoped-env-hint: ${env} reads only ${env}-prefixed secrets. The unprefixed key is ` +
|
|
144
|
+
`not a fallback and is never generated for ${env}, so another environment's value ` +
|
|
145
|
+
'cannot be used by accident.'
|
|
146
|
+
);
|
|
147
|
+
return lines;
|
|
148
|
+
}
|
|
149
|
+
|
|
120
150
|
/**
|
|
121
151
|
* @param {Object} opts
|
|
122
152
|
* @param {string[]} opts.missing
|
|
@@ -125,10 +155,11 @@ function buildKvNamespaceAlternateHints(missing, secrets, appName) {
|
|
|
125
155
|
* @param {string} [opts.envTemplatePath]
|
|
126
156
|
* @param {string} [opts.envTemplate]
|
|
127
157
|
* @param {Object} [opts.secrets]
|
|
158
|
+
* @param {{ envKey?: string }|null} [opts.scopedKv]
|
|
128
159
|
* @returns {string}
|
|
129
160
|
*/
|
|
130
161
|
function buildMissingSecretsErrorMessage(opts) {
|
|
131
|
-
const { missing, secretsFilePaths, appName, envTemplatePath, envTemplate, secrets } = opts;
|
|
162
|
+
const { missing, secretsFilePaths, appName, envTemplatePath, envTemplate, secrets, scopedKv } = opts;
|
|
132
163
|
const fileInfo = formatMissingSecretsFileInfo(secretsFilePaths);
|
|
133
164
|
const lines = [`Missing secrets: ${missing.join(', ')}`];
|
|
134
165
|
if (fileInfo) lines.push(fileInfo.trim());
|
|
@@ -138,6 +169,9 @@ function buildMissingSecretsErrorMessage(opts) {
|
|
|
138
169
|
if (templateLine) lines.push(`Env-template-line: ${ref}|${templateLine}`);
|
|
139
170
|
}
|
|
140
171
|
if (appName) lines.push(`App-name: ${appName}`);
|
|
172
|
+
for (const hint of buildStrictScopedKeyHints(missing, scopedKv)) {
|
|
173
|
+
lines.push(hint);
|
|
174
|
+
}
|
|
141
175
|
if (hasStaleOAuthTemplatePaths(missing) && appName) {
|
|
142
176
|
lines.push(`Stale-template-hint: oauth-leftover | repair ${appName}`);
|
|
143
177
|
}
|
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
const config = require('../core/config');
|
|
13
|
-
const logger = require('./logger');
|
|
14
13
|
const { maskSensitiveData } = require('./log-redaction');
|
|
15
14
|
const {
|
|
16
15
|
refreshClientToken,
|
|
@@ -205,12 +204,17 @@ async function tryClientTokenAuth(environment, appName, controllerUrl) {
|
|
|
205
204
|
return null;
|
|
206
205
|
}
|
|
207
206
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
207
|
+
// Two causes reach here: no credentials at all, or credentials the controller rejected.
|
|
208
|
+
// Saying "put it in secrets.local.yaml" for the second sends the reader after a key that is
|
|
209
|
+
// already present and simply not valid, so name the rejection instead.
|
|
210
|
+
function throwNoDeploymentAuth(credentialApp, exchangeError) {
|
|
211
|
+
const err = new Error(exchangeError
|
|
212
|
+
? `Client credentials for '${credentialApp}' were rejected by the controller: ${exchangeError.message}. ` +
|
|
213
|
+
`Re-issue them with: aifabrix login && aifabrix app rotate-secret ${credentialApp}`
|
|
214
|
+
: `No authentication method available. For CI: set AIFABRIX_DEPLOYMENT_AUTH=client-credentials and MISO_CLIENTID/MISO_CLIENTSECRET in .env or '${credentialApp}-client-idKeyVault' in secrets.local.yaml. For interactive use: aifabrix login`);
|
|
212
215
|
err.statusCode = 401;
|
|
213
216
|
err.authFailure = true;
|
|
217
|
+
if (exchangeError) err.cause = exchangeError;
|
|
214
218
|
throw err;
|
|
215
219
|
}
|
|
216
220
|
|
|
@@ -262,8 +266,10 @@ async function getDeploymentAuth(controllerUrl, environment, appName, options =
|
|
|
262
266
|
controller: controllerUrl
|
|
263
267
|
};
|
|
264
268
|
}
|
|
265
|
-
} catch {
|
|
266
|
-
//
|
|
269
|
+
} catch (err) {
|
|
270
|
+
// Keep it: credentials existed and the controller turned them down, which is a
|
|
271
|
+
// different problem from having none, and the only place that fact is observable.
|
|
272
|
+
throwNoDeploymentAuth(credentialApp, err);
|
|
267
273
|
}
|
|
268
274
|
}
|
|
269
275
|
|
|
@@ -341,7 +347,7 @@ async function forceRefreshDeviceToken(controllerUrl) {
|
|
|
341
347
|
|
|
342
348
|
// Must have refresh token to force refresh
|
|
343
349
|
if (!tokenInfo.refreshToken) {
|
|
344
|
-
|
|
350
|
+
warnRefreshFailureOnce(controllerUrl, 'Cannot refresh: no refresh token available. Please login again using: aifabrix login');
|
|
345
351
|
return null;
|
|
346
352
|
}
|
|
347
353
|
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Plan 117 URL path prefix for url://public (
|
|
2
|
+
* Plan 117 URL path prefix for url://public (scoped environments only when gated).
|
|
3
3
|
*
|
|
4
|
-
* @fileoverview baseEffective ∧ derived envKey ∈
|
|
4
|
+
* @fileoverview baseEffective ∧ derived envKey ∈ SCOPED_RUN_ENVIRONMENTS. Caller must pass prefix only when `traefik` is on (see url-declarative-resolve).
|
|
5
5
|
* @author AI Fabrix Team
|
|
6
6
|
* @version 1.0.0
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
'use strict';
|
|
10
10
|
|
|
11
|
+
const { isScopedRunEnvironmentKey } = require('./environment-scoped-resources');
|
|
12
|
+
|
|
11
13
|
/**
|
|
12
14
|
* @param {boolean} useEnvironmentScopedResources - config gate
|
|
13
15
|
* @param {boolean} appEnvironmentScopedResources - application.yaml
|
|
14
16
|
* @param {string} derivedEnvKey - from client id (dev|tst|pro|miso)
|
|
15
|
-
* @returns {string} '' | '/dev' | '/tst'
|
|
17
|
+
* @returns {string} '' | '/dev' | '/tst' | '/pro'
|
|
16
18
|
*/
|
|
17
19
|
function computePublicUrlPathPrefix(useEnvironmentScopedResources, appEnvironmentScopedResources, derivedEnvKey) {
|
|
18
20
|
const baseEffective = Boolean(useEnvironmentScopedResources) && Boolean(appEnvironmentScopedResources);
|
|
@@ -20,13 +22,7 @@ function computePublicUrlPathPrefix(useEnvironmentScopedResources, appEnvironmen
|
|
|
20
22
|
return '';
|
|
21
23
|
}
|
|
22
24
|
const k = String(derivedEnvKey || '').toLowerCase();
|
|
23
|
-
|
|
24
|
-
return '/dev';
|
|
25
|
-
}
|
|
26
|
-
if (k === 'tst') {
|
|
27
|
-
return '/tst';
|
|
28
|
-
}
|
|
29
|
-
return '';
|
|
25
|
+
return isScopedRunEnvironmentKey(k) ? `/${k}` : '';
|
|
30
26
|
}
|
|
31
27
|
|
|
32
28
|
module.exports = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aifabrix/builder",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.63.0",
|
|
4
4
|
"description": "AI Fabrix Builder — CLI and developer scripts (pnpm + af)",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -35,12 +35,14 @@
|
|
|
35
35
|
"lint:fix": "eslint . --ext .js --fix",
|
|
36
36
|
"test": "node tests/scripts/test-wrapper.js",
|
|
37
37
|
"build": "npm run lint && npm run test",
|
|
38
|
-
"build:ci": "npm run lint && npm run check:schema-sync && npm run check:flags && npm run check:cli-help && npm run test:ci",
|
|
38
|
+
"build:ci": "npm run lint && npm run check:schema-sync && npm run check:flags && npm run check:cli-help && npm run check:mirrored-templates && npm run test:ci",
|
|
39
39
|
"test:ci": "bash tests/scripts/ci-simulate.sh",
|
|
40
40
|
"check:schema-sync": "node scripts/check-datasource-test-run-schema-sync.js",
|
|
41
41
|
"check:flags": "jest tests/lib/schema/flag-map-validation-run.test.js --runInBand --config jest.config.default.js",
|
|
42
42
|
"check:cli-help": "node scripts/check-cli-help-examples.js",
|
|
43
|
+
"check:mirrored-templates": "node scripts/check-mirrored-templates.js",
|
|
43
44
|
"test:manual": "jest --config jest.config.manual.js --runInBand",
|
|
45
|
+
"test:manual:public": "jest --config jest.config.manual-public.js --runInBand",
|
|
44
46
|
"test:integration": "jest --config jest.config.integration.js --runInBand",
|
|
45
47
|
"test:integration:fixtures": "jest --config jest.config.integration.fixtures.js --runInBand",
|
|
46
48
|
"test:hubspot-wizard": "node integration/hubspot-test/test.js",
|
package/templates/README.md
CHANGED
|
@@ -26,6 +26,12 @@ Application templates are folder-based and located under `templates/applications
|
|
|
26
26
|
| `aifabrix-miso/packages/miso-controller/configs/infra.parameter.yaml` | `lib/schema/infra.parameter.yaml` |
|
|
27
27
|
| `aifabrix-dataplane/builder/dataplane/` (`env.template`, `rbac.yaml`, `application.yaml`) | `templates/applications/dataplane/` |
|
|
28
28
|
|
|
29
|
+
The Dataplane `env.template` is the source of truth for
|
|
30
|
+
`DIMENSION_ABAC_ENFORCEMENT_ENABLED` and its default `false`. Do not hand-edit
|
|
31
|
+
the bundled mirror. `aifabrix setup --dimension-abac on|off` stores an operator
|
|
32
|
+
selection outside the template and overlays the resolved lowercase value during
|
|
33
|
+
local runtime and deployment-manifest materialization.
|
|
34
|
+
|
|
29
35
|
**Schema master (dataplane):** Also copies dataplane `app/schemas/json/` core integration schemas + `type/*` and `configs/deployment-rules.yaml` into `lib/schema/` (same contract as dataplane `make sync-json`). Do not hand-edit those mirrors — edit dataplane SSOT, then sync. `lib/schema/infra.parameter.yaml` is the Miso catalog mirror above; other Builder-owned schemas in `lib/schema/` (wizard and the rest) are left alone.
|
|
30
36
|
|
|
31
37
|
Overrides: `AIFABRIX_MISO_SRC`, `AIFABRIX_DATAPLANE_SRC` / `AIFABRIX_DATAPLANE_ROOT` (repo roots). Install with `aifabrix up-platform` / `up-builder-api` — do not hand-edit the template copies as primary.
|
|
@@ -5,7 +5,7 @@ app:
|
|
|
5
5
|
description: 'Business Transformation services — HTTP orchestration for connected-system workspace lifecycle via Dataplane Enterprise MCP and governance APIs.'
|
|
6
6
|
type: webapp
|
|
7
7
|
language: typescript
|
|
8
|
-
version: 2.
|
|
8
|
+
version: 2.63.0
|
|
9
9
|
|
|
10
10
|
# Image Configuration
|
|
11
11
|
image:
|
|
@@ -5,7 +5,7 @@ app:
|
|
|
5
5
|
description: "AI Fabrix Dataplane is a secure, in-tenant integration and automation layer that supplies governed, normalized, and explainable enterprise data to AI agents. Using CIP as a declarative standard, it enforces RBAC and ABAC, executes integrations, and exposes trusted data via MCP and OpenAPI."
|
|
6
6
|
type: webapp
|
|
7
7
|
language: python # Explicitly specify Python language
|
|
8
|
-
version: 2.0.
|
|
8
|
+
version: 2.0.82
|
|
9
9
|
# Image Configuration
|
|
10
10
|
# Set tag to match your build (example: aifabrix build dataplane -t 1.0.0).
|
|
11
11
|
# Registry is required so the controller can pull the image (avoids docker-not-found on the controller host).
|
|
@@ -26,7 +26,7 @@ frontDoorRouting:
|
|
|
26
26
|
pattern: /data/*
|
|
27
27
|
enabled: true
|
|
28
28
|
# Docker url://internal full URLs: http://dataplane:PORT only (ingress /data is not on in-container routes).
|
|
29
|
-
internalDockerUseOriginOnly:
|
|
29
|
+
internalDockerUseOriginOnly: true
|
|
30
30
|
host: ${DEV_USERNAME}.${REMOTE_HOST}
|
|
31
31
|
tls: ${TLS_ENABLED}
|
|
32
32
|
# Origin RFC 9728 PRM (outside /data PathPrefix) — Builder Traefik pass-through (plan 171.0).
|
|
@@ -44,7 +44,7 @@ API_KEY_BYPASS_ROLES=full-access
|
|
|
44
44
|
|
|
45
45
|
# API Configuration
|
|
46
46
|
API_V1_STR=/api/v1
|
|
47
|
-
VERSION=2.0.
|
|
47
|
+
VERSION=2.0.82
|
|
48
48
|
# Minimum supported aifabrix-builder CLI semver (advertised on /health and /api/v1/health; empty = no gate)
|
|
49
49
|
# MIN_BUILDER_CLI_VERSION=2.45.0
|
|
50
50
|
MIN_BUILDER_CLI_VERSION=2.45.5
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# AI Fabrix Builder app config — INTEGRATOR server type.
|
|
2
|
+
#
|
|
3
|
+
# Same code as builder/builder-server/, different installation. One source tree, two
|
|
4
|
+
# manifests: the Dockerfile's `integrator` target and this folder's env.template are the
|
|
5
|
+
# only things that differ, so there is no forked codebase to keep in step.
|
|
6
|
+
#
|
|
7
|
+
# Enterprise is the existing full development box with Docker. Integrator is a single
|
|
8
|
+
# container giving developers SSH-only access, with no Docker execution inside it.
|
|
9
|
+
|
|
10
|
+
app:
|
|
11
|
+
key: integration-server
|
|
12
|
+
displayName: AI Fabrix Integration Server
|
|
13
|
+
description: AI Fabrix Integration Server onboards developers over SSH into one container. It creates their Linux accounts and authorized keys, and gives them SSH-only access without Docker inside that container.
|
|
14
|
+
type: webapp
|
|
15
|
+
version: 2.0.10
|
|
16
|
+
image:
|
|
17
|
+
name: aifabrix/integration-server
|
|
18
|
+
tag: latest
|
|
19
|
+
registry: aifabrixacr.azurecr.io
|
|
20
|
+
registryMode: external
|
|
21
|
+
# Base 3006 keeps it clear of builder-server (3002), dataplane (3001), mori-controller
|
|
22
|
+
# (3004) and builder-api (3005), so both server types can run on one dev host.
|
|
23
|
+
# Builder derives the host port as `port` + developerId * 100 (dev01 -> 3106).
|
|
24
|
+
port: 3006
|
|
25
|
+
webSocketsEnabled: true
|
|
26
|
+
# This container administers its own operating system: it creates the developers'
|
|
27
|
+
# Linux accounts, writes /etc/ssh/authorized_keys.d and runs sshd. Builder otherwise
|
|
28
|
+
# runs containers as the invoking host user, which cannot do any of that.
|
|
29
|
+
runAsRoot: true
|
|
30
|
+
# Developers reach this installation over SSH, so that port has to be published as well
|
|
31
|
+
# as the HTTP one. The base keeps SSH's well-known number and the published port follows
|
|
32
|
+
# the same formula as the main port — 22 + developerId * 100 — so dev01 gets 122 and two
|
|
33
|
+
# developers on one host cannot collide. Bootstrap advertises it via SSH_PUBLIC_PORT.
|
|
34
|
+
additionalPorts:
|
|
35
|
+
- name: ssh
|
|
36
|
+
containerPort: 22
|
|
37
|
+
hostPort: 22
|
|
38
|
+
requires:
|
|
39
|
+
database: false
|
|
40
|
+
redis: false
|
|
41
|
+
storage: true
|
|
42
|
+
build:
|
|
43
|
+
language: typescript
|
|
44
|
+
context: ../..
|
|
45
|
+
dockerfile: builder/builder-server/Dockerfile
|
|
46
|
+
# Selects the Dockerfile's integrator stage. Without it Docker builds the last stage,
|
|
47
|
+
# which is enterprise — the same image under a different name. This is the one build
|
|
48
|
+
# difference between the two installations.
|
|
49
|
+
target: integrator
|
|
50
|
+
# No envOutputPath: this installation only runs in Docker, and Builder treats the
|
|
51
|
+
# value as a directory — pointing it at a file name creates a folder of that name.
|
|
52
|
+
#
|
|
53
|
+
# Reload runs the real entrypoint, not a bare watcher. The generated compose bind-mounts
|
|
54
|
+
# the repository over /app and clears the image ENTRYPOINT, which for this installation
|
|
55
|
+
# meant no sshd, no account-ops worker, and the API running as root — so nothing about
|
|
56
|
+
# SSH, provisioning or privilege separation worked in the mode developers actually use,
|
|
57
|
+
# and a queued account was never created. The entrypoint finds the build itself —
|
|
58
|
+
# /app/dist in the image, /app/builder-server/dist under the bind mount — because the
|
|
59
|
+
# compose template HTML-escapes `=`, so a VAR=value prefix never survives as an
|
|
60
|
+
# assignment.
|
|
61
|
+
#
|
|
62
|
+
# The API therefore starts from the host's build, so a code change is
|
|
63
|
+
# `npm run build --prefix builder-server` and a restart, not an in-container watch. That
|
|
64
|
+
# is deliberate: the watcher ran as root and left root-owned dist/ behind, which then
|
|
65
|
+
# broke the next `pnpm check` on the host.
|
|
66
|
+
reloadStart: exec /docker-entrypoint-integrator.sh
|
|
67
|
+
# Ingress. A distinct prefix from builder-server's /server/*, which is not cosmetic: the
|
|
68
|
+
# two installation types are alternatives in production, but a development host runs both,
|
|
69
|
+
# and two applications declaring the same host and prefix collide. Traefik then routes the
|
|
70
|
+
# prefix to whichever container was created last, so integrator-only routes answer 404
|
|
71
|
+
# from the enterprise container — a failure that looks like a bug in the route rather than
|
|
72
|
+
# a collision in the manifests.
|
|
73
|
+
#
|
|
74
|
+
# The pattern is a PathPrefix, not an allowlist: it forwards every path beneath it, so the
|
|
75
|
+
# application refuses anything else that arrives through the front door. Filtering at the
|
|
76
|
+
# ingress is still required and is tracked as open in 029 V4.
|
|
77
|
+
frontDoorRouting:
|
|
78
|
+
pattern: /integrator/*
|
|
79
|
+
enabled: true
|
|
80
|
+
host: ${DEV_USERNAME}.${REMOTE_HOST}
|
|
81
|
+
tls: ${TLS_ENABLED}
|
|
82
|
+
internalDockerUseOriginOnly: true
|
|
83
|
+
repository:
|
|
84
|
+
enabled: false
|
|
85
|
+
repositoryUrl: ''
|
|
86
|
+
deployment:
|
|
87
|
+
controllerUrl: ''
|
|
88
|
+
environment: dev
|
|
89
|
+
healthCheck:
|
|
90
|
+
path: /health
|
|
91
|
+
interval: 30
|