@a5c-ai/krate 5.0.1-staging.660d2b90f → 5.0.1-staging.69cb593ea
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/Dockerfile +31 -29
- package/bin/krate-demo.mjs +0 -0
- package/bin/krate-server.mjs +0 -0
- package/dist/krate-controller-ui.json +808 -10
- package/dist/krate-lifecycle.json +1 -1
- package/dist/krate-runtime-snapshot.json +223 -53
- package/dist/krate-summary.json +40 -3
- package/docs/agents/gaps-agent-mux-to-krate-crds.md +298 -0
- package/docs/architecture-v2.md +431 -0
- package/docs/openapi.yaml +1275 -0
- package/docs/requirements-v2.md +238 -0
- package/docs/sdk-api-reference.md +782 -0
- package/docs/system-spec-v2.md +352 -0
- package/docs/todos.md +4 -0
- package/docs/web-console-spec.md +433 -0
- package/package.json +1 -1
- package/scripts/validate-ui.mjs +305 -207
- package/src/agent-adapter-controller.js +169 -0
- package/src/agent-approval-controller.js +47 -0
- package/src/agent-dispatch-controller.js +130 -7
- package/src/agent-gateway-config-controller.js +147 -0
- package/src/agent-memory-controller.js +357 -0
- package/src/agent-memory-import.js +327 -0
- package/src/agent-memory-query.js +292 -0
- package/src/agent-memory-repository-source-controller.js +255 -0
- package/src/agent-mux-client.js +1 -1
- package/src/agent-permission-review.js +102 -14
- package/src/agent-project-controller.js +117 -0
- package/src/agent-provider-config-controller.js +150 -0
- package/src/agent-secret-config-grant-controller.js +282 -0
- package/src/agent-session-transcript-controller.js +189 -0
- package/src/agent-stack-controller.js +52 -1
- package/src/agent-subagent-controller.js +160 -0
- package/src/agent-transport-binding-controller.js +121 -0
- package/src/agent-trigger-controller.js +273 -0
- package/src/agent-workspace-controller.js +702 -0
- package/src/agent-writeback-controller.js +302 -0
- package/src/api-controller.js +338 -3
- package/src/async-controller.js +207 -0
- package/src/audit-controller.js +191 -0
- package/src/auth.js +48 -6
- package/src/controller-client.js +112 -38
- package/src/controller-ui.js +96 -16
- package/src/data-plane.js +3 -2
- package/src/event-bus.js +61 -0
- package/src/external/conflict-controller.js +225 -0
- package/src/external/github/auth.js +96 -0
- package/src/external/github/cicd.js +180 -0
- package/src/external/github/git-forge.js +240 -0
- package/src/external/github/index.js +144 -0
- package/src/external/github/issue-tracking.js +163 -0
- package/src/external/provider-adapter.js +161 -0
- package/src/external/provider-resource-factory.js +161 -0
- package/src/external/sync-controller.js +235 -0
- package/src/external/webhook-controller.js +144 -0
- package/src/external/write-controller.js +283 -0
- package/src/gitea-backend.js +36 -0
- package/src/gitea-service.js +173 -0
- package/src/http-server.js +226 -0
- package/src/index.js +27 -0
- package/src/kubernetes-controller-async.js +531 -0
- package/src/kubernetes-controller.js +156 -84
- package/src/notification-controller.js +178 -0
- package/src/org-scoping.js +5 -0
- package/src/resource-model.js +26 -8
- package/src/runner-controller.js +272 -0
- package/src/snapshot-cache.js +157 -0
- package/tests/agent-adapter-controller.test.js +361 -0
- package/tests/agent-dispatch-controller.test.js +139 -0
- package/tests/agent-gateway-config-controller.test.js +386 -0
- package/tests/agent-memory-controller.test.js +308 -0
- package/tests/agent-memory-import-snapshot.test.js +477 -0
- package/tests/agent-memory-query.test.js +404 -0
- package/tests/agent-memory-repository-source.test.js +514 -0
- package/tests/agent-permission-review-v2.test.js +317 -0
- package/tests/agent-project-controller.test.js +302 -0
- package/tests/agent-provider-config-controller.test.js +376 -0
- package/tests/agent-resources.test.js +35 -19
- package/tests/agent-secret-config-grant.test.js +231 -0
- package/tests/agent-session-transcript-controller.test.js +499 -0
- package/tests/agent-subagent-controller.test.js +201 -0
- package/tests/agent-transport-binding-controller.test.js +294 -0
- package/tests/agent-trigger-routes.test.js +190 -0
- package/tests/agent-trigger-sources.test.js +245 -0
- package/tests/agent-workspace-controller.test.js +181 -0
- package/tests/agent-writeback.test.js +292 -0
- package/tests/approval-persistence.test.js +171 -0
- package/tests/async-controller.test.js +252 -0
- package/tests/audit-controller.test.js +227 -0
- package/tests/codespace-controller.test.js +318 -0
- package/tests/controller-client.test.js +133 -0
- package/tests/deployment.test.js +43 -29
- package/tests/e2e/lifecycle.test.js +5 -2
- package/tests/event-bus-integration.test.js +190 -0
- package/tests/external-github-forge.test.js +560 -0
- package/tests/external-github-issues-cicd.test.js +520 -0
- package/tests/external-integration.test.js +470 -0
- package/tests/external-persistence.test.js +340 -0
- package/tests/external-provider-adapter.test.js +365 -0
- package/tests/external-resource-model.test.js +215 -0
- package/tests/external-webhook-sync.test.js +287 -0
- package/tests/external-write-conflict.test.js +353 -0
- package/tests/gitea-service.test.js +253 -0
- package/tests/health-check-real.test.js +165 -0
- package/tests/integration/full-flow.test.js +266 -0
- package/tests/krate.test.js +58 -6
- package/tests/memory-search-wiring.test.js +270 -0
- package/tests/notification-controller.test.js +196 -0
- package/tests/notification-integration.test.js +179 -0
- package/tests/org-scoping.test.js +687 -0
- package/tests/runner-controller.test.js +327 -0
- package/tests/runner-integration.test.js +231 -0
- package/tests/session-cookie-hmac.test.js +151 -0
- package/tests/snapshot-performance.test.js +315 -0
- package/tests/sse-events.test.js +107 -0
- package/tests/webhook-trigger.test.js +198 -0
- package/tests/workspace-volumes.test.js +312 -0
- package/tests/writeback-persistence.test.js +207 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import {
|
|
4
|
+
createAgentProviderConfigController,
|
|
5
|
+
validateAgentProviderConfig,
|
|
6
|
+
createResource,
|
|
7
|
+
AGENT_PROVIDER_CONFIG_CONTROLLER_BOUNDARY
|
|
8
|
+
} from '../src/index.js';
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Acceptance criteria: Slice 1.2c — Agent Provider Config Controller
|
|
12
|
+
//
|
|
13
|
+
// An AgentProviderConfig defines how to connect to an LLM model provider.
|
|
14
|
+
// It specifies provider name/type, API endpoint, credential secret reference,
|
|
15
|
+
// default model, rate limit configuration, and feature flags.
|
|
16
|
+
//
|
|
17
|
+
// Supported provider types: anthropic, openai, azure-openai, google-vertex,
|
|
18
|
+
// foundry, custom.
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
const VALID_PROVIDER_TYPES = ['anthropic', 'openai', 'azure-openai', 'google-vertex', 'foundry', 'custom'];
|
|
22
|
+
|
|
23
|
+
function makeProviderConfig(name, overrides = {}) {
|
|
24
|
+
return createResource('AgentProviderConfig', { name, namespace: 'krate-org-default' }, {
|
|
25
|
+
organizationRef: 'default',
|
|
26
|
+
providerType: 'anthropic',
|
|
27
|
+
endpoint: 'https://api.anthropic.com/v1',
|
|
28
|
+
credentialRef: 'anthropic-api-key',
|
|
29
|
+
defaultModel: 'claude-opus-4-5',
|
|
30
|
+
...overrides
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// 1. Factory and shape
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
test('createAgentProviderConfigController returns controller with validate, resolveEndpoint, getFeatureFlags', () => {
|
|
39
|
+
const controller = createAgentProviderConfigController();
|
|
40
|
+
assert.ok(controller, 'controller must be truthy');
|
|
41
|
+
assert.equal(typeof controller.validate, 'function', 'controller must expose validate');
|
|
42
|
+
assert.equal(typeof controller.resolveEndpoint, 'function', 'controller must expose resolveEndpoint');
|
|
43
|
+
assert.equal(typeof controller.getFeatureFlags, 'function', 'controller must expose getFeatureFlags');
|
|
44
|
+
assert.equal(controller.role, 'agent-provider-config-controller', 'controller must declare its role');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// 2. validate — happy path
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
test('validate accepts valid config with name, providerType, endpoint, credentialRef', () => {
|
|
52
|
+
const controller = createAgentProviderConfigController();
|
|
53
|
+
const config = makeProviderConfig('anthropic-provider');
|
|
54
|
+
const result = controller.validate(config);
|
|
55
|
+
|
|
56
|
+
assert.equal(result.valid, true, 'valid config must pass validation');
|
|
57
|
+
assert.ok(Array.isArray(result.errors), 'result must contain an errors array');
|
|
58
|
+
assert.equal(result.errors.length, 0, 'errors array must be empty for a valid config');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// 3. validate — missing name
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
test('validate rejects config with missing name', () => {
|
|
66
|
+
const controller = createAgentProviderConfigController();
|
|
67
|
+
const config = {
|
|
68
|
+
apiVersion: 'krate.a5c.ai/v1alpha1',
|
|
69
|
+
kind: 'AgentProviderConfig',
|
|
70
|
+
metadata: { namespace: 'krate-org-default', labels: {}, annotations: {} },
|
|
71
|
+
spec: {
|
|
72
|
+
providerType: 'anthropic',
|
|
73
|
+
endpoint: 'https://api.anthropic.com/v1',
|
|
74
|
+
credentialRef: 'anthropic-api-key'
|
|
75
|
+
},
|
|
76
|
+
status: {}
|
|
77
|
+
};
|
|
78
|
+
const result = controller.validate(config);
|
|
79
|
+
|
|
80
|
+
assert.equal(result.valid, false, 'config without a name must fail validation');
|
|
81
|
+
assert.ok(result.errors.length > 0, 'errors array must not be empty');
|
|
82
|
+
assert.ok(
|
|
83
|
+
result.errors.some((e) => /name/i.test(e)),
|
|
84
|
+
'at least one error must mention "name"'
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// 4. validate — missing providerType
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
test('validate rejects config with missing providerType', () => {
|
|
93
|
+
const controller = createAgentProviderConfigController();
|
|
94
|
+
const config = makeProviderConfig('no-type-provider');
|
|
95
|
+
delete config.spec.providerType;
|
|
96
|
+
const result = controller.validate(config);
|
|
97
|
+
|
|
98
|
+
assert.equal(result.valid, false, 'config without providerType must fail validation');
|
|
99
|
+
assert.ok(result.errors.length > 0, 'errors array must not be empty');
|
|
100
|
+
assert.ok(
|
|
101
|
+
result.errors.some((e) => /providerType/i.test(e)),
|
|
102
|
+
'at least one error must mention "providerType"'
|
|
103
|
+
);
|
|
104
|
+
assert.ok(
|
|
105
|
+
result.errors.some((e) => VALID_PROVIDER_TYPES.some((t) => e.includes(t))),
|
|
106
|
+
'error must enumerate valid provider types'
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
// 5. validate — invalid providerType
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
|
|
114
|
+
test('validate rejects config with invalid providerType', () => {
|
|
115
|
+
const controller = createAgentProviderConfigController();
|
|
116
|
+
const config = makeProviderConfig('bad-type-provider', { providerType: 'cohere' });
|
|
117
|
+
const result = controller.validate(config);
|
|
118
|
+
|
|
119
|
+
assert.equal(result.valid, false, 'config with unsupported providerType must fail validation');
|
|
120
|
+
assert.ok(result.errors.length > 0, 'errors array must not be empty');
|
|
121
|
+
assert.ok(
|
|
122
|
+
result.errors.some((e) => /providerType/i.test(e)),
|
|
123
|
+
'at least one error must mention "providerType"'
|
|
124
|
+
);
|
|
125
|
+
assert.ok(
|
|
126
|
+
result.errors.some((e) => VALID_PROVIDER_TYPES.some((t) => e.includes(t))),
|
|
127
|
+
'error must enumerate valid provider types'
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
// 6. validate — missing credentialRef (security: always require)
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
|
|
135
|
+
test('validate rejects config with missing credentialRef', () => {
|
|
136
|
+
const controller = createAgentProviderConfigController();
|
|
137
|
+
const config = makeProviderConfig('no-cred-provider');
|
|
138
|
+
delete config.spec.credentialRef;
|
|
139
|
+
const result = controller.validate(config);
|
|
140
|
+
|
|
141
|
+
assert.equal(result.valid, false, 'config without credentialRef must fail validation');
|
|
142
|
+
assert.ok(result.errors.length > 0, 'errors array must not be empty');
|
|
143
|
+
assert.ok(
|
|
144
|
+
result.errors.some((e) => /credentialRef/i.test(e)),
|
|
145
|
+
'at least one error must mention "credentialRef"'
|
|
146
|
+
);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
// 7. resolveEndpoint — returns endpoint from spec
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
|
|
153
|
+
test('resolveEndpoint returns endpoint from spec when explicitly set', () => {
|
|
154
|
+
const controller = createAgentProviderConfigController();
|
|
155
|
+
const config = makeProviderConfig('explicit-endpoint-provider', {
|
|
156
|
+
providerType: 'anthropic',
|
|
157
|
+
endpoint: 'https://custom.anthropic.proxy/v1'
|
|
158
|
+
});
|
|
159
|
+
const endpoint = controller.resolveEndpoint(config);
|
|
160
|
+
|
|
161
|
+
assert.equal(endpoint, 'https://custom.anthropic.proxy/v1', 'must return explicit spec endpoint');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// ---------------------------------------------------------------------------
|
|
165
|
+
// 8. resolveEndpoint — defaults for known providers
|
|
166
|
+
// ---------------------------------------------------------------------------
|
|
167
|
+
|
|
168
|
+
test('resolveEndpoint returns default endpoint for anthropic when no endpoint in spec', () => {
|
|
169
|
+
const controller = createAgentProviderConfigController();
|
|
170
|
+
const config = makeProviderConfig('anthropic-default-provider', { providerType: 'anthropic' });
|
|
171
|
+
delete config.spec.endpoint;
|
|
172
|
+
const endpoint = controller.resolveEndpoint(config);
|
|
173
|
+
|
|
174
|
+
assert.ok(endpoint.includes('api.anthropic.com'), 'must return anthropic default endpoint');
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('resolveEndpoint returns default endpoint for openai when no endpoint in spec', () => {
|
|
178
|
+
const controller = createAgentProviderConfigController();
|
|
179
|
+
const config = makeProviderConfig('openai-default-provider', {
|
|
180
|
+
providerType: 'openai',
|
|
181
|
+
credentialRef: 'openai-api-key'
|
|
182
|
+
});
|
|
183
|
+
delete config.spec.endpoint;
|
|
184
|
+
const endpoint = controller.resolveEndpoint(config);
|
|
185
|
+
|
|
186
|
+
assert.ok(endpoint.includes('api.openai.com'), 'must return openai default endpoint');
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// ---------------------------------------------------------------------------
|
|
190
|
+
// 9. getFeatureFlags — returns flags from spec with defaults
|
|
191
|
+
// ---------------------------------------------------------------------------
|
|
192
|
+
|
|
193
|
+
test('getFeatureFlags returns feature flags from spec with defaults applied', () => {
|
|
194
|
+
const controller = createAgentProviderConfigController();
|
|
195
|
+
const config = makeProviderConfig('flags-provider', {
|
|
196
|
+
featureFlags: { streaming: true, vision: false }
|
|
197
|
+
});
|
|
198
|
+
const flags = controller.getFeatureFlags(config);
|
|
199
|
+
|
|
200
|
+
assert.ok(flags, 'getFeatureFlags must return a value');
|
|
201
|
+
assert.equal(typeof flags, 'object', 'flags must be an object');
|
|
202
|
+
assert.equal(flags.streaming, true, 'streaming flag must be set from spec');
|
|
203
|
+
assert.equal(flags.vision, false, 'vision flag must be set from spec');
|
|
204
|
+
// tool_use should have a default
|
|
205
|
+
assert.ok('tool_use' in flags, 'tool_use flag must have a default');
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test('getFeatureFlags returns all defaults when no featureFlags in spec', () => {
|
|
209
|
+
const controller = createAgentProviderConfigController();
|
|
210
|
+
const config = makeProviderConfig('no-flags-provider');
|
|
211
|
+
const flags = controller.getFeatureFlags(config);
|
|
212
|
+
|
|
213
|
+
assert.ok(flags, 'getFeatureFlags must return a value even with no spec flags');
|
|
214
|
+
assert.ok('streaming' in flags, 'streaming must have a default');
|
|
215
|
+
assert.ok('tool_use' in flags, 'tool_use must have a default');
|
|
216
|
+
assert.ok('vision' in flags, 'vision must have a default');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// ---------------------------------------------------------------------------
|
|
220
|
+
// 10. getRateLimits — returns rate limit config with defaults
|
|
221
|
+
// ---------------------------------------------------------------------------
|
|
222
|
+
|
|
223
|
+
test('getRateLimits returns rate limit config with defaults', () => {
|
|
224
|
+
const controller = createAgentProviderConfigController();
|
|
225
|
+
const config = makeProviderConfig('rate-limit-provider', {
|
|
226
|
+
rateLimits: { requestsPerMinute: 60 }
|
|
227
|
+
});
|
|
228
|
+
const limits = controller.getRateLimits(config);
|
|
229
|
+
|
|
230
|
+
assert.ok(limits, 'getRateLimits must return a value');
|
|
231
|
+
assert.equal(limits.requestsPerMinute, 60, 'must use spec requestsPerMinute');
|
|
232
|
+
assert.ok(Number.isFinite(limits.tokensPerMinute), 'tokensPerMinute must have a numeric default');
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test('getRateLimits returns all defaults when no rateLimits in spec', () => {
|
|
236
|
+
const controller = createAgentProviderConfigController();
|
|
237
|
+
const config = makeProviderConfig('no-limits-provider');
|
|
238
|
+
const limits = controller.getRateLimits(config);
|
|
239
|
+
|
|
240
|
+
assert.ok(limits, 'getRateLimits must return a value');
|
|
241
|
+
assert.ok(Number.isFinite(limits.requestsPerMinute), 'requestsPerMinute must have a numeric default');
|
|
242
|
+
assert.ok(Number.isFinite(limits.tokensPerMinute), 'tokensPerMinute must have a numeric default');
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// ---------------------------------------------------------------------------
|
|
246
|
+
// 11. validate — rejects null resource
|
|
247
|
+
// ---------------------------------------------------------------------------
|
|
248
|
+
|
|
249
|
+
test('validate rejects null resource with a clear error', () => {
|
|
250
|
+
const controller = createAgentProviderConfigController();
|
|
251
|
+
const result = controller.validate(null);
|
|
252
|
+
|
|
253
|
+
assert.equal(result.valid, false, 'null resource must fail validation');
|
|
254
|
+
assert.ok(result.errors.length > 0, 'errors array must not be empty');
|
|
255
|
+
assert.ok(
|
|
256
|
+
result.errors.some((e) => /null|undefined/i.test(e)),
|
|
257
|
+
'error must mention null or undefined'
|
|
258
|
+
);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// ---------------------------------------------------------------------------
|
|
262
|
+
// 12. BOUNDARY — exported with correct role
|
|
263
|
+
// ---------------------------------------------------------------------------
|
|
264
|
+
|
|
265
|
+
test('AGENT_PROVIDER_CONFIG_CONTROLLER_BOUNDARY is exported with correct role', () => {
|
|
266
|
+
assert.ok(AGENT_PROVIDER_CONFIG_CONTROLLER_BOUNDARY, 'BOUNDARY must be exported');
|
|
267
|
+
assert.equal(
|
|
268
|
+
AGENT_PROVIDER_CONFIG_CONTROLLER_BOUNDARY.role,
|
|
269
|
+
'agent-provider-config-controller',
|
|
270
|
+
'BOUNDARY role must be "agent-provider-config-controller"'
|
|
271
|
+
);
|
|
272
|
+
assert.ok(
|
|
273
|
+
Array.isArray(AGENT_PROVIDER_CONFIG_CONTROLLER_BOUNDARY.owns),
|
|
274
|
+
'BOUNDARY must declare owned concerns'
|
|
275
|
+
);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// ---------------------------------------------------------------------------
|
|
279
|
+
// 13. getSupportedProviderTypes — returns valid provider types array
|
|
280
|
+
// ---------------------------------------------------------------------------
|
|
281
|
+
|
|
282
|
+
test('getSupportedProviderTypes returns valid provider types array', () => {
|
|
283
|
+
const controller = createAgentProviderConfigController();
|
|
284
|
+
const types = controller.getSupportedProviderTypes();
|
|
285
|
+
|
|
286
|
+
assert.ok(Array.isArray(types), 'getSupportedProviderTypes must return an array');
|
|
287
|
+
assert.ok(types.length >= 6, 'must return at least 6 provider types');
|
|
288
|
+
assert.ok(types.includes('anthropic'), 'must include anthropic');
|
|
289
|
+
assert.ok(types.includes('openai'), 'must include openai');
|
|
290
|
+
assert.ok(types.includes('azure-openai'), 'must include azure-openai');
|
|
291
|
+
assert.ok(types.includes('google-vertex'), 'must include google-vertex');
|
|
292
|
+
assert.ok(types.includes('foundry'), 'must include foundry');
|
|
293
|
+
assert.ok(types.includes('custom'), 'must include custom');
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
// ---------------------------------------------------------------------------
|
|
297
|
+
// 14. validateAgentProviderConfig — standalone export follows existing pattern
|
|
298
|
+
// ---------------------------------------------------------------------------
|
|
299
|
+
|
|
300
|
+
test('validateAgentProviderConfig standalone export follows existing pattern', () => {
|
|
301
|
+
assert.equal(typeof validateAgentProviderConfig, 'function', 'validateAgentProviderConfig must be a named export');
|
|
302
|
+
|
|
303
|
+
const config = makeProviderConfig('standalone-validate-provider');
|
|
304
|
+
const result = validateAgentProviderConfig(config);
|
|
305
|
+
|
|
306
|
+
assert.ok(result, 'validateAgentProviderConfig must return a result');
|
|
307
|
+
assert.ok('valid' in result, 'result must have a valid property');
|
|
308
|
+
assert.ok(Array.isArray(result.errors), 'result must have an errors array');
|
|
309
|
+
assert.equal(result.valid, true, 'a fully-specified config must pass standalone validation');
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
// ---------------------------------------------------------------------------
|
|
313
|
+
// 15. validate — all valid provider types are accepted
|
|
314
|
+
// ---------------------------------------------------------------------------
|
|
315
|
+
|
|
316
|
+
test('validate accepts all valid provider types', () => {
|
|
317
|
+
const controller = createAgentProviderConfigController();
|
|
318
|
+
for (const providerType of VALID_PROVIDER_TYPES) {
|
|
319
|
+
const config = makeProviderConfig(`${providerType}-provider`, { providerType });
|
|
320
|
+
const result = controller.validate(config);
|
|
321
|
+
assert.equal(result.valid, true, `providerType "${providerType}" must pass validation`);
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// ---------------------------------------------------------------------------
|
|
326
|
+
// 16. validate — accumulates multiple errors
|
|
327
|
+
// ---------------------------------------------------------------------------
|
|
328
|
+
|
|
329
|
+
test('validate accumulates all errors when multiple fields are invalid', () => {
|
|
330
|
+
const controller = createAgentProviderConfigController();
|
|
331
|
+
const config = {
|
|
332
|
+
apiVersion: 'krate.a5c.ai/v1alpha1',
|
|
333
|
+
kind: 'AgentProviderConfig',
|
|
334
|
+
metadata: { namespace: 'krate-org-default', labels: {}, annotations: {} },
|
|
335
|
+
spec: { providerType: 'bad-provider' },
|
|
336
|
+
status: {}
|
|
337
|
+
};
|
|
338
|
+
const result = controller.validate(config);
|
|
339
|
+
|
|
340
|
+
assert.equal(result.valid, false, 'config with multiple invalid fields must fail');
|
|
341
|
+
assert.ok(result.errors.length >= 2, 'must accumulate at least two errors');
|
|
342
|
+
assert.ok(
|
|
343
|
+
result.errors.some((e) => /name/i.test(e)),
|
|
344
|
+
'errors must include a name error'
|
|
345
|
+
);
|
|
346
|
+
assert.ok(
|
|
347
|
+
result.errors.some((e) => /providerType/i.test(e)),
|
|
348
|
+
'errors must include a providerType error'
|
|
349
|
+
);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
// ---------------------------------------------------------------------------
|
|
353
|
+
// 17. resolveEndpoint — throws on null resource
|
|
354
|
+
// ---------------------------------------------------------------------------
|
|
355
|
+
|
|
356
|
+
test('resolveEndpoint throws on null resource', () => {
|
|
357
|
+
const controller = createAgentProviderConfigController();
|
|
358
|
+
assert.throws(
|
|
359
|
+
() => controller.resolveEndpoint(null),
|
|
360
|
+
/null|undefined/i,
|
|
361
|
+
'resolveEndpoint must throw on null resource'
|
|
362
|
+
);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
// ---------------------------------------------------------------------------
|
|
366
|
+
// 18. getFeatureFlags — throws on null resource
|
|
367
|
+
// ---------------------------------------------------------------------------
|
|
368
|
+
|
|
369
|
+
test('getFeatureFlags throws on null resource', () => {
|
|
370
|
+
const controller = createAgentProviderConfigController();
|
|
371
|
+
assert.throws(
|
|
372
|
+
() => controller.getFeatureFlags(null),
|
|
373
|
+
/null|undefined/i,
|
|
374
|
+
'getFeatureFlags must throw on null resource'
|
|
375
|
+
);
|
|
376
|
+
});
|
|
@@ -20,7 +20,7 @@ const AGENT_CONFIG_KINDS = [
|
|
|
20
20
|
'AgentSkill',
|
|
21
21
|
'AgentTriggerRule',
|
|
22
22
|
'AgentContextLabel',
|
|
23
|
-
'
|
|
23
|
+
'KrateWorkspacePolicy',
|
|
24
24
|
'AgentServiceAccount',
|
|
25
25
|
'AgentRoleBinding',
|
|
26
26
|
'AgentSecretGrant',
|
|
@@ -28,8 +28,13 @@ const AGENT_CONFIG_KINDS = [
|
|
|
28
28
|
'AgentAdapter',
|
|
29
29
|
'AgentTransportBinding',
|
|
30
30
|
'AgentProviderConfig',
|
|
31
|
-
'
|
|
32
|
-
'AgentGatewayConfig'
|
|
31
|
+
'KrateProject',
|
|
32
|
+
'AgentGatewayConfig',
|
|
33
|
+
'AgentMemoryRepository',
|
|
34
|
+
'AgentMemorySource',
|
|
35
|
+
'AgentMemoryOntology',
|
|
36
|
+
'AgentMemoryAssociation',
|
|
37
|
+
'KrateWorkspace'
|
|
33
38
|
];
|
|
34
39
|
|
|
35
40
|
const AGENT_AGGREGATED_KINDS = [
|
|
@@ -37,16 +42,19 @@ const AGENT_AGGREGATED_KINDS = [
|
|
|
37
42
|
'AgentDispatchAttempt',
|
|
38
43
|
'AgentSession',
|
|
39
44
|
'AgentContextBundle',
|
|
40
|
-
'
|
|
45
|
+
'KrateArtifact',
|
|
41
46
|
'AgentApproval',
|
|
42
|
-
'AgentWorkspace',
|
|
43
47
|
'AgentTriggerExecution',
|
|
44
48
|
'AgentCapabilityRequirement',
|
|
45
49
|
'WorkItemSessionLink',
|
|
46
50
|
'WorkItemWorkspaceLink',
|
|
47
51
|
'AgentSessionTranscript',
|
|
48
52
|
'AgentSessionAttachment',
|
|
49
|
-
'
|
|
53
|
+
'KrateWorkspaceRuntime',
|
|
54
|
+
'AgentMemorySnapshot',
|
|
55
|
+
'AgentMemoryQuery',
|
|
56
|
+
'AgentMemoryUpdate',
|
|
57
|
+
'AgentRunMemoryImport'
|
|
50
58
|
];
|
|
51
59
|
|
|
52
60
|
const ALL_AGENT_KINDS = [...AGENT_CONFIG_KINDS, ...AGENT_AGGREGATED_KINDS];
|
|
@@ -61,7 +69,7 @@ function minimalSpecForKind(kind) {
|
|
|
61
69
|
AgentSkill: { organizationRef: 'default', format: 'markdown', sourceRef: 'skills/debug.md' },
|
|
62
70
|
AgentTriggerRule: { organizationRef: 'default', sources: ['ci-failure'], agentStack: 'default-stack', taskKind: 'fix' },
|
|
63
71
|
AgentContextLabel: { organizationRef: 'default', promptFragment: 'Always run tests before committing', allowedSources: ['admin'] },
|
|
64
|
-
|
|
72
|
+
KrateWorkspacePolicy: { organizationRef: 'default', mode: 'worktree', retentionPolicy: '7d' },
|
|
65
73
|
AgentServiceAccount: { organizationRef: 'default', namespace: 'krate-agents', serviceAccountName: 'agent-runner' },
|
|
66
74
|
AgentRoleBinding: { organizationRef: 'default', subject: 'agent-runner', roleRef: 'agent-role', scope: 'namespace' },
|
|
67
75
|
AgentSecretGrant: { organizationRef: 'default', subject: 'agent-runner', secretRef: 'api-keys', purpose: 'API access' },
|
|
@@ -70,9 +78,9 @@ function minimalSpecForKind(kind) {
|
|
|
70
78
|
AgentDispatchAttempt: { organizationRef: 'default', agentDispatchRun: 'run-1', attemptReason: 'initial', agentStackSnapshot: { baseAgent: 'claude-code' } },
|
|
71
79
|
AgentSession: { organizationRef: 'default', agentMuxSessionId: 'sess-123', dispatchRun: 'run-1' },
|
|
72
80
|
AgentContextBundle: { organizationRef: 'default', dispatchRun: 'run-1', digest: 'sha256:abc', sources: ['repo-context'] },
|
|
73
|
-
|
|
81
|
+
KrateArtifact: { organizationRef: 'default', dispatchRun: 'run-1', kind: 'patch', digest: 'sha256:def' },
|
|
74
82
|
AgentApproval: { organizationRef: 'default', dispatchRun: 'run-1', action: 'write-back', requestedBy: 'agent-runner' },
|
|
75
|
-
|
|
83
|
+
KrateWorkspace: { organizationRef: 'default', repository: 'app', volumeSpec: { storageClassName: 'standard', capacity: '10Gi', accessModes: ['ReadWriteOnce'] } },
|
|
76
84
|
AgentTriggerExecution: { organizationRef: 'default', triggerRule: 'on-ci-fail', sourceEvent: 'pipeline-failed', decision: 'dispatch' },
|
|
77
85
|
AgentCapabilityRequirement: { organizationRef: 'default', ownerRef: 'stack-1', requiredRoles: ['shell', 'git'] },
|
|
78
86
|
WorkItemSessionLink: { organizationRef: 'default', workItemRef: 'issue-1', agentSession: 'sess-123' },
|
|
@@ -80,11 +88,19 @@ function minimalSpecForKind(kind) {
|
|
|
80
88
|
AgentAdapter: { organizationRef: 'default', adapterType: 'claude-code', transport: 'stdio' },
|
|
81
89
|
AgentTransportBinding: { organizationRef: 'default', adapterRef: 'claude-code', endpoint: 'https://agent.example.test', protocol: 'https' },
|
|
82
90
|
AgentProviderConfig: { organizationRef: 'default', provider: 'anthropic', authType: 'api-key' },
|
|
83
|
-
|
|
91
|
+
KrateProject: { organizationRef: 'default', displayName: 'Platform' },
|
|
84
92
|
AgentGatewayConfig: { organizationRef: 'default', gatewayUrl: 'https://mux.example.test' },
|
|
85
93
|
AgentSessionTranscript: { organizationRef: 'default', sessionRef: 'sess-123', messages: [{ role: 'user', content: 'hello' }] },
|
|
86
94
|
AgentSessionAttachment: { organizationRef: 'default', sessionRef: 'sess-123', sourceType: 'upload', digest: 'sha256:abc' },
|
|
87
|
-
|
|
95
|
+
KrateWorkspaceRuntime: { organizationRef: 'default', workspaceRef: 'ws-1', status: 'running' },
|
|
96
|
+
AgentMemoryRepository: { organizationRef: 'default', repositoryRef: 'memory-repo', defaultBranch: 'main', layoutProfile: 'standard' },
|
|
97
|
+
AgentMemorySource: { organizationRef: 'default', repositoryRef: 'memory-repo', appliesTo: 'team:platform', include: ['decisions/**', 'runbooks/**'] },
|
|
98
|
+
AgentMemoryOntology: { organizationRef: 'default', memoryRepository: 'memory-repo', ontologyPath: '.memory/ontology.yaml' },
|
|
99
|
+
AgentMemoryAssociation: { organizationRef: 'default', memoryRef: 'decision-001', targetRef: 'issue-42', relationship: 'informs' },
|
|
100
|
+
AgentMemorySnapshot: { organizationRef: 'default', memoryRepository: 'memory-repo', requestedRef: 'refs/heads/main', resolvedCommit: 'a'.repeat(40) },
|
|
101
|
+
AgentMemoryQuery: { organizationRef: 'default', snapshotRef: 'snap-1', requester: 'agent-runner', query: 'deployment patterns' },
|
|
102
|
+
AgentMemoryUpdate: { organizationRef: 'default', memoryRepository: 'memory-repo', sourceRun: 'run-1', changes: [{ path: 'decisions/001.md', op: 'add' }] },
|
|
103
|
+
AgentRunMemoryImport: { organizationRef: 'default', memoryRepository: 'memory-repo', source: 'babysitter-run-42', include: ['journal', 'effects'] }
|
|
88
104
|
};
|
|
89
105
|
return specs[kind];
|
|
90
106
|
}
|
|
@@ -194,19 +210,19 @@ describe('storageClassForKind for agent kinds', () => {
|
|
|
194
210
|
});
|
|
195
211
|
|
|
196
212
|
describe('kind set counts', () => {
|
|
197
|
-
it('CONFIG_KINDS has
|
|
198
|
-
assert.equal(CONFIG_KINDS.size,
|
|
213
|
+
it('CONFIG_KINDS has 46 members', () => {
|
|
214
|
+
assert.equal(CONFIG_KINDS.size, 46);
|
|
199
215
|
});
|
|
200
216
|
|
|
201
|
-
it('AGGREGATED_KINDS has
|
|
202
|
-
assert.equal(AGGREGATED_KINDS.size,
|
|
217
|
+
it('AGGREGATED_KINDS has 29 members', () => {
|
|
218
|
+
assert.equal(AGGREGATED_KINDS.size, 29);
|
|
203
219
|
});
|
|
204
220
|
|
|
205
|
-
it('ALL_KINDS has
|
|
206
|
-
assert.equal(ALL_KINDS.size,
|
|
221
|
+
it('ALL_KINDS has 75 members', () => {
|
|
222
|
+
assert.equal(ALL_KINDS.size, 75);
|
|
207
223
|
});
|
|
208
224
|
|
|
209
|
-
it('listResourceDefinitions returns
|
|
210
|
-
assert.equal(listResourceDefinitions().length,
|
|
225
|
+
it('listResourceDefinitions returns 75 definitions', () => {
|
|
226
|
+
assert.equal(listResourceDefinitions().length, 75);
|
|
211
227
|
});
|
|
212
228
|
});
|