@aiwg/cli 2026.10.9 → 2026.10.10

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 (91) hide show
  1. package/agentic/code/providers/model-capabilities.v1.json +197 -3
  2. package/bin/aiwg.mjs +8 -0
  3. package/dist/src/cli/handlers/mc-admission.js +39 -0
  4. package/dist/src/cli/handlers/mc.js +2 -31
  5. package/dist/src/cli/handlers/models-help.js +52 -0
  6. package/dist/src/cli/handlers/models.js +11 -81
  7. package/dist/src/cli/handlers/use-addon-profile.js +72 -0
  8. package/dist/src/cli/handlers/use.js +191 -230
  9. package/dist/src/cli/ui.js +1 -0
  10. package/dist/src/config/aiwg-config.js +38 -184
  11. package/dist/src/config/cli-help.js +54 -0
  12. package/dist/src/config/cli.js +5 -69
  13. package/dist/src/config/config-normalization.js +10 -0
  14. package/dist/src/config/worker-activation-gate.js +26 -0
  15. package/dist/src/config/worker-adoption-cli.js +102 -0
  16. package/dist/src/config/worker-adoption.js +368 -0
  17. package/dist/src/config/worker-config-digest.js +14 -0
  18. package/dist/src/config/worker-group-templates.js +150 -0
  19. package/dist/src/config/worker-guidance-warning.js +28 -0
  20. package/dist/src/config/worker-owner-lock.js +152 -0
  21. package/dist/src/config/worker-parallelism.js +154 -0
  22. package/dist/src/config/worker-write.js +75 -0
  23. package/dist/src/decision/telemetry/exporter.js +2 -1
  24. package/dist/src/decision/telemetry/index.js +1 -0
  25. package/dist/src/decision/telemetry/mapping.js +43 -0
  26. package/dist/src/decision/telemetry/otlp-http.js +5 -2
  27. package/dist/src/decision/telemetry/redaction.js +6 -1
  28. package/dist/src/decision/telemetry/types.js +3 -0
  29. package/dist/src/decision/telemetry/worker.js +69 -0
  30. package/dist/src/lint/rule-activation.js +15 -7
  31. package/dist/src/lint/runner.js +2 -2
  32. package/dist/src/mcp/server.mjs +2 -11
  33. package/dist/src/mcp/stdio-lifecycle.mjs +27 -0
  34. package/dist/src/mcp/tools/worker-failure-diagnostics.mjs +9 -0
  35. package/dist/src/mcp/tools/worker-invoke.mjs +6 -6
  36. package/dist/src/mission-protocol/codecs.js +8 -38
  37. package/dist/src/mission-protocol/worker-contract-diagnostics.js +62 -0
  38. package/dist/src/mission-protocol/worker-invocation-types.js +2 -0
  39. package/dist/src/models/conductor-data-policy.d.mts +8 -0
  40. package/dist/src/models/conductor-data-policy.mjs +190 -0
  41. package/dist/src/models/config-loader.js +19 -3
  42. package/dist/src/models/config-writer.js +27 -0
  43. package/dist/src/models/model-capabilities.v1.json +197 -3
  44. package/dist/src/models/provider-policy.js +7 -155
  45. package/dist/src/models/resolver.js +2 -293
  46. package/dist/src/models/worker-artifact-binding.mjs +12 -0
  47. package/dist/src/models/worker-direct-readiness-policy.js +155 -0
  48. package/dist/src/models/worker-direct-readiness.js +9 -218
  49. package/dist/src/models/worker-health-readiness.js +105 -26
  50. package/dist/src/models/worker-installation.mjs +12 -14
  51. package/dist/src/models/worker-member-references.js +64 -0
  52. package/dist/src/models/worker-plan.js +317 -0
  53. package/dist/src/models/worker-selection-policy.js +217 -0
  54. package/dist/src/models/wrapper-deployment.js +1 -1
  55. package/dist/src/serve/shared-host-admission-checks.js +116 -0
  56. package/dist/src/serve/shared-host-scheduler.js +25 -107
  57. package/dist/src/serve/shared-host-starvation.js +73 -0
  58. package/dist/src/serve/worker-artifacts.d.mts +4 -0
  59. package/dist/src/serve/worker-artifacts.mjs +56 -0
  60. package/dist/src/serve/worker-command-request.js +68 -0
  61. package/dist/src/serve/worker-invocation-paths.js +144 -0
  62. package/dist/src/serve/worker-invocation-preparation.js +169 -0
  63. package/dist/src/serve/worker-invocation.js +24 -291
  64. package/dist/src/serve/worker-stdin-frame.d.mts +2 -2
  65. package/dist/src/serve/worker-stdin-frame.mjs +6 -6
  66. package/dist/src/smiths/context-pipeline/parallelism-section.js +19 -7
  67. package/package.json +9 -1
  68. package/schemas/mission-v1.schema.json +20 -19
  69. package/schemas/models/model-config-compatibility.v1.schema.json +16 -0
  70. package/tools/agents/providers/claude-post-deploy.mjs +35 -0
  71. package/tools/agents/providers/claude.mjs +3 -30
  72. package/tools/agents/providers/codex-post-deploy.mjs +13 -0
  73. package/tools/agents/providers/codex.mjs +2 -8
  74. package/tools/agents/providers/conductor-data-guard.mjs +61 -0
  75. package/tools/agents/providers/rule-delivery.mjs +3 -16
  76. package/tools/agents/providers/rule-presentation.mjs +38 -0
  77. package/tools/plugin/package-plugins.mjs +1 -1
  78. package/tools/ralph-external/lib/claude-adapter.mjs +2 -2
  79. package/tools/ralph-external/lib/codex-adapter.mjs +3 -3
  80. package/tools/ralph-external/lib/managed-worker-attempt.mjs +86 -0
  81. package/tools/ralph-external/lib/provider-adapter.mjs +11 -260
  82. package/tools/ralph-external/lib/session-attempt.mjs +85 -0
  83. package/tools/ralph-external/lib/strict-worker-json.mjs +190 -0
  84. package/tools/ralph-external/lib/worker-artifacts.mjs +209 -0
  85. package/tools/ralph-external/lib/worker-final.mjs +143 -0
  86. package/tools/ralph-external/lib/worker-inputs.mjs +55 -0
  87. package/tools/ralph-external/lib/worker-isolation.mjs +2 -44
  88. package/tools/ralph-external/lib/worker-launch-cli.mjs +47 -0
  89. package/tools/ralph-external/lib/worker-native-sandbox.mjs +83 -0
  90. package/tools/ralph-external/session-launcher.mjs +73 -217
  91. package/vscode-extension/schemas/aiwg.config.v1.json +161 -2
@@ -0,0 +1,144 @@
1
+ /** Inspect worker paths and read bounded briefs without following symlinks. */
2
+ import path from 'node:path';
3
+ import { constants } from 'node:fs';
4
+ import { lstat, realpath, open } from 'node:fs/promises';
5
+ import { workerConfigDigest } from '../config/aiwg-config.js';
6
+ /** Include the Codex transport capture in the existing output ownership checks. */
7
+ export function workerOutputPaths(invocation) {
8
+ const paths = { stdout: invocation.stdoutPath, stderr: invocation.stderrPath, final: invocation.finalPath };
9
+ const lastMessage = `${invocation.finalPath}.last-message`;
10
+ if (invocation.transport === 'shell' && invocation.selected.cli === 'codex' && invocation.argv?.includes(lastMessage)) {
11
+ paths.lastMessage = lastMessage;
12
+ }
13
+ return paths;
14
+ }
15
+ export async function inspectWorkerPath(filename, kind) {
16
+ if (!path.isAbsolute(filename) || filename.includes('\0'))
17
+ throw new Error('Absolute NUL-free paths are required');
18
+ if (filename.split('/').includes('..'))
19
+ throw new Error('Worker path traversal is not allowed');
20
+ const normalized = path.resolve(filename);
21
+ let current = normalized;
22
+ while (true) {
23
+ try {
24
+ const canonical = await realpath(current);
25
+ if (canonical !== current)
26
+ throw new Error(`Symlink path is not allowed: ${filename}`);
27
+ if (current !== normalized)
28
+ return false;
29
+ const stat = await lstat(normalized);
30
+ if (kind === 'directory' ? !stat.isDirectory() : !stat.isFile() || stat.nlink !== 1) {
31
+ throw new Error(`Expected an unaliased ${kind}: ${filename}`);
32
+ }
33
+ return true;
34
+ }
35
+ catch (error) {
36
+ if (error.code !== 'ENOENT')
37
+ throw error;
38
+ const parent = path.dirname(current);
39
+ if (parent === current)
40
+ throw error;
41
+ current = parent;
42
+ }
43
+ }
44
+ }
45
+ const inspectPath = inspectWorkerPath;
46
+ export async function readBrief(filename) {
47
+ if (!await inspectPath(filename, 'file'))
48
+ return null;
49
+ const handle = await open(filename, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
50
+ try {
51
+ const stat = await handle.stat();
52
+ if (!stat.isFile() || stat.nlink !== 1 || stat.size > 262144)
53
+ throw new Error('Brief must be a bounded regular file (256 KiB)');
54
+ const buffer = Buffer.alloc(262145);
55
+ let length = 0;
56
+ while (length < buffer.length) {
57
+ const { bytesRead } = await handle.read(buffer, length, buffer.length - length, null);
58
+ if (!bytesRead)
59
+ break;
60
+ length += bytesRead;
61
+ }
62
+ if (length > 262144)
63
+ throw new Error('Brief exceeds 256 KiB');
64
+ return buffer.subarray(0, length);
65
+ }
66
+ finally {
67
+ await handle.close();
68
+ }
69
+ }
70
+ /** Canonical containment shared by launch metadata and saved-plan authority. */
71
+ const beneath = (root, filename) => {
72
+ const r = path.relative(root, filename);
73
+ return r === '' || !r.startsWith('..') && !path.isAbsolute(r);
74
+ };
75
+ async function resolveRealish(target) {
76
+ const parts = target.split('/').filter(Boolean);
77
+ for (let i = parts.length; i >= 0; i--) {
78
+ const candidate = i === 0 ? '/' : `/${parts.slice(0, i).join('/')}`;
79
+ try {
80
+ const real = await realpath(candidate);
81
+ const remainder = parts.slice(i).join('/');
82
+ return remainder ? path.join(real, remainder) : real;
83
+ }
84
+ catch (error) {
85
+ if (error.code !== 'ENOENT')
86
+ throw error;
87
+ }
88
+ }
89
+ return target;
90
+ }
91
+ export async function realBeneath(root, filename) {
92
+ const [realRoot, realFilename] = await Promise.all([resolveRealish(root), resolveRealish(filename)]);
93
+ return beneath(realRoot, realFilename);
94
+ }
95
+ /** The persisted conductor plan, never worker-supplied JSON, owns the minted ID. */
96
+ export async function assertConductorWorkerPlan(saved, filename, drivers) {
97
+ if (!filename)
98
+ throw new Error('Conductor-owned saved plan is required to verify invocation identity');
99
+ const invocation = saved.invocation;
100
+ if (!invocation?.cwd)
101
+ throw new Error('Conductor plan has no worker cwd');
102
+ const roots = [invocation.cwd, ...(drivers[invocation.selected.cli]?.qualification.installation?.workerWritableRoots ?? [])];
103
+ for (const root of roots) {
104
+ if (await realBeneath(root, filename))
105
+ throw new Error('Invocation plan must be outside worker-writable roots');
106
+ }
107
+ if (!await inspectWorkerPath(filename, 'file'))
108
+ throw new Error('Conductor-owned saved plan is missing');
109
+ // Pin every ancestor, just as launch input reads do, and refuse aliased files.
110
+ let directory = await open('/', constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
111
+ try {
112
+ const components = filename.split('/').filter(Boolean), leaf = components.pop();
113
+ for (const component of components) {
114
+ const next = await open(`/proc/self/fd/${directory.fd}/${component}`, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
115
+ await directory.close();
116
+ directory = next;
117
+ }
118
+ const handle = await open(`/proc/self/fd/${directory.fd}/${leaf}`, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
119
+ try {
120
+ const stat = await handle.stat();
121
+ if (!stat.isFile() || stat.nlink !== 1 || stat.uid !== process.getuid() || stat.size > 1048576) {
122
+ throw new Error('Conductor-owned saved plan must be bounded, unaliased and owned');
123
+ }
124
+ const bytes = Buffer.alloc(1048577);
125
+ let length = 0;
126
+ while (length < bytes.length) {
127
+ const { bytesRead } = await handle.read(bytes, length, bytes.length - length, length);
128
+ if (!bytesRead)
129
+ break;
130
+ length += bytesRead;
131
+ }
132
+ if (length > 1048576 || workerConfigDigest(JSON.parse(bytes.subarray(0, length).toString('utf8'))) !== workerConfigDigest(saved)) {
133
+ throw new Error('Invocation identity differs from conductor-owned saved plan');
134
+ }
135
+ }
136
+ finally {
137
+ await handle.close();
138
+ }
139
+ }
140
+ finally {
141
+ await directory.close();
142
+ }
143
+ }
144
+ //# sourceMappingURL=worker-invocation-paths.js.map
@@ -0,0 +1,169 @@
1
+ /** Validate and render a prepared invocation against trusted installation controls. */
2
+ import path from 'node:path';
3
+ import { createHash } from 'node:crypto';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { validateWorkerArtifacts } from './worker-artifacts.mjs';
6
+ import { assertValidWorkerPayload } from '../mission-protocol/codecs.js';
7
+ import { qualificationDigest } from '../models/qualification-digest.mjs';
8
+ import { qualifyWorkerInvocationProfile, assertOperatorWorkerNativeArgv, assertWorkerSelectionAllowed, assertWorkerLaunchMode, requiresWorkerExternalBoundary, assertWorkerHealthQualification, WorkerPolicyError, DEFAULT_WORKER_SELECTION } from '../models/provider-policy.js';
9
+ /**
10
+ * The exact digest formula an MCP worker binding's `inputSchemaDigest`/
11
+ * `outputSchemaDigest` are pinned against (used below and reused verbatim by
12
+ * the #2892 MCP worker-invoke bridge's own live `tools/list` schema-drift
13
+ * check — see `src/mcp/tools/worker-invoke.mjs` M3). Deliberately the raw
14
+ * `JSON.stringify` of the schema object, NOT `workerConfigDigest`'s
15
+ * canonicalized (sorted-key) form — a schema's own declared key order is
16
+ * part of what gets pinned at qualification time.
17
+ */
18
+ export function mcpToolSchemaDigest(schema) {
19
+ return `sha256:${createHash('sha256').update(JSON.stringify(schema)).digest('hex')}`;
20
+ }
21
+ export async function prepareWorkerInvocation(invocation, qualification) {
22
+ const result = structuredClone(invocation);
23
+ validateWorkerArtifacts(result);
24
+ // shellCommand is derived syntax, never authority supplied by a brief.
25
+ if (result.transport === 'shell')
26
+ result.shellCommand = '';
27
+ // Validate the established invocation shape first. Health is optional and
28
+ // is checked after policy, installation and transport qualification below.
29
+ const contract = { ...result };
30
+ delete contract.health;
31
+ assertValidWorkerPayload('workerResolvedInvocation', contract);
32
+ if ((qualification.platform ?? process.platform) !== 'linux') {
33
+ throw new Error('Unsupported worker invocation platform; Linux is required');
34
+ }
35
+ if (!Number.isFinite(qualification.now) || Date.parse(result.expiresAt) <= qualification.now) {
36
+ throw new Error('Worker invocation is expired or has no validation clock');
37
+ }
38
+ if (qualification.adapter.id !== qualification.adapterId) {
39
+ throw new Error('Worker invocation adapter identity mismatch');
40
+ }
41
+ assertWorkerLaunchMode(qualification.adapter, qualification.permissions);
42
+ if (result.isolation !== undefined && result.isolation !== (qualification.permissions?.launchMode ?? 'systemd-unit')) {
43
+ throw new WorkerPolicyError('OG_PIN_MISMATCH', 'Worker launch mode differs from the execution policy');
44
+ }
45
+ for (const [key, value] of Object.entries(qualification.expectedDigests ?? {})) {
46
+ if (result[key] !== value)
47
+ throw new WorkerPolicyError('OG_PIN_MISMATCH', `Worker invocation ${key} changed`);
48
+ }
49
+ const verified = Date.parse(qualification.adapter.qualification.verifiedAt ?? '');
50
+ const policyDays = qualification.policy?.freshness?.policyDays ?? DEFAULT_WORKER_SELECTION.freshness.policyDays;
51
+ if (!Number.isFinite(verified) || verified > qualification.now || qualification.now - verified >= policyDays * 86400000) {
52
+ throw new Error('Worker installation qualification is stale or invalid');
53
+ }
54
+ assertWorkerSelectionAllowed(result.selected, qualification);
55
+ const profile = qualifyWorkerInvocationProfile(qualification.adapter, {
56
+ profile: result.selected.profileRef, transport: result.transport,
57
+ cliVersion: qualification.cliVersion,
58
+ requiredControls: qualification.requiredControls, policy: qualification.policy,
59
+ permissions: qualification.permissions, effort: result.selected.effort.value, boundary: qualification.boundary,
60
+ mcp: qualification.mcp,
61
+ });
62
+ if (requiresWorkerExternalBoundary(qualification.adapter, { ...qualification,
63
+ profile: result.selected.profileRef, transport: result.transport, effort: result.selected.effort.value })) {
64
+ const installed = qualification.installation;
65
+ const evidence = profile.installationQualification;
66
+ if (!installed || !evidence || ['scopeDigest', 'fixtureDigest', 'verifiedAt', 'evidenceRef']
67
+ .some(key => installed.evidence?.[key] !== evidence[key])
68
+ || !qualification.boundary || qualificationDigest(qualification.boundary) !== qualificationDigest(installed.boundary)) {
69
+ throw new WorkerPolicyError('OG_ADAPTER_UNQUALIFIED', 'Worker installation evidence and qualified boundary must match');
70
+ }
71
+ }
72
+ if (profile.installationQualification) {
73
+ const installedAt = Date.parse(profile.installationQualification.verifiedAt);
74
+ if (!Number.isFinite(installedAt) || installedAt > qualification.now
75
+ || qualification.now - installedAt >= policyDays * 86400000) {
76
+ throw new Error('Worker installation qualification is stale or invalid');
77
+ }
78
+ }
79
+ const scopeDigest = profile.installationQualification?.scopeDigest ?? qualification.adapter.qualification.fixtureDigest;
80
+ if (result.selected.qualificationDigest !== scopeDigest)
81
+ throw new WorkerPolicyError('OG_PIN_MISMATCH', 'Worker qualification digest changed');
82
+ const absolute = (value) => typeof value === 'string' && path.posix.isAbsolute(value) && !value.includes('\0');
83
+ for (const key of ['cwd', 'briefPath', 'stdoutPath', 'stderrPath', 'finalPath']) {
84
+ if (!absolute(result[key]))
85
+ throw new Error(`Worker invocation requires absolute ${key}`);
86
+ }
87
+ const briefPath = path.posix.normalize(result.briefPath);
88
+ if ([result.stdoutPath, result.stderrPath, result.finalPath].some(value => path.posix.normalize(value) === briefPath)
89
+ || path.posix.normalize(result.stdoutPath) === path.posix.normalize(result.stderrPath)) {
90
+ throw new Error('Worker invocation input/output paths must be distinct');
91
+ }
92
+ if (result.transport === 'shell') {
93
+ assertOperatorWorkerNativeArgv(qualification.adapter, result.argv, result.selected, qualification.permissions);
94
+ if (!absolute(result.argv[0]) || result.argv[0].includes('=') || result.argv.some(value => value.includes('\0'))) {
95
+ throw new WorkerPolicyError('OG_CONFIG_REFERENCE', 'Worker invocation requires an absolute binary without equals and NUL-free argv');
96
+ }
97
+ if (result.envNames.some(name => !profile.envAllowlist.includes(name))) {
98
+ throw new Error('Worker invocation environment is outside the qualified allowlist');
99
+ }
100
+ const quote = (value) => `'${value.replaceAll("'", "'\"'\"'")}'`;
101
+ // Only validated environment names appear as shell syntax; their values
102
+ // remain in the conductor environment and are never printed in the plan.
103
+ const environment = result.envNames.map(name => `${name}="\${${name}-}"`).join(' ');
104
+ // The frozen 'closed' value means empty stdin (immediate EOF). 'brief-json'
105
+ // pipes a conductor-built params document framed from the brief file
106
+ // (worker-stdin-frame.mjs), so brief text never enters any argv.
107
+ const worker = `env -i${environment ? ` ${environment}` : ''} ${result.argv.map(quote).join(' ')}`;
108
+ const outputs = `> ${quote(result.stdoutPath)} 2> ${quote(result.stderrPath)}`;
109
+ if (result.stdin === 'brief-json') {
110
+ const framer = fileURLToPath(new URL('./worker-stdin-frame.mjs', import.meta.url));
111
+ const frame = [process.execPath, framer, result.briefPath, result.selected.model, result.cwd].map(quote).join(' ');
112
+ result.shellCommand = `cd ${quote(result.cwd)} && env -i ${frame} | ${worker} ${outputs}`;
113
+ }
114
+ else if (result.stdin === 'adapter-frame') {
115
+ const binding = qualification.stdinFramerBinding;
116
+ if (!binding || !absolute(binding.storePath) || !/^sha256:[a-f0-9]{64}$/.test(binding.storeDigest)) {
117
+ throw new Error('Adapter stdin framer requires a trusted installation binding');
118
+ }
119
+ const framer = fileURLToPath(new URL('./worker-stdin-frame.mjs', import.meta.url));
120
+ const frame = [process.execPath, framer, '--adapter-frame', result.briefPath, result.cwd,
121
+ result.selected.cli, qualification.adapterId, result.selected.qualificationDigest, result.briefDigest,
122
+ binding.storePath, binding.storeDigest, JSON.stringify(qualification.policy ?? null), result.selected.model].map(quote).join(' ');
123
+ result.shellCommand = `cd ${quote(result.cwd)} && env -i${environment ? ` ${environment}` : ''} ${frame} | ${worker} ${outputs}`;
124
+ }
125
+ else if (result.stdin === 'closed' || result.stdin === 'brief-text') {
126
+ const stdin = result.stdin === 'closed' ? '< /dev/null' : `< ${quote(result.briefPath)}`;
127
+ result.shellCommand = `cd ${quote(result.cwd)} && ${worker} ${stdin} ${outputs}`;
128
+ }
129
+ else {
130
+ throw new Error(`Unsupported worker stdin mode: ${result.stdin}`);
131
+ }
132
+ }
133
+ else {
134
+ const binding = result.mcp;
135
+ for (const key of ['serverRef', 'tool', 'inputSchemaDigest', 'outputSchemaDigest', 'sessionIsolation']) {
136
+ if (binding[key] !== profile.mcp[key])
137
+ throw new WorkerPolicyError('OG_PIN_MISMATCH', `MCP worker binding changed: ${key}`);
138
+ }
139
+ if (binding.timeoutSeconds > result.limits.wallSeconds) {
140
+ throw new Error('MCP worker timeout exceeds the invocation deadline');
141
+ }
142
+ for (const key of ['input', 'output']) {
143
+ const schema = qualification.mcpSchemas?.[key];
144
+ if (!schema || mcpToolSchemaDigest(schema) !== binding[`${key}SchemaDigest`]) {
145
+ throw new WorkerPolicyError('OG_PIN_MISMATCH', `MCP worker ${key} schema digest changed`);
146
+ }
147
+ }
148
+ const { default: Ajv2020 } = await import('ajv/dist/2020.js');
149
+ const { default: addFormats } = await import('ajv-formats');
150
+ const validate = addFormats(new Ajv2020({ strict: true, allErrors: true })).compile(qualification.mcpSchemas.input);
151
+ if (!validate(binding.arguments))
152
+ throw new Error('MCP worker arguments violate the pinned input schema');
153
+ }
154
+ assertValidWorkerPayload('workerResolvedInvocation', result);
155
+ if (result.health !== undefined) {
156
+ if (result.health.profileRef !== result.selected.profileRef
157
+ || result.health.hardTimeoutSeconds > Math.min(result.limits.wallSeconds, result.mcp?.timeoutSeconds ?? Infinity)) {
158
+ throw new Error('Worker health profile or wall ceiling mismatch');
159
+ }
160
+ if (['native', 'external'].includes(result.health.stepEnforcement)
161
+ && profile.controls.stepCap?.support !== result.health.stepEnforcement)
162
+ throw new Error('Unqualified worker step enforcement');
163
+ }
164
+ assertWorkerHealthQualification(profile, { profileRef: result.selected.profileRef, cliVersion: qualification.cliVersion,
165
+ accountScope: qualification.installation?.accountScope ?? '', inferenceProvider: result.selected.inferenceProvider,
166
+ model: result.selected.model, effort: result.selected.effort.value });
167
+ return result;
168
+ }
169
+ //# sourceMappingURL=worker-invocation-preparation.js.map