@aifabrix/builder 2.59.0 → 2.60.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/agent-kit/git-identity.js +180 -0
- package/lib/agent-kit/setup.js +3 -0
- package/lib/agent-kit/start.js +45 -1
- package/lib/api/role-assistant-test-job.api.js +60 -0
- package/lib/api/work-search.api.js +13 -6
- package/lib/app/deploy.js +8 -1
- package/lib/app/show-display.js +1 -0
- package/lib/app/show-online.js +15 -0
- package/lib/build/standard-docker-build.js +4 -1
- package/lib/cli/setup-app.js +14 -1
- package/lib/cli/setup-utility.js +24 -1
- package/lib/commands/role-assistant.js +7 -0
- package/lib/core/env-platform-expand.js +93 -0
- package/lib/core/secrets-env-content.js +37 -3
- package/lib/deployment/installation/azure-infra-stage.js +3 -1
- package/lib/deployment/installation/infra-catalog.js +2 -5
- package/lib/generator/builders.js +17 -0
- package/lib/generator/helpers.js +23 -2
- package/lib/generator/index.js +10 -6
- package/lib/role-assistant/test-cases-search.js +3 -0
- package/lib/role-assistant/test-job-runner.js +125 -0
- package/lib/role-assistant/test-runner-search.js +44 -2
- package/lib/schema/application-schema.json +193 -2
- package/lib/schema/infra-parameter.schema.json +139 -33
- package/lib/schema/infra.parameter.yaml +28 -28
- package/lib/schema/infrastructure-schema.json +10 -35
- package/lib/utils/compose-generate-docker-compose.js +12 -0
- package/lib/utils/docker-build.js +29 -8
- package/lib/utils/docker-manifest-public-port.js +36 -0
- package/lib/utils/platform-resolution.js +226 -0
- package/lib/utils/resolve-docker-image-ref.js +8 -3
- package/package.json +2 -2
- package/templates/README.md +2 -1
- package/templates/agent-kit/agent-kit.yaml +4 -1
- package/templates/agent-kit/instructions/AGENTKIT.md +1 -1
- package/templates/agent-kit/instructions/root.AGENTS.md +2 -0
- package/templates/agent-kit/skills/aifabrix-connected-system/SKILL.md +33 -1
- package/templates/agent-kit/skills/aifabrix-connected-system/scripts/delivery-verdict.js +176 -0
- package/templates/agent-kit/skills/aifabrix-plan/SKILL.md +3 -0
- package/templates/agent-kit/skills/aifabrix-plan/references/interaction.md +10 -3
- package/templates/agent-kit/skills/aifabrix-prove/SKILL.md +22 -0
- package/templates/agent-kit/skills/aifabrix-role-assistant/SKILL.md +21 -0
- package/templates/agent-kit/skills/shared/feedback.md +31 -0
- package/templates/agent-kit/skills/shared/hosts.md +13 -3
- package/templates/agent-kit/skills/shared/status.md +85 -0
- package/templates/agent-kit/workspace/BUILDER_IMPROVEMENT_FINDINGS.md +27 -0
- package/templates/applications/builder-api/application.yaml +1 -1
- package/templates/applications/dataplane/application.yaml +30 -2
- package/templates/applications/dataplane/env.template +37 -4
- package/templates/applications/miso-controller/application.yaml +24 -1
- package/templates/applications/miso-controller/env.template +21 -34
- package/templates/marketplace/main.json +70 -222
- package/templates/python/Dockerfile.hbs +2 -0
- package/templates/typescript/Dockerfile.hbs +2 -0
- package/templates/typescript/docker-compose.hbs +12 -0
|
@@ -249,6 +249,18 @@ function buildComposeTemplateContext(ctx) {
|
|
|
249
249
|
networkName: ctx.networkName,
|
|
250
250
|
containerName: ctx.containerName,
|
|
251
251
|
misoEnvironment: resolveMisoEnvironment(ctx.options),
|
|
252
|
+
// Containers are run as the invoking host user so bind-mounted files stay writable.
|
|
253
|
+
// An app that administers its own operating system — creating accounts, managing
|
|
254
|
+
// sshd — cannot work that way and declares `runAsRoot: true` to opt out.
|
|
255
|
+
runAsRoot: ctx.appConfig?.runAsRoot === true,
|
|
256
|
+
// Extra ports for an application serving a second protocol from the same container,
|
|
257
|
+
// such as SSH beside HTTP. Each host port uses the main port's formula so developers
|
|
258
|
+
// on one host do not collide.
|
|
259
|
+
additionalPorts: (ctx.appConfig?.additionalPorts ?? []).map((entry) => ({
|
|
260
|
+
name: entry.name,
|
|
261
|
+
containerPort: entry.containerPort,
|
|
262
|
+
hostPort: ctx.idNum === 0 ? entry.hostPort : entry.hostPort + ctx.idNum * 100
|
|
263
|
+
})),
|
|
252
264
|
devMountPath: ctx.devMountPath,
|
|
253
265
|
localDataMountPath,
|
|
254
266
|
reloadStartCommand: ctx.reloadStartCommand,
|
|
@@ -96,8 +96,15 @@ function handleDockerClose(code, ctx) {
|
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
function buildDockerCliArgs(imageName, tag, dockerfilePath, contextPath, buildArgs,
|
|
99
|
+
function buildDockerCliArgs(imageName, tag, dockerfilePath, contextPath, buildArgs, options = {}) {
|
|
100
|
+
const { noCache = false, target } = options;
|
|
100
101
|
const args = ['build', '-t', `${imageName}:${tag}`, '-f', dockerfilePath];
|
|
102
|
+
// A multi-stage Dockerfile builds its last stage unless told otherwise. `build.target`
|
|
103
|
+
// lets one Dockerfile serve several images from the same source, which is the only way
|
|
104
|
+
// an app can ship two variants without duplicating its build.
|
|
105
|
+
if (target) {
|
|
106
|
+
args.push('--target', String(target));
|
|
107
|
+
}
|
|
101
108
|
for (const [key, value] of Object.entries(buildArgs)) {
|
|
102
109
|
if (value !== null && value !== undefined && String(value).length > 0) {
|
|
103
110
|
args.push('--build-arg', `${key}=${String(value)}`);
|
|
@@ -111,9 +118,12 @@ function buildDockerCliArgs(imageName, tag, dockerfilePath, contextPath, buildAr
|
|
|
111
118
|
}
|
|
112
119
|
|
|
113
120
|
function createDockerSpawnParams(options) {
|
|
114
|
-
const { imageName, tag, dockerfilePath, contextPath, buildArgs, noCache, env } = options;
|
|
121
|
+
const { imageName, tag, dockerfilePath, contextPath, buildArgs, noCache, env, target } = options;
|
|
115
122
|
const spawnEnv = { ...process.env, ...(env || {}) };
|
|
116
|
-
const args = buildDockerCliArgs(imageName, tag, dockerfilePath, contextPath, buildArgs || {},
|
|
123
|
+
const args = buildDockerCliArgs(imageName, tag, dockerfilePath, contextPath, buildArgs || {}, {
|
|
124
|
+
noCache,
|
|
125
|
+
target
|
|
126
|
+
});
|
|
117
127
|
return { spawnEnv, args };
|
|
118
128
|
}
|
|
119
129
|
|
|
@@ -163,7 +173,8 @@ function runDockerBuildProcess(buildOpts) {
|
|
|
163
173
|
reject,
|
|
164
174
|
env = {},
|
|
165
175
|
buildArgs = {},
|
|
166
|
-
noCache = false
|
|
176
|
+
noCache = false,
|
|
177
|
+
target
|
|
167
178
|
} = buildOpts;
|
|
168
179
|
const { spawnEnv, args } = createDockerSpawnParams({
|
|
169
180
|
imageName,
|
|
@@ -172,7 +183,8 @@ function runDockerBuildProcess(buildOpts) {
|
|
|
172
183
|
contextPath,
|
|
173
184
|
buildArgs,
|
|
174
185
|
noCache,
|
|
175
|
-
env
|
|
186
|
+
env,
|
|
187
|
+
target
|
|
176
188
|
});
|
|
177
189
|
const dockerProcess = spawn('docker', args, {
|
|
178
190
|
shell: process.platform === 'win32',
|
|
@@ -192,7 +204,12 @@ function runDockerBuildProcess(buildOpts) {
|
|
|
192
204
|
* @returns {Promise<void>} Resolves when build completes
|
|
193
205
|
* @throws {Error} If build fails
|
|
194
206
|
*/
|
|
195
|
-
async function executeDockerBuild(imageName, dockerfilePath, contextPath, tag, buildArgs = {},
|
|
207
|
+
async function executeDockerBuild(imageName, dockerfilePath, contextPath, tag, buildArgs = {}, options = false) {
|
|
208
|
+
// Historically the sixth argument was the `noCache` boolean. It is now an options
|
|
209
|
+
// object so a build stage can be named too; the boolean form still works, because
|
|
210
|
+
// this function is exported and callers outside this module pass it that way.
|
|
211
|
+
const { noCache = false, target } =
|
|
212
|
+
typeof options === 'boolean' ? { noCache: options } : options || {};
|
|
196
213
|
const spinner = ora({ text: 'Starting Docker build...', spinner: 'dots' }).start();
|
|
197
214
|
const fsSync = require('fs');
|
|
198
215
|
const path = require('path');
|
|
@@ -228,7 +245,8 @@ async function executeDockerBuild(imageName, dockerfilePath, contextPath, tag, b
|
|
|
228
245
|
reject,
|
|
229
246
|
env: dockerCliEnv,
|
|
230
247
|
buildArgs: resolvedBuildArgs,
|
|
231
|
-
noCache
|
|
248
|
+
noCache,
|
|
249
|
+
target
|
|
232
250
|
});
|
|
233
251
|
});
|
|
234
252
|
}
|
|
@@ -251,7 +269,10 @@ async function executeBuild(imageName, dockerfilePath, contextPath, tag, options
|
|
|
251
269
|
options &&
|
|
252
270
|
(options.cache === false || options.noCache === true || options['no-cache'] === true)
|
|
253
271
|
);
|
|
254
|
-
await executeDockerBuild(imageName, dockerfilePath, contextPath, tag, buildArgs,
|
|
272
|
+
await executeDockerBuild(imageName, dockerfilePath, contextPath, tag, buildArgs, {
|
|
273
|
+
noCache,
|
|
274
|
+
target: options && options.target
|
|
275
|
+
});
|
|
255
276
|
await applyTagAliases(imageName, tag, resolveTagAliases(tag, options));
|
|
256
277
|
}
|
|
257
278
|
|
|
@@ -81,6 +81,41 @@ async function mergeDockerManifestPublishedPort(envVars, appDoc) {
|
|
|
81
81
|
envVars[publicPortKey] = String(devIdNum > 0 ? pubBase + devIdNum * 100 : pubBase);
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Publish `<NAME>_PUBLIC_PORT` for every `additionalPorts` entry.
|
|
86
|
+
*
|
|
87
|
+
* An application serving a second protocol needs to tell clients which port to use, and
|
|
88
|
+
* that is the *published* host port, not the one it listens on inside the container. The
|
|
89
|
+
* two differ by the developer-id offset, so a template that types the number by hand
|
|
90
|
+
* silently disagrees with the manifest the moment either changes — and the application
|
|
91
|
+
* then advertises a port nothing is listening on.
|
|
92
|
+
*
|
|
93
|
+
* Named after the port, so `additionalPorts: [{ name: ssh, hostPort: 2200 }]` gives
|
|
94
|
+
* `SSH_PUBLIC_PORT`, which an env.template consumes as `${SSH_PUBLIC_PORT}`.
|
|
95
|
+
*
|
|
96
|
+
* @param {Object} envVars - Mutated map from buildEnvVarMap
|
|
97
|
+
* @param {Object|null|undefined} appDoc - application.yaml root
|
|
98
|
+
* @returns {Promise<void>}
|
|
99
|
+
*/
|
|
100
|
+
async function mergeAdditionalPublishedPorts(envVars, appDoc) {
|
|
101
|
+
const entries = appDoc && Array.isArray(appDoc.additionalPorts) ? appDoc.additionalPorts : [];
|
|
102
|
+
if (entries.length === 0) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const devIdNum = await require('./env-map').getDeveloperIdNumber(null);
|
|
106
|
+
for (const entry of entries) {
|
|
107
|
+
if (!entry || typeof entry.name !== 'string') {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const base = Number(entry.hostPort);
|
|
111
|
+
if (!Number.isFinite(base)) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const key = `${entry.name.trim().toUpperCase().replace(/-/g, '_')}_PUBLIC_PORT`;
|
|
115
|
+
envVars[key] = String(devIdNum > 0 ? base + devIdNum * 100 : base);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
84
119
|
/**
|
|
85
120
|
* Rewrite first line for the matched *_PUBLIC_PORT after interpolateEnvVars (early kv pass may have wrong value).
|
|
86
121
|
* @param {string} resolved
|
|
@@ -111,6 +146,7 @@ function rewriteDockerManifestPublicPortEnvLine(resolved, envVars, appDoc) {
|
|
|
111
146
|
module.exports = {
|
|
112
147
|
findDockerHostKeyForAppKey,
|
|
113
148
|
publicPortKeyForAppKey,
|
|
149
|
+
mergeAdditionalPublishedPorts,
|
|
114
150
|
mergeDockerManifestPublishedPort,
|
|
115
151
|
rewriteDockerManifestPublicPortEnvLine
|
|
116
152
|
};
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform and environment configuration resolution (plan 200.0).
|
|
3
|
+
*
|
|
4
|
+
* The deployment's Azure resource group is matched against the manifest `platforms` list, yielding
|
|
5
|
+
* `{platform}` and `{platform.name}`. Platform values expand from those, then the matching
|
|
6
|
+
* `environments` entry overrides by name. The result is one flat set of names that `env.template`
|
|
7
|
+
* references as `{NAME}`.
|
|
8
|
+
*
|
|
9
|
+
* Pure by contract: manifest + resource group + environment key in, resolved set out. No
|
|
10
|
+
* filesystem, no Azure, no network — the same function must be implementable in Miso and produce
|
|
11
|
+
* identical output for the shared test vectors.
|
|
12
|
+
*
|
|
13
|
+
* @fileoverview Resource-group driven configuration resolution
|
|
14
|
+
* @author AI Fabrix Team
|
|
15
|
+
* @version 1.0.0
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
'use strict';
|
|
19
|
+
|
|
20
|
+
/** Local Docker has no resource group; every local run resolves as this platform. */
|
|
21
|
+
const LOCAL_PLATFORM = 'dev';
|
|
22
|
+
/** Capture for `*` in a resource pattern: one group-name segment. */
|
|
23
|
+
const NAME_CAPTURE = '[A-Za-z0-9][A-Za-z0-9-]*';
|
|
24
|
+
const NAME_WILDCARD = '*';
|
|
25
|
+
|
|
26
|
+
function escapeLiteral(text) {
|
|
27
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Compiles a `resource` pattern into a matcher.
|
|
32
|
+
* `{platform}` matches one of `options`; `*` captures a name segment.
|
|
33
|
+
* @param {Object} entry - Platform entry
|
|
34
|
+
* @returns {{ regex: RegExp, order: string[] }} Matcher and the capture order
|
|
35
|
+
*/
|
|
36
|
+
function compileResourcePattern(entry) {
|
|
37
|
+
const order = [];
|
|
38
|
+
let source = '';
|
|
39
|
+
const pattern = String(entry.resource || '');
|
|
40
|
+
const tokens = pattern.split(/(\{platform\}|\*)/);
|
|
41
|
+
for (const token of tokens) {
|
|
42
|
+
if (token === '{platform}') {
|
|
43
|
+
const options = Array.isArray(entry.options) ? entry.options : [];
|
|
44
|
+
source += `(${options.map(escapeLiteral).join('|')})`;
|
|
45
|
+
order.push('platform');
|
|
46
|
+
} else if (token === NAME_WILDCARD) {
|
|
47
|
+
source += `(${NAME_CAPTURE})`;
|
|
48
|
+
order.push('name');
|
|
49
|
+
} else if (token) {
|
|
50
|
+
source += escapeLiteral(token);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return { regex: new RegExp(`^${source}$`), order };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {Object} entry - Platform entry
|
|
58
|
+
* @throws {Error} When the entry cannot yield a platform
|
|
59
|
+
*/
|
|
60
|
+
function assertPlatformDeclaration(entry) {
|
|
61
|
+
const hasToken = String(entry.resource || '').includes('{platform}');
|
|
62
|
+
const hasOptions = Array.isArray(entry.options) && entry.options.length > 0;
|
|
63
|
+
if (hasToken && !hasOptions) {
|
|
64
|
+
throw new Error(`Platform "${entry.resource}" uses {platform} but declares no options`);
|
|
65
|
+
}
|
|
66
|
+
if (!hasToken && !entry.platform) {
|
|
67
|
+
throw new Error(`Platform "${entry.resource}" has no {platform} token, so it must declare platform:`);
|
|
68
|
+
}
|
|
69
|
+
if (hasToken && entry.platform) {
|
|
70
|
+
throw new Error(`Platform "${entry.resource}" declares both {platform} and platform:; use one`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function matchEntry(entry, resourceGroup) {
|
|
75
|
+
assertPlatformDeclaration(entry);
|
|
76
|
+
const { regex, order } = compileResourcePattern(entry);
|
|
77
|
+
const found = regex.exec(resourceGroup);
|
|
78
|
+
if (!found) return null;
|
|
79
|
+
const captured = {};
|
|
80
|
+
order.forEach((key, index) => {
|
|
81
|
+
captured[key] = found[index + 1];
|
|
82
|
+
});
|
|
83
|
+
const platform = captured.platform || entry.platform;
|
|
84
|
+
const name = entry.name === NAME_WILDCARD ? captured.name : entry.name;
|
|
85
|
+
if (!name) {
|
|
86
|
+
throw new Error(`Platform "${entry.resource}" uses name: * but the pattern captures no name segment`);
|
|
87
|
+
}
|
|
88
|
+
return { entry, platform, name };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Matches a resource group against the manifest platforms.
|
|
93
|
+
* @param {Array} platforms - Manifest `platforms`
|
|
94
|
+
* @param {string} resourceGroup - Target Azure resource group
|
|
95
|
+
* @returns {{ entry: Object, platform: string, name: string }} The single match
|
|
96
|
+
* @throws {Error} On no match or more than one match
|
|
97
|
+
*/
|
|
98
|
+
function matchPlatform(platforms, resourceGroup) {
|
|
99
|
+
const entries = Array.isArray(platforms) ? platforms : [];
|
|
100
|
+
const group = String(resourceGroup || '').trim();
|
|
101
|
+
if (!group) {
|
|
102
|
+
throw new Error('A resource group is required to resolve a platform');
|
|
103
|
+
}
|
|
104
|
+
const matches = entries.map(entry => matchEntry(entry, group)).filter(Boolean);
|
|
105
|
+
if (matches.length === 0) {
|
|
106
|
+
const tried = entries.map(entry => `"${entry.resource}"`).join(', ') || '(none declared)';
|
|
107
|
+
throw new Error(`Resource group "${group}" matches no platform. Patterns tried: ${tried}`);
|
|
108
|
+
}
|
|
109
|
+
if (matches.length > 1) {
|
|
110
|
+
const ambiguous = matches.map(match => `"${match.entry.resource}"`).join(', ');
|
|
111
|
+
throw new Error(`Resource group "${group}" matches more than one platform: ${ambiguous}. Make the patterns exclusive.`);
|
|
112
|
+
}
|
|
113
|
+
return matches[0];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Expands `{platform}`, `{platform.name}` and `{resourceGroup}` in a value.
|
|
118
|
+
* @param {string} value - Raw value
|
|
119
|
+
* @param {Object} context - `platform`, `name`, `resourceGroup`
|
|
120
|
+
* @returns {string}
|
|
121
|
+
* @throws {Error} On an unknown token
|
|
122
|
+
*/
|
|
123
|
+
function expandPlatformTokens(value, context) {
|
|
124
|
+
const text = value === undefined || value === null ? '' : String(value);
|
|
125
|
+
return text.replace(/\{([a-zA-Z][a-zA-Z.]*)\}/g, (match, token) => {
|
|
126
|
+
if (token === 'platform') return context.platform;
|
|
127
|
+
if (token === 'platform.name') return context.name;
|
|
128
|
+
if (token === 'resourceGroup') return context.resourceGroup;
|
|
129
|
+
throw new Error(`Unsupported token ${match}; use {platform}, {platform.name} or {resourceGroup}`);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function applyItems(target, items, context) {
|
|
134
|
+
for (const item of Array.isArray(items) ? items : []) {
|
|
135
|
+
target.set(item.name, expandPlatformTokens(item.value, context));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Local runs have no resource group, but still need platform values. The first entry that can
|
|
141
|
+
* produce the local platform supplies them; an `environments` entry still overrides by name.
|
|
142
|
+
* A `*` name cannot be captured without a group, so it resolves to ''.
|
|
143
|
+
* @param {Array} platforms - Manifest platforms
|
|
144
|
+
* @returns {{ entry: Object, platform: string, name: string }|null}
|
|
145
|
+
*/
|
|
146
|
+
function matchLocalPlatform(platforms) {
|
|
147
|
+
const entry = platforms.find(item =>
|
|
148
|
+
item.platform === LOCAL_PLATFORM ||
|
|
149
|
+
(Array.isArray(item.options) && item.options.includes(LOCAL_PLATFORM)));
|
|
150
|
+
if (!entry) return null;
|
|
151
|
+
return { entry, platform: LOCAL_PLATFORM, name: entry.name === NAME_WILDCARD ? '' : entry.name };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* @param {Object} manifest - Application manifest
|
|
156
|
+
* @param {string} resourceGroup - Target resource group ('' for local Docker)
|
|
157
|
+
* @returns {{ entry: Object, platform: string, name: string }|null}
|
|
158
|
+
*/
|
|
159
|
+
function matchIfDeclared(manifest, resourceGroup, allowLocalPlatform) {
|
|
160
|
+
const platforms = manifest && Array.isArray(manifest.platforms) ? manifest.platforms : [];
|
|
161
|
+
if (platforms.length === 0) return null;
|
|
162
|
+
if (!resourceGroup) return allowLocalPlatform ? matchLocalPlatform(platforms) : null;
|
|
163
|
+
return matchPlatform(platforms, resourceGroup);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function environmentEntry(manifest, environment) {
|
|
167
|
+
const entries = manifest && Array.isArray(manifest.environments) ? manifest.environments : [];
|
|
168
|
+
return entries.find(item => item.environment === environment) || null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function applyBuiltIns(values, context, environment) {
|
|
172
|
+
const builtIns = [
|
|
173
|
+
// PLATFORM is omitted when nothing determined it, so a consumer fails closed on {PLATFORM}
|
|
174
|
+
// rather than receiving a guess.
|
|
175
|
+
...(context.platform ? [['PLATFORM', context.platform]] : []),
|
|
176
|
+
['PLATFORM_NAME', context.name],
|
|
177
|
+
['RESOURCE_GROUP', context.resourceGroup],
|
|
178
|
+
['ENVIRONMENT', environment]
|
|
179
|
+
];
|
|
180
|
+
// Built-ins never overwrite an authored name of the same spelling.
|
|
181
|
+
for (const [key, value] of builtIns) {
|
|
182
|
+
if (!values.has(key)) values.set(key, value);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Resolves the configuration set for one deployment.
|
|
188
|
+
* @param {Object} manifest - Application manifest with optional `platforms` / `environments`
|
|
189
|
+
* @param {Object} target - `resourceGroup` (omitted for local Docker), `environment`, and
|
|
190
|
+
* `allowLocalPlatform` (default true). A caller that is not a local run — the Controller, which
|
|
191
|
+
* knows the environment but may not know the group — passes false so a missing group resolves no
|
|
192
|
+
* platform at all instead of silently claiming `dev`.
|
|
193
|
+
* @returns {{ platform: string|null, name: string|null, resourceGroup: string|null, values: Object }}
|
|
194
|
+
*/
|
|
195
|
+
function resolveConfiguration(manifest, target = {}) {
|
|
196
|
+
const resourceGroup = target.resourceGroup ? String(target.resourceGroup).trim() : '';
|
|
197
|
+
const environment = target.environment ? String(target.environment).trim() : '';
|
|
198
|
+
const allowLocalPlatform = target.allowLocalPlatform !== false;
|
|
199
|
+
const matched = matchIfDeclared(manifest, resourceGroup, allowLocalPlatform);
|
|
200
|
+
const localPlatform = allowLocalPlatform ? LOCAL_PLATFORM : '';
|
|
201
|
+
const context = {
|
|
202
|
+
platform: matched ? matched.platform : localPlatform,
|
|
203
|
+
name: matched ? matched.name : '',
|
|
204
|
+
resourceGroup
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const values = new Map();
|
|
208
|
+
if (matched) applyItems(values, matched.entry.configuration, context);
|
|
209
|
+
const envEntry = environmentEntry(manifest, environment);
|
|
210
|
+
if (envEntry) applyItems(values, envEntry.configuration, context);
|
|
211
|
+
applyBuiltIns(values, context, environment);
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
platform: context.platform || null,
|
|
215
|
+
name: matched && matched.name ? matched.name : null,
|
|
216
|
+
resourceGroup: resourceGroup || null,
|
|
217
|
+
values: Object.fromEntries(values)
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
module.exports = {
|
|
222
|
+
LOCAL_PLATFORM,
|
|
223
|
+
matchPlatform,
|
|
224
|
+
expandPlatformTokens,
|
|
225
|
+
resolveConfiguration
|
|
226
|
+
};
|
|
@@ -148,16 +148,21 @@ function qualifyRepository(registry, repository) {
|
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
/**
|
|
151
|
-
* Deployment/registration image reference (`registry/name:tag`)
|
|
151
|
+
* Deployment/registration image reference (`registry/name:tag`).
|
|
152
|
+
*
|
|
153
|
+
* Deliberately ignores the installation registry (`dev set-registry`): a generated manifest is a
|
|
154
|
+
* committed artifact shared by the team, so it must carry the application's own `image.registry`,
|
|
155
|
+
* not one developer's local default. `push` applies the installation registry when it publishes,
|
|
156
|
+
* and an explicit `options.registry` still wins here for CI that targets another registry.
|
|
152
157
|
* @param {Object} variables - Application manifest
|
|
153
158
|
* @param {string} fallbackName - Name when the manifest has no image name or app key
|
|
154
|
-
* @param {Object} [options] -
|
|
159
|
+
* @param {Object} [options] - `registry` (explicit override only)
|
|
155
160
|
* @returns {string}
|
|
156
161
|
*/
|
|
157
162
|
function buildManifestImageReference(variables, fallbackName, options = {}) {
|
|
158
163
|
const imageName = variables?.image?.name || variables?.app?.key || fallbackName;
|
|
159
164
|
const tag = variables?.image?.tag || 'latest';
|
|
160
|
-
const { registry } = resolveEffectiveRegistry(variables, options);
|
|
165
|
+
const { registry } = resolveEffectiveRegistry(variables, { ...options, installationRegistry: '' });
|
|
161
166
|
return `${qualifyRepository(registry, imageName)}:${tag}`;
|
|
162
167
|
}
|
|
163
168
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aifabrix/builder",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.60.0",
|
|
4
4
|
"description": "AI Fabrix Builder — CLI and developer scripts (pnpm + af)",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -133,7 +133,7 @@
|
|
|
133
133
|
"dependencies": {
|
|
134
134
|
"@aifabrix/miso-client": "4.23.1",
|
|
135
135
|
"@azure/identity": "4.11.1",
|
|
136
|
-
"adm-zip": "^0.6.
|
|
136
|
+
"adm-zip": "^0.6.1",
|
|
137
137
|
"ajv": "^8.20.0",
|
|
138
138
|
"ajv-formats": "^3.0.1",
|
|
139
139
|
"axios": "^1.18.1",
|
package/templates/README.md
CHANGED
|
@@ -23,9 +23,10 @@ Application templates are folder-based and located under `templates/applications
|
|
|
23
23
|
| `aifabrix-miso/builder/miso-controller/` (`env.template`, `rbac.yaml`, `application.yaml`) | `templates/applications/miso-controller/` |
|
|
24
24
|
| `aifabrix-miso/builder/keycloak/` (`env.template`, `application.yaml`, `Dockerfile`, `cimd-lenient-parse/`) | `templates/applications/keycloak/` |
|
|
25
25
|
| `aifabrix-miso/infrastructure/marketplace/` (`main.json`, `createUiDefinition.json`) | `templates/marketplace/` |
|
|
26
|
+
| `aifabrix-miso/packages/miso-controller/configs/infra.parameter.yaml` | `lib/schema/infra.parameter.yaml` |
|
|
26
27
|
| `aifabrix-dataplane/builder/dataplane/` (`env.template`, `rbac.yaml`, `application.yaml`) | `templates/applications/dataplane/` |
|
|
27
28
|
|
|
28
|
-
**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. Builder-owned schemas in `lib/schema/` (wizard
|
|
29
|
+
**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.
|
|
29
30
|
|
|
30
31
|
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.
|
|
31
32
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
schemaVersion: "1"
|
|
2
|
-
kitVersion: "1.
|
|
2
|
+
kitVersion: "1.2.0"
|
|
3
3
|
minimumBuilderVersion: "2.54.2"
|
|
4
4
|
skills:
|
|
5
5
|
- aifabrix-plan
|
|
@@ -26,6 +26,9 @@ managed:
|
|
|
26
26
|
- source: workspace/README.md
|
|
27
27
|
target: README.md
|
|
28
28
|
mode: if-missing
|
|
29
|
+
- source: workspace/BUILDER_IMPROVEMENT_FINDINGS.md
|
|
30
|
+
target: BUILDER_IMPROVEMENT_FINDINGS.md
|
|
31
|
+
mode: if-missing
|
|
29
32
|
optional:
|
|
30
33
|
- source: adapters/cursor/commands
|
|
31
34
|
target: .cursor/commands
|
|
@@ -97,7 +97,7 @@ mobile app (Code). That is not `claude --cloud` (a separate cloud VM). Claude
|
|
|
97
97
|
aifabrix agent-kit start claude
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
-
tmux session name defaults to this folder. Remote Control name is `hostname-user-folder` (for example `
|
|
100
|
+
tmux session name defaults to this folder. Remote Control name is `hostname-user-folder` (for example `devhost-alice-acme-workspace`). Override with `--name`; opt out with `--no-remote-control` and/or `--skip-approve-all`. Detach: `Ctrl-b` then `d`. Reattach: `tmux attach -t <folder>`. Use `aifabrix agent-kit list` and `agent-kit kill <folder>` to manage sessions. Same account on phone and PC (`claude auth login`, Pro/Max/Team — not an API key). On the phone: Claude app → **Code**. Skills: `/aifabrix-plan` (and the other `/aifabrix-*` names). First `claude` must run inside this workspace, not your home directory. Full notes: `.agents/skills/shared/hosts.md`.
|
|
101
101
|
{{/if}}
|
|
102
102
|
{{#if codex}}
|
|
103
103
|
|
|
@@ -14,6 +14,8 @@ Do not edit platform product source (`aifabrix-dataplane`, `aifabrix-miso`, `aif
|
|
|
14
14
|
|
|
15
15
|
Vendor limits, tenant ids, credentials, PII stance, partitions, and the designated mutation subject live only in `integration/<systemKey>/README.md`.
|
|
16
16
|
|
|
17
|
+
`agent-kit update` rewrites `.agents/skills/**`, `AGENTKIT.md`, and managed sections; `--force` discards local edits. Do not edit them: file gaps in `BUILDER_IMPROVEMENT_FINDINGS.md`, sanitized. Never weaken a gate to clear one. Routing: `.agents/skills/shared/feedback.md`.
|
|
18
|
+
|
|
17
19
|
## First greeting
|
|
18
20
|
|
|
19
21
|
On a first “Hi”, start with the business problem in plain language. Explain
|
|
@@ -10,7 +10,14 @@ description: >
|
|
|
10
10
|
# aifabrix-connected-system
|
|
11
11
|
|
|
12
12
|
Phases: `plan` | `validate` | `build` | `publish`.
|
|
13
|
-
|
|
13
|
+
|
|
14
|
+
**No phase argument means report only.** Inspect, print the combined status matrix,
|
|
15
|
+
recommend one next phase, and stop. Do not upload, deploy, advance a plan task, create
|
|
16
|
+
proof assets, run cases, or mutate anything until the user names a phase or picks a route:
|
|
17
|
+
[status.md](../shared/status.md).
|
|
18
|
+
|
|
19
|
+
Resolve phase: explicit argument → ask. Package state informs the recommendation, never
|
|
20
|
+
the action.
|
|
14
21
|
|
|
15
22
|
Stop at the phase gate. Do not run the next phase without `next-yes`
|
|
16
23
|
([interaction.md](../aifabrix-plan/references/interaction.md)).
|
|
@@ -22,6 +29,28 @@ Stop at the phase gate. Do not run the next phase without `next-yes`
|
|
|
22
29
|
| build | READY | BUILT |
|
|
23
30
|
| publish | package exists and local validate passes | PASS or PASS_WITH_SKIPS |
|
|
24
31
|
|
|
32
|
+
READY and BUILT are **package** gates: they describe files on disk, not a working or
|
|
33
|
+
published system. Report them qualified, beside the Delivery row
|
|
34
|
+
([status.md](../shared/status.md)).
|
|
35
|
+
|
|
36
|
+
## Publish and certification verdicts
|
|
37
|
+
|
|
38
|
+
Compute the verdict with
|
|
39
|
+
[scripts/delivery-verdict.js](scripts/delivery-verdict.js) from what you observed; do not
|
|
40
|
+
choose it by narrative.
|
|
41
|
+
|
|
42
|
+
| Observation | Verdict |
|
|
43
|
+
| --- | --- |
|
|
44
|
+
| Online state Draft | publish incomplete; prove BLOCKED; Delivery NEEDS_FIXES |
|
|
45
|
+
| Operations FAILED | NEEDS_FIXES |
|
|
46
|
+
| Known ABAC, persistence, capability, or live-evidence failure | NEEDS_FIXES while open |
|
|
47
|
+
| Bronze or TECHNICALLY_READY alone | not PASS |
|
|
48
|
+
| Online Published **and** acceptable Operations, Trust, Governance and live evidence | PASS |
|
|
49
|
+
| The above with only explicitly allowed skips | PASS_WITH_SKIPS |
|
|
50
|
+
|
|
51
|
+
`PASS_WITH_SKIPS` covers skips the user allowed by name. It may never cover a known
|
|
52
|
+
failure, an unrun gate, or a gate whose result you did not see.
|
|
53
|
+
|
|
25
54
|
Read current CLI help before composing commands. You own orchestration, not CLI implementation.
|
|
26
55
|
Host runtimes (local vs SSH): [hosts.md](../shared/hosts.md).
|
|
27
56
|
CLI login from chat: [login.md](../shared/login.md).
|
|
@@ -38,6 +67,7 @@ System key: argument → active plan → sole `integration/` folder → ask once
|
|
|
38
67
|
|
|
39
68
|
## References
|
|
40
69
|
|
|
70
|
+
- [status.md](../shared/status.md) — read-only default, route options, status matrix
|
|
41
71
|
- [delivery-gates.md](references/delivery-gates.md) — BID/wizard/repair, upload vs deploy, tests, identity, protection
|
|
42
72
|
- [contract-checklist.md](references/contract-checklist.md) — exposed, FK, viewpoints, RBAC/ABAC
|
|
43
73
|
- [production-safety.md](references/production-safety.md) — designated subject, credential-deferred E2E
|
|
@@ -52,6 +82,8 @@ System key: argument → active plan → sole `integration/` folder → ask once
|
|
|
52
82
|
- Upload without deploy is not published and not PASS
|
|
53
83
|
- No system-level sync of views
|
|
54
84
|
- No publish continuation after an unresolved hard failure
|
|
85
|
+
- A known failure stays open until that same concern is rerun and passes
|
|
86
|
+
- No bare READY/BUILT/PASS headline when Delivery is NEEDS_FIXES or BLOCKED
|
|
55
87
|
|
|
56
88
|
## Trigger examples
|
|
57
89
|
|