@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.
Files changed (120) hide show
  1. package/lib/agent-kit/codex-app-server.js +44 -0
  2. package/lib/agent-kit/git-identity.js +180 -0
  3. package/lib/agent-kit/init.js +4 -17
  4. package/lib/agent-kit/session-report.js +15 -10
  5. package/lib/agent-kit/setup-auth.js +131 -0
  6. package/lib/agent-kit/setup-context.js +118 -0
  7. package/lib/agent-kit/setup-github.js +70 -0
  8. package/lib/agent-kit/setup-managed.js +79 -0
  9. package/lib/agent-kit/setup-process.js +42 -0
  10. package/lib/agent-kit/setup-session.js +53 -0
  11. package/lib/agent-kit/setup-state.js +52 -0
  12. package/lib/agent-kit/setup-workspace.js +113 -0
  13. package/lib/agent-kit/setup.js +87 -0
  14. package/lib/agent-kit/start.js +48 -3
  15. package/lib/agent-kit/tmux-sessions.js +20 -3
  16. package/lib/api/dev.api.js +1 -1
  17. package/lib/api/miso-health.api.js +26 -0
  18. package/lib/api/role-assistant-test-job.api.js +60 -0
  19. package/lib/api/types/dev.types.js +3 -3
  20. package/lib/api/work-search.api.js +13 -6
  21. package/lib/app/deploy.js +8 -103
  22. package/lib/app/show-display.js +1 -0
  23. package/lib/app/show-online.js +15 -0
  24. package/lib/build/standard-docker-build.js +4 -1
  25. package/lib/cli/index.js +4 -0
  26. package/lib/cli/setup-app.js +14 -1
  27. package/lib/cli/setup-dev-path-commands.js +4 -0
  28. package/lib/cli/setup-dev.js +7 -4
  29. package/lib/cli/setup-help.js +86 -0
  30. package/lib/cli/setup-onboarding.js +37 -0
  31. package/lib/cli/setup-utility-repair.js +7 -5
  32. package/lib/cli/setup-utility.js +24 -1
  33. package/lib/commands/agent-kit-help.js +21 -7
  34. package/lib/commands/agent-kit-sessions.js +126 -0
  35. package/lib/commands/agent-kit-setup.js +44 -0
  36. package/lib/commands/agent-kit.js +5 -79
  37. package/lib/commands/dev-init.js +21 -23
  38. package/lib/commands/dev-refresh.js +2 -2
  39. package/lib/commands/governance-verify-external.js +12 -5
  40. package/lib/commands/repair-datasource.js +9 -9
  41. package/lib/commands/repair.js +45 -8
  42. package/lib/commands/role-assistant.js +7 -0
  43. package/lib/commands/upload-qualification-client.js +56 -11
  44. package/lib/commands/upload-qualification-poll.js +51 -22
  45. package/lib/commands/wizard-dataplane.js +16 -52
  46. package/lib/core/env-platform-expand.js +93 -0
  47. package/lib/core/secrets-env-content.js +37 -3
  48. package/lib/datasource/datasource-validate-summary.js +8 -5
  49. package/lib/datasource/sync-defaults.js +53 -0
  50. package/lib/deployment/installation/azure-infra-stage.js +13 -1
  51. package/lib/deployment/installation/azure-preflight.js +13 -2
  52. package/lib/deployment/installation/azure-readiness.js +24 -2
  53. package/lib/deployment/installation/environment-auth.js +60 -20
  54. package/lib/deployment/installation/environment-stage.js +9 -5
  55. package/lib/deployment/installation/infra-catalog.js +10 -7
  56. package/lib/deployment/installation/infra-manifest.js +5 -0
  57. package/lib/deployment/installation/registry-auth-mode.js +33 -10
  58. package/lib/deployment/marketplace-environment.js +26 -1
  59. package/lib/generator/builders.js +17 -0
  60. package/lib/generator/helpers.js +23 -2
  61. package/lib/generator/index.js +33 -18
  62. package/lib/integration-definition/apply.js +2 -2
  63. package/lib/internal/fs-real-sync.js +4 -1
  64. package/lib/programmatic/bearer-auth.js +3 -62
  65. package/lib/programmatic/builder-api-startup.js +51 -0
  66. package/lib/programmatic/builder-miso-runtime.js +167 -0
  67. package/lib/programmatic/openapi-descriptions.js +3 -3
  68. package/lib/programmatic/openapi-schema-descriptions-core.js +3 -3
  69. package/lib/programmatic/openapi-spec.js +2 -1
  70. package/lib/programmatic/route-handler-map.js +3 -2
  71. package/lib/role-assistant/test-cases-search.js +3 -0
  72. package/lib/role-assistant/test-job-runner.js +125 -0
  73. package/lib/role-assistant/test-runner-search.js +44 -2
  74. package/lib/schema/application-schema.json +193 -2
  75. package/lib/schema/environment-deploy-request.schema.json +1 -1
  76. package/lib/schema/external-datasource.schema.json +20 -5
  77. package/lib/schema/infra-parameter.schema.json +140 -34
  78. package/lib/schema/infra.parameter.yaml +30 -29
  79. package/lib/schema/infrastructure-schema.json +133 -14
  80. package/lib/schema/installation-environment.schema.json +1 -1
  81. package/lib/schema/wizard-config.schema.json +1 -1
  82. package/lib/utils/compose-generate-docker-compose.js +12 -0
  83. package/lib/utils/config-paths.js +24 -29
  84. package/lib/utils/config-registry-preference.js +13 -6
  85. package/lib/utils/dev-init-ssh-merge.js +5 -4
  86. package/lib/utils/dev-user-groups.js +2 -1
  87. package/lib/utils/docker-build.js +29 -8
  88. package/lib/utils/docker-daemon-tls-ca.js +2 -2
  89. package/lib/utils/docker-manifest-public-port.js +36 -0
  90. package/lib/utils/error-formatters/validation-errors.js +25 -5
  91. package/lib/utils/help-builder.js +4 -3
  92. package/lib/utils/platform-resolution.js +226 -0
  93. package/lib/utils/resolve-docker-image-ref.js +8 -3
  94. package/lib/utils/token-manager.js +29 -36
  95. package/package.json +4 -3
  96. package/templates/README.md +2 -1
  97. package/templates/agent-kit/agent-kit.yaml +4 -1
  98. package/templates/agent-kit/instructions/AGENTKIT.md +40 -3
  99. package/templates/agent-kit/instructions/root.AGENTS.md +42 -1
  100. package/templates/agent-kit/skills/aifabrix-connected-system/SKILL.md +33 -1
  101. package/templates/agent-kit/skills/aifabrix-connected-system/scripts/delivery-verdict.js +176 -0
  102. package/templates/agent-kit/skills/aifabrix-plan/SKILL.md +3 -0
  103. package/templates/agent-kit/skills/aifabrix-plan/references/interaction.md +10 -3
  104. package/templates/agent-kit/skills/aifabrix-prove/SKILL.md +22 -0
  105. package/templates/agent-kit/skills/aifabrix-role-assistant/SKILL.md +21 -0
  106. package/templates/agent-kit/skills/shared/feedback.md +31 -0
  107. package/templates/agent-kit/skills/shared/hosts.md +35 -7
  108. package/templates/agent-kit/skills/shared/status.md +85 -0
  109. package/templates/agent-kit/workspace/BUILDER_IMPROVEMENT_FINDINGS.md +27 -0
  110. package/templates/agent-kit/workspace/README.md +14 -1
  111. package/templates/applications/builder-api/application.yaml +1 -1
  112. package/templates/applications/dataplane/application.yaml +30 -2
  113. package/templates/applications/dataplane/env.template +37 -4
  114. package/templates/applications/miso-controller/application.yaml +24 -1
  115. package/templates/applications/miso-controller/env.template +22 -35
  116. package/templates/external-system/external-datasource.yaml.hbs +7 -6
  117. package/templates/marketplace/main.json +281 -75
  118. package/templates/python/Dockerfile.hbs +2 -0
  119. package/templates/typescript/Dockerfile.hbs +2 -0
  120. package/templates/typescript/docker-compose.hbs +12 -0
@@ -0,0 +1,126 @@
1
+ /** CLI actions for listing, attaching, and stopping Agent Kit sessions. */
2
+ 'use strict';
3
+
4
+ const inquirer = require('inquirer');
5
+ const logger = require('../utils/logger');
6
+ const { handleCommandError } = require('../utils/cli-utils');
7
+ const { formatSuccessLine } = require('../utils/cli-layout-chalk');
8
+ const { runList, runKill, runAttach } = require('../agent-kit/tmux-sessions');
9
+ const { listCodexAppServers, stopCodexAppServer } = require('../agent-kit/codex-app-server');
10
+ const { formatSessionList, formatMissingKillName } = require('../agent-kit/session-report');
11
+ const { LIST_HELP, KILL_HELP } = require('./agent-kit-help');
12
+
13
+ function emitListReport(report, asJson) {
14
+ logger.log(asJson ? JSON.stringify(report, null, 2) : formatSessionList(report));
15
+ }
16
+
17
+ function emitKillReport(report, asJson) {
18
+ logger.log(asJson ? JSON.stringify(report, null, 2)
19
+ : formatSuccessLine(report.kind === 'codex-app-server'
20
+ ? `Sent stop signal to Codex app server PID ${report.pid}`
21
+ : `Stopped tmux session ${report.session}`));
22
+ }
23
+
24
+ function listSessions(options = {}) {
25
+ const report = (options.runList || runList)(options);
26
+ const codexServers = (options.listCodexAppServers || listCodexAppServers)(options);
27
+ return { ...report, codexServers, serverCount: codexServers.length };
28
+ }
29
+
30
+ async function runListLeaf(options = {}) {
31
+ try {
32
+ const report = listSessions(options);
33
+ emitListReport(report, Boolean(options.json));
34
+ const tty = options.isTTY === undefined
35
+ ? Boolean(process.stdin.isTTY && process.stdout.isTTY) : options.isTTY;
36
+ if (!tty || options.json || report.sessions.length === 0) return;
37
+ const choices = report.sessions.map(session => ({
38
+ name: `${session.name} (${session.attached ? 'attached' : 'detached'})`, value: session.name
39
+ }));
40
+ choices.push({ name: 'Close', value: null });
41
+ const answer = await (options.prompt || inquirer.prompt)([{
42
+ type: 'list', name: 'name', message: 'Attach to a tmux session:', choices
43
+ }]);
44
+ if (answer.name) (options.runAttach || runAttach)({ ...options, name: answer.name });
45
+ } catch (error) {
46
+ handleCommandError(error, 'agent-kit list');
47
+ process.exitCode = 1;
48
+ }
49
+ }
50
+
51
+ async function selectKillSession(options = {}) {
52
+ const tty = options.isTTY === undefined
53
+ ? Boolean(process.stdin.isTTY && process.stdout.isTTY) : options.isTTY;
54
+ if (!tty || options.json) return { state: 'missing' };
55
+ const report = listSessions(options);
56
+ emitListReport(report, false);
57
+ if (report.sessions.length + report.codexServers.length === 0) return { state: 'empty' };
58
+ const choices = report.sessions.map(session => ({
59
+ name: `tmux: ${session.name} (${session.attached ? 'attached' : 'detached'})`,
60
+ value: { kind: 'tmux', name: session.name }
61
+ }));
62
+ report.codexServers.forEach(server => choices.push({
63
+ name: `Codex app server (PID ${server.pid})`,
64
+ value: { kind: 'codex-app-server', ...server }
65
+ }));
66
+ choices.push({ name: 'Cancel', value: null });
67
+ const answer = await (options.prompt || inquirer.prompt)([{
68
+ type: 'list', name: 'selection', message: 'Select one session to stop:', choices
69
+ }]);
70
+ if (!answer.selection) return { state: 'cancelled' };
71
+ if (answer.selection.kind === 'codex-app-server') {
72
+ const confirm = await (options.prompt || inquirer.prompt)([{
73
+ type: 'confirm', name: 'stop', default: false,
74
+ message: `Stop Codex app server PID ${answer.selection.pid}? This may disconnect this chat; the client may reconnect.`
75
+ }]);
76
+ if (!confirm.stop) return { state: 'cancelled' };
77
+ }
78
+ return { state: 'selected', ...answer.selection };
79
+ }
80
+
81
+ function addListCommand(group) {
82
+ group.command('list')
83
+ .description('List sessions and choose a tmux session to attach')
84
+ .option('--json', 'Machine-readable output')
85
+ .addHelpText('after', LIST_HELP)
86
+ .action(runListLeaf);
87
+ }
88
+
89
+ function addKillCommand(group) {
90
+ group.command('kill [name]')
91
+ .description('Stop a tmux session by name, or choose a session interactively')
92
+ .option('--json', 'Machine-readable output')
93
+ .addHelpText('after', KILL_HELP)
94
+ .action(runKillLeaf);
95
+ }
96
+
97
+ async function runKillLeaf(name, options = {}) {
98
+ try {
99
+ const selection = name ? { state: 'selected', kind: 'tmux', name } : await selectKillSession(options);
100
+ if (selection.state === 'missing') {
101
+ const report = formatMissingKillName(Boolean(options.json));
102
+ logger.error(report.stderr);
103
+ if (report.stdout) logger.log(report.stdout);
104
+ process.exitCode = 1;
105
+ return;
106
+ }
107
+ if (selection.state === 'empty' || selection.state === 'cancelled') {
108
+ logger.log('No session stopped.');
109
+ return;
110
+ }
111
+ const report = selection.kind === 'codex-app-server'
112
+ ? (options.stopCodexAppServer || stopCodexAppServer)(selection, options)
113
+ : (options.runKill || runKill)({ ...options, name: selection.name });
114
+ emitKillReport(report, Boolean(options.json));
115
+ } catch (error) {
116
+ handleCommandError(error, 'agent-kit kill');
117
+ process.exitCode = 1;
118
+ }
119
+ }
120
+
121
+ function addSessionCommands(group) {
122
+ addListCommand(group);
123
+ addKillCommand(group);
124
+ }
125
+
126
+ module.exports = { addSessionCommands, listSessions, runListLeaf, selectKillSession, runKillLeaf };
@@ -0,0 +1,44 @@
1
+ /** Minimal setup command: controller required, local Codex workspace by default. */
2
+ 'use strict';
3
+
4
+ const logger = require('../utils/logger');
5
+ const { formatBlockingError } = require('../utils/cli-layout-chalk');
6
+ const { maskSensitiveData } = require('../utils/log-redaction');
7
+ const { runSetup } = require('../agent-kit/setup');
8
+
9
+ const SETUP_HELP = `
10
+ Start onboarding with only a Controller URL. Defaults to Codex and a local Git
11
+ workspace under the AI Fabrix work directory; GitHub is not required.
12
+ If the work directory is unset, setup asks once and saves it.
13
+
14
+ Examples:
15
+ $ aifabrix agent-kit setup https://controller.example.com
16
+ $ aifabrix agent-kit setup https://controller.example.com --code claude
17
+ $ aifabrix agent-kit setup https://controller.example.com --repo example-org/example-workspace
18
+
19
+ With --repo owner/name, setup reuses a matching local checkout, clones an accessible
20
+ remote, or creates a private repository and pushes. Unresolved access stops safely.
21
+ Without --repo, files stay local and no GitHub login or push occurs.
22
+ Folder name is the repository name, or workspace without --repo. Change environment with
23
+ aifabrix auth --set-environment <environment>.
24
+ Requires an interactive Linux/macOS terminal, git and tmux (gh only for GitHub).
25
+ Installing a missing Claude CLI through npm requires Node.js 22+.
26
+ `;
27
+
28
+ function addSetupCommand(group) {
29
+ group.command('setup <controllerUrl>')
30
+ .description('Onboard a local Codex workspace; optionally use Claude or GitHub')
31
+ .option('--repo <owner/name>', 'GitHub repository to reuse, clone, or create privately')
32
+ .option('--code <agent>', 'Coding agent: codex or claude', 'codex')
33
+ .addHelpText('after', SETUP_HELP)
34
+ .action(async(controllerUrl, options) => {
35
+ try {
36
+ await runSetup({ ...options, controllerUrl });
37
+ } catch (error) {
38
+ logger.error(formatBlockingError(maskSensitiveData(error.message)));
39
+ process.exitCode = 1;
40
+ }
41
+ });
42
+ }
43
+
44
+ module.exports = { addSetupCommand, SETUP_HELP };
@@ -1,5 +1,4 @@
1
1
  /** `aifabrix agent-kit` command group. */
2
-
3
2
  'use strict';
4
3
 
5
4
  const logger = require('../utils/logger');
@@ -11,8 +10,8 @@ const {
11
10
  } = require('../utils/cli-layout-chalk');
12
11
  const { runInstall, runUpdate } = require('../agent-kit/run');
13
12
  const { runStart } = require('../agent-kit/start');
14
- const { runList, runKill } = require('../agent-kit/tmux-sessions');
15
- const { formatSessionList, formatMissingKillName } = require('../agent-kit/session-report');
13
+ const { addSetupCommand } = require('./agent-kit-setup');
14
+ const { addSessionCommands } = require('./agent-kit-sessions');
16
15
  const path = require('path');
17
16
  const { runInit, runRemoteCreate } = require('../agent-kit/init');
18
17
  const { enrichInitHostOptions, enrichInitRemoteOptions, enrichRemoteOptions, enrichApproveAllOptions } = require('../agent-kit/remote-prompts');
@@ -33,8 +32,6 @@ const {
33
32
  REMOTE_GROUP_HELP,
34
33
  REMOTE_HELP,
35
34
  START_HELP,
36
- LIST_HELP,
37
- KILL_HELP,
38
35
  HOST_OPTION_HELP,
39
36
  APPROVE_ALL_OPTION_HELP
40
37
  } = require('./agent-kit-help');
@@ -131,32 +128,6 @@ function emitStartReport(json, asJson) {
131
128
  logger.log(formatSuccessLine(json.dryRun ? 'Dry-run complete' : 'Ready'));
132
129
  }
133
130
 
134
- /**
135
- * @param {object} json
136
- * @param {boolean} asJson
137
- * @returns {void}
138
- */
139
- function emitListReport(json, asJson) {
140
- if (asJson) {
141
- logger.log(JSON.stringify(json, null, 2));
142
- return;
143
- }
144
- logger.log(formatSessionList(json));
145
- }
146
-
147
- /**
148
- * @param {object} json
149
- * @param {boolean} asJson
150
- * @returns {void}
151
- */
152
- function emitKillReport(json, asJson) {
153
- if (asJson) {
154
- logger.log(JSON.stringify(json, null, 2));
155
- return;
156
- }
157
- logger.log(formatSuccessLine(`Stopped tmux session ${json.session}`));
158
- }
159
-
160
131
  /**
161
132
  * @param {Function} runner
162
133
  * @param {object} options
@@ -185,6 +156,7 @@ function setupAgentKitCommands(program) {
185
156
  .description('Install, init, and update the AI Fabrix Agent Kit in a customer workspace')
186
157
  .addHelpText('after', GROUP_HELP);
187
158
 
159
+ addSetupCommand(group);
188
160
  addInitCommand(group);
189
161
  addInstallCommand(group);
190
162
  addStatusCommand(group);
@@ -192,8 +164,7 @@ function setupAgentKitCommands(program) {
192
164
  addValidateCommand(group);
193
165
  addRemoteCommand(group);
194
166
  addStartCommand(group);
195
- addListCommand(group);
196
- addKillCommand(group);
167
+ addSessionCommands(group);
197
168
  }
198
169
 
199
170
  /**
@@ -254,7 +225,7 @@ function addRemoteCommand(group) {
254
225
  .command('create')
255
226
  .description('Create a private GitHub repository (origin) and push this workspace')
256
227
  .option('--org <owner>', 'Hosting org or user (required when non-TTY)')
257
- .option('--name <repo>', 'Remote repository name (default: directory basename)')
228
+ .option('--name <repo>', 'New GitHub repository name (default: workspace folder name)')
258
229
  .option('--visibility <mode>', 'private or public', 'private')
259
230
  .option('--yes', 'Silent: skip TTY confirmation (pair with --org)')
260
231
  .option('--dry-run', 'Plan without creating the remote')
@@ -451,49 +422,4 @@ function addStartCommand(group) {
451
422
  });
452
423
  }
453
424
 
454
- /**
455
- * @param {import('commander').Command} group
456
- */
457
- function addListCommand(group) {
458
- group
459
- .command('list')
460
- .description('List active tmux sessions for the current user')
461
- .option('--json', 'Machine-readable output')
462
- .addHelpText('after', LIST_HELP)
463
- .action(async(options) => {
464
- try {
465
- emitListReport(runList(options), Boolean(options.json));
466
- } catch (error) {
467
- handleCommandError(error, 'agent-kit list');
468
- process.exitCode = 1;
469
- }
470
- });
471
- }
472
-
473
- /**
474
- * @param {import('commander').Command} group
475
- */
476
- function addKillCommand(group) {
477
- group
478
- .command('kill [name]')
479
- .description('Stop one tmux session by exact name')
480
- .option('--json', 'Machine-readable output')
481
- .addHelpText('after', KILL_HELP)
482
- .action(async(name, options) => {
483
- if (!name) {
484
- const report = formatMissingKillName(Boolean(options.json));
485
- logger.error(report.stderr);
486
- if (report.stdout) logger.log(report.stdout);
487
- process.exitCode = 1;
488
- return;
489
- }
490
- try {
491
- emitKillReport(runKill({ ...options, name }), Boolean(options.json));
492
- } catch (error) {
493
- handleCommandError(error, 'agent-kit kill');
494
- process.exitCode = 1;
495
- }
496
- });
497
- }
498
-
499
425
  module.exports = { setupAgentKitCommands };
@@ -29,13 +29,13 @@ const {
29
29
  isLinuxCaSudoRequiredError
30
30
  } = require('../utils/dev-ca-install');
31
31
  const { runOptionalHostsSetup } = require('../utils/dev-hosts-helper');
32
- const { mergeDevSshConfigAfterInit } = require('../utils/dev-init-ssh-merge');
32
+ const { mergeDevSshConfigAfterInit, hostnameFromBaseUrl } = require('../utils/dev-init-ssh-merge');
33
33
  const { resolveInitOptions } = require('../utils/dev-init-resolve');
34
34
  const {
35
35
  isHealthHttpServerError,
36
36
  formatEnsureServerTrustedFailure
37
37
  } = require('../utils/dev-init-health-messages');
38
- const { getBadRequestHint, logCertTroubleshootingHint } = require('../utils/dev-init-cert-hints');
38
+ const { getBadRequestHint } = require('../utils/dev-init-cert-hints');
39
39
  const { augmentAndSaveCaPemForRemoteDocker } = require('../utils/dev-ca-docker-augment');
40
40
 
41
41
  /**
@@ -249,7 +249,7 @@ async function _runSshKeyRegistrationStep(baseUrl, issueResponse, keyPem, config
249
249
  } catch (err) {
250
250
  const msg = err.status === 400 ? getBadRequestHint() : (err.message || String(err));
251
251
  logger.log(chalk.yellow(' ⚠ Could not register SSH key: ' + msg));
252
- logCertTroubleshootingHint(configDir, devId, baseUrl);
252
+ throw new Error(`Onboarding incomplete for developer ${devId}: SSH key registration failed. Ask the server administrator to check role access, then retry dev init with a new PIN.`);
253
253
  }
254
254
  }
255
255
 
@@ -262,24 +262,23 @@ async function _runSshKeyRegistrationStep(baseUrl, issueResponse, keyPem, config
262
262
  * @param {string} [serverCaPem] - Dev root CA for Node TLS
263
263
  */
264
264
  async function applySettingsFromServer(baseUrl, devId, issueResponse, keyPem, serverCaPem) {
265
- const configDir = getConfigDirForPaths();
266
- if (issueResponse.settings && typeof issueResponse.settings === 'object') {
267
- await config.mergeRemoteSettings(issueResponse.settings);
268
- logger.log(chalk.green(' ') + formatSuccessLine('Config updated from server (issue-cert response)'));
269
- return;
270
- }
271
- logger.log(chalk.gray(' Fetching settings...'));
272
265
  try {
273
- if (keyPem && typeof keyPem === 'string') {
274
- logger.log(chalk.gray(' Using client certificate for TLS'));
266
+ let settings = issueResponse.settings;
267
+ if (!settings) {
268
+ logger.log(chalk.gray(' Fetching settings...'));
269
+ settings = await devApi.getSettings(baseUrl, issueResponse.certificate, keyPem, serverCaPem);
275
270
  }
276
- const settings = await devApi.getSettings(baseUrl, issueResponse.certificate, keyPem, serverCaPem);
277
- await config.mergeRemoteSettings(settings);
271
+ if (!settings || typeof settings !== 'object' || Array.isArray(settings)) throw new Error('Server settings are missing or invalid.');
272
+ const fresh = { ...settings, 'developer-id': devId, 'remote-server': baseUrl,
273
+ 'sync-ssh-user': settings['sync-ssh-user'] || `dev${devId}`,
274
+ 'sync-ssh-host': settings['sync-ssh-host'] || hostnameFromBaseUrl(baseUrl) };
275
+ await config.mergeRemoteSettings(fresh);
278
276
  logger.log(chalk.green(' ') + formatSuccessLine('Config updated from server'));
277
+ return fresh;
279
278
  } catch (err) {
280
279
  const msg = err.status === 400 ? getBadRequestHint() : (err.message || String(err));
281
- logger.log(chalk.yellow(' ⚠ Could not fetch settings (server may not support cert yet): ' + msg));
282
- logCertTroubleshootingHint(configDir, devId, baseUrl);
280
+ logger.log(chalk.yellow(' ⚠ Could not fetch settings: ' + msg));
281
+ throw new Error(`Onboarding incomplete for developer ${devId}: current server settings could not be applied. Saved settings from another developer will not be used. Ask the server administrator to check role access, then retry dev init with a new PIN.`);
283
282
  }
284
283
  }
285
284
 
@@ -317,7 +316,7 @@ async function resolveServerCaPemForTls(baseUrl, options) {
317
316
  * @param {{ hostAlias: string|null, syncUser: string, syncHost: string|null }} p
318
317
  */
319
318
  function logOnboardingFinished({ hostAlias, syncUser, syncHost }) {
320
- logger.log(formatSuccessParagraph('Onboarding complete. You can use remote Docker and Mutagen sync.'));
319
+ logger.log(formatSuccessParagraph('Certificate, server settings and SSH key registration complete. Access depends on your server permissions.'));
321
320
  if (hostAlias) {
322
321
  logger.log(
323
322
  chalk.gray(' You can also open an SSH session on the builder with ') +
@@ -367,14 +366,13 @@ async function runDevInit(options) {
367
366
  );
368
367
  await saveCertAndConfig(configDir, devId, issueResponse.certificate, keyPem, caPem);
369
368
 
370
- await applySettingsFromServer(baseUrl, devId, issueResponse, keyPem, serverCaPemForTls || undefined);
371
- await augmentAndSaveCaPemForRemoteDocker(configDir, devId, caPem, {
372
- settings: issueResponse.settings,
373
- remoteServerUrl: baseUrl,
369
+ const settings = await applySettingsFromServer(baseUrl, devId, issueResponse, keyPem, serverCaPemForTls || undefined);
370
+ await _runSshKeyRegistrationStep(baseUrl, issueResponse, keyPem, configDir, devId, serverCaPemForTls || undefined);
371
+ if (settings['docker-endpoint']) await augmentAndSaveCaPemForRemoteDocker(configDir, devId, caPem, {
372
+ settings,
374
373
  warn: (msg) => logger.log(chalk.yellow(` ⚠ ${msg}`))
375
374
  });
376
- await _runSshKeyRegistrationStep(baseUrl, issueResponse, keyPem, configDir, devId, serverCaPemForTls || undefined);
377
- const sshInfo = await mergeDevSshConfigAfterInit(baseUrl, devId);
375
+ const sshInfo = await mergeDevSshConfigAfterInit(baseUrl, devId, settings);
378
376
  logOnboardingFinished(sshInfo);
379
377
  }
380
378
 
@@ -82,9 +82,9 @@ async function runCertificateRefresh(auth) {
82
82
  const issueResponse = await requestCertificate(auth.serverUrl, devId, pin, csrPem, serverCaPem);
83
83
  const caPem = mergeCaPemBlocks(serverCaPem, issueResponse.caCertificate, issueResponse.ca);
84
84
  await saveCertAndConfig(configDir, devId, issueResponse.certificate, keyPem, caPem);
85
- await applySettingsFromServer(auth.serverUrl, devId, issueResponse, keyPem, serverCaPem);
85
+ const settings = await applySettingsFromServer(auth.serverUrl, devId, issueResponse, keyPem, serverCaPem);
86
86
  await augmentAndSaveCaPemForRemoteDocker(configDir, devId, caPem, {
87
- settings: issueResponse.settings,
87
+ settings,
88
88
  remoteServerUrl: auth.serverUrl,
89
89
  warn: (msg) => logger.log(chalk.yellow(` ⚠ ${msg}`))
90
90
  });
@@ -10,7 +10,7 @@ const {
10
10
  aggregateCoverage,
11
11
  buildPackOverrideResult
12
12
  } = require('./verify-governance-result-builders');
13
- const { VERDICT, verdictFromPassed } = require('../lifecycle/product-model');
13
+ const { VERDICT } = require('../lifecycle/product-model');
14
14
 
15
15
  /**
16
16
  * @async
@@ -43,14 +43,21 @@ async function runGovernanceVerifyForExternalSystem(systemKey, options = {}) {
43
43
  };
44
44
  }
45
45
 
46
- const allVerified = datasourceRows.every(r => r.verdict === VERDICT.VERIFIED);
46
+ const allNotApplicable = datasourceRows.every(
47
+ r => r.verdict === VERDICT.NOT_APPLICABLE
48
+ );
49
+ const allClosed = datasourceRows.every(
50
+ r => r.verdict === VERDICT.VERIFIED || r.verdict === VERDICT.NOT_APPLICABLE
51
+ );
47
52
  const coverage = aggregateCoverage(datasourceRows);
48
53
  return {
49
54
  systemKey: resolvedKey,
50
55
  command: 'governance verify',
51
- verdict: verdictFromPassed(allVerified),
52
- policyCoveragePercent: coverage.policy ?? (allVerified ? 100 : 0),
53
- dimensionCoveragePercent: coverage.dimension ?? (allVerified ? 100 : 0),
56
+ verdict: allNotApplicable
57
+ ? VERDICT.NOT_APPLICABLE
58
+ : (allClosed ? VERDICT.VERIFIED : VERDICT.FAILED),
59
+ policyCoveragePercent: coverage.policy ?? (allClosed ? 100 : 0),
60
+ dimensionCoveragePercent: coverage.dimension ?? (allClosed ? 100 : 0),
54
61
  enforcementScenarios: sumEnforcementScenarios(datasourceRows),
55
62
  datasourceRows
56
63
  };
@@ -16,11 +16,12 @@ const {
16
16
  repairNoneLlmToServiceContract,
17
17
  stripDeprecatedServiceCategory
18
18
  } = require('./repair-datasource-service-contract');
19
+ const {
20
+ DEFAULT_DATASOURCE_SYNC,
21
+ createDefaultDatasourceSync
22
+ } = require('../datasource/sync-defaults');
19
23
 
20
- const DEFAULT_SYNC = {
21
- mode: 'pull',
22
- batchSize: 500
23
- };
24
+ const DEFAULT_SYNC = DEFAULT_DATASOURCE_SYNC;
24
25
 
25
26
  const MINIMAL_METADATA_SCHEMA = {
26
27
  type: 'object',
@@ -348,7 +349,7 @@ function repairExposeFromAttributes(parsed, changes) {
348
349
  }
349
350
 
350
351
  /**
351
- * Adds default sync section if missing or empty. Not applied for entityType 'none'.
352
+ * Adds a default sync section only when absent. Not applied for entityType 'none'.
352
353
  *
353
354
  * @param {Object} parsed - Parsed datasource (mutated)
354
355
  * @param {string[]} changes - Change log
@@ -356,10 +357,9 @@ function repairExposeFromAttributes(parsed, changes) {
356
357
  */
357
358
  function repairSyncSection(parsed, changes) {
358
359
  if (isNoneEntityType(parsed?.entityType)) return false;
359
- const sync = parsed.sync;
360
- if (sync && typeof sync === 'object' && Object.keys(sync).length > 0) return false;
361
- parsed.sync = { ...DEFAULT_SYNC };
362
- changes.push('Added default sync section (mode: pull, batchSize: 500)');
360
+ if (Object.prototype.hasOwnProperty.call(parsed, 'sync')) return false;
361
+ parsed.sync = createDefaultDatasourceSync();
362
+ changes.push('Added default sync section (enabled: true, mode: pull, schedule: @monthly UTC, batchSize: 500)');
363
363
  return true;
364
364
  }
365
365
 
@@ -1,11 +1,11 @@
1
1
  /**
2
- * Repair external integration config: fix drift between config and files on disk.
2
+ * Repair application documentation and external integration configuration drift.
3
3
  *
4
4
  * Aligns application.yaml with actual system/datasource files, fixes system key mismatch,
5
5
  * creates missing externalIntegration block, extracts rbac.yaml from system when needed,
6
6
  * and regenerates the deployment manifest.
7
7
  *
8
- * @fileoverview Repair external integration drift
8
+ * @fileoverview Repair application README or external integration drift
9
9
  * @author AI Fabrix Team
10
10
  * @version 2.0.0
11
11
  */
@@ -13,6 +13,10 @@
13
13
  'use strict';
14
14
 
15
15
  const fs = require('fs');
16
+ const path = require('path');
17
+ const { generateReadmeMd } = require('../app/readme');
18
+ const { backupIntegrationFile } = require('../utils/integration-file-backup');
19
+ const { trackRepairWrite } = require('./repair-changed-files');
16
20
  const { detectAppType } = require('../utils/paths');
17
21
  const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
18
22
  const { loadConfigFile, isYamlPath } = require('../utils/config-format');
@@ -43,7 +47,7 @@ function loadConfigAndDiscover(appPath, configPath) {
43
47
  * Validates repair inputs and resolves app path and config path.
44
48
  * @param {string} appName - Application name
45
49
  * @param {Object} options - Command options (auth)
46
- * @returns {Promise<{ appPath: string, configPath: string, dryRun: boolean, authOption: string|undefined }>}
50
+ * @returns {Promise<{ appPath: string, configPath: string, dryRun: boolean, authOption: string|undefined, isExternal: boolean }>}
47
51
  */
48
52
  async function validateAndResolveRepairPaths(appName, options) {
49
53
  if (!appName || typeof appName !== 'string') throw new Error('App name is required');
@@ -52,10 +56,42 @@ async function validateAndResolveRepairPaths(appName, options) {
52
56
  throw new Error('Option --auth must be a string');
53
57
  }
54
58
  const { appPath, isExternal } = await detectAppType(appName, options);
55
- if (!isExternal) throw new Error(`App '${appName}' is not an external integration`);
59
+ if (!isExternal) validateApplicationRepairOptions(appName, options);
56
60
  const configPath = resolveApplicationConfigPath(appPath);
57
61
  if (!fs.existsSync(configPath)) throw new Error(`Application config not found: ${configPath}`);
58
- return { appPath, configPath, dryRun, authOption };
62
+ return { appPath, configPath, dryRun, authOption, isExternal };
63
+ }
64
+
65
+ /** Validate the documentation-only repair supported for normal applications. */
66
+ function validateApplicationRepairOptions(appName, options) {
67
+ const integrationOptions = ['all', 'api', 'auth', 'rbac', 'expose', 'sync', 'test',
68
+ 'splitDatasources', 'rbacPreset', 'authWebhook', 'authSync'];
69
+ if (!options.doc || integrationOptions.some(key => options[key] !== undefined && options[key] !== false && options[key] !== null)) {
70
+ throw new Error(`App '${appName}' is not an external integration. Normal applications support repair --doc only (with --dry-run or --no-backup).`);
71
+ }
72
+ }
73
+
74
+ /** Regenerate a normal application's README using its current local configuration. */
75
+ function repairApplicationReadme(appName, appPath, configPath, backupCtx) {
76
+ const variables = loadConfigFile(configPath);
77
+ const content = generateReadmeMd(appName, { ...variables, ...variables.app });
78
+ const readmePath = path.join(appPath, 'README.md');
79
+ trackRepairWrite(readmePath, backupCtx);
80
+ if (!backupCtx.dryRun) {
81
+ backupIntegrationFile(readmePath, backupCtx);
82
+ fs.writeFileSync(readmePath, content, { mode: 0o644, encoding: 'utf8' });
83
+ }
84
+ return {
85
+ updated: true,
86
+ applicationReadme: true,
87
+ actionsPerformed: true,
88
+ readmeRegenerated: true,
89
+ manifestRegenerated: false,
90
+ changes: ['Regenerated README.md from application configuration'],
91
+ changedFiles: backupCtx.dryRun ? backupCtx.wouldChangeFiles : backupCtx.changedFiles,
92
+ backupPaths: backupCtx.backupPaths,
93
+ warnings: []
94
+ };
59
95
  }
60
96
 
61
97
  /**
@@ -79,17 +115,18 @@ function createRepairBackupContext(options, dryRun) {
79
115
  }
80
116
 
81
117
  /**
82
- * Repairs external integration config: syncs files, aligns keys, creates rbac, regenerates manifest
118
+ * Repairs external integration config or regenerates a normal application's README.
83
119
  * @async
84
120
  * @param {string} appName - Application/integration name
85
121
  * @param {Object} [options] - Options
86
122
  * @param {boolean} [options.dryRun] - If true, only report changes; do not write
87
- * @param {boolean} [options.doc] - If true, regenerate README.md from deployment manifest
123
+ * @param {boolean} [options.doc] - Regenerate README.md from application config or integration deployment manifest
88
124
  * @returns {Promise<Object>}
89
125
  */
90
126
  async function repairExternalIntegration(appName, options = {}) {
91
- const { appPath, configPath, dryRun, authOption } = await validateAndResolveRepairPaths(appName, options);
127
+ const { appPath, configPath, dryRun, authOption, isExternal } = await validateAndResolveRepairPaths(appName, options);
92
128
  const { backupPaths, backupCtx } = createRepairBackupContext(options, dryRun);
129
+ if (!isExternal) return repairApplicationReadme(appName, appPath, configPath, backupCtx);
93
130
  const loaded = loadConfigAndDiscover(appPath, configPath);
94
131
  return runRepairExternalIntegration({
95
132
  appName,
@@ -41,6 +41,10 @@ const {
41
41
  patchCaseStatusAfterRun
42
42
  } = require('../role-assistant/test-pack-sync');
43
43
  const { collectString } = require('./governance');
44
+ const {
45
+ hasRoleAssistantTestSubject,
46
+ runPublishedCaseAsSubject
47
+ } = require('../role-assistant/test-job-runner');
44
48
  const {
45
49
  sectionTitle,
46
50
  headerKeyValue,
@@ -338,6 +342,9 @@ async function resolveExecutableCaseResult(roleKey, roleCase, opts, result, verd
338
342
  run: false,
339
343
  noPublish: true
340
344
  });
345
+ if (hasRoleAssistantTestSubject(opts)) {
346
+ return runPublishedCaseAsSubject(roleKey, roleCase, opts, authContext);
347
+ }
341
348
  const evidencePreflight = await assertRoleAssistantEvidencePreflight(roleCase, authContext);
342
349
  const executionResult = await runRoleAssistantCaseLifecycle(roleKey, roleCase, authContext, opts);
343
350
  const executionVerdict = lifecycleVerdictFromCaseExecution(executionResult, verdict);