@aifabrix/builder 2.58.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/codex-app-server.js +44 -0
- package/lib/agent-kit/git-identity.js +180 -0
- package/lib/agent-kit/init.js +4 -17
- package/lib/agent-kit/session-report.js +15 -10
- package/lib/agent-kit/setup-auth.js +131 -0
- package/lib/agent-kit/setup-context.js +118 -0
- package/lib/agent-kit/setup-github.js +70 -0
- package/lib/agent-kit/setup-managed.js +79 -0
- package/lib/agent-kit/setup-process.js +42 -0
- package/lib/agent-kit/setup-session.js +53 -0
- package/lib/agent-kit/setup-state.js +52 -0
- package/lib/agent-kit/setup-workspace.js +113 -0
- package/lib/agent-kit/setup.js +87 -0
- package/lib/agent-kit/start.js +48 -3
- package/lib/agent-kit/tmux-sessions.js +20 -3
- package/lib/api/dev.api.js +1 -1
- package/lib/api/miso-health.api.js +26 -0
- package/lib/api/role-assistant-test-job.api.js +60 -0
- package/lib/api/types/dev.types.js +3 -3
- package/lib/api/work-search.api.js +13 -6
- package/lib/app/deploy.js +8 -103
- 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/index.js +4 -0
- package/lib/cli/setup-app.js +14 -1
- package/lib/cli/setup-dev-path-commands.js +4 -0
- package/lib/cli/setup-dev.js +7 -4
- package/lib/cli/setup-help.js +86 -0
- package/lib/cli/setup-onboarding.js +37 -0
- package/lib/cli/setup-utility-repair.js +7 -5
- package/lib/cli/setup-utility.js +24 -1
- package/lib/commands/agent-kit-help.js +21 -7
- package/lib/commands/agent-kit-sessions.js +126 -0
- package/lib/commands/agent-kit-setup.js +44 -0
- package/lib/commands/agent-kit.js +5 -79
- package/lib/commands/dev-init.js +21 -23
- package/lib/commands/dev-refresh.js +2 -2
- package/lib/commands/governance-verify-external.js +12 -5
- package/lib/commands/repair-datasource.js +9 -9
- package/lib/commands/repair.js +45 -8
- package/lib/commands/role-assistant.js +7 -0
- package/lib/commands/upload-qualification-client.js +56 -11
- package/lib/commands/upload-qualification-poll.js +51 -22
- package/lib/commands/wizard-dataplane.js +16 -52
- package/lib/core/env-platform-expand.js +93 -0
- package/lib/core/secrets-env-content.js +37 -3
- package/lib/datasource/datasource-validate-summary.js +8 -5
- package/lib/datasource/sync-defaults.js +53 -0
- package/lib/deployment/installation/azure-infra-stage.js +13 -1
- package/lib/deployment/installation/azure-preflight.js +13 -2
- package/lib/deployment/installation/azure-readiness.js +24 -2
- package/lib/deployment/installation/environment-auth.js +60 -20
- package/lib/deployment/installation/environment-stage.js +9 -5
- package/lib/deployment/installation/infra-catalog.js +10 -7
- package/lib/deployment/installation/infra-manifest.js +5 -0
- package/lib/deployment/installation/registry-auth-mode.js +33 -10
- package/lib/deployment/marketplace-environment.js +26 -1
- package/lib/generator/builders.js +17 -0
- package/lib/generator/helpers.js +23 -2
- package/lib/generator/index.js +33 -18
- package/lib/integration-definition/apply.js +2 -2
- package/lib/internal/fs-real-sync.js +4 -1
- package/lib/programmatic/bearer-auth.js +3 -62
- package/lib/programmatic/builder-api-startup.js +51 -0
- package/lib/programmatic/builder-miso-runtime.js +167 -0
- package/lib/programmatic/openapi-descriptions.js +3 -3
- package/lib/programmatic/openapi-schema-descriptions-core.js +3 -3
- package/lib/programmatic/openapi-spec.js +2 -1
- package/lib/programmatic/route-handler-map.js +3 -2
- 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/environment-deploy-request.schema.json +1 -1
- package/lib/schema/external-datasource.schema.json +20 -5
- package/lib/schema/infra-parameter.schema.json +140 -34
- package/lib/schema/infra.parameter.yaml +30 -29
- package/lib/schema/infrastructure-schema.json +133 -14
- package/lib/schema/installation-environment.schema.json +1 -1
- package/lib/schema/wizard-config.schema.json +1 -1
- package/lib/utils/compose-generate-docker-compose.js +12 -0
- package/lib/utils/config-paths.js +24 -29
- package/lib/utils/config-registry-preference.js +13 -6
- package/lib/utils/dev-init-ssh-merge.js +5 -4
- package/lib/utils/dev-user-groups.js +2 -1
- package/lib/utils/docker-build.js +29 -8
- package/lib/utils/docker-daemon-tls-ca.js +2 -2
- package/lib/utils/docker-manifest-public-port.js +36 -0
- package/lib/utils/error-formatters/validation-errors.js +25 -5
- package/lib/utils/help-builder.js +4 -3
- package/lib/utils/platform-resolution.js +226 -0
- package/lib/utils/resolve-docker-image-ref.js +8 -3
- package/lib/utils/token-manager.js +29 -36
- package/package.json +4 -3
- package/templates/README.md +2 -1
- package/templates/agent-kit/agent-kit.yaml +4 -1
- package/templates/agent-kit/instructions/AGENTKIT.md +40 -3
- package/templates/agent-kit/instructions/root.AGENTS.md +42 -1
- 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 +35 -7
- package/templates/agent-kit/skills/shared/status.md +85 -0
- package/templates/agent-kit/workspace/BUILDER_IMPROVEMENT_FINDINGS.md +27 -0
- package/templates/agent-kit/workspace/README.md +14 -1
- 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 +22 -35
- package/templates/external-system/external-datasource.yaml.hbs +7 -6
- package/templates/marketplace/main.json +281 -75
- package/templates/python/Dockerfile.hbs +2 -0
- package/templates/typescript/Dockerfile.hbs +2 -0
- package/templates/typescript/docker-compose.hbs +12 -0
|
@@ -29,7 +29,6 @@ standardUpInfraEnsureKeys:
|
|
|
29
29
|
- databases-dataplane-3-urlKeyVault
|
|
30
30
|
- databases-dataplane-3-passwordKeyVault
|
|
31
31
|
# Miso-controller Entra onboarding (vendor app); required for aifabrix resolve miso-controller
|
|
32
|
-
- entra-callback-state-secret
|
|
33
32
|
- entra-callback-uri
|
|
34
33
|
- entra-client-idKeyVault
|
|
35
34
|
- entra-client-secretKeyVault
|
|
@@ -98,7 +97,8 @@ parameters:
|
|
|
98
97
|
category: Redis
|
|
99
98
|
sensitive: true
|
|
100
99
|
valueSource: providerOutput
|
|
101
|
-
|
|
100
|
+
# Not needed at first-time install: Redis is provisioned later (env deploy / TA), not at marketplace bootstrap.
|
|
101
|
+
requiredOnSystemOnboarding: false
|
|
102
102
|
scope: system
|
|
103
103
|
generator:
|
|
104
104
|
type: literal
|
|
@@ -348,20 +348,6 @@ parameters:
|
|
|
348
348
|
|
|
349
349
|
# Miso-controller Entra ID onboarding (vendor multi-tenant app). Empty client/callback values
|
|
350
350
|
# allow local resolve and start-dev; set real values before Entra admin-consent flows.
|
|
351
|
-
- key: entra-callback-state-secret
|
|
352
|
-
category: Microsoft Entra ID
|
|
353
|
-
sensitive: true
|
|
354
|
-
valueSource: generated
|
|
355
|
-
requiredOnSystemOnboarding: true
|
|
356
|
-
scope: system
|
|
357
|
-
generator:
|
|
358
|
-
type: randomBytes32
|
|
359
|
-
ensureOn: [upInfra, resolveApp]
|
|
360
|
-
azure:
|
|
361
|
-
notes: >-
|
|
362
|
-
Shared HMAC secret (min 16 chars) for OAuth state between controller and Mori callback;
|
|
363
|
-
maps to ENTRA_CALLBACK_STATE_SECRET in miso-controller env.template.
|
|
364
|
-
|
|
365
351
|
- key: entra-callback-uri
|
|
366
352
|
category: Microsoft Entra ID
|
|
367
353
|
sensitive: false
|
|
@@ -440,8 +426,9 @@ parameters:
|
|
|
440
426
|
# side is decrypted by the other. The read was already happening in the shipped template
|
|
441
427
|
# and the policy denied it. Declared rather than silently permitted, and kept to the one
|
|
442
428
|
# application that needs it instead of moving the key to a scope that grants everyone.
|
|
443
|
-
|
|
429
|
+
visibility:
|
|
444
430
|
- dataplane
|
|
431
|
+
- mori-controller
|
|
445
432
|
scope: app
|
|
446
433
|
generator:
|
|
447
434
|
type: randomBytes32
|
|
@@ -453,6 +440,32 @@ parameters:
|
|
|
453
440
|
notes: >-
|
|
454
441
|
32-byte secret as base64; generated on first ensure. Rotate in production.
|
|
455
442
|
|
|
443
|
+
- key: integration-server-secrets-encryptionKeyVault
|
|
444
|
+
ownerAppKey: integration-server
|
|
445
|
+
# Deliberately NOT shared with any other app, which is the point of the row.
|
|
446
|
+
# An integrator installation hands this key to every developer it provisions: it is
|
|
447
|
+
# written into each managed ~/.aifabrix/config.yaml as `secrets-encryption`, so the
|
|
448
|
+
# CLI can decrypt what that installation's own secrets API encrypted. A key shared
|
|
449
|
+
# with another application would therefore be readable by everyone holding a shell
|
|
450
|
+
# in that container, and one leak would reach every installation using it.
|
|
451
|
+
# One installation, one key, so the blast radius stays inside it.
|
|
452
|
+
#
|
|
453
|
+
# Before this row existed, integration-server's env.template read
|
|
454
|
+
# `kv://builder-server-secrets-encryptionKeyVault` — another application's key.
|
|
455
|
+
# That resolves only by being granted, and granting it grants exactly the access
|
|
456
|
+
# above; without a grant it silently generated an unrelated value instead, so the
|
|
457
|
+
# two server types drifted apart with nothing reporting it.
|
|
458
|
+
scope: app
|
|
459
|
+
generator:
|
|
460
|
+
type: randomBytes32
|
|
461
|
+
ensureOn: [upInfra, resolveApp]
|
|
462
|
+
azure:
|
|
463
|
+
vaultSecretName: integration-server-secrets-encryptionKeyVault
|
|
464
|
+
notes: >-
|
|
465
|
+
32-byte secret as base64; generated on first ensure, per installation. Rotating it
|
|
466
|
+
requires re-running configuration sync so every managed config.yaml is rewritten;
|
|
467
|
+
secrets already stored by that installation do not survive the rotation.
|
|
468
|
+
|
|
456
469
|
# Local kv:// name matches miso-controller env.template (JWT_SECRET); not the generic {appKey}-secrets-jwtKeyVault Azure name.
|
|
457
470
|
- key: miso-controller-jwt-secretKeyVault
|
|
458
471
|
ownerAppKey: miso-controller
|
|
@@ -793,18 +806,6 @@ parameters:
|
|
|
793
806
|
Optional local testing/development bearer bypass. A supplied local value is resolved; when absent,
|
|
794
807
|
local and Azure application deployment resolve it as empty and do not fail.
|
|
795
808
|
|
|
796
|
-
- key: mori-callback-api-keyKeyVault
|
|
797
|
-
ownerAppKey: miso-controller
|
|
798
|
-
scope: app
|
|
799
|
-
generator:
|
|
800
|
-
type: randomBytes32
|
|
801
|
-
ensureOn: [resolveApp]
|
|
802
|
-
azure:
|
|
803
|
-
vaultSecretName: mori-callback-api-keyKeyVault
|
|
804
|
-
legacyVaultSecretNames:
|
|
805
|
-
- marketplace-mori-callback-api-key
|
|
806
|
-
notes: Miso Controller callback credential; generated only when its resolved manifest requires it.
|
|
807
|
-
|
|
808
809
|
# App semver when env.template / conversion yields kv://version (local secrets backfill).
|
|
809
810
|
- key: version
|
|
810
811
|
scope: app
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
-
"$id": "
|
|
3
|
+
"$id": "aifabrix://schema/infrastructure-schema.json",
|
|
4
4
|
"title": "AI Fabrix Installation Infrastructure Manifest",
|
|
5
5
|
"description": "Committed, non-secret environment-infra.json for an installation workspace under infrastructure/<resource-group>/. Secrets appear only as valueFromEnv references.",
|
|
6
6
|
"metadata": {
|
|
7
7
|
"key": "infrastructure-schema",
|
|
8
8
|
"name": "Installation Infrastructure Manifest Schema",
|
|
9
9
|
"description": "JSON schema for infrastructure/<resource-group>/environment-infra.json",
|
|
10
|
-
"version": "2.
|
|
10
|
+
"version": "2.1.0",
|
|
11
11
|
"type": "schema",
|
|
12
12
|
"category": "infrastructure",
|
|
13
13
|
"author": "AI Fabrix Team",
|
|
14
14
|
"createdAt": "2024-01-01T00:00:00Z",
|
|
15
|
-
"updatedAt": "2026-09-
|
|
15
|
+
"updatedAt": "2026-09-24T00:00:00Z",
|
|
16
16
|
"compatibility": {
|
|
17
17
|
"minVersion": "2.0.0",
|
|
18
18
|
"maxVersion": "2.0.0",
|
|
@@ -26,6 +26,14 @@
|
|
|
26
26
|
],
|
|
27
27
|
"dependencies": [],
|
|
28
28
|
"changelog": [
|
|
29
|
+
{
|
|
30
|
+
"version": "2.1.0",
|
|
31
|
+
"date": "2026-09-24T00:00:00Z",
|
|
32
|
+
"breaking": false,
|
|
33
|
+
"changes": [
|
|
34
|
+
"Removed moriCallbackApiKey and entraCallbackStateSecret: the shipped Marketplace template no longer declares them. The installation receives its Mori key at licence registration, and Entra consent state is issued server-side (Mori 227.5)."
|
|
35
|
+
]
|
|
36
|
+
},
|
|
29
37
|
{
|
|
30
38
|
"version": "2.0.0",
|
|
31
39
|
"date": "2026-09-16T00:00:00Z",
|
|
@@ -501,12 +509,11 @@
|
|
|
501
509
|
"acrResourceId",
|
|
502
510
|
"deployRoleAssignments",
|
|
503
511
|
"moriBaseUrl",
|
|
504
|
-
"moriCallbackApiKey",
|
|
505
|
-
"entraCallbackStateSecret",
|
|
506
512
|
"entraIdClientId",
|
|
507
513
|
"entraIdClientSecret",
|
|
508
514
|
"entraIdTenantId",
|
|
509
515
|
"licenseEdition",
|
|
516
|
+
"capacityProfile",
|
|
510
517
|
"onboardingMode",
|
|
511
518
|
"misoImageTag",
|
|
512
519
|
"keycloakImageTag"
|
|
@@ -981,7 +988,7 @@
|
|
|
981
988
|
}
|
|
982
989
|
}
|
|
983
990
|
},
|
|
984
|
-
"
|
|
991
|
+
"deployRegistryRoleAssignment": {
|
|
985
992
|
"type": "object",
|
|
986
993
|
"required": [
|
|
987
994
|
"value",
|
|
@@ -1009,13 +1016,24 @@
|
|
|
1009
1016
|
},
|
|
1010
1017
|
"required": {
|
|
1011
1018
|
"type": "boolean"
|
|
1019
|
+
},
|
|
1020
|
+
"restoreValue": {
|
|
1021
|
+
"type": [
|
|
1022
|
+
"string",
|
|
1023
|
+
"boolean",
|
|
1024
|
+
"number",
|
|
1025
|
+
"object",
|
|
1026
|
+
"array"
|
|
1027
|
+
],
|
|
1028
|
+
"description": "Value to restore when credential mode no longer applies."
|
|
1029
|
+
},
|
|
1030
|
+
"restoreSource": {
|
|
1031
|
+
"type": "string",
|
|
1032
|
+
"minLength": 1
|
|
1012
1033
|
}
|
|
1013
1034
|
}
|
|
1014
1035
|
},
|
|
1015
|
-
"
|
|
1016
|
-
"$ref": "#/definitions/envReference"
|
|
1017
|
-
},
|
|
1018
|
-
"entraCallbackStateSecret": {
|
|
1036
|
+
"moriBaseUrl": {
|
|
1019
1037
|
"type": "object",
|
|
1020
1038
|
"required": [
|
|
1021
1039
|
"value",
|
|
@@ -1025,7 +1043,13 @@
|
|
|
1025
1043
|
"additionalProperties": false,
|
|
1026
1044
|
"properties": {
|
|
1027
1045
|
"value": {
|
|
1028
|
-
"type":
|
|
1046
|
+
"type": [
|
|
1047
|
+
"string",
|
|
1048
|
+
"boolean",
|
|
1049
|
+
"number",
|
|
1050
|
+
"object",
|
|
1051
|
+
"array"
|
|
1052
|
+
]
|
|
1029
1053
|
},
|
|
1030
1054
|
"source": {
|
|
1031
1055
|
"type": "string",
|
|
@@ -1037,9 +1061,6 @@
|
|
|
1037
1061
|
},
|
|
1038
1062
|
"required": {
|
|
1039
1063
|
"type": "boolean"
|
|
1040
|
-
},
|
|
1041
|
-
"requiredAt": {
|
|
1042
|
-
"const": "tenantActivation"
|
|
1043
1064
|
}
|
|
1044
1065
|
}
|
|
1045
1066
|
},
|
|
@@ -1242,6 +1263,98 @@
|
|
|
1242
1263
|
"type": "boolean"
|
|
1243
1264
|
}
|
|
1244
1265
|
}
|
|
1266
|
+
},
|
|
1267
|
+
"moriLicensePublicKey": {
|
|
1268
|
+
"type": "object",
|
|
1269
|
+
"required": [
|
|
1270
|
+
"value",
|
|
1271
|
+
"source",
|
|
1272
|
+
"description"
|
|
1273
|
+
],
|
|
1274
|
+
"additionalProperties": false,
|
|
1275
|
+
"properties": {
|
|
1276
|
+
"value": {
|
|
1277
|
+
"type": [
|
|
1278
|
+
"string",
|
|
1279
|
+
"boolean",
|
|
1280
|
+
"number",
|
|
1281
|
+
"object",
|
|
1282
|
+
"array"
|
|
1283
|
+
]
|
|
1284
|
+
},
|
|
1285
|
+
"source": {
|
|
1286
|
+
"type": "string",
|
|
1287
|
+
"minLength": 1
|
|
1288
|
+
},
|
|
1289
|
+
"description": {
|
|
1290
|
+
"type": "string",
|
|
1291
|
+
"minLength": 1
|
|
1292
|
+
},
|
|
1293
|
+
"required": {
|
|
1294
|
+
"type": "boolean"
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
},
|
|
1298
|
+
"moriLicenseIssuer": {
|
|
1299
|
+
"type": "object",
|
|
1300
|
+
"required": [
|
|
1301
|
+
"value",
|
|
1302
|
+
"source",
|
|
1303
|
+
"description"
|
|
1304
|
+
],
|
|
1305
|
+
"additionalProperties": false,
|
|
1306
|
+
"properties": {
|
|
1307
|
+
"value": {
|
|
1308
|
+
"type": [
|
|
1309
|
+
"string",
|
|
1310
|
+
"boolean",
|
|
1311
|
+
"number",
|
|
1312
|
+
"object",
|
|
1313
|
+
"array"
|
|
1314
|
+
]
|
|
1315
|
+
},
|
|
1316
|
+
"source": {
|
|
1317
|
+
"type": "string",
|
|
1318
|
+
"minLength": 1
|
|
1319
|
+
},
|
|
1320
|
+
"description": {
|
|
1321
|
+
"type": "string",
|
|
1322
|
+
"minLength": 1
|
|
1323
|
+
},
|
|
1324
|
+
"required": {
|
|
1325
|
+
"type": "boolean"
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
},
|
|
1329
|
+
"capacityProfile": {
|
|
1330
|
+
"type": "object",
|
|
1331
|
+
"required": [
|
|
1332
|
+
"value",
|
|
1333
|
+
"source",
|
|
1334
|
+
"description"
|
|
1335
|
+
],
|
|
1336
|
+
"additionalProperties": false,
|
|
1337
|
+
"properties": {
|
|
1338
|
+
"value": {
|
|
1339
|
+
"enum": [
|
|
1340
|
+
"s",
|
|
1341
|
+
"m",
|
|
1342
|
+
"l",
|
|
1343
|
+
"xl"
|
|
1344
|
+
]
|
|
1345
|
+
},
|
|
1346
|
+
"source": {
|
|
1347
|
+
"type": "string",
|
|
1348
|
+
"minLength": 1
|
|
1349
|
+
},
|
|
1350
|
+
"description": {
|
|
1351
|
+
"type": "string",
|
|
1352
|
+
"minLength": 1
|
|
1353
|
+
},
|
|
1354
|
+
"required": {
|
|
1355
|
+
"type": "boolean"
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1245
1358
|
}
|
|
1246
1359
|
}
|
|
1247
1360
|
},
|
|
@@ -1326,6 +1439,12 @@
|
|
|
1326
1439
|
},
|
|
1327
1440
|
"portalUrl": {
|
|
1328
1441
|
"$ref": "#/definitions/output"
|
|
1442
|
+
},
|
|
1443
|
+
"azureOpenAiCreated": {
|
|
1444
|
+
"$ref": "#/definitions/output"
|
|
1445
|
+
},
|
|
1446
|
+
"azureOpenAiSkipReason": {
|
|
1447
|
+
"$ref": "#/definitions/output"
|
|
1329
1448
|
}
|
|
1330
1449
|
}
|
|
1331
1450
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
-
"$id": "
|
|
3
|
+
"$id": "aifabrix://schema/installation-environment.schema.json",
|
|
4
4
|
"title": "AI Fabrix Installation Environment File",
|
|
5
5
|
"description": "Committed, non-secret infrastructure/<resource-group>/environment-dev|tst|pro.json. Inherited shared values name environment-infra.json as their source; request is the exact non-secret Miso deploy projection.",
|
|
6
6
|
"metadata": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
-
"$id": "
|
|
3
|
+
"$id": "aifabrix://schema/wizard-config.schema.json",
|
|
4
4
|
"title": "AI Fabrix Wizard Configuration Schema",
|
|
5
5
|
"description": "Schema for validating wizard.yaml configuration files for headless external system creation",
|
|
6
6
|
"type": "object",
|
|
@@ -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,
|
|
@@ -101,13 +101,8 @@ function createHomeAndSecretsPathFunctions(getConfigFn, saveConfigFn) {
|
|
|
101
101
|
return;
|
|
102
102
|
}
|
|
103
103
|
const resolved = path.resolve(trimmed);
|
|
104
|
-
await setPathConfig(
|
|
105
|
-
|
|
106
|
-
saveConfigFn,
|
|
107
|
-
'aifabrix-work',
|
|
108
|
-
resolved,
|
|
109
|
-
'Work path must be a non-empty string'
|
|
110
|
-
);
|
|
104
|
+
await setPathConfig(getConfigFn, saveConfigFn, 'aifabrix-work', resolved,
|
|
105
|
+
'Work path must be a non-empty string');
|
|
111
106
|
},
|
|
112
107
|
async getAifabrixSecretsPath() {
|
|
113
108
|
return getPathConfig(getConfigFn, 'aifabrix-secrets');
|
|
@@ -168,6 +163,17 @@ async function resolveEnvConfigPathToAbsolute(raw, getConfigFn) {
|
|
|
168
163
|
return path.normalize(path.resolve(base, trimmed));
|
|
169
164
|
}
|
|
170
165
|
|
|
166
|
+
function findBuilderDirectory(base) {
|
|
167
|
+
if (!base) return null;
|
|
168
|
+
const candidate = path.join(base, 'builder');
|
|
169
|
+
try {
|
|
170
|
+
if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) return candidate;
|
|
171
|
+
} catch {
|
|
172
|
+
/* Missing or inaccessible builder directory. */
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
171
177
|
function createEnvConfigPathFunctions(getConfigFn, saveConfigFn) {
|
|
172
178
|
return {
|
|
173
179
|
/**
|
|
@@ -199,30 +205,16 @@ function createEnvConfigPathFunctions(getConfigFn, saveConfigFn) {
|
|
|
199
205
|
return path.dirname(absolute);
|
|
200
206
|
}
|
|
201
207
|
const pathsMod = require('./paths');
|
|
202
|
-
const
|
|
203
|
-
if (!base) {
|
|
204
|
-
return null;
|
|
205
|
-
}
|
|
206
|
-
const b = path.join(base, 'builder');
|
|
207
|
-
try {
|
|
208
|
-
if (fs.existsSync(b) && fs.statSync(b).isDirectory()) {
|
|
209
|
-
return b;
|
|
210
|
-
}
|
|
211
|
-
} catch {
|
|
212
|
-
/* ignore */
|
|
213
|
-
}
|
|
214
|
-
return null;
|
|
215
|
-
};
|
|
216
|
-
const fromProject = tryDir(pathsMod.getProjectRoot());
|
|
208
|
+
const fromProject = findBuilderDirectory(pathsMod.getProjectRoot());
|
|
217
209
|
if (fromProject) {
|
|
218
210
|
return fromProject;
|
|
219
211
|
}
|
|
220
|
-
const fromEffective =
|
|
212
|
+
const fromEffective = findBuilderDirectory(pathsMod.getBuilderRoot());
|
|
221
213
|
if (fromEffective) {
|
|
222
214
|
return fromEffective;
|
|
223
215
|
}
|
|
224
216
|
const work = pathsMod.getAifabrixWork();
|
|
225
|
-
return
|
|
217
|
+
return findBuilderDirectory(work);
|
|
226
218
|
}
|
|
227
219
|
};
|
|
228
220
|
}
|
|
@@ -368,11 +360,10 @@ function applySecretsUrlFromRemote(config) {
|
|
|
368
360
|
config['aifabrix-secrets'] = `${base}/api/dev/secrets`;
|
|
369
361
|
}
|
|
370
362
|
|
|
371
|
-
function
|
|
363
|
+
function applySyncHost(config) {
|
|
372
364
|
const host = hostnameFromUrl(config['remote-server']);
|
|
373
365
|
if (!host) return;
|
|
374
366
|
if (!config['sync-ssh-host']) config['sync-ssh-host'] = host;
|
|
375
|
-
if (!config['docker-endpoint']) config['docker-endpoint'] = `tcp://${host}:2376`;
|
|
376
367
|
}
|
|
377
368
|
|
|
378
369
|
async function mergeRemoteSettingsImpl(getConfigFn, saveConfigFn, settings) {
|
|
@@ -386,8 +377,13 @@ async function mergeRemoteSettingsImpl(getConfigFn, saveConfigFn, settings) {
|
|
|
386
377
|
if (value === '') continue;
|
|
387
378
|
config[key] = value;
|
|
388
379
|
}
|
|
380
|
+
// Current settings are authoritative: never inherit Docker access from another identity.
|
|
381
|
+
if (typeof settings['docker-endpoint'] !== 'string' || !settings['docker-endpoint'].trim()) {
|
|
382
|
+
delete config['docker-endpoint'];
|
|
383
|
+
delete config['docker-tls-skip-verify'];
|
|
384
|
+
}
|
|
389
385
|
applySecretsUrlFromRemote(config);
|
|
390
|
-
|
|
386
|
+
applySyncHost(config);
|
|
391
387
|
await saveConfigFn(config);
|
|
392
388
|
}
|
|
393
389
|
|
|
@@ -428,7 +424,6 @@ module.exports = {
|
|
|
428
424
|
resolveEnvConfigPathToAbsolute,
|
|
429
425
|
SETTINGS_RESPONSE_KEYS,
|
|
430
426
|
applySecretsUrlFromRemote,
|
|
431
|
-
applySyncAndDockerFromHost,
|
|
427
|
+
applySyncAndDockerFromHost: applySyncHost,
|
|
432
428
|
hostnameFromUrl
|
|
433
429
|
};
|
|
434
|
-
|
|
@@ -14,13 +14,15 @@ const { getRuntimeConfigFile } = require('../core/config-runtime-paths');
|
|
|
14
14
|
|
|
15
15
|
const REGISTRY_CONFIG_KEY = 'registry';
|
|
16
16
|
const HOST_LABEL = '[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?';
|
|
17
|
-
const REGISTRY_HOST_RE = new RegExp(`^${HOST_LABEL}(?:\\.${HOST_LABEL})
|
|
17
|
+
const REGISTRY_HOST_RE = new RegExp(`^${HOST_LABEL}(?:\\.${HOST_LABEL})*$`);
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
* Normalizes and validates a registry host
|
|
21
|
-
* Rejects schemes, paths, embedded credentials, query/fragment text and whitespace.
|
|
20
|
+
* Normalizes and validates a registry host.
|
|
21
|
+
* Rejects schemes, paths, embedded credentials, query/fragment text and whitespace. A port is
|
|
22
|
+
* rejected because the deployment `image` contract (application-schema.json, shared with the
|
|
23
|
+
* Controller) accepts only one colon, which separates the tag.
|
|
22
24
|
* @param {string} value - Raw registry value
|
|
23
|
-
* @returns {string} Lower-case host
|
|
25
|
+
* @returns {string} Lower-case host
|
|
24
26
|
* @throws {Error} When the value is empty or not a plain host
|
|
25
27
|
*/
|
|
26
28
|
function normalizeRegistryHost(value) {
|
|
@@ -33,8 +35,13 @@ function normalizeRegistryHost(value) {
|
|
|
33
35
|
throw new Error(`Invalid registry "${raw}": use a host name such as myregistry.azurecr.io (no scheme, path or credentials)`);
|
|
34
36
|
}
|
|
35
37
|
const host = withoutSlash.toLowerCase();
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
if (host.includes(':')) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
`Invalid registry "${raw}": a port is not supported because the deployed image reference keeps one colon for the tag. ` +
|
|
41
|
+
'Use a host name such as myregistry.azurecr.io.'
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
if (!REGISTRY_HOST_RE.test(host)) {
|
|
38
45
|
throw new Error(`Invalid registry "${raw}": use a host name such as myregistry.azurecr.io`);
|
|
39
46
|
}
|
|
40
47
|
return host;
|
|
@@ -163,16 +163,17 @@ async function mergeDevSshConfigBlock(p) {
|
|
|
163
163
|
* Update ~/.ssh/config with Host <user>.<host> for interactive SSH; log result.
|
|
164
164
|
* @param {string} baseUrl - Builder Server base URL
|
|
165
165
|
* @param {string} devId - Developer ID (digits)
|
|
166
|
+
* @param {Object} [freshSettings] - Current authenticated server settings; never mix with saved identity
|
|
166
167
|
* @returns {Promise<{ hostAlias: string|null, syncUser: string, syncHost: string|null }>}
|
|
167
168
|
*/
|
|
168
|
-
async function mergeDevSshConfigAfterInit(baseUrl, devId) {
|
|
169
|
-
const syncHost = (await config.getSyncSshHost()) || hostnameFromBaseUrl(baseUrl);
|
|
170
|
-
const syncUser = (await config.getSyncSshUser()) || `dev${devId}`;
|
|
169
|
+
async function mergeDevSshConfigAfterInit(baseUrl, devId, freshSettings) {
|
|
170
|
+
const syncHost = (freshSettings ? freshSettings['sync-ssh-host'] : await config.getSyncSshHost()) || hostnameFromBaseUrl(baseUrl);
|
|
171
|
+
const syncUser = (freshSettings ? freshSettings['sync-ssh-user'] : await config.getSyncSshUser()) || `dev${devId}`;
|
|
171
172
|
if (!syncHost) {
|
|
172
173
|
return { hostAlias: null, syncUser, syncHost: null };
|
|
173
174
|
}
|
|
174
175
|
try {
|
|
175
|
-
const rootTrimmed = trimDeveloperRootDomain(await config.getDeveloperRootDomain());
|
|
176
|
+
const rootTrimmed = trimDeveloperRootDomain(freshSettings ? freshSettings['developer-root-domain'] : await config.getDeveloperRootDomain());
|
|
176
177
|
return await mergeDevSshConfigBlock({
|
|
177
178
|
baseUrl,
|
|
178
179
|
devId,
|
|
@@ -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
|
|
|
@@ -146,7 +146,7 @@ function dockerEndpointFromRemoteServer(remoteServerUrl) {
|
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
/**
|
|
149
|
-
* Resolve docker-endpoint
|
|
149
|
+
* Resolve only an explicitly advertised docker-endpoint; a server hostname is not Docker access.
|
|
150
150
|
* @param {{ settings?: Object|null, remoteServerUrl?: string|null }} ctx
|
|
151
151
|
* @returns {string|null}
|
|
152
152
|
*/
|
|
@@ -156,7 +156,7 @@ function resolveDockerEndpointForCaAugment(ctx) {
|
|
|
156
156
|
if (typeof fromSettings === 'string' && fromSettings.trim()) {
|
|
157
157
|
return fromSettings.trim();
|
|
158
158
|
}
|
|
159
|
-
return
|
|
159
|
+
return null;
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
/**
|