@a5c-ai/krate 5.0.1-staging.00fa5317c
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 -0
- package/README.md +183 -0
- package/bin/krate-demo.mjs +23 -0
- package/bin/krate-server.mjs +14 -0
- package/dist/krate-controller-ui.json +3205 -0
- package/dist/krate-lifecycle.json +201 -0
- package/dist/krate-runtime-snapshot.json +3125 -0
- package/dist/krate-summary.json +724 -0
- package/docs/README.md +61 -0
- package/docs/agents/README.md +83 -0
- package/docs/agents/acceptance-test-matrix.md +193 -0
- package/docs/agents/agent-mux-adapter-contract.md +167 -0
- package/docs/agents/agent-mux-source-map.md +310 -0
- package/docs/agents/agent-run-memory-import-spec.md +256 -0
- package/docs/agents/agent-stack-management-spec.md +421 -0
- package/docs/agents/api-contract-spec.md +309 -0
- package/docs/agents/artifacts-writeback-spec.md +145 -0
- package/docs/agents/chart-packaging-spec.md +128 -0
- package/docs/agents/ci-orchestration-spec.md +140 -0
- package/docs/agents/context-assembly-spec.md +219 -0
- package/docs/agents/controller-reconciliation-spec.md +255 -0
- package/docs/agents/crd-schema-spec.md +315 -0
- package/docs/agents/decision-log-open-questions.md +169 -0
- package/docs/agents/developer-implementation-checklist.md +329 -0
- package/docs/agents/dispatching-design.md +262 -0
- package/docs/agents/gaps-agent-mux-to-krate-crds.md +298 -0
- package/docs/agents/glossary.md +66 -0
- package/docs/agents/implementation-blueprint.md +324 -0
- package/docs/agents/implementation-rollout-slices.md +251 -0
- package/docs/agents/memory-context-integration-spec.md +194 -0
- package/docs/agents/memory-ontology-schema-spec.md +253 -0
- package/docs/agents/memory-operations-runbook.md +121 -0
- package/docs/agents/mvp-vertical-slice-spec.md +146 -0
- package/docs/agents/observability-audit-spec.md +265 -0
- package/docs/agents/operator-runbook.md +174 -0
- package/docs/agents/org-memory-api-payload-examples.md +333 -0
- package/docs/agents/org-memory-controller-sequence-spec.md +181 -0
- package/docs/agents/org-memory-e2e-fixture-plan.md +161 -0
- package/docs/agents/org-memory-ui-implementation-map.md +114 -0
- package/docs/agents/org-memory-vertical-slice-spec.md +168 -0
- package/docs/agents/org-resource-model-delta-spec.md +111 -0
- package/docs/agents/org-route-resource-model-spec.md +183 -0
- package/docs/agents/org-scoping-namespace-spec.md +114 -0
- package/docs/agents/rbac-secrets-management-spec.md +406 -0
- package/docs/agents/repository-page-integration-spec.md +255 -0
- package/docs/agents/resource-contract-examples.md +808 -0
- package/docs/agents/resource-relationship-map.md +190 -0
- package/docs/agents/security-threat-model.md +188 -0
- package/docs/agents/shared-memory-company-brain-spec.md +358 -0
- package/docs/agents/storage-migration-spec.md +168 -0
- package/docs/agents/subagent-orchestration-spec.md +152 -0
- package/docs/agents/system-overview.md +88 -0
- package/docs/agents/tools-mcp-skills-spec.md +189 -0
- package/docs/agents/traceability-matrix.md +79 -0
- package/docs/agents/ui-flow-spec.md +211 -0
- package/docs/agents/ui-ux-system-spec.md +426 -0
- package/docs/agents/workspace-lifecycle-spec.md +166 -0
- package/docs/architecture-spec.md +78 -0
- package/docs/components/control-plane.md +78 -0
- package/docs/components/data-plane.md +69 -0
- package/docs/components/hooks-events.md +67 -0
- package/docs/components/identity-rbac-policy.md +73 -0
- package/docs/components/kubevela-oam.md +70 -0
- package/docs/components/operations-publishing.md +81 -0
- package/docs/components/runners-ci.md +66 -0
- package/docs/components/web-ui.md +94 -0
- package/docs/external/README.md +47 -0
- package/docs/external/bidirectional-sync-design.md +134 -0
- package/docs/external/cicd-interface.md +64 -0
- package/docs/external/external-backend-controllers.md +170 -0
- package/docs/external/external-backend-crds.md +234 -0
- package/docs/external/external-backend-ui-spec.md +151 -0
- package/docs/external/external-backend-ux-flows.md +115 -0
- package/docs/external/external-object-mapping.md +125 -0
- package/docs/external/git-forge-interface.md +68 -0
- package/docs/external/github-integration-design.md +151 -0
- package/docs/external/issue-tracking-interface.md +66 -0
- package/docs/external/provider-capability-manifests.md +204 -0
- package/docs/external/provider-catalog.md +139 -0
- package/docs/external/provider-rollout-testing.md +78 -0
- package/docs/external/research-results.md +48 -0
- package/docs/external/security-auth-permissions.md +81 -0
- package/docs/external/sync-state-machines.md +108 -0
- package/docs/external/unified-external-backend-model.md +107 -0
- package/docs/external/user-facing-changes.md +67 -0
- package/docs/gaps.md +161 -0
- package/docs/install.md +94 -0
- package/docs/krate-design.md +334 -0
- package/docs/local-minikube.md +55 -0
- package/docs/ontology/README.md +32 -0
- package/docs/ontology/bounded-contexts.md +29 -0
- package/docs/ontology/events-and-hooks.md +32 -0
- package/docs/ontology/oam-kubevela.md +32 -0
- package/docs/ontology/operations-and-release.md +25 -0
- package/docs/ontology/personas-and-actors.md +32 -0
- package/docs/ontology/policies-and-invariants.md +33 -0
- package/docs/ontology/problem-space.md +30 -0
- package/docs/ontology/resource-contracts.md +40 -0
- package/docs/ontology/resource-taxonomy.md +42 -0
- package/docs/ontology/runners-and-ci.md +29 -0
- package/docs/ontology/solution-space.md +24 -0
- package/docs/ontology/storage-and-data-boundaries.md +29 -0
- package/docs/ontology/validation-matrix.md +24 -0
- package/docs/ontology/web-ui-excellent-flows.md +32 -0
- package/docs/ontology/workflows.md +39 -0
- package/docs/ontology/world.md +35 -0
- package/docs/openapi.yaml +1275 -0
- package/docs/product-requirements.md +62 -0
- package/docs/roadmap-mvp.md +87 -0
- package/docs/system-requirements.md +90 -0
- package/docs/tests/README.md +53 -0
- package/docs/tests/agent-qa-plan.md +63 -0
- package/docs/tests/browser-ui-tests.md +62 -0
- package/docs/tests/ci-quality-gates.md +48 -0
- package/docs/tests/coverage-model.md +64 -0
- package/docs/tests/e2e-scenario-tests.md +53 -0
- package/docs/tests/fixtures-test-data.md +63 -0
- package/docs/tests/observability-reliability-tests.md +54 -0
- package/docs/tests/product-test-matrix.md +145 -0
- package/docs/tests/qa-adoption-roadmap.md +130 -0
- package/docs/tests/qa-automation-plan.md +101 -0
- package/docs/tests/security-compliance-tests.md +57 -0
- package/docs/tests/test-framework-tools.md +88 -0
- package/docs/tests/test-suite-layout.md +121 -0
- package/docs/tests/unit-integration-tests.md +48 -0
- package/docs/todo-kyverno +714 -0
- package/docs/todos.md +4 -0
- package/docs/user-stories.md +78 -0
- package/examples/minikube-demo.yaml +190 -0
- package/examples/oam-application.yaml +23 -0
- package/examples/policy-kyverno-pr-title.yaml +18 -0
- package/package.json +63 -0
- package/scripts/build.mjs +29 -0
- package/scripts/setup-minikube.mjs +65 -0
- package/scripts/smoke.mjs +37 -0
- package/scripts/validate-doc-coverage.mjs +152 -0
- package/scripts/validate-package.mjs +93 -0
- package/scripts/validate-ui.mjs +278 -0
- package/src/agent-adapter-controller.js +169 -0
- package/src/agent-approval-controller.js +170 -0
- package/src/agent-context-bundles.js +242 -0
- package/src/agent-dispatch-controller.js +209 -0
- 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 +280 -0
- package/src/agent-permission-review.js +250 -0
- 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 +347 -0
- package/src/agent-subagent-controller.js +160 -0
- package/src/agent-transport-binding-controller.js +121 -0
- package/src/agent-trigger-controller.js +381 -0
- package/src/agent-workspace-controller.js +702 -0
- package/src/agent-writeback-controller.js +302 -0
- package/src/api-controller.js +541 -0
- package/src/argocd-gitops.js +43 -0
- package/src/async-controller.js +207 -0
- package/src/audit-controller.js +191 -0
- package/src/auth.js +307 -0
- package/src/component-catalog.js +41 -0
- package/src/control-plane.js +136 -0
- package/src/controller-client.js +72 -0
- package/src/controller-ui.js +617 -0
- package/src/data-plane.js +179 -0
- 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 +131 -0
- package/src/gitea-service.js +173 -0
- package/src/handoff.js +98 -0
- package/src/hooks-events.js +63 -0
- package/src/http-server.js +377 -0
- package/src/identity-policy.js +86 -0
- package/src/index.js +57 -0
- package/src/kubernetes-controller-async.js +511 -0
- package/src/kubernetes-controller.js +878 -0
- package/src/kubernetes-resource-gateway.js +48 -0
- package/src/notification-controller.js +178 -0
- package/src/operations.js +112 -0
- package/src/org-scoping.js +5 -0
- package/src/resource-model.js +221 -0
- package/src/runner-controller.js +272 -0
- package/src/runners-ci.js +48 -0
- package/src/runtime.js +196 -0
- package/src/snapshot-cache.js +157 -0
- package/src/web-ui.js +40 -0
- package/tests/agent-adapter-controller.test.js +361 -0
- package/tests/agent-approval-controller.test.js +173 -0
- package/tests/agent-context-bundles.test.js +278 -0
- package/tests/agent-dispatch-controller.test.js +315 -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-mux-client.test.js +204 -0
- package/tests/agent-permission-review-v2.test.js +317 -0
- package/tests/agent-permission-review.test.js +209 -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 +228 -0
- package/tests/agent-secret-config-grant.test.js +231 -0
- package/tests/agent-session-transcript-controller.test.js +499 -0
- package/tests/agent-stack-controller.test.js +221 -0
- package/tests/agent-subagent-controller.test.js +201 -0
- package/tests/agent-transport-binding-controller.test.js +294 -0
- package/tests/agent-trigger-controller.test.js +211 -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/deployment.test.js +407 -0
- package/tests/e2e/lifecycle.test.js +117 -0
- 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 +756 -0
- 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 +247 -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,221 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import { createAgentStackController, createResource } from '../src/index.js';
|
|
4
|
+
|
|
5
|
+
function makeStack(name, spec = {}) {
|
|
6
|
+
return createResource('AgentStack', { name, namespace: 'krate-org-default' }, {
|
|
7
|
+
organizationRef: 'default',
|
|
8
|
+
baseAgent: 'claude-code',
|
|
9
|
+
adapter: 'anthropic',
|
|
10
|
+
runtimeIdentity: { serviceAccountRef: 'sa-default' },
|
|
11
|
+
...spec
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function makeToolProfile(name) {
|
|
16
|
+
return createResource('AgentToolProfile', { name, namespace: 'krate-org-default' }, {
|
|
17
|
+
organizationRef: 'default',
|
|
18
|
+
filesystemPolicy: 'read-write',
|
|
19
|
+
approvalPolicyByTool: {}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function makeMcpServer(name) {
|
|
24
|
+
return createResource('AgentMcpServer', { name, namespace: 'krate-org-default' }, {
|
|
25
|
+
organizationRef: 'default',
|
|
26
|
+
transport: 'stdio',
|
|
27
|
+
scope: 'workspace'
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function makeSkill(name, overrides = {}) {
|
|
32
|
+
return createResource('AgentSkill', { name, namespace: 'krate-org-default' }, {
|
|
33
|
+
organizationRef: 'default',
|
|
34
|
+
format: 'markdown',
|
|
35
|
+
sourceRef: 'git://repo/skills/' + name,
|
|
36
|
+
...overrides
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function makeSubagent(name, overrides = {}) {
|
|
41
|
+
return createResource('AgentSubagent', { name, namespace: 'krate-org-default' }, {
|
|
42
|
+
organizationRef: 'default',
|
|
43
|
+
rolePrompt: 'Test subagent',
|
|
44
|
+
taskKinds: ['code-review'],
|
|
45
|
+
...overrides
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function makeContextLabel(name) {
|
|
50
|
+
return createResource('AgentContextLabel', { name, namespace: 'krate-org-default' }, {
|
|
51
|
+
organizationRef: 'default',
|
|
52
|
+
promptFragment: 'context for ' + name,
|
|
53
|
+
allowedSources: ['manual']
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function makeServiceAccount(name) {
|
|
58
|
+
return createResource('AgentServiceAccount', { name, namespace: 'krate-org-default' }, {
|
|
59
|
+
organizationRef: 'default',
|
|
60
|
+
namespace: 'krate-org-default',
|
|
61
|
+
serviceAccountName: name
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function makeRoleBinding(name, subject) {
|
|
66
|
+
return createResource('AgentRoleBinding', { name, namespace: 'krate-org-default' }, {
|
|
67
|
+
organizationRef: 'default',
|
|
68
|
+
subject,
|
|
69
|
+
roleRef: 'agent-developer',
|
|
70
|
+
scope: 'namespace'
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function makeSecretGrant(name, subject, purpose) {
|
|
75
|
+
return createResource('AgentSecretGrant', { name, namespace: 'krate-org-default' }, {
|
|
76
|
+
organizationRef: 'default',
|
|
77
|
+
subject,
|
|
78
|
+
secretRef: 'secret-' + purpose,
|
|
79
|
+
purpose
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
test('Stack with all refs present results in Ready=True', () => {
|
|
84
|
+
const controller = createAgentStackController();
|
|
85
|
+
const stack = makeStack('full-stack', {
|
|
86
|
+
toolPolicy: 'tool-profile-1',
|
|
87
|
+
mcpServerRefs: ['mcp-github'],
|
|
88
|
+
skillRefs: ['skill-review'],
|
|
89
|
+
subagentRefs: ['sub-linter'],
|
|
90
|
+
contextLabelRefs: ['ctx-security']
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const resources = {
|
|
94
|
+
AgentStack: [stack],
|
|
95
|
+
AgentToolProfile: [makeToolProfile('tool-profile-1')],
|
|
96
|
+
AgentMcpServer: [makeMcpServer('mcp-github')],
|
|
97
|
+
AgentSkill: [makeSkill('skill-review')],
|
|
98
|
+
AgentSubagent: [makeSubagent('sub-linter')],
|
|
99
|
+
AgentContextLabel: [makeContextLabel('ctx-security')],
|
|
100
|
+
AgentServiceAccount: [makeServiceAccount('sa-default')],
|
|
101
|
+
AgentRoleBinding: [makeRoleBinding('rb-1', 'sa-default')],
|
|
102
|
+
AgentSecretGrant: [makeSecretGrant('sg-model', 'sa-default', 'model-provider')]
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const result = controller.reconcileStack(stack, resources);
|
|
106
|
+
const readyCondition = result.conditions.find((c) => c.type === 'Ready');
|
|
107
|
+
assert.equal(readyCondition.status, 'True', 'Ready condition should be True when all refs are present');
|
|
108
|
+
assert.equal(result.validation, 'valid');
|
|
109
|
+
assert.equal(result.capabilities.tools.length, 1);
|
|
110
|
+
assert.equal(result.capabilities.mcpServers.length, 1);
|
|
111
|
+
assert.equal(result.capabilities.skills.length, 1);
|
|
112
|
+
assert.equal(result.capabilities.subagents.length, 1);
|
|
113
|
+
assert.equal(result.capabilities.contextLabels.length, 1);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('Stack with missing MCP server results in McpHealthy=False and Ready=False', () => {
|
|
117
|
+
const controller = createAgentStackController();
|
|
118
|
+
const stack = makeStack('missing-mcp-stack', {
|
|
119
|
+
mcpServerRefs: ['mcp-github', 'mcp-missing']
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const resources = {
|
|
123
|
+
AgentStack: [stack],
|
|
124
|
+
AgentMcpServer: [makeMcpServer('mcp-github')],
|
|
125
|
+
AgentServiceAccount: [makeServiceAccount('sa-default')],
|
|
126
|
+
AgentRoleBinding: [makeRoleBinding('rb-1', 'sa-default')],
|
|
127
|
+
AgentSecretGrant: [makeSecretGrant('sg-model', 'sa-default', 'model-provider')]
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const result = controller.reconcileStack(stack, resources);
|
|
131
|
+
const mcpCondition = result.conditions.find((c) => c.type === 'McpHealthy');
|
|
132
|
+
const readyCondition = result.conditions.find((c) => c.type === 'Ready');
|
|
133
|
+
assert.equal(mcpCondition.status, 'False', 'McpHealthy should be False when MCP server is missing');
|
|
134
|
+
assert.equal(readyCondition.status, 'False', 'Ready should be False when McpHealthy is False');
|
|
135
|
+
assert.ok(mcpCondition.message.includes('mcp-missing'));
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test('Stack with missing skill results in SkillsValidated=False and Ready=False', () => {
|
|
139
|
+
const controller = createAgentStackController();
|
|
140
|
+
const stack = makeStack('missing-skill-stack', {
|
|
141
|
+
skillRefs: ['skill-review', 'skill-ghost']
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const resources = {
|
|
145
|
+
AgentStack: [stack],
|
|
146
|
+
AgentSkill: [makeSkill('skill-review')],
|
|
147
|
+
AgentServiceAccount: [makeServiceAccount('sa-default')],
|
|
148
|
+
AgentRoleBinding: [makeRoleBinding('rb-1', 'sa-default')],
|
|
149
|
+
AgentSecretGrant: [makeSecretGrant('sg-model', 'sa-default', 'model-provider')]
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const result = controller.reconcileStack(stack, resources);
|
|
153
|
+
const skillsCondition = result.conditions.find((c) => c.type === 'SkillsValidated');
|
|
154
|
+
const readyCondition = result.conditions.find((c) => c.type === 'Ready');
|
|
155
|
+
assert.equal(skillsCondition.status, 'False', 'SkillsValidated should be False when skill is missing');
|
|
156
|
+
assert.equal(readyCondition.status, 'False', 'Ready should be False when SkillsValidated is False');
|
|
157
|
+
assert.ok(skillsCondition.message.includes('skill-ghost'));
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test('Minimal stack with no capability refs results in Ready=True', () => {
|
|
161
|
+
const controller = createAgentStackController();
|
|
162
|
+
const stack = makeStack('minimal-stack');
|
|
163
|
+
|
|
164
|
+
const resources = {
|
|
165
|
+
AgentStack: [stack],
|
|
166
|
+
AgentServiceAccount: [makeServiceAccount('sa-default')],
|
|
167
|
+
AgentRoleBinding: [makeRoleBinding('rb-1', 'sa-default')],
|
|
168
|
+
AgentSecretGrant: [makeSecretGrant('sg-model', 'sa-default', 'model-provider')]
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const result = controller.reconcileStack(stack, resources);
|
|
172
|
+
const readyCondition = result.conditions.find((c) => c.type === 'Ready');
|
|
173
|
+
assert.equal(readyCondition.status, 'True', 'Ready should be True for minimal stack with identity present');
|
|
174
|
+
assert.equal(result.validation, 'valid');
|
|
175
|
+
assert.equal(result.capabilities.tools.length, 0);
|
|
176
|
+
assert.equal(result.capabilities.mcpServers.length, 0);
|
|
177
|
+
assert.equal(result.capabilities.skills.length, 0);
|
|
178
|
+
assert.equal(result.capabilities.subagents.length, 0);
|
|
179
|
+
assert.equal(result.capabilities.contextLabels.length, 0);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test('listStackCapabilities returns correct normalized capability list', () => {
|
|
183
|
+
const controller = createAgentStackController();
|
|
184
|
+
const stack = makeStack('caps-stack', {
|
|
185
|
+
toolPolicy: 'tp-1',
|
|
186
|
+
mcpServerRefs: ['mcp-a', 'mcp-b'],
|
|
187
|
+
skillRefs: ['skill-x'],
|
|
188
|
+
subagentRefs: ['sub-y'],
|
|
189
|
+
contextLabelRefs: ['ctx-z']
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const resources = {
|
|
193
|
+
AgentToolProfile: [makeToolProfile('tp-1')],
|
|
194
|
+
AgentMcpServer: [makeMcpServer('mcp-a')],
|
|
195
|
+
AgentSkill: [makeSkill('skill-x')],
|
|
196
|
+
AgentSubagent: [makeSubagent('sub-y')],
|
|
197
|
+
AgentContextLabel: [makeContextLabel('ctx-z')]
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const capabilities = controller.listStackCapabilities(stack, resources);
|
|
201
|
+
assert.equal(capabilities.length, 6);
|
|
202
|
+
|
|
203
|
+
const tool = capabilities.find((c) => c.kind === 'tool');
|
|
204
|
+
assert.equal(tool.name, 'tp-1');
|
|
205
|
+
assert.equal(tool.status, 'resolved');
|
|
206
|
+
|
|
207
|
+
const mcpA = capabilities.find((c) => c.kind === 'mcp' && c.name === 'mcp-a');
|
|
208
|
+
assert.equal(mcpA.status, 'resolved');
|
|
209
|
+
|
|
210
|
+
const mcpB = capabilities.find((c) => c.kind === 'mcp' && c.name === 'mcp-b');
|
|
211
|
+
assert.equal(mcpB.status, 'missing');
|
|
212
|
+
|
|
213
|
+
const skill = capabilities.find((c) => c.kind === 'skill');
|
|
214
|
+
assert.equal(skill.status, 'resolved');
|
|
215
|
+
|
|
216
|
+
const subagent = capabilities.find((c) => c.kind === 'subagent');
|
|
217
|
+
assert.equal(subagent.status, 'resolved');
|
|
218
|
+
|
|
219
|
+
const ctxLabel = capabilities.find((c) => c.kind === 'contextLabel');
|
|
220
|
+
assert.equal(ctxLabel.status, 'resolved');
|
|
221
|
+
});
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import { createAgentSubagentController, AGENT_SUBAGENT_CONTROLLER_BOUNDARY, createResource } from '../src/index.js';
|
|
4
|
+
|
|
5
|
+
function makeSubagent(name, specOverrides = {}) {
|
|
6
|
+
return createResource('AgentSubagent', { name, namespace: 'krate-org-default' }, {
|
|
7
|
+
organizationRef: 'default',
|
|
8
|
+
rolePrompt: 'You are a specialized subagent',
|
|
9
|
+
taskKinds: ['code-review', 'linting'],
|
|
10
|
+
role: 'reviewer',
|
|
11
|
+
parentStackRef: 'parent-stack-1',
|
|
12
|
+
...specOverrides
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function makeParentStack(name) {
|
|
17
|
+
return createResource('AgentStack', { name, namespace: 'krate-org-default' }, {
|
|
18
|
+
organizationRef: 'default',
|
|
19
|
+
baseAgent: 'claude-code',
|
|
20
|
+
adapter: 'anthropic',
|
|
21
|
+
runtimeIdentity: { serviceAccountRef: 'sa-default' }
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// 1. createAgentSubagentController returns controller with validate, dispatch, getToolScope
|
|
26
|
+
test('createAgentSubagentController returns controller with expected methods', () => {
|
|
27
|
+
const controller = createAgentSubagentController();
|
|
28
|
+
assert.ok(controller, 'controller should be created');
|
|
29
|
+
assert.equal(typeof controller.validate, 'function', 'should have validate method');
|
|
30
|
+
assert.equal(typeof controller.dispatchSubagent, 'function', 'should have dispatchSubagent method');
|
|
31
|
+
assert.equal(typeof controller.getToolScope, 'function', 'should have getToolScope method');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// 2. validate accepts valid subagent (name, orgRef, parentStackRef, role)
|
|
35
|
+
test('validate accepts valid subagent with name, orgRef, parentStackRef, role', () => {
|
|
36
|
+
const controller = createAgentSubagentController();
|
|
37
|
+
const subagent = makeSubagent('valid-sub');
|
|
38
|
+
const result = controller.validate(subagent);
|
|
39
|
+
assert.equal(result.valid, true, 'valid subagent should pass validation');
|
|
40
|
+
assert.equal(result.errors.length, 0, 'should have no errors');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// 3. validate rejects missing name
|
|
44
|
+
test('validate rejects subagent missing metadata.name', () => {
|
|
45
|
+
const controller = createAgentSubagentController();
|
|
46
|
+
const subagent = makeSubagent('temp-name');
|
|
47
|
+
delete subagent.metadata.name;
|
|
48
|
+
const result = controller.validate(subagent);
|
|
49
|
+
assert.equal(result.valid, false, 'should be invalid without name');
|
|
50
|
+
assert.ok(result.errors.some(e => e.includes('name')), 'error should mention name');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// 4. validate rejects missing parentStackRef
|
|
54
|
+
test('validate rejects subagent missing parentStackRef', () => {
|
|
55
|
+
const controller = createAgentSubagentController();
|
|
56
|
+
const subagent = makeSubagent('no-parent', { parentStackRef: undefined });
|
|
57
|
+
const result = controller.validate(subagent);
|
|
58
|
+
assert.equal(result.valid, false, 'should be invalid without parentStackRef');
|
|
59
|
+
assert.ok(result.errors.some(e => e.includes('parentStackRef')), 'error should mention parentStackRef');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// 5. validate rejects missing role
|
|
63
|
+
test('validate rejects subagent missing role', () => {
|
|
64
|
+
const controller = createAgentSubagentController();
|
|
65
|
+
const subagent = makeSubagent('no-role', { role: undefined });
|
|
66
|
+
const result = controller.validate(subagent);
|
|
67
|
+
assert.equal(result.valid, false, 'should be invalid without role');
|
|
68
|
+
assert.ok(result.errors.some(e => e.includes('role')), 'error should mention role');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// 6. getToolScope returns allowed tools from spec
|
|
72
|
+
test('getToolScope returns allowed tools list from spec.toolScope.allowed', () => {
|
|
73
|
+
const controller = createAgentSubagentController();
|
|
74
|
+
const subagent = makeSubagent('tool-sub', {
|
|
75
|
+
toolScope: { allowed: ['Read', 'Grep', 'Glob'], denied: ['Bash'] }
|
|
76
|
+
});
|
|
77
|
+
const scope = controller.getToolScope(subagent);
|
|
78
|
+
assert.deepEqual(scope.allowed, ['Read', 'Grep', 'Glob']);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// 7. getToolScope returns all tools when no restriction set
|
|
82
|
+
test('getToolScope returns unrestricted scope when no toolScope set', () => {
|
|
83
|
+
const controller = createAgentSubagentController();
|
|
84
|
+
const subagent = makeSubagent('open-sub');
|
|
85
|
+
const scope = controller.getToolScope(subagent);
|
|
86
|
+
assert.equal(scope.unrestricted, true, 'should be unrestricted when no toolScope set');
|
|
87
|
+
assert.deepEqual(scope.allowed, [], 'allowed should be empty when unrestricted');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// 8. getDeniedTools returns denied tools list
|
|
91
|
+
test('getDeniedTools returns denied tools from spec.toolScope.denied', () => {
|
|
92
|
+
const controller = createAgentSubagentController();
|
|
93
|
+
const subagent = makeSubagent('restricted-sub', {
|
|
94
|
+
toolScope: { allowed: ['Read'], denied: ['Bash', 'Write'] }
|
|
95
|
+
});
|
|
96
|
+
const denied = controller.getDeniedTools(subagent);
|
|
97
|
+
assert.deepEqual(denied, ['Bash', 'Write'], 'should return denied tools');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// 9. dispatchSubagent creates a dispatch record with parentSessionRef
|
|
101
|
+
test('dispatchSubagent creates dispatch record with parentSessionRef', () => {
|
|
102
|
+
const controller = createAgentSubagentController();
|
|
103
|
+
const subagent = makeSubagent('dispatch-sub');
|
|
104
|
+
const parentStack = makeParentStack('parent-stack-1');
|
|
105
|
+
const result = controller.dispatchSubagent({
|
|
106
|
+
subagent,
|
|
107
|
+
parentSessionRef: 'session-parent-abc',
|
|
108
|
+
taskKind: 'code-review',
|
|
109
|
+
namespace: 'krate-org-default',
|
|
110
|
+
organizationRef: 'default',
|
|
111
|
+
resources: { AgentStack: [parentStack] }
|
|
112
|
+
});
|
|
113
|
+
assert.ok(result.dispatchRecord, 'should return a dispatch record');
|
|
114
|
+
assert.equal(result.dispatchRecord.spec.parentSessionRef, 'session-parent-abc', 'should record parentSessionRef');
|
|
115
|
+
assert.ok(result.dispatchRecord.metadata.name, 'dispatch record should have a name');
|
|
116
|
+
assert.equal(result.error, undefined, 'should have no error');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// 10. dispatchSubagent rejects when parent session not provided
|
|
120
|
+
test('dispatchSubagent rejects when parentSessionRef not provided', () => {
|
|
121
|
+
const controller = createAgentSubagentController();
|
|
122
|
+
const subagent = makeSubagent('dispatch-no-parent');
|
|
123
|
+
const result = controller.dispatchSubagent({
|
|
124
|
+
subagent,
|
|
125
|
+
parentSessionRef: undefined,
|
|
126
|
+
taskKind: 'code-review',
|
|
127
|
+
namespace: 'krate-org-default',
|
|
128
|
+
organizationRef: 'default',
|
|
129
|
+
resources: {}
|
|
130
|
+
});
|
|
131
|
+
assert.equal(result.error, true, 'should return error when no parentSessionRef');
|
|
132
|
+
assert.ok(result.reason.includes('parentSessionRef') || result.message.includes('parentSessionRef'), 'error should mention parentSessionRef');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// 11. getSupervisionConfig returns supervision settings (monitorInterval, maxDuration, autoTerminate)
|
|
136
|
+
test('getSupervisionConfig returns configured supervision settings', () => {
|
|
137
|
+
const controller = createAgentSubagentController();
|
|
138
|
+
const subagent = makeSubagent('supervised-sub', {
|
|
139
|
+
supervision: {
|
|
140
|
+
monitorInterval: 30,
|
|
141
|
+
maxDuration: 3600,
|
|
142
|
+
autoTerminate: true
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
const config = controller.getSupervisionConfig(subagent);
|
|
146
|
+
assert.equal(config.monitorInterval, 30, 'should return monitorInterval');
|
|
147
|
+
assert.equal(config.maxDuration, 3600, 'should return maxDuration');
|
|
148
|
+
assert.equal(config.autoTerminate, true, 'should return autoTerminate');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// 12. getSupervisionConfig returns defaults when not configured
|
|
152
|
+
test('getSupervisionConfig returns default supervision settings when not configured', () => {
|
|
153
|
+
const controller = createAgentSubagentController();
|
|
154
|
+
const subagent = makeSubagent('default-supervision-sub');
|
|
155
|
+
const config = controller.getSupervisionConfig(subagent);
|
|
156
|
+
assert.ok(typeof config.monitorInterval === 'number', 'monitorInterval should be a number');
|
|
157
|
+
assert.ok(typeof config.maxDuration === 'number', 'maxDuration should be a number');
|
|
158
|
+
assert.ok(typeof config.autoTerminate === 'boolean', 'autoTerminate should be a boolean');
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// 13. validateTaskRouting accepts valid routing (role matches available subagents)
|
|
162
|
+
test('validateTaskRouting accepts routing when role matches available subagent', () => {
|
|
163
|
+
const controller = createAgentSubagentController();
|
|
164
|
+
const subagents = [
|
|
165
|
+
makeSubagent('sub-reviewer', { role: 'reviewer' }),
|
|
166
|
+
makeSubagent('sub-tester', { role: 'tester' })
|
|
167
|
+
];
|
|
168
|
+
const result = controller.validateTaskRouting({ role: 'reviewer', taskKind: 'code-review', subagents });
|
|
169
|
+
assert.equal(result.valid, true, 'routing to existing role should be valid');
|
|
170
|
+
assert.ok(result.matchedSubagent, 'should return the matched subagent');
|
|
171
|
+
assert.equal(result.matchedSubagent.metadata.name, 'sub-reviewer');
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// 14. validateTaskRouting rejects routing to non-existent role
|
|
175
|
+
test('validateTaskRouting rejects routing to non-existent role', () => {
|
|
176
|
+
const controller = createAgentSubagentController();
|
|
177
|
+
const subagents = [
|
|
178
|
+
makeSubagent('sub-reviewer', { role: 'reviewer' })
|
|
179
|
+
];
|
|
180
|
+
const result = controller.validateTaskRouting({ role: 'deployer', taskKind: 'deploy', subagents });
|
|
181
|
+
assert.equal(result.valid, false, 'routing to non-existent role should be invalid');
|
|
182
|
+
assert.ok(result.error.includes('deployer') || result.error.includes('role'), 'error should mention the missing role');
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// 15. getSubagentStatus returns status from spec (idle, active, completed, failed)
|
|
186
|
+
test('getSubagentStatus returns status from spec.status field', () => {
|
|
187
|
+
const controller = createAgentSubagentController();
|
|
188
|
+
const subagent = makeSubagent('active-sub');
|
|
189
|
+
subagent.status = { phase: 'active', sessionRef: 'session-xyz' };
|
|
190
|
+
const status = controller.getSubagentStatus(subagent);
|
|
191
|
+
assert.equal(status.phase, 'active', 'should return the phase from status');
|
|
192
|
+
assert.equal(status.sessionRef, 'session-xyz', 'should return sessionRef from status');
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// 16. BOUNDARY exported with correct role
|
|
196
|
+
test('AGENT_SUBAGENT_CONTROLLER_BOUNDARY is exported with correct role', () => {
|
|
197
|
+
assert.ok(AGENT_SUBAGENT_CONTROLLER_BOUNDARY, 'BOUNDARY should be exported');
|
|
198
|
+
assert.equal(AGENT_SUBAGENT_CONTROLLER_BOUNDARY.role, 'agent-subagent-controller', 'role should be agent-subagent-controller');
|
|
199
|
+
assert.ok(Array.isArray(AGENT_SUBAGENT_CONTROLLER_BOUNDARY.owns), 'owns should be an array');
|
|
200
|
+
assert.ok(Array.isArray(AGENT_SUBAGENT_CONTROLLER_BOUNDARY.delegatesTo), 'delegatesTo should be an array');
|
|
201
|
+
});
|