@a5c-ai/krate 5.0.1-staging.04a3db697

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. package/Dockerfile +31 -0
  2. package/README.md +183 -0
  3. package/bin/krate-demo.mjs +23 -0
  4. package/bin/krate-server.mjs +14 -0
  5. package/dist/krate-controller-ui.json +3067 -0
  6. package/dist/krate-lifecycle.json +201 -0
  7. package/dist/krate-runtime-snapshot.json +2955 -0
  8. package/dist/krate-summary.json +722 -0
  9. package/docs/README.md +61 -0
  10. package/docs/agents/README.md +83 -0
  11. package/docs/agents/acceptance-test-matrix.md +193 -0
  12. package/docs/agents/agent-mux-adapter-contract.md +167 -0
  13. package/docs/agents/agent-mux-source-map.md +310 -0
  14. package/docs/agents/agent-run-memory-import-spec.md +256 -0
  15. package/docs/agents/agent-stack-management-spec.md +421 -0
  16. package/docs/agents/api-contract-spec.md +309 -0
  17. package/docs/agents/artifacts-writeback-spec.md +145 -0
  18. package/docs/agents/chart-packaging-spec.md +128 -0
  19. package/docs/agents/ci-orchestration-spec.md +140 -0
  20. package/docs/agents/context-assembly-spec.md +219 -0
  21. package/docs/agents/controller-reconciliation-spec.md +255 -0
  22. package/docs/agents/crd-schema-spec.md +315 -0
  23. package/docs/agents/decision-log-open-questions.md +169 -0
  24. package/docs/agents/developer-implementation-checklist.md +329 -0
  25. package/docs/agents/dispatching-design.md +262 -0
  26. package/docs/agents/gaps-agent-mux-to-krate-crds.md +298 -0
  27. package/docs/agents/glossary.md +66 -0
  28. package/docs/agents/implementation-blueprint.md +324 -0
  29. package/docs/agents/implementation-rollout-slices.md +251 -0
  30. package/docs/agents/memory-context-integration-spec.md +194 -0
  31. package/docs/agents/memory-ontology-schema-spec.md +253 -0
  32. package/docs/agents/memory-operations-runbook.md +121 -0
  33. package/docs/agents/mvp-vertical-slice-spec.md +146 -0
  34. package/docs/agents/observability-audit-spec.md +265 -0
  35. package/docs/agents/operator-runbook.md +174 -0
  36. package/docs/agents/org-memory-api-payload-examples.md +333 -0
  37. package/docs/agents/org-memory-controller-sequence-spec.md +181 -0
  38. package/docs/agents/org-memory-e2e-fixture-plan.md +161 -0
  39. package/docs/agents/org-memory-ui-implementation-map.md +114 -0
  40. package/docs/agents/org-memory-vertical-slice-spec.md +168 -0
  41. package/docs/agents/org-resource-model-delta-spec.md +111 -0
  42. package/docs/agents/org-route-resource-model-spec.md +183 -0
  43. package/docs/agents/org-scoping-namespace-spec.md +114 -0
  44. package/docs/agents/rbac-secrets-management-spec.md +406 -0
  45. package/docs/agents/repository-page-integration-spec.md +255 -0
  46. package/docs/agents/resource-contract-examples.md +808 -0
  47. package/docs/agents/resource-relationship-map.md +190 -0
  48. package/docs/agents/security-threat-model.md +188 -0
  49. package/docs/agents/shared-memory-company-brain-spec.md +358 -0
  50. package/docs/agents/storage-migration-spec.md +168 -0
  51. package/docs/agents/subagent-orchestration-spec.md +152 -0
  52. package/docs/agents/system-overview.md +88 -0
  53. package/docs/agents/tools-mcp-skills-spec.md +189 -0
  54. package/docs/agents/traceability-matrix.md +79 -0
  55. package/docs/agents/ui-flow-spec.md +211 -0
  56. package/docs/agents/ui-ux-system-spec.md +426 -0
  57. package/docs/agents/workspace-lifecycle-spec.md +166 -0
  58. package/docs/architecture-spec.md +78 -0
  59. package/docs/components/control-plane.md +78 -0
  60. package/docs/components/data-plane.md +69 -0
  61. package/docs/components/hooks-events.md +67 -0
  62. package/docs/components/identity-rbac-policy.md +73 -0
  63. package/docs/components/kubevela-oam.md +70 -0
  64. package/docs/components/operations-publishing.md +81 -0
  65. package/docs/components/runners-ci.md +66 -0
  66. package/docs/components/web-ui.md +94 -0
  67. package/docs/external/README.md +47 -0
  68. package/docs/external/bidirectional-sync-design.md +134 -0
  69. package/docs/external/cicd-interface.md +64 -0
  70. package/docs/external/external-backend-controllers.md +170 -0
  71. package/docs/external/external-backend-crds.md +234 -0
  72. package/docs/external/external-backend-ui-spec.md +151 -0
  73. package/docs/external/external-backend-ux-flows.md +115 -0
  74. package/docs/external/external-object-mapping.md +125 -0
  75. package/docs/external/git-forge-interface.md +68 -0
  76. package/docs/external/github-integration-design.md +151 -0
  77. package/docs/external/issue-tracking-interface.md +66 -0
  78. package/docs/external/provider-capability-manifests.md +204 -0
  79. package/docs/external/provider-catalog.md +139 -0
  80. package/docs/external/provider-rollout-testing.md +78 -0
  81. package/docs/external/research-results.md +48 -0
  82. package/docs/external/security-auth-permissions.md +81 -0
  83. package/docs/external/sync-state-machines.md +108 -0
  84. package/docs/external/unified-external-backend-model.md +107 -0
  85. package/docs/external/user-facing-changes.md +67 -0
  86. package/docs/gaps.md +161 -0
  87. package/docs/install.md +94 -0
  88. package/docs/krate-design.md +334 -0
  89. package/docs/local-minikube.md +55 -0
  90. package/docs/ontology/README.md +32 -0
  91. package/docs/ontology/bounded-contexts.md +29 -0
  92. package/docs/ontology/events-and-hooks.md +32 -0
  93. package/docs/ontology/oam-kubevela.md +32 -0
  94. package/docs/ontology/operations-and-release.md +25 -0
  95. package/docs/ontology/personas-and-actors.md +32 -0
  96. package/docs/ontology/policies-and-invariants.md +33 -0
  97. package/docs/ontology/problem-space.md +30 -0
  98. package/docs/ontology/resource-contracts.md +40 -0
  99. package/docs/ontology/resource-taxonomy.md +42 -0
  100. package/docs/ontology/runners-and-ci.md +29 -0
  101. package/docs/ontology/solution-space.md +24 -0
  102. package/docs/ontology/storage-and-data-boundaries.md +29 -0
  103. package/docs/ontology/validation-matrix.md +24 -0
  104. package/docs/ontology/web-ui-excellent-flows.md +32 -0
  105. package/docs/ontology/workflows.md +39 -0
  106. package/docs/ontology/world.md +35 -0
  107. package/docs/product-requirements.md +62 -0
  108. package/docs/roadmap-mvp.md +87 -0
  109. package/docs/system-requirements.md +90 -0
  110. package/docs/tests/README.md +53 -0
  111. package/docs/tests/agent-qa-plan.md +63 -0
  112. package/docs/tests/browser-ui-tests.md +62 -0
  113. package/docs/tests/ci-quality-gates.md +48 -0
  114. package/docs/tests/coverage-model.md +64 -0
  115. package/docs/tests/e2e-scenario-tests.md +53 -0
  116. package/docs/tests/fixtures-test-data.md +63 -0
  117. package/docs/tests/observability-reliability-tests.md +54 -0
  118. package/docs/tests/product-test-matrix.md +145 -0
  119. package/docs/tests/qa-adoption-roadmap.md +130 -0
  120. package/docs/tests/qa-automation-plan.md +101 -0
  121. package/docs/tests/security-compliance-tests.md +57 -0
  122. package/docs/tests/test-framework-tools.md +88 -0
  123. package/docs/tests/test-suite-layout.md +121 -0
  124. package/docs/tests/unit-integration-tests.md +48 -0
  125. package/docs/todo-kyverno +714 -0
  126. package/docs/todos.md +4 -0
  127. package/docs/user-stories.md +78 -0
  128. package/examples/minikube-demo.yaml +190 -0
  129. package/examples/oam-application.yaml +23 -0
  130. package/examples/policy-kyverno-pr-title.yaml +18 -0
  131. package/package.json +63 -0
  132. package/scripts/build.mjs +29 -0
  133. package/scripts/setup-minikube.mjs +65 -0
  134. package/scripts/smoke.mjs +37 -0
  135. package/scripts/validate-doc-coverage.mjs +152 -0
  136. package/scripts/validate-package.mjs +93 -0
  137. package/scripts/validate-ui.mjs +236 -0
  138. package/src/agent-adapter-controller.js +169 -0
  139. package/src/agent-approval-controller.js +170 -0
  140. package/src/agent-context-bundles.js +242 -0
  141. package/src/agent-dispatch-controller.js +209 -0
  142. package/src/agent-gateway-config-controller.js +147 -0
  143. package/src/agent-memory-controller.js +357 -0
  144. package/src/agent-memory-import.js +327 -0
  145. package/src/agent-memory-query.js +292 -0
  146. package/src/agent-memory-repository-source-controller.js +255 -0
  147. package/src/agent-mux-client.js +280 -0
  148. package/src/agent-permission-review.js +250 -0
  149. package/src/agent-project-controller.js +117 -0
  150. package/src/agent-provider-config-controller.js +150 -0
  151. package/src/agent-secret-config-grant-controller.js +282 -0
  152. package/src/agent-session-transcript-controller.js +189 -0
  153. package/src/agent-stack-controller.js +347 -0
  154. package/src/agent-subagent-controller.js +160 -0
  155. package/src/agent-transport-binding-controller.js +121 -0
  156. package/src/agent-trigger-controller.js +321 -0
  157. package/src/agent-workspace-controller.js +447 -0
  158. package/src/agent-writeback-controller.js +302 -0
  159. package/src/api-controller.js +541 -0
  160. package/src/argocd-gitops.js +43 -0
  161. package/src/async-controller.js +207 -0
  162. package/src/audit-controller.js +191 -0
  163. package/src/auth.js +307 -0
  164. package/src/component-catalog.js +41 -0
  165. package/src/control-plane.js +136 -0
  166. package/src/controller-client.js +50 -0
  167. package/src/controller-ui.js +551 -0
  168. package/src/data-plane.js +178 -0
  169. package/src/event-bus.js +61 -0
  170. package/src/external/conflict-controller.js +225 -0
  171. package/src/external/github/auth.js +96 -0
  172. package/src/external/github/cicd.js +180 -0
  173. package/src/external/github/git-forge.js +240 -0
  174. package/src/external/github/index.js +144 -0
  175. package/src/external/github/issue-tracking.js +163 -0
  176. package/src/external/provider-adapter.js +161 -0
  177. package/src/external/provider-resource-factory.js +161 -0
  178. package/src/external/sync-controller.js +235 -0
  179. package/src/external/webhook-controller.js +144 -0
  180. package/src/external/write-controller.js +283 -0
  181. package/src/gitea-backend.js +95 -0
  182. package/src/gitea-service.js +173 -0
  183. package/src/handoff.js +98 -0
  184. package/src/hooks-events.js +63 -0
  185. package/src/http-server.js +377 -0
  186. package/src/identity-policy.js +86 -0
  187. package/src/index.js +55 -0
  188. package/src/kubernetes-controller-async.js +511 -0
  189. package/src/kubernetes-controller.js +878 -0
  190. package/src/kubernetes-resource-gateway.js +48 -0
  191. package/src/operations.js +112 -0
  192. package/src/org-scoping.js +5 -0
  193. package/src/resource-model.js +221 -0
  194. package/src/runners-ci.js +48 -0
  195. package/src/runtime.js +196 -0
  196. package/src/snapshot-cache.js +157 -0
  197. package/src/web-ui.js +40 -0
  198. package/tests/agent-adapter-controller.test.js +361 -0
  199. package/tests/agent-approval-controller.test.js +173 -0
  200. package/tests/agent-context-bundles.test.js +278 -0
  201. package/tests/agent-dispatch-controller.test.js +315 -0
  202. package/tests/agent-gateway-config-controller.test.js +386 -0
  203. package/tests/agent-memory-controller.test.js +308 -0
  204. package/tests/agent-memory-import-snapshot.test.js +477 -0
  205. package/tests/agent-memory-query.test.js +404 -0
  206. package/tests/agent-memory-repository-source.test.js +514 -0
  207. package/tests/agent-mux-client.test.js +204 -0
  208. package/tests/agent-permission-review-v2.test.js +317 -0
  209. package/tests/agent-permission-review.test.js +209 -0
  210. package/tests/agent-project-controller.test.js +302 -0
  211. package/tests/agent-provider-config-controller.test.js +376 -0
  212. package/tests/agent-resources.test.js +228 -0
  213. package/tests/agent-secret-config-grant.test.js +231 -0
  214. package/tests/agent-session-transcript-controller.test.js +499 -0
  215. package/tests/agent-stack-controller.test.js +221 -0
  216. package/tests/agent-subagent-controller.test.js +201 -0
  217. package/tests/agent-transport-binding-controller.test.js +294 -0
  218. package/tests/agent-trigger-controller.test.js +211 -0
  219. package/tests/agent-trigger-routes.test.js +190 -0
  220. package/tests/agent-trigger-sources.test.js +245 -0
  221. package/tests/agent-workspace-controller.test.js +181 -0
  222. package/tests/agent-writeback.test.js +292 -0
  223. package/tests/approval-persistence.test.js +171 -0
  224. package/tests/async-controller.test.js +252 -0
  225. package/tests/audit-controller.test.js +227 -0
  226. package/tests/deployment.test.js +396 -0
  227. package/tests/e2e/lifecycle.test.js +117 -0
  228. package/tests/external-github-forge.test.js +560 -0
  229. package/tests/external-github-issues-cicd.test.js +520 -0
  230. package/tests/external-integration.test.js +470 -0
  231. package/tests/external-persistence.test.js +340 -0
  232. package/tests/external-provider-adapter.test.js +365 -0
  233. package/tests/external-resource-model.test.js +215 -0
  234. package/tests/external-webhook-sync.test.js +287 -0
  235. package/tests/external-write-conflict.test.js +353 -0
  236. package/tests/gitea-service.test.js +253 -0
  237. package/tests/health-check-real.test.js +165 -0
  238. package/tests/integration/full-flow.test.js +266 -0
  239. package/tests/krate.test.js +727 -0
  240. package/tests/memory-search-wiring.test.js +270 -0
  241. package/tests/org-scoping.test.js +687 -0
  242. package/tests/session-cookie-hmac.test.js +151 -0
  243. package/tests/snapshot-performance.test.js +247 -0
  244. package/tests/sse-events.test.js +107 -0
  245. package/tests/workspace-volumes.test.js +312 -0
  246. 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
+ });