@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
|
@@ -8,8 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
'use strict';
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
let misoClientSingleton = null;
|
|
11
|
+
const { getBuilderMisoRuntime } = require('./builder-miso-runtime');
|
|
13
12
|
|
|
14
13
|
/**
|
|
15
14
|
* @param {import('http').IncomingMessage} req
|
|
@@ -36,65 +35,10 @@ function assertNoForbiddenAuthHeaders(req) {
|
|
|
36
35
|
}
|
|
37
36
|
|
|
38
37
|
/**
|
|
39
|
-
* @returns {
|
|
40
|
-
*/
|
|
41
|
-
function resolveControllerUrlFromEnv() {
|
|
42
|
-
const raw =
|
|
43
|
-
process.env.MISO_CONTROLLER_URL ||
|
|
44
|
-
process.env.CONTROLLER_URL ||
|
|
45
|
-
process.env.MISO_WEB_SERVER_URL ||
|
|
46
|
-
'';
|
|
47
|
-
const url = String(raw).trim().replace(/\/$/, '');
|
|
48
|
-
if (!url) {
|
|
49
|
-
throw new Error('MISO_CONTROLLER_URL (or CONTROLLER_URL) is required for token validation');
|
|
50
|
-
}
|
|
51
|
-
return url;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* @returns {import('@aifabrix/miso-client/dist/types/config.types').MisoClientConfig}
|
|
56
|
-
*/
|
|
57
|
-
function buildMisoClientConfigFromEnv() {
|
|
58
|
-
const clientId = (process.env.MISO_CLIENTID || '').trim();
|
|
59
|
-
const clientSecret = (process.env.MISO_CLIENTSECRET || '').trim();
|
|
60
|
-
if (!clientId || !clientSecret) {
|
|
61
|
-
throw new Error('MISO_CLIENTID and MISO_CLIENTSECRET are required for builder-api auth');
|
|
62
|
-
}
|
|
63
|
-
return {
|
|
64
|
-
clientId,
|
|
65
|
-
clientSecret,
|
|
66
|
-
controllerUrl: resolveControllerUrlFromEnv(),
|
|
67
|
-
logLevel: (process.env.LOG_LEVEL || 'info').toLowerCase()
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Test hook — reset cached MisoClient between Jest cases.
|
|
73
|
-
*/
|
|
74
|
-
function resetMisoClientForTests() {
|
|
75
|
-
misoClientSingleton = null;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* @returns {Promise<import('@aifabrix/miso-client').MisoClient>}
|
|
38
|
+
* @returns {Promise<import('@aifabrix/miso-client').MisoClient>} Initialized shared client
|
|
80
39
|
*/
|
|
81
40
|
async function getMisoClientForAuth() {
|
|
82
|
-
|
|
83
|
-
try {
|
|
84
|
-
({ MisoClient } = require('@aifabrix/miso-client'));
|
|
85
|
-
} catch {
|
|
86
|
-
const err = new Error('@aifabrix/miso-client is required for builder-api auth');
|
|
87
|
-
err.statusCode = 503;
|
|
88
|
-
throw err;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (!misoClientSingleton) {
|
|
92
|
-
misoClientSingleton = new MisoClient(buildMisoClientConfigFromEnv());
|
|
93
|
-
}
|
|
94
|
-
if (!misoClientSingleton.isInitialized()) {
|
|
95
|
-
await misoClientSingleton.initialize();
|
|
96
|
-
}
|
|
97
|
-
return misoClientSingleton;
|
|
41
|
+
return getBuilderMisoRuntime().getClient();
|
|
98
42
|
}
|
|
99
43
|
|
|
100
44
|
/**
|
|
@@ -167,9 +111,6 @@ async function authenticateRequest(req, opts = {}) {
|
|
|
167
111
|
module.exports = {
|
|
168
112
|
extractBearerToken,
|
|
169
113
|
assertNoForbiddenAuthHeaders,
|
|
170
|
-
resolveControllerUrlFromEnv,
|
|
171
|
-
buildMisoClientConfigFromEnv,
|
|
172
|
-
resetMisoClientForTests,
|
|
173
114
|
getMisoClientForAuth,
|
|
174
115
|
validateBearerToken,
|
|
175
116
|
authenticateRequest
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** @fileoverview Await SDK bootstrap and token proof before accepting requests. */
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { initializeBuilderMisoRuntime, closeBuilderMisoRuntime } = require('./builder-miso-runtime');
|
|
5
|
+
const { ensureBuilderApiRuntimeConfigDir } = require('./builder-api-runtime-config');
|
|
6
|
+
|
|
7
|
+
/** @param {Object} server HTTP server @returns {Promise<void>} Listener closed */
|
|
8
|
+
function closeServer(server) {
|
|
9
|
+
return new Promise(resolve => {
|
|
10
|
+
server.close(resolve);
|
|
11
|
+
server.closeAllConnections();
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {Object} [options] Startup dependencies
|
|
17
|
+
* @returns {Promise<{server: Object, close: Function}>} Running application
|
|
18
|
+
*/
|
|
19
|
+
async function startBuilderApi(options = {}) {
|
|
20
|
+
ensureBuilderApiRuntimeConfigDir();
|
|
21
|
+
let server;
|
|
22
|
+
let closing;
|
|
23
|
+
const close = () => {
|
|
24
|
+
if (!closing) {
|
|
25
|
+
closing = Promise.all([server ? closeServer(server) : undefined, closeBuilderMisoRuntime()]);
|
|
26
|
+
}
|
|
27
|
+
return closing;
|
|
28
|
+
};
|
|
29
|
+
try {
|
|
30
|
+
const runtime = await initializeBuilderMisoRuntime(options.initSecrets);
|
|
31
|
+
await runtime.health();
|
|
32
|
+
const createServer = options.createServer || require('./builder-api-server').createBuilderApiServer;
|
|
33
|
+
server = createServer(options.port ?? Number(process.env.PORT || 3005));
|
|
34
|
+
await new Promise((resolve, reject) => {
|
|
35
|
+
server.once('error', reject);
|
|
36
|
+
server.once('listening', () => {
|
|
37
|
+
server.removeListener('error', reject); resolve();
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
return { server, close };
|
|
41
|
+
} catch (error) {
|
|
42
|
+
await close().catch(() => {});
|
|
43
|
+
// Only our allowlisted health errors can cross the process boundary.
|
|
44
|
+
const failure = new Error('Builder API startup failed: Miso application runtime or authentication unavailable');
|
|
45
|
+
failure.statusCode = 503;
|
|
46
|
+
if (error?.statusCode === 503 && error?.errorType?.startsWith('/Errors/miso_')) throw error;
|
|
47
|
+
throw failure;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = { startBuilderApi };
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builder's process-owned SDK runtime and application-token health check.
|
|
3
|
+
* @fileoverview SDK owns providers, secrets, renewal and connections.
|
|
4
|
+
*/
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const { validateMisoApplicationToken } = require('../api/miso-health.api');
|
|
8
|
+
let activeRuntime;
|
|
9
|
+
|
|
10
|
+
/** @param {string} code Safe category @returns {Error} Service-unavailable error */
|
|
11
|
+
function runtimeError(code) {
|
|
12
|
+
const messages = {
|
|
13
|
+
miso_authentication_failed: 'Miso application authentication failed',
|
|
14
|
+
miso_timeout: 'Miso application token check timed out',
|
|
15
|
+
miso_unreachable: 'Miso Controller is unavailable',
|
|
16
|
+
miso_runtime_unavailable: 'Miso application runtime is unavailable'
|
|
17
|
+
};
|
|
18
|
+
return Object.assign(new Error(messages[code]), {
|
|
19
|
+
statusCode: 503, code, errorType: `/Errors/${code}`
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** @param {unknown} error SDK error @returns {string} Safe category */
|
|
24
|
+
function failureCode(error) {
|
|
25
|
+
const status = error?.statusCode || error?.errorResponse?.status || error?.response?.status;
|
|
26
|
+
if ([401, 403].includes(status) || error?.code === 'authorization-denied') {
|
|
27
|
+
return 'miso_authentication_failed';
|
|
28
|
+
}
|
|
29
|
+
if (['ECONNABORTED', 'ETIMEDOUT', 'ERR_CANCELED'].includes(error?.code)) {
|
|
30
|
+
return 'miso_timeout';
|
|
31
|
+
}
|
|
32
|
+
return 'miso_unreachable';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** SDK resource ownership and fail-closed application state. */
|
|
36
|
+
class RuntimeController {
|
|
37
|
+
/** @param {Object} runtime SDK runtime */
|
|
38
|
+
constructor(runtime) {
|
|
39
|
+
this.runtime = runtime;
|
|
40
|
+
this.unsubscribe = runtime.onInvalidated(reason => {
|
|
41
|
+
this.unavailable = reason === 'authorization-denied'
|
|
42
|
+
? 'miso_authentication_failed' : 'miso_runtime_unavailable';
|
|
43
|
+
this.abort?.abort();
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** @returns {void} Throws when the SDK snapshot is unavailable */
|
|
48
|
+
assertAvailable() {
|
|
49
|
+
if (this.unavailable) throw runtimeError(this.unavailable);
|
|
50
|
+
try {
|
|
51
|
+
this.runtime.secrets.get('');
|
|
52
|
+
} catch {
|
|
53
|
+
throw runtimeError(this.unavailable || 'miso_runtime_unavailable');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** @returns {Object} Initialized client */
|
|
58
|
+
getClient() {
|
|
59
|
+
this.assertAvailable();
|
|
60
|
+
return this.runtime.client;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** @returns {Promise<void>} Idempotent cleanup */
|
|
64
|
+
close() {
|
|
65
|
+
if (!this.closing) {
|
|
66
|
+
this.unavailable = this.unavailable || 'miso_runtime_unavailable';
|
|
67
|
+
this.abort?.abort();
|
|
68
|
+
this.unsubscribe();
|
|
69
|
+
this.closing = Promise.resolve().then(() => this.runtime.close()).catch(() => {
|
|
70
|
+
throw runtimeError('miso_runtime_unavailable');
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return this.closing;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** @returns {Promise<void>} Fresh, coalesced token proof; never caches success */
|
|
77
|
+
health() {
|
|
78
|
+
this.assertAvailable();
|
|
79
|
+
if (!this.pending) {
|
|
80
|
+
this.abort = new AbortController();
|
|
81
|
+
this.pending = runProbe(this.runtime.client, this.abort,
|
|
82
|
+
() => this.assertAvailable(), code => {
|
|
83
|
+
this.unavailable = code;
|
|
84
|
+
void this.close().catch(() => {});
|
|
85
|
+
}, () => {
|
|
86
|
+
this.pending = undefined;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return this.pending;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** @param {Object} runtime SDK runtime @returns {RuntimeController} Consumer controls */
|
|
94
|
+
function createRuntimeController(runtime) {
|
|
95
|
+
return new RuntimeController(runtime);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Coalesce until transport settles, including SDK-owned token acquisition after a deadline.
|
|
100
|
+
* @param {Object} client SDK client
|
|
101
|
+
* @param {AbortController} abort Cancellation
|
|
102
|
+
* @param {Function} assertAvailable Runtime guard
|
|
103
|
+
* @param {Function} invalidate Terminal rejection
|
|
104
|
+
* @param {Function} settled Release in-flight work
|
|
105
|
+
* @returns {Promise<void>} Bounded health result
|
|
106
|
+
*/
|
|
107
|
+
function runProbe(client, abort, assertAvailable, invalidate, settled) {
|
|
108
|
+
let timer;
|
|
109
|
+
const deadline = new Promise((_, reject) => {
|
|
110
|
+
timer = setTimeout(() => {
|
|
111
|
+
abort.abort(); reject(runtimeError('miso_timeout'));
|
|
112
|
+
}, 3000);
|
|
113
|
+
});
|
|
114
|
+
const request = validateMisoApplicationToken(client, abort.signal).then(() => {
|
|
115
|
+
assertAvailable();
|
|
116
|
+
}).catch(error => {
|
|
117
|
+
let code = failureCode(error);
|
|
118
|
+
try {
|
|
119
|
+
assertAvailable();
|
|
120
|
+
} catch (unavailable) {
|
|
121
|
+
code = unavailable.code;
|
|
122
|
+
}
|
|
123
|
+
if (code === 'miso_authentication_failed') invalidate(code);
|
|
124
|
+
throw runtimeError(code);
|
|
125
|
+
}).finally(() => {
|
|
126
|
+
clearTimeout(timer); settled();
|
|
127
|
+
});
|
|
128
|
+
return Promise.race([request, deadline]);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** @param {Function} [init] SDK factory for tests @returns {Promise<Object>} Runtime controls */
|
|
132
|
+
async function initializeBuilderMisoRuntime(init) {
|
|
133
|
+
if (activeRuntime) throw runtimeError('miso_runtime_unavailable');
|
|
134
|
+
const factory = init || require('@aifabrix/miso-client/bootstrap').initSecrets;
|
|
135
|
+
try {
|
|
136
|
+
activeRuntime = createRuntimeController(await factory());
|
|
137
|
+
return activeRuntime;
|
|
138
|
+
} catch {
|
|
139
|
+
throw runtimeError('miso_runtime_unavailable');
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** @returns {Object} Active guarded runtime */
|
|
144
|
+
function getBuilderMisoRuntime() {
|
|
145
|
+
if (!activeRuntime) throw runtimeError('miso_runtime_unavailable');
|
|
146
|
+
return activeRuntime;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** @returns {Promise<void>} Close and release process-owned runtime */
|
|
150
|
+
async function closeBuilderMisoRuntime() {
|
|
151
|
+
const current = activeRuntime;
|
|
152
|
+
activeRuntime = undefined;
|
|
153
|
+
await current?.close();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** @param {boolean} [ready] Readiness response @returns {Promise<Object>} Healthy payload */
|
|
157
|
+
async function checkBuilderMisoHealth(ready = false) {
|
|
158
|
+
await getBuilderMisoRuntime().health();
|
|
159
|
+
return ready
|
|
160
|
+
? { status: 'ready', dataplaneUrl: process.env.DATAPLANE_URL || null }
|
|
161
|
+
: { status: 'ok' };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
module.exports = {
|
|
165
|
+
initializeBuilderMisoRuntime, getBuilderMisoRuntime, closeBuilderMisoRuntime,
|
|
166
|
+
checkBuilderMisoHealth, createRuntimeController
|
|
167
|
+
};
|
|
@@ -213,14 +213,14 @@ const OPERATION_DESCRIPTIONS = Object.freeze({
|
|
|
213
213
|
tags: ['repository']
|
|
214
214
|
},
|
|
215
215
|
health: {
|
|
216
|
-
summary: '
|
|
216
|
+
summary: 'Application-token health probe',
|
|
217
217
|
description:
|
|
218
|
-
'Returns ok
|
|
218
|
+
'Returns ok only after Miso accepts the application client token. Returns 503 when authentication, runtime validity or connectivity fails. No caller authentication; user credentials cannot substitute for the application token.',
|
|
219
219
|
tags: ['infra']
|
|
220
220
|
},
|
|
221
221
|
ready: {
|
|
222
222
|
summary: 'Readiness probe',
|
|
223
|
-
description: '
|
|
223
|
+
description: 'Verifies the application client token with Miso and returns ready with configured dataplaneUrl. Returns 503 for an unusable token or unavailable Miso runtime. Does not verify Dataplane connectivity.',
|
|
224
224
|
tags: ['infra']
|
|
225
225
|
},
|
|
226
226
|
getBuilderHelp: {
|
|
@@ -113,11 +113,11 @@ const CORE_SCHEMA_DESCRIPTIONS = {
|
|
|
113
113
|
deleted: 'Always true when the workspace folder was removed.'
|
|
114
114
|
},
|
|
115
115
|
HealthData: {
|
|
116
|
-
_schema: '
|
|
117
|
-
status: 'Always ok
|
|
116
|
+
_schema: 'Health payload after Miso accepts the application client token.',
|
|
117
|
+
status: 'Always ok after successful application-token validation.'
|
|
118
118
|
},
|
|
119
119
|
ReadyData: {
|
|
120
|
-
_schema: 'Readiness
|
|
120
|
+
_schema: 'Readiness after Miso application-token validation; does not verify Dataplane connectivity.',
|
|
121
121
|
status: 'Always ready when the service accepts traffic.',
|
|
122
122
|
dataplaneUrl: 'Configured dev Dataplane base URL, or null when unset.'
|
|
123
123
|
},
|
|
@@ -68,7 +68,8 @@ function buildStandardErrorResponses() {
|
|
|
68
68
|
408: problemResponseContent('Request exceeded capability timeout.'),
|
|
69
69
|
409: problemResponseContent('Workspace lock held by another in-flight operation.'),
|
|
70
70
|
413: problemResponseContent('Request body exceeds MAX_BODY_BYTES.'),
|
|
71
|
-
500: problemResponseContent('Unexpected server error.')
|
|
71
|
+
500: problemResponseContent('Unexpected server error.'),
|
|
72
|
+
503: problemResponseContent('Miso application authentication failed, token check timed out, or runtime/Controller unavailable. Safe failure category appears in type.')
|
|
72
73
|
};
|
|
73
74
|
}
|
|
74
75
|
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
'use strict';
|
|
10
10
|
|
|
11
|
+
const { checkBuilderMisoHealth } = require('./builder-miso-runtime');
|
|
11
12
|
const { buildContext } = require('./route-handlers-context');
|
|
12
13
|
const { listSystems, createSystem, getSystemManifest, deleteSystem } = require('./workspace-ops');
|
|
13
14
|
const { persistSuppliedOpenApi } = require('./persist-supplied-openapi');
|
|
@@ -230,8 +231,8 @@ const HANDLER_MAP = {
|
|
|
230
231
|
getRepositoryStatus: (params, auth) => getRepositoryStatus(buildContext(auth, params.systemKey)),
|
|
231
232
|
runRepositoryDispatchPipeline: (params, auth, body) =>
|
|
232
233
|
runRepositoryDispatchPipeline(buildContext(auth, params.systemKey), body),
|
|
233
|
-
health: () => (
|
|
234
|
-
ready: () => (
|
|
234
|
+
health: () => checkBuilderMisoHealth(),
|
|
235
|
+
ready: () => checkBuilderMisoHealth(true),
|
|
235
236
|
getBuilderHelp: (_params, _auth, _body, query) => handleGetBuilderHelp(query || {}),
|
|
236
237
|
getEvidenceMetadata: (params, auth) =>
|
|
237
238
|
getEvidenceMetadata(buildContext(auth, '_evidence'), params.roleKey),
|
|
@@ -142,6 +142,9 @@ function evaluateSearchExpectations(expectedSearch, observation) {
|
|
|
142
142
|
|
|
143
143
|
function skillsResourceEntry(skillsPayload, resourceType) {
|
|
144
144
|
const root = skillsEnvelope(skillsPayload) || asObject(skillsPayload);
|
|
145
|
+
if (String(root.resourceType || '').trim() === resourceType) {
|
|
146
|
+
return root;
|
|
147
|
+
}
|
|
145
148
|
const rts = asObject(asObject(root).search).resourceTypes;
|
|
146
149
|
return asObject(asObject(rts)[resourceType]);
|
|
147
150
|
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run one published Role Assistant case as an explicitly selected subject.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Async test-job isolation for the Role Assistant CLI
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
10
|
+
const { resolveUserIdOrEmail } = require('../identity/resolve-user-id');
|
|
11
|
+
const {
|
|
12
|
+
enqueueRoleAssistantTestJob,
|
|
13
|
+
getRoleAssistantTestJob
|
|
14
|
+
} = require('../api/role-assistant-test-job.api');
|
|
15
|
+
const { roleAssistantSystemKey } = require('../utils/role-assistant-paths');
|
|
16
|
+
|
|
17
|
+
function selectedSubject(opts) {
|
|
18
|
+
const ids = Array.isArray(opts.subjectUserId)
|
|
19
|
+
? opts.subjectUserId.map((value) => String(value || '').trim()).filter(Boolean)
|
|
20
|
+
: [];
|
|
21
|
+
const emails = Array.isArray(opts.subjectEmail)
|
|
22
|
+
? opts.subjectEmail.map((value) => String(value || '').trim()).filter(Boolean)
|
|
23
|
+
: [];
|
|
24
|
+
if (ids.length + emails.length > 1) {
|
|
25
|
+
throw new Error('Role Assistant case execution accepts exactly one isolation subject.');
|
|
26
|
+
}
|
|
27
|
+
return { userId: ids[0] || '', email: emails[0] || '' };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function hasRoleAssistantTestSubject(opts) {
|
|
31
|
+
const subject = selectedSubject(opts);
|
|
32
|
+
return Boolean(subject.userId || subject.email);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function resolveSubjectUserId(opts, authConfig) {
|
|
36
|
+
const subject = selectedSubject(opts);
|
|
37
|
+
if (subject.userId) {
|
|
38
|
+
return subject.userId;
|
|
39
|
+
}
|
|
40
|
+
const controllerUrl = await resolveControllerUrl();
|
|
41
|
+
return resolveUserIdOrEmail(controllerUrl, authConfig, subject.email);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function waitFor(milliseconds) {
|
|
45
|
+
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function pollJob(roleKey, authContext, jobId, opts) {
|
|
49
|
+
const timeoutMs = Number(opts.pollTimeoutMs) || 180000;
|
|
50
|
+
const intervalMs = Number(opts.pollIntervalMs) || 750;
|
|
51
|
+
const startedAt = Date.now();
|
|
52
|
+
let job = null;
|
|
53
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
54
|
+
job = await getRoleAssistantTestJob(
|
|
55
|
+
authContext.dataplaneUrl,
|
|
56
|
+
authContext.authConfig,
|
|
57
|
+
roleAssistantSystemKey(roleKey),
|
|
58
|
+
jobId
|
|
59
|
+
);
|
|
60
|
+
const status = String(job?.status || '').trim().toLowerCase();
|
|
61
|
+
if (['completed', 'failed', 'cancelled'].includes(status)) {
|
|
62
|
+
return job;
|
|
63
|
+
}
|
|
64
|
+
await waitFor(intervalMs);
|
|
65
|
+
}
|
|
66
|
+
throw new Error(`Role Assistant test job ${jobId} did not finish within ${timeoutMs} ms.`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function evaluationFromJobCase(roleCase, job, row, fallbackJobId = null) {
|
|
70
|
+
const assertions = Array.isArray(row?.assertions)
|
|
71
|
+
? row.assertions.map((assertion) => ({
|
|
72
|
+
...assertion,
|
|
73
|
+
passed: String(assertion?.kind || '').toLowerCase() !== 'fail'
|
|
74
|
+
}))
|
|
75
|
+
: [];
|
|
76
|
+
return {
|
|
77
|
+
caseId: row?.caseId || roleCase.caseId,
|
|
78
|
+
sourceFile: roleCase.sourceFile || null,
|
|
79
|
+
passed: row?.passed === true,
|
|
80
|
+
lifecycleState: row?.lifecycleState || String(job?.status || 'failed'),
|
|
81
|
+
assertions,
|
|
82
|
+
failedAssertions: assertions.filter((assertion) => assertion.passed === false),
|
|
83
|
+
warnings: assertions.filter((assertion) => assertion.kind === 'warn'),
|
|
84
|
+
execution: {
|
|
85
|
+
processExecutionId: row?.processExecutionId || null,
|
|
86
|
+
finalStatus: row?.lifecycleState || job?.status || null,
|
|
87
|
+
waitReason: null,
|
|
88
|
+
interactions: [],
|
|
89
|
+
workResultError: job?.errorMessage || null,
|
|
90
|
+
testJobId: job?.jobId || job?.id || fallbackJobId || null
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function runPublishedCaseAsSubject(roleKey, roleCase, opts, authContext) {
|
|
96
|
+
const subjectUserId = await resolveSubjectUserId(opts, authContext.authConfig);
|
|
97
|
+
const accepted = await enqueueRoleAssistantTestJob(
|
|
98
|
+
authContext.dataplaneUrl,
|
|
99
|
+
authContext.authConfig,
|
|
100
|
+
roleAssistantSystemKey(roleKey),
|
|
101
|
+
{ caseId: roleCase.caseId, subjectUserId }
|
|
102
|
+
);
|
|
103
|
+
const jobId = String(accepted?.jobId || '').trim();
|
|
104
|
+
if (!jobId) {
|
|
105
|
+
throw new Error('Role Assistant test job enqueue did not return jobId.');
|
|
106
|
+
}
|
|
107
|
+
const job = await pollJob(roleKey, authContext, jobId, opts);
|
|
108
|
+
const row = Array.isArray(job?.cases)
|
|
109
|
+
? job.cases.find((candidate) => candidate?.caseId === roleCase.caseId)
|
|
110
|
+
: null;
|
|
111
|
+
if (!row) {
|
|
112
|
+
throw new Error(
|
|
113
|
+
job?.errorMessage ||
|
|
114
|
+
`Published Role Assistant case '${roleCase.caseId}' returned no result in job ${jobId}.`
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
return evaluationFromJobCase(roleCase, job, row, jobId);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
module.exports = {
|
|
121
|
+
hasRoleAssistantTestSubject,
|
|
122
|
+
runPublishedCaseAsSubject,
|
|
123
|
+
evaluationFromJobCase,
|
|
124
|
+
selectedSubject
|
|
125
|
+
};
|
|
@@ -17,7 +17,7 @@ const {
|
|
|
17
17
|
wantsSkillsOnlyPath
|
|
18
18
|
} = require('./test-runner-workhub-helpers');
|
|
19
19
|
|
|
20
|
-
function
|
|
20
|
+
function directOperationKeys(spec) {
|
|
21
21
|
const raw = Array.isArray(spec && spec.operations) ? spec.operations : [];
|
|
22
22
|
const keys = [];
|
|
23
23
|
for (const item of raw) {
|
|
@@ -33,6 +33,42 @@ function operationKeys(spec) {
|
|
|
33
33
|
return keys;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
function operationClassKeys(spec) {
|
|
37
|
+
const classes = spec && typeof spec.operationClasses === 'object'
|
|
38
|
+
? spec.operationClasses
|
|
39
|
+
: {};
|
|
40
|
+
const keys = [];
|
|
41
|
+
for (const rows of Object.values(classes)) {
|
|
42
|
+
if (!Array.isArray(rows)) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
for (const row of rows) {
|
|
46
|
+
const key = String(row || '').trim();
|
|
47
|
+
if (key && !keys.includes(key)) {
|
|
48
|
+
keys.push(key);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return keys;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function operationKeys(spec) {
|
|
56
|
+
return [...new Set([...directOperationKeys(spec), ...operationClassKeys(spec)])];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function skillsRequestOptions(testCase) {
|
|
60
|
+
if (!wantsSkillsOnlyPath(testCase)) {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
const input = resolveInput(testCase);
|
|
64
|
+
const resourceType = String(input.resourceType || '').trim();
|
|
65
|
+
const operation = String(input.operation || '').trim();
|
|
66
|
+
if (operation) {
|
|
67
|
+
return { detail: 'operation', resourceType, operation };
|
|
68
|
+
}
|
|
69
|
+
return resourceType ? { detail: 'resource', resourceType } : undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
36
72
|
function pickResourceType(skillsPayload, operation) {
|
|
37
73
|
const rts = resourceTypesMap(skillsPayload);
|
|
38
74
|
const keys = Object.keys(rts);
|
|
@@ -139,7 +175,12 @@ async function runSearchCaseLifecycle(roleKey, testCase, runtimeContext) {
|
|
|
139
175
|
throw new Error('Role assistant search case requires dataplane auth context.');
|
|
140
176
|
}
|
|
141
177
|
const roleRef = resolveRoleRef(roleKey, testCase);
|
|
142
|
-
const skillsRes = await getWorkSkills(
|
|
178
|
+
const skillsRes = await getWorkSkills(
|
|
179
|
+
dataplaneUrl,
|
|
180
|
+
authConfig,
|
|
181
|
+
roleRef,
|
|
182
|
+
skillsRequestOptions(testCase)
|
|
183
|
+
);
|
|
143
184
|
if (wantsSkillsOnlyPath(testCase)) {
|
|
144
185
|
return searchLifecycleResult(roleKey, testCase, roleRef, skillsRes, null, null);
|
|
145
186
|
}
|
|
@@ -157,6 +198,7 @@ module.exports = {
|
|
|
157
198
|
wantsSearchPath,
|
|
158
199
|
wantsSkillsOnlyPath,
|
|
159
200
|
operationKeys,
|
|
201
|
+
skillsRequestOptions,
|
|
160
202
|
pickResourceType,
|
|
161
203
|
resolveRoleRef,
|
|
162
204
|
buildSearchArguments,
|