@cat-factory/orchestration 0.251.0 → 0.253.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/dist/modules/execution/DeployerStepController.d.ts.map +1 -1
- package/dist/modules/execution/DeployerStepController.js +12 -2
- package/dist/modules/execution/DeployerStepController.js.map +1 -1
- package/dist/modules/execution/DisposerStepController.d.ts.map +1 -1
- package/dist/modules/execution/DisposerStepController.js +15 -17
- package/dist/modules/execution/DisposerStepController.js.map +1 -1
- package/dist/modules/execution/prReport.environments.d.ts +8 -3
- package/dist/modules/execution/prReport.environments.d.ts.map +1 -1
- package/dist/modules/execution/prReport.environments.js +25 -40
- package/dist/modules/execution/prReport.environments.js.map +1 -1
- package/dist/modules/execution/runOutcome.boundary.d.ts.map +1 -1
- package/dist/modules/execution/runOutcome.boundary.js +29 -0
- package/dist/modules/execution/runOutcome.boundary.js.map +1 -1
- package/dist/modules/execution/toolServers.logic.d.ts +6 -3
- package/dist/modules/execution/toolServers.logic.d.ts.map +1 -1
- package/dist/modules/execution/toolServers.logic.js +6 -3
- package/dist/modules/execution/toolServers.logic.js.map +1 -1
- package/dist/validation/validateBinaryGenerators.d.ts +24 -0
- package/dist/validation/validateBinaryGenerators.d.ts.map +1 -0
- package/dist/validation/validateBinaryGenerators.js +138 -0
- package/dist/validation/validateBinaryGenerators.js.map +1 -0
- package/dist/validation/validateRegistrations.d.ts.map +1 -1
- package/dist/validation/validateRegistrations.js +5 -356
- package/dist/validation/validateRegistrations.js.map +1 -1
- package/dist/validation/validateToolServers.d.ts +24 -0
- package/dist/validation/validateToolServers.d.ts.map +1 -0
- package/dist/validation/validateToolServers.js +337 -0
- package/dist/validation/validateToolServers.js.map +1 -0
- package/package.json +11 -11
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toolServers.logic.js","sourceRoot":"","sources":["../../../src/modules/execution/toolServers.logic.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,0BAA0B,EAAE,MAAM,wBAAwB,CAAA;AAGnE,0FAA0F;AAC1F,2FAA2F;AAC3F,EAAE;AACF,kGAAkG;AAClG,gGAAgG;AAChG,gGAAgG;AAChG,gGAAgG;AAChG,+FAA+F;AAC/F,wCAAwC;AAExC
|
|
1
|
+
{"version":3,"file":"toolServers.logic.js","sourceRoot":"","sources":["../../../src/modules/execution/toolServers.logic.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { BinaryGeneratorRegistry } from '@cat-factory/kernel';
|
|
2
|
+
import type { RegistrationProblem } from './validateRegistrations.js';
|
|
3
|
+
/**
|
|
4
|
+
* Section 9 of `collectRegistrationProblems`: every generative binary integration a deployment
|
|
5
|
+
* registers must be a definition the platform can actually dispatch against.
|
|
6
|
+
*
|
|
7
|
+
* Its own module rather than more of `validateRegistrations.ts` because the section is a cohesive
|
|
8
|
+
* concern with a growing rule set, and its host had reached the file-size ratchet.
|
|
9
|
+
*
|
|
10
|
+
* Boot is the only place these can be caught. There is no write boundary that ever refused them
|
|
11
|
+
* (they are code), and every failure below is silent at run time in the same expensive way: a
|
|
12
|
+
* malformed definition or an unparseable contract becomes an integration the brief describes with
|
|
13
|
+
* no operations, a credential key that is not a valid environment-variable name is dropped by the
|
|
14
|
+
* harness's env validation and reappears as an unexplained 401 mid-run, and a cleartext endpoint
|
|
15
|
+
* puts that credential on the wire from inside the run container. Each of those costs a run to
|
|
16
|
+
* discover and names nothing that points back at the registration.
|
|
17
|
+
*
|
|
18
|
+
* A declared MEDIA TYPE that contradicts the declared modalities is an error too, not a warning:
|
|
19
|
+
* both halves drive selection (a step's content-type coverage is checked against `modalities`,
|
|
20
|
+
* while the brief tells the agent the `mediaTypes`), so an integration claiming `audio` while
|
|
21
|
+
* listing `image/png` will be picked for one job and asked to do the other.
|
|
22
|
+
*/
|
|
23
|
+
export declare function checkBinaryGenerators(registry: BinaryGeneratorRegistry | undefined): RegistrationProblem[];
|
|
24
|
+
//# sourceMappingURL=validateBinaryGenerators.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validateBinaryGenerators.d.ts","sourceRoot":"","sources":["../../src/validation/validateBinaryGenerators.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAA;AAelE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAErE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,uBAAuB,GAAG,SAAS,GAC5C,mBAAmB,EAAE,CAqBvB"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { describeFoundationalProblem, isAllowedMcpHttpUrl, validateFoundationalDefinition, } from '@cat-factory/kernel';
|
|
2
|
+
import { binaryCredentialInjectionName, binaryGeneratorDefinitionIssues, comparableCredentialInjectionName, modalitiesOfMediaType, } from '@cat-factory/contracts';
|
|
3
|
+
/**
|
|
4
|
+
* Section 9 of `collectRegistrationProblems`: every generative binary integration a deployment
|
|
5
|
+
* registers must be a definition the platform can actually dispatch against.
|
|
6
|
+
*
|
|
7
|
+
* Its own module rather than more of `validateRegistrations.ts` because the section is a cohesive
|
|
8
|
+
* concern with a growing rule set, and its host had reached the file-size ratchet.
|
|
9
|
+
*
|
|
10
|
+
* Boot is the only place these can be caught. There is no write boundary that ever refused them
|
|
11
|
+
* (they are code), and every failure below is silent at run time in the same expensive way: a
|
|
12
|
+
* malformed definition or an unparseable contract becomes an integration the brief describes with
|
|
13
|
+
* no operations, a credential key that is not a valid environment-variable name is dropped by the
|
|
14
|
+
* harness's env validation and reappears as an unexplained 401 mid-run, and a cleartext endpoint
|
|
15
|
+
* puts that credential on the wire from inside the run container. Each of those costs a run to
|
|
16
|
+
* discover and names nothing that points back at the registration.
|
|
17
|
+
*
|
|
18
|
+
* A declared MEDIA TYPE that contradicts the declared modalities is an error too, not a warning:
|
|
19
|
+
* both halves drive selection (a step's content-type coverage is checked against `modalities`,
|
|
20
|
+
* while the brief tells the agent the `mediaTypes`), so an integration claiming `audio` while
|
|
21
|
+
* listing `image/png` will be picked for one job and asked to do the other.
|
|
22
|
+
*/
|
|
23
|
+
export function checkBinaryGenerators(registry) {
|
|
24
|
+
const problems = [];
|
|
25
|
+
if (!registry)
|
|
26
|
+
return problems;
|
|
27
|
+
const valid = [];
|
|
28
|
+
for (const definition of registry.all()) {
|
|
29
|
+
const issues = binaryGeneratorDefinitionIssues(definition);
|
|
30
|
+
if (issues.length > 0) {
|
|
31
|
+
problems.push({
|
|
32
|
+
severity: 'error',
|
|
33
|
+
code: 'binary_generator_invalid',
|
|
34
|
+
message: `Generative binary integration "${definition.id}" is not a valid definition: ${issues.join('; ')}`,
|
|
35
|
+
});
|
|
36
|
+
// The checks below read fields this parse just called malformed, so reporting them too
|
|
37
|
+
// would restate one fault as several.
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
valid.push(definition);
|
|
41
|
+
problems.push(...checkBinaryGeneratorDetails(definition));
|
|
42
|
+
}
|
|
43
|
+
problems.push(...checkInjectionNameCollisions(valid));
|
|
44
|
+
return problems;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The one check that spans DEFINITIONS: two integrations may not inject different values into one
|
|
48
|
+
* environment variable.
|
|
49
|
+
*
|
|
50
|
+
* Within a definition the schema already refuses a repeated injection name. Across definitions the
|
|
51
|
+
* same name is legitimate and common, because one vendor behind an image endpoint and a music
|
|
52
|
+
* endpoint is one account: what makes that case safe is that both look the value up under the SAME
|
|
53
|
+
* key, so whichever integration is resolved first sets the variable to exactly what the other
|
|
54
|
+
* wanted. Different keys behind one name is the opposite, and there is no arbitration that makes
|
|
55
|
+
* it right. Serving the first claimant sets the variable the second integration's brief tells the
|
|
56
|
+
* agent to read, so the agent authenticates one vendor with another's key; withholding it (what
|
|
57
|
+
* dispatch does, since a mothership node validates nothing) costs both integrations every run.
|
|
58
|
+
*
|
|
59
|
+
* Neither outcome is what the deployment meant, and the remedy is one `envName` on one definition,
|
|
60
|
+
* which is why this is an error at boot rather than a warning nobody acts on. Only definitions
|
|
61
|
+
* that PARSED are compared: a malformed one has already been reported, and reading its credentials
|
|
62
|
+
* here would restate that fault as a second, more confusing one.
|
|
63
|
+
*/
|
|
64
|
+
function checkInjectionNameCollisions(definitions) {
|
|
65
|
+
// Grouped by the COMPARABLE (case-folded) name and reported under the spelling the deployment
|
|
66
|
+
// wrote, because `ACME_KEY` and `acme_key` are one variable wherever the environment is
|
|
67
|
+
// case-insensitive and two everywhere else: the pair collides on exactly the platform where the
|
|
68
|
+
// operator has the least chance of noticing.
|
|
69
|
+
const claims = new Map();
|
|
70
|
+
for (const definition of definitions) {
|
|
71
|
+
for (const credential of definition.credentials ?? []) {
|
|
72
|
+
const claim = claims.get(comparableCredentialInjectionName(credential)) ?? {
|
|
73
|
+
spelling: binaryCredentialInjectionName(credential),
|
|
74
|
+
byKey: new Map(),
|
|
75
|
+
};
|
|
76
|
+
claim.byKey.set(credential.key, [...(claim.byKey.get(credential.key) ?? []), definition.id]);
|
|
77
|
+
claims.set(comparableCredentialInjectionName(credential), claim);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const problems = [];
|
|
81
|
+
for (const [, { spelling: envName, byKey }] of claims) {
|
|
82
|
+
if (byKey.size < 2)
|
|
83
|
+
continue;
|
|
84
|
+
const described = [...byKey]
|
|
85
|
+
.map(([key, ids]) => `"${key}" (${ids.join(', ')})`)
|
|
86
|
+
.sort()
|
|
87
|
+
.join(' and ');
|
|
88
|
+
problems.push({
|
|
89
|
+
severity: 'error',
|
|
90
|
+
code: 'binary_generator_injection_name_collision',
|
|
91
|
+
message: `Generative binary integrations disagree about environment variable "${envName}": it is ` +
|
|
92
|
+
`declared for lookup keys ${described}. One variable cannot hold both values, so an agent ` +
|
|
93
|
+
`told to read it for one integration would authenticate with the other's credential. Give ` +
|
|
94
|
+
`one of them a distinct \`envName\`, or point both at the same lookup key if they really ` +
|
|
95
|
+
`share an account.`,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return problems;
|
|
99
|
+
}
|
|
100
|
+
/** The per-definition checks a valid PARSE cannot make: the endpoint, contracts, media types. */
|
|
101
|
+
function checkBinaryGeneratorDetails(definition) {
|
|
102
|
+
const problems = [];
|
|
103
|
+
const invalid = (code, message) => {
|
|
104
|
+
problems.push({ severity: 'error', code, message });
|
|
105
|
+
};
|
|
106
|
+
// The same rule an HTTP tool server's URL is held to, and for the same reason the helper
|
|
107
|
+
// states: a declared credential rides this request, so cleartext off loopback puts it on the
|
|
108
|
+
// wire. (The helper is MCP-named because that was its first caller; the rule is not.)
|
|
109
|
+
if (definition.endpoint && !isAllowedMcpHttpUrl(definition.endpoint)) {
|
|
110
|
+
invalid('insecure_binary_generator_endpoint', `Generative binary integration "${definition.id}" has endpoint "${definition.endpoint}". Its ` +
|
|
111
|
+
`credential is sent with every request, so the endpoint must be https (plain http is ` +
|
|
112
|
+
`accepted only on loopback).`);
|
|
113
|
+
}
|
|
114
|
+
for (const problem of validateFoundationalDefinition({ contracts: definition.contracts })) {
|
|
115
|
+
invalid('binary_generator_invalid', `Generative binary integration "${definition.id}": ${describeFoundationalProblem(problem)}`);
|
|
116
|
+
}
|
|
117
|
+
const declared = new Set(definition.modalities);
|
|
118
|
+
for (const mediaType of definition.mediaTypes ?? []) {
|
|
119
|
+
const consistent = modalitiesOfMediaType(mediaType);
|
|
120
|
+
// An UNRECOGNISED media type is not a fault: the platform's classifier is not a registry of
|
|
121
|
+
// every format that exists, and refusing one would make registering a new codec impossible.
|
|
122
|
+
// A recognised one that CONTRADICTS the declaration is, because both drive selection.
|
|
123
|
+
//
|
|
124
|
+
// Contradiction is an empty INTERSECTION, not an absent member, and for 3D that is the whole
|
|
125
|
+
// difference: a `.glb` is consistent with both `3d-model` and `3d-scene` because the container
|
|
126
|
+
// does not record which it holds, so requiring every member would refuse a scene generator
|
|
127
|
+
// for declaring the only format it can emit.
|
|
128
|
+
if (consistent.length > 0 && !consistent.some((modality) => declared.has(modality))) {
|
|
129
|
+
const names = consistent.join('/');
|
|
130
|
+
invalid('binary_generator_modality_mismatch', `Generative binary integration "${definition.id}" declares media type "${mediaType}" ` +
|
|
131
|
+
`(${names}) but lists none of those among its modalities ` +
|
|
132
|
+
`(${definition.modalities.join(', ')}). A step selecting it for ${names} would be ` +
|
|
133
|
+
`refused, and one selecting it for the listed modalities would be told it can emit this.`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return problems;
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=validateBinaryGenerators.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validateBinaryGenerators.js","sourceRoot":"","sources":["../../src/validation/validateBinaryGenerators.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validateRegistrations.d.ts","sourceRoot":"","sources":["../../src/validation/validateRegistrations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"validateRegistrations.d.ts","sourceRoot":"","sources":["../../src/validation/validateRegistrations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAG5D,OAAO,KAAK,EAEV,uBAAuB,EACvB,0BAA0B,EAC1B,2BAA2B,EAC3B,YAAY,EACZ,wBAAwB,EACxB,gBAAgB,EAChB,sBAAsB,EACtB,oBAAoB,EACpB,gBAAgB,EACjB,MAAM,qBAAqB,CAAA;AAiD5B,4FAA4F;AAC5F,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,OAAO,GAAG,MAAM,CAAA;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,iBAAiB,EAAE,iBAAiB,CAAA;IACpC;;;;OAIG;IACH,YAAY,EAAE,YAAY,CAAA;IAC1B;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,gBAAgB,CAAA;IACnC;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,gBAAgB,CAAA;IACnC;;;;;OAKG;IACH,wBAAwB,CAAC,EAAE,wBAAwB,CAAA;IACnD;;;;;;OAMG;IACH,2BAA2B,CAAC,EAAE,2BAA2B,CAAA;IACzD;;;;;;OAMG;IACH,uBAAuB,CAAC,EAAE,uBAAuB,CAAA;IACjD;;;;;OAKG;IACH,sBAAsB,CAAC,EAAE,sBAAsB,CAAA;IAC/C;;;;;;;;;OASG;IACH,eAAe,CAAC,EAAE,oBAAoB,CAAA;IACtC;;;;;;;OAOG;IACH,0BAA0B,CAAC,EAAE,0BAA0B,CAAA;CACxD;AAED,uFAAuF;AACvF,MAAM,WAAW,4BAA4B;IAC3C,+EAA+E;IAC/E,UAAU,EAAE,mBAAmB,CAAA;IAC/B,qGAAqG;IACrG,kBAAkB,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IACxC,qGAAqG;IACrG,kBAAkB,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IACxC;;;;;OAKG;IACH,eAAe,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IACrC;;;;OAIG;IACH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,IAAI,CAAA;IAC/C;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,OAAO,CAAA;CAC5D;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,4BAA4B,GACjC,mBAAmB,EAAE,CA4FvB;AA6sBD;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,4BAA4B,GAAG,IAAI,CAuB9E;AAOD,yGAAyG;AACzG,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,4BAA4B,GAAG,IAAI,CASlF;AAED,uFAAuF;AACvF,wBAAgB,gCAAgC,IAAI,IAAI,CAEvD"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { INLINE_ENGINE_SYSTEM_PROMPTS, runsInContainer } from '@cat-factory/agents';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { checkBinaryGenerators } from './validateBinaryGenerators.js';
|
|
3
|
+
import { CI_FIXER_AGENT_KIND, CONFLICT_RESOLVER_AGENT_KIND, FIXER_AGENT_KIND, ON_CALL_AGENT_KIND, describeFoundationalProblem, seedPipelines, stubGateContext, validateFoundationalDefinition, } from '@cat-factory/kernel';
|
|
4
|
+
import { descriptorConditionHasPredicate, duplicatedDescriptorSectionCaptions, isDeploymentScopedSource, foundationalServiceDefinitionIssues, isNamespacedId, isValidResultViewId, RESULT_VIEW_ID_SET, } from '@cat-factory/contracts';
|
|
5
|
+
import { checkKindToolServers } from './validateToolServers.js';
|
|
4
6
|
// ---------------------------------------------------------------------------
|
|
5
7
|
// Boot-time validation of the deployment's registered extensions (agent kinds, gates,
|
|
6
8
|
// pipelines). A typo'd gate `helperKind`, an unknown `resultView`, or a pipeline naming a
|
|
@@ -101,7 +103,7 @@ export function collectRegistrationProblems(opts) {
|
|
|
101
103
|
// 8. Deployment-registered FOUNDATIONAL SERVICES (only when a registry is supplied).
|
|
102
104
|
problems.push(...checkFoundationalServices(opts));
|
|
103
105
|
// 9. Deployment-registered GENERATIVE BINARY INTEGRATIONS (only when a registry is supplied).
|
|
104
|
-
problems.push(...checkBinaryGenerators(opts));
|
|
106
|
+
problems.push(...checkBinaryGenerators(opts.registries.binaryGeneratorRegistry));
|
|
105
107
|
// 10. Deployment-registered PROMPT FRAGMENTS (only when a registry is supplied).
|
|
106
108
|
problems.push(...checkPromptFragments(opts));
|
|
107
109
|
return problems;
|
|
@@ -170,81 +172,6 @@ function checkPromptFragments(opts) {
|
|
|
170
172
|
}
|
|
171
173
|
return problems;
|
|
172
174
|
}
|
|
173
|
-
/**
|
|
174
|
-
* Section 9 of {@link collectRegistrationProblems}: every generative binary integration a
|
|
175
|
-
* deployment registers must be a definition the platform can actually dispatch against.
|
|
176
|
-
*
|
|
177
|
-
* Boot is the only place these can be caught. There is no write boundary that ever refused them
|
|
178
|
-
* (they are code), and every failure below is silent at run time in the same expensive way: a
|
|
179
|
-
* malformed definition or an unparseable contract becomes an integration the brief describes with
|
|
180
|
-
* no operations, a credential key that is not a valid environment-variable name is dropped by the
|
|
181
|
-
* harness's env validation and reappears as an unexplained 401 mid-run, and a cleartext endpoint
|
|
182
|
-
* puts that credential on the wire from inside the run container. Each of those costs a run to
|
|
183
|
-
* discover and names nothing that points back at the registration.
|
|
184
|
-
*
|
|
185
|
-
* A declared MEDIA TYPE that contradicts the declared modalities is an error too, not a warning:
|
|
186
|
-
* both halves drive selection (a step's content-type coverage is checked against `modalities`,
|
|
187
|
-
* while the brief tells the agent the `mediaTypes`), so an integration claiming `audio` while
|
|
188
|
-
* listing `image/png` will be picked for one job and asked to do the other.
|
|
189
|
-
*/
|
|
190
|
-
function checkBinaryGenerators(opts) {
|
|
191
|
-
const problems = [];
|
|
192
|
-
if (!opts.registries.binaryGeneratorRegistry)
|
|
193
|
-
return problems;
|
|
194
|
-
for (const definition of opts.registries.binaryGeneratorRegistry.all()) {
|
|
195
|
-
const issues = binaryGeneratorDefinitionIssues(definition);
|
|
196
|
-
if (issues.length > 0) {
|
|
197
|
-
problems.push({
|
|
198
|
-
severity: 'error',
|
|
199
|
-
code: 'binary_generator_invalid',
|
|
200
|
-
message: `Generative binary integration "${definition.id}" is not a valid definition: ${issues.join('; ')}`,
|
|
201
|
-
});
|
|
202
|
-
// The checks below read fields this parse just called malformed, so reporting them too
|
|
203
|
-
// would restate one fault as several.
|
|
204
|
-
continue;
|
|
205
|
-
}
|
|
206
|
-
problems.push(...checkBinaryGeneratorDetails(definition));
|
|
207
|
-
}
|
|
208
|
-
return problems;
|
|
209
|
-
}
|
|
210
|
-
/** The per-definition checks a valid PARSE cannot make: the endpoint, contracts, media types. */
|
|
211
|
-
function checkBinaryGeneratorDetails(definition) {
|
|
212
|
-
const problems = [];
|
|
213
|
-
const invalid = (code, message) => {
|
|
214
|
-
problems.push({ severity: 'error', code, message });
|
|
215
|
-
};
|
|
216
|
-
// The same rule an HTTP tool server's URL is held to, and for the same reason the helper
|
|
217
|
-
// states: a declared credential rides this request, so cleartext off loopback puts it on the
|
|
218
|
-
// wire. (The helper is MCP-named because that was its first caller; the rule is not.)
|
|
219
|
-
if (definition.endpoint && !isAllowedMcpHttpUrl(definition.endpoint)) {
|
|
220
|
-
invalid('insecure_binary_generator_endpoint', `Generative binary integration "${definition.id}" has endpoint "${definition.endpoint}". Its ` +
|
|
221
|
-
`credential is sent with every request, so the endpoint must be https (plain http is ` +
|
|
222
|
-
`accepted only on loopback).`);
|
|
223
|
-
}
|
|
224
|
-
for (const problem of validateFoundationalDefinition({ contracts: definition.contracts })) {
|
|
225
|
-
invalid('binary_generator_invalid', `Generative binary integration "${definition.id}": ${describeFoundationalProblem(problem)}`);
|
|
226
|
-
}
|
|
227
|
-
const declared = new Set(definition.modalities);
|
|
228
|
-
for (const mediaType of definition.mediaTypes ?? []) {
|
|
229
|
-
const consistent = modalitiesOfMediaType(mediaType);
|
|
230
|
-
// An UNRECOGNISED media type is not a fault: the platform's classifier is not a registry of
|
|
231
|
-
// every format that exists, and refusing one would make registering a new codec impossible.
|
|
232
|
-
// A recognised one that CONTRADICTS the declaration is, because both drive selection.
|
|
233
|
-
//
|
|
234
|
-
// Contradiction is an empty INTERSECTION, not an absent member, and for 3D that is the whole
|
|
235
|
-
// difference: a `.glb` is consistent with both `3d-model` and `3d-scene` because the container
|
|
236
|
-
// does not record which it holds, so requiring every member would refuse a scene generator
|
|
237
|
-
// for declaring the only format it can emit.
|
|
238
|
-
if (consistent.length > 0 && !consistent.some((modality) => declared.has(modality))) {
|
|
239
|
-
const names = consistent.join('/');
|
|
240
|
-
invalid('binary_generator_modality_mismatch', `Generative binary integration "${definition.id}" declares media type "${mediaType}" ` +
|
|
241
|
-
`(${names}) but lists none of those among its modalities ` +
|
|
242
|
-
`(${definition.modalities.join(', ')}). A step selecting it for ${names} would be ` +
|
|
243
|
-
`refused, and one selecting it for the listed modalities would be told it can emit this.`);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
return problems;
|
|
247
|
-
}
|
|
248
175
|
/**
|
|
249
176
|
* Section 8 of {@link collectRegistrationProblems}: every foundational service a deployment
|
|
250
177
|
* registers in code must be a definition the platform would have accepted over its own write
|
|
@@ -435,284 +362,6 @@ function checkKindSkills(kind, registry) {
|
|
|
435
362
|
}
|
|
436
363
|
return problems;
|
|
437
364
|
}
|
|
438
|
-
/**
|
|
439
|
-
* A kind's declared TOOL SERVERS: the whole-list checks (unregistered ids, the per-dispatch budget,
|
|
440
|
-
* the container surface), with each definition's own checks in
|
|
441
|
-
* {@link checkToolServerDefinition}. "Declared for" includes assigned servers, see
|
|
442
|
-
* {@link checkAgentCapabilities}.
|
|
443
|
-
*
|
|
444
|
-
* - an unregistered id is an ERROR, like an unregistered skill;
|
|
445
|
-
* - past EITHER dimension of the per-dispatch budget is a WARNING, because the dispatch drops the
|
|
446
|
-
* excess under `over_budget` rather than failing: the run still works, with fewer tools than the
|
|
447
|
-
* deployment believes it wired, and boot is the only place the DECLARATIONS past the line can be
|
|
448
|
-
* named. A warning also keeps the accretion case honest, a kind going over budget through
|
|
449
|
-
* `assignToolServers` calls in several packages none of which is individually wrong. Both
|
|
450
|
-
* dimensions are checked because the dispatch enforces both, and a byte-driven drop is the one
|
|
451
|
-
* that surprises: a handful of servers with fat `env`/`args`/`headers` blocks is under the count
|
|
452
|
-
* and over the payload;
|
|
453
|
-
* - tool servers on a NON-container kind is a WARNING: an inline LLM call has no CLI to wire them
|
|
454
|
-
* into, so they can never take effect. A warning rather than an error because a deployment may
|
|
455
|
-
* deliberately declare them ahead of moving the kind onto a container surface.
|
|
456
|
-
*/
|
|
457
|
-
function checkKindToolServers(kind, registry) {
|
|
458
|
-
const problems = [];
|
|
459
|
-
const tools = registry.toolServersFor(kind);
|
|
460
|
-
for (const id of tools.unknown) {
|
|
461
|
-
problems.push({
|
|
462
|
-
severity: 'error',
|
|
463
|
-
code: 'unknown_tool_server',
|
|
464
|
-
message: `Agent kind "${kind}" declares tool server "${id}", which is not registered. Call ` +
|
|
465
|
-
`registry.registerToolServer({ id: '${id}', … }) before registering the kind, or ` +
|
|
466
|
-
`declare the server inline.`,
|
|
467
|
-
});
|
|
468
|
-
}
|
|
469
|
-
for (const server of tools.servers)
|
|
470
|
-
problems.push(...checkToolServerDefinition(kind, server));
|
|
471
|
-
problems.push(...checkToolServerBudget(kind, tools.servers));
|
|
472
|
-
if (tools.servers.length && !runsInContainer(kind, registry)) {
|
|
473
|
-
problems.push({
|
|
474
|
-
severity: 'warn',
|
|
475
|
-
code: 'tool_servers_without_container',
|
|
476
|
-
message: `Agent kind "${kind}" declares tool servers but does not run in a container — an ` +
|
|
477
|
-
`inline LLM step has no agent CLI to wire them into, so they will never be available. ` +
|
|
478
|
-
`Give the kind a container surface (agent.surface: 'container-explore' / ` +
|
|
479
|
-
`'container-coding') or drop the tool servers.`,
|
|
480
|
-
});
|
|
481
|
-
}
|
|
482
|
-
return problems;
|
|
483
|
-
}
|
|
484
|
-
/**
|
|
485
|
-
* The per-dispatch budget, BOTH dimensions, as warnings: the dispatch drops the excess under
|
|
486
|
-
* `over_budget` and the run works with fewer tools, so a registration fault belongs at boot rather
|
|
487
|
-
* than in a refusal that takes the deployment down.
|
|
488
|
-
*
|
|
489
|
-
* The byte check measures {@link toolServerDeclaredBytes}, which is a FLOOR (a resolved credential
|
|
490
|
-
* only adds to it), so a declaration already past the budget here is certainly past it at dispatch.
|
|
491
|
-
* What boot deliberately does NOT claim is WHICH servers a run will lose: the dispatch keeps every
|
|
492
|
-
* server that still fits, so once bytes are what bind the survivors are not a prefix of the
|
|
493
|
-
* declaration. The count and the budget are what the author acts on, and the run itself names each
|
|
494
|
-
* server it dropped.
|
|
495
|
-
*/
|
|
496
|
-
function checkToolServerBudget(kind, servers) {
|
|
497
|
-
const problems = [];
|
|
498
|
-
if (servers.length > TOOL_SERVER_BUDGET.maxServers) {
|
|
499
|
-
problems.push({
|
|
500
|
-
severity: 'warn',
|
|
501
|
-
code: 'too_many_tool_servers',
|
|
502
|
-
message: `Agent kind "${kind}" has ${servers.length} tool servers declared for it, past the ` +
|
|
503
|
-
`per-dispatch budget of ${TOOL_SERVER_BUDGET.maxServers}. A dispatch wires the first ` +
|
|
504
|
-
`${TOOL_SERVER_BUDGET.maxServers} in declaration order and states the rest to the agent as ` +
|
|
505
|
-
`unavailable (over_budget). Drop some, or split the work across kinds.`,
|
|
506
|
-
});
|
|
507
|
-
}
|
|
508
|
-
const bytes = servers.reduce((total, server) => total + toolServerDeclaredBytes(server), 0);
|
|
509
|
-
if (bytes > TOOL_SERVER_BUDGET.maxTotalBytes) {
|
|
510
|
-
problems.push({
|
|
511
|
-
severity: 'warn',
|
|
512
|
-
code: 'tool_servers_over_byte_budget',
|
|
513
|
-
message: `Agent kind "${kind}" declares tool servers whose transport config alone measures ${bytes} ` +
|
|
514
|
-
`bytes, past the per-dispatch budget of ${TOOL_SERVER_BUDGET.maxTotalBytes}. A dispatch ` +
|
|
515
|
-
`wires servers until that budget is spent and states the rest to the agent as unavailable ` +
|
|
516
|
-
`(over_budget); resolved credentials only add to this figure, and which servers lose out ` +
|
|
517
|
-
`depends on their sizes. Trim the env/args/headers blocks, or split the work across kinds.`,
|
|
518
|
-
});
|
|
519
|
-
}
|
|
520
|
-
return problems;
|
|
521
|
-
}
|
|
522
|
-
/**
|
|
523
|
-
* ONE tool server definition:
|
|
524
|
-
*
|
|
525
|
-
* - a malformed MCP server id is an ERROR, because it becomes both a tool-name fragment and a
|
|
526
|
-
* Codex TOML table key, so the CLI fails on it far from the registration that caused it;
|
|
527
|
-
* - an `allowedTools` entry that is not a valid tool NAME is an ERROR, the comma case above all:
|
|
528
|
-
* the harness joins the whole list into one `--allowedTools` argument with commas, so an entry
|
|
529
|
-
* carrying one splits into two patterns and the second matches nothing, while the prompt goes on
|
|
530
|
-
* advertising the name verbatim. That is the "told about a tool it cannot call" failure the
|
|
531
|
-
* unavailability vocabulary exists to prevent;
|
|
532
|
-
* - a definition NO harness could ever serve is a WARNING, and it is the only check here a run
|
|
533
|
-
* structurally cannot report: an `http` server narrowed to `harnesses: ['codex']` (whose client
|
|
534
|
-
* is stdio-only), or anything narrowed to `['pi']` (which has no MCP client), is never dropped
|
|
535
|
-
* FOR A REASON on any run. It simply never applies, so no prompt and no log line ever mentions
|
|
536
|
-
* it. A warning rather than an error because the declaration is inert rather than dangerous;
|
|
537
|
-
* - a cleartext `http://` endpoint off loopback is an ERROR: a resolved credential rides that
|
|
538
|
-
* request as a header, and the harness refuses the same URL at the job boundary — so allowing
|
|
539
|
-
* it here only moves the failure to a place with no registration to point at.
|
|
540
|
-
*/
|
|
541
|
-
function checkToolServerDefinition(kind, server) {
|
|
542
|
-
const problems = [];
|
|
543
|
-
const on = `(on agent kind "${kind}")`;
|
|
544
|
-
if (!isValidMcpServerId(server.id)) {
|
|
545
|
-
problems.push({
|
|
546
|
-
severity: 'error',
|
|
547
|
-
code: 'invalid_tool_server_id',
|
|
548
|
-
message: `Tool server "${server.id}" ${on} has an invalid id. It becomes part of the tool names ` +
|
|
549
|
-
`the CLI exposes (mcp__<id>__<tool>) and a Codex config key, so it must match ` +
|
|
550
|
-
`[a-z0-9][a-z0-9_-]*.`,
|
|
551
|
-
});
|
|
552
|
-
}
|
|
553
|
-
for (const tool of server.allowedTools ?? []) {
|
|
554
|
-
if (isValidMcpToolName(tool))
|
|
555
|
-
continue;
|
|
556
|
-
problems.push({
|
|
557
|
-
severity: 'error',
|
|
558
|
-
code: 'invalid_tool_server_tool_name',
|
|
559
|
-
message: `Tool server "${server.id}" ${on} restricts allowedTools to "${tool}", which is not a ` +
|
|
560
|
-
`single tool name (letters, digits, "_", "." and "-"). The harness joins the list into ` +
|
|
561
|
-
`one --allowedTools argument with commas, so an entry with a comma or whitespace becomes ` +
|
|
562
|
-
`patterns that match nothing while the prompt still advertises the name. List each tool ` +
|
|
563
|
-
`as its own entry.`,
|
|
564
|
-
});
|
|
565
|
-
}
|
|
566
|
-
const servable = mcpServableHarnesses(server);
|
|
567
|
-
if (servable.length === 0) {
|
|
568
|
-
problems.push({
|
|
569
|
-
severity: 'warn',
|
|
570
|
-
code: 'tool_server_unservable',
|
|
571
|
-
message: `Tool server "${server.id}" ${on} declares transport "${server.transport.kind}" for ` +
|
|
572
|
-
`harnesses [${(server.harnesses ?? MCP_SUPPORTED_HARNESSES).join(', ')}], and no harness ` +
|
|
573
|
-
`can serve that combination, so the server never applies to any run and no prompt or log ` +
|
|
574
|
-
`line will say why. Codex's MCP client is stdio-only and Pi has none at all. Widen the ` +
|
|
575
|
-
`harnesses, change the transport, or drop the declaration.`,
|
|
576
|
-
});
|
|
577
|
-
}
|
|
578
|
-
if (server.transport.kind === 'http' && !isAllowedMcpHttpUrl(server.transport.url)) {
|
|
579
|
-
problems.push({
|
|
580
|
-
severity: 'error',
|
|
581
|
-
code: 'insecure_tool_server_url',
|
|
582
|
-
message: `Tool server "${server.id}" ${on} has url "${server.transport.url}". An HTTP tool server ` +
|
|
583
|
-
`carries its resolved credential in a request header, so the url must be https (plain ` +
|
|
584
|
-
`http is accepted only on loopback).`,
|
|
585
|
-
});
|
|
586
|
-
}
|
|
587
|
-
for (const secret of server.secretKeys ?? []) {
|
|
588
|
-
problems.push(...checkToolServerSecret(kind, server, secret));
|
|
589
|
-
}
|
|
590
|
-
problems.push(...checkToolServerOAuth(kind, server));
|
|
591
|
-
return problems;
|
|
592
|
-
}
|
|
593
|
-
/**
|
|
594
|
-
* A tool server's OAUTH declaration, when it has one. Four rules, and each of them names a failure
|
|
595
|
-
* that is otherwise invisible until a run or a button press:
|
|
596
|
-
*
|
|
597
|
-
* - `oauth` on a `stdio` server is an ERROR. A stdio server is a child process the CLI spawns;
|
|
598
|
-
* there is no request to authorise, so the declaration is inert and reads as configured. The
|
|
599
|
-
* dispatch drops the OAuth half silently for the mothership case, which is exactly why boot is
|
|
600
|
-
* where the fault has to be named.
|
|
601
|
-
* - a declared endpoint that fails the URL floor is an ERROR, and this is the sharpest one here:
|
|
602
|
-
* an OAuth exchange carries the client secret and the tokens, so a cleartext endpoint puts both
|
|
603
|
-
* on the wire. A DISCOVERED endpoint is held to the same rule at the moment it is read.
|
|
604
|
-
* - a reserved `clientSecretKey` is an ERROR for the reason every capability credential is: the
|
|
605
|
-
* declaration names both the key it wants and the token endpoint that key is posted to.
|
|
606
|
-
* - a `secretKeys` entry naming the same header the access token rides is a WARNING. Both would
|
|
607
|
-
* land in one header map and the granted token wins, so the static credential silently does
|
|
608
|
-
* nothing — which reads, to whoever declared it, as the platform ignoring their credential.
|
|
609
|
-
*/
|
|
610
|
-
function checkToolServerOAuth(kind, server) {
|
|
611
|
-
const oauth = server.oauth;
|
|
612
|
-
if (!oauth)
|
|
613
|
-
return [];
|
|
614
|
-
const problems = [];
|
|
615
|
-
const on = `(on agent kind "${kind}")`;
|
|
616
|
-
if (server.transport.kind !== 'http') {
|
|
617
|
-
problems.push({
|
|
618
|
-
severity: 'error',
|
|
619
|
-
code: 'oauth_requires_http_transport',
|
|
620
|
-
message: `Tool server "${server.id}" ${on} declares OAuth on a "${server.transport.kind}" ` +
|
|
621
|
-
`transport. OAuth authenticates a REQUEST, and a stdio server is a child process the ` +
|
|
622
|
-
`agent CLI spawns with no request to authorise — pass its credential through secretKeys ` +
|
|
623
|
-
`instead.`,
|
|
624
|
-
});
|
|
625
|
-
}
|
|
626
|
-
for (const [field, url] of [
|
|
627
|
-
['authorizationUrl', oauth.authorizationUrl],
|
|
628
|
-
['tokenUrl', oauth.tokenUrl],
|
|
629
|
-
]) {
|
|
630
|
-
if (url === undefined || isAllowedMcpHttpUrl(url))
|
|
631
|
-
continue;
|
|
632
|
-
problems.push({
|
|
633
|
-
severity: 'error',
|
|
634
|
-
code: 'insecure_oauth_endpoint',
|
|
635
|
-
message: `Tool server "${server.id}" ${on} declares OAuth ${field} "${url}". The exchange carries ` +
|
|
636
|
-
`the client secret and the access token, so the endpoint must be https (plain http is ` +
|
|
637
|
-
`accepted only on loopback).`,
|
|
638
|
-
});
|
|
639
|
-
}
|
|
640
|
-
if (oauth.clientSecretKey !== undefined && isReservedPlatformEnvKey(oauth.clientSecretKey)) {
|
|
641
|
-
problems.push({
|
|
642
|
-
severity: 'error',
|
|
643
|
-
code: 'reserved_credential_key',
|
|
644
|
-
message: `Tool server "${server.id}" ${on} declares OAuth client secret ` +
|
|
645
|
-
reservedEnvKeyMessage(oauth.clientSecretKey),
|
|
646
|
-
});
|
|
647
|
-
}
|
|
648
|
-
const tokenHeader = (oauth.header ?? MCP_OAUTH_DEFAULT_HEADER).toLowerCase();
|
|
649
|
-
for (const secret of server.secretKeys ?? []) {
|
|
650
|
-
if (secret.header?.toLowerCase() !== tokenHeader)
|
|
651
|
-
continue;
|
|
652
|
-
problems.push({
|
|
653
|
-
severity: 'warn',
|
|
654
|
-
code: 'oauth_header_collision',
|
|
655
|
-
message: `Tool server "${server.id}" ${on} declares credential "${secret.key}" on header ` +
|
|
656
|
-
`"${secret.header}", which is also where its OAuth access token is sent. The granted ` +
|
|
657
|
-
`token wins, so that credential reaches the server as nothing at all — send it under a ` +
|
|
658
|
-
`different header, or drop it.`,
|
|
659
|
-
});
|
|
660
|
-
}
|
|
661
|
-
return problems;
|
|
662
|
-
}
|
|
663
|
-
/**
|
|
664
|
-
* ONE credential a tool server declares. The reserved-key check is the sharpest rule in this file:
|
|
665
|
-
* a definition names both the key it wants and the endpoint that key is sent to, so
|
|
666
|
-
* `{ key: 'ENCRYPTION_KEY', header: 'Authorization' }` is a registration that boots clean and ships
|
|
667
|
-
* the deployment's master sealing key to a third party. The generative-integration half of the same
|
|
668
|
-
* rule is enforced by its credential SCHEMA (there is no schema here — a tool server is a TypeScript
|
|
669
|
-
* registration), and dispatch refuses both again for the mothership case.
|
|
670
|
-
*/
|
|
671
|
-
function checkToolServerSecret(kind, server, secret) {
|
|
672
|
-
const problems = [];
|
|
673
|
-
const on = `(on agent kind "${kind}")`;
|
|
674
|
-
if (isReservedPlatformEnvKey(secret.key)) {
|
|
675
|
-
problems.push({
|
|
676
|
-
severity: 'error',
|
|
677
|
-
code: 'reserved_credential_key',
|
|
678
|
-
message: `Tool server "${server.id}" ${on} declares credential ${reservedEnvKeyMessage(secret.key)}`,
|
|
679
|
-
});
|
|
680
|
-
}
|
|
681
|
-
if (secret.envName === undefined)
|
|
682
|
-
return problems;
|
|
683
|
-
// The injection name is NOT held to the reserved floor (it reads nothing), so it carries its own
|
|
684
|
-
// rule: a value set as `PATH` or `npm_config_registry` reconfigures the server's process instead
|
|
685
|
-
// of authenticating a call. Dispatch drops one too, for the mothership case.
|
|
686
|
-
if (!isEnvVariableName(secret.envName)) {
|
|
687
|
-
problems.push({
|
|
688
|
-
severity: 'error',
|
|
689
|
-
code: 'invalid_credential_env_name',
|
|
690
|
-
message: `Tool server "${server.id}" ${on} declares credential envName "${secret.envName}", which ` +
|
|
691
|
-
`is not a valid environment variable name. It becomes a variable of the server's process, ` +
|
|
692
|
-
`and the harness drops anything else.`,
|
|
693
|
-
});
|
|
694
|
-
}
|
|
695
|
-
if (isToolchainEnvName(secret.envName)) {
|
|
696
|
-
problems.push({
|
|
697
|
-
severity: 'error',
|
|
698
|
-
code: 'toolchain_credential_env_name',
|
|
699
|
-
message: `Tool server "${server.id}" ${on} declares credential ${toolchainEnvNameMessage(secret.envName)}`,
|
|
700
|
-
});
|
|
701
|
-
}
|
|
702
|
-
// An `http` server sends its value as a HEADER, so an injection name would be read by nothing.
|
|
703
|
-
// A warning rather than an error: the declaration still works, it just says something that cannot
|
|
704
|
-
// take effect, and failing boot over it would be out of proportion.
|
|
705
|
-
if (server.transport.kind === 'http' && secret.header) {
|
|
706
|
-
problems.push({
|
|
707
|
-
severity: 'warn',
|
|
708
|
-
code: 'unused_credential_env_name',
|
|
709
|
-
message: `Tool server "${server.id}" ${on} declares credential envName "${secret.envName}" on a ` +
|
|
710
|
-
`key that names a header. An http server's value is sent as that header, so the injection ` +
|
|
711
|
-
`name is never used.`,
|
|
712
|
-
});
|
|
713
|
-
}
|
|
714
|
-
return problems;
|
|
715
|
-
}
|
|
716
365
|
/**
|
|
717
366
|
* Section 3 of {@link collectRegistrationProblems}: a coherence WARNING for a kind that declares
|
|
718
367
|
* postOps but whose agent step is not structured output — those post-ops read `result.custom` and
|