@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,294 @@
1
+ import assert from 'node:assert/strict';
2
+ import test from 'node:test';
3
+ import {
4
+ createAgentTransportBindingController,
5
+ validateAgentTransportBinding,
6
+ createResource,
7
+ AGENT_TRANSPORT_BINDING_CONTROLLER_BOUNDARY
8
+ } from '../src/index.js';
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // Acceptance criteria: Slice 1.2b — Agent Transport Binding Controller
12
+ //
13
+ // An AgentTransportBinding connects an adapter to a specific endpoint.
14
+ // It specifies binding name, adapterRef, connection endpoint, protocol,
15
+ // health status tracking, and a reconnect policy.
16
+ //
17
+ // All tests in this file are expected to FAIL until the controller is
18
+ // implemented and exported from src/index.js.
19
+ // ---------------------------------------------------------------------------
20
+
21
+ const VALID_PROTOCOLS = ['stdio', 'http', 'websocket', 'unix'];
22
+
23
+ function makeBinding(name, overrides = {}) {
24
+ return createResource('AgentTransportBinding', { name, namespace: 'krate-org-default' }, {
25
+ organizationRef: 'default',
26
+ adapterRef: 'claude-code-adapter',
27
+ endpoint: 'http://localhost:8080',
28
+ protocol: 'http',
29
+ ...overrides
30
+ });
31
+ }
32
+
33
+ // ---------------------------------------------------------------------------
34
+ // 1. Factory and shape
35
+ // ---------------------------------------------------------------------------
36
+
37
+ test('createAgentTransportBindingController returns a controller with validate method', () => {
38
+ const controller = createAgentTransportBindingController();
39
+ assert.ok(controller, 'controller must be truthy');
40
+ assert.equal(typeof controller.validate, 'function', 'controller must expose a validate method');
41
+ assert.equal(controller.role, 'agent-transport-binding-controller', 'controller must declare its role');
42
+ });
43
+
44
+ // ---------------------------------------------------------------------------
45
+ // 2. validate — happy path
46
+ // ---------------------------------------------------------------------------
47
+
48
+ test('validate accepts valid binding with name, adapterRef, endpoint, protocol', () => {
49
+ const controller = createAgentTransportBindingController();
50
+ const binding = makeBinding('my-transport-binding');
51
+ const result = controller.validate(binding);
52
+
53
+ assert.equal(result.valid, true, 'valid binding must pass validation');
54
+ assert.ok(Array.isArray(result.errors), 'result must contain an errors array');
55
+ assert.equal(result.errors.length, 0, 'errors array must be empty for a valid binding');
56
+ });
57
+
58
+ // ---------------------------------------------------------------------------
59
+ // 3. validate — missing name
60
+ // ---------------------------------------------------------------------------
61
+
62
+ test('validate rejects missing name', () => {
63
+ const controller = createAgentTransportBindingController();
64
+ const binding = {
65
+ apiVersion: 'krate.a5c.ai/v1alpha1',
66
+ kind: 'AgentTransportBinding',
67
+ metadata: { namespace: 'krate-org-default', labels: {}, annotations: {} },
68
+ spec: {
69
+ organizationRef: 'default',
70
+ adapterRef: 'claude-code-adapter',
71
+ endpoint: 'http://localhost:8080',
72
+ protocol: 'http'
73
+ },
74
+ status: {}
75
+ };
76
+ const result = controller.validate(binding);
77
+
78
+ assert.equal(result.valid, false, 'binding without name must fail validation');
79
+ assert.ok(result.errors.length > 0, 'errors array must not be empty');
80
+ assert.ok(
81
+ result.errors.some((e) => /name/i.test(e)),
82
+ 'at least one error must mention "name"'
83
+ );
84
+ });
85
+
86
+ // ---------------------------------------------------------------------------
87
+ // 4. validate — missing adapterRef
88
+ // ---------------------------------------------------------------------------
89
+
90
+ test('validate rejects missing adapterRef', () => {
91
+ const controller = createAgentTransportBindingController();
92
+ const binding = makeBinding('no-adapter-binding');
93
+ delete binding.spec.adapterRef;
94
+ const result = controller.validate(binding);
95
+
96
+ assert.equal(result.valid, false, 'binding without adapterRef must fail validation');
97
+ assert.ok(result.errors.length > 0, 'errors array must not be empty');
98
+ assert.ok(
99
+ result.errors.some((e) => /adapterRef/i.test(e)),
100
+ 'at least one error must mention "adapterRef"'
101
+ );
102
+ });
103
+
104
+ // ---------------------------------------------------------------------------
105
+ // 5. validate — missing endpoint
106
+ // ---------------------------------------------------------------------------
107
+
108
+ test('validate rejects missing endpoint', () => {
109
+ const controller = createAgentTransportBindingController();
110
+ const binding = makeBinding('no-endpoint-binding');
111
+ delete binding.spec.endpoint;
112
+ const result = controller.validate(binding);
113
+
114
+ assert.equal(result.valid, false, 'binding without endpoint must fail validation');
115
+ assert.ok(result.errors.length > 0, 'errors array must not be empty');
116
+ assert.ok(
117
+ result.errors.some((e) => /endpoint/i.test(e)),
118
+ 'at least one error must mention "endpoint"'
119
+ );
120
+ });
121
+
122
+ // ---------------------------------------------------------------------------
123
+ // 6. validate — invalid protocol
124
+ // ---------------------------------------------------------------------------
125
+
126
+ test('validate rejects invalid protocol (not in stdio/http/websocket/unix)', () => {
127
+ const controller = createAgentTransportBindingController();
128
+ const binding = makeBinding('bad-protocol-binding', { protocol: 'grpc' });
129
+ const result = controller.validate(binding);
130
+
131
+ assert.equal(result.valid, false, 'binding with unsupported protocol must fail validation');
132
+ assert.ok(result.errors.length > 0, 'errors array must not be empty');
133
+ assert.ok(
134
+ result.errors.some((e) => /protocol/i.test(e)),
135
+ 'at least one error must mention "protocol"'
136
+ );
137
+ assert.ok(
138
+ result.errors.some((e) => VALID_PROTOCOLS.some((p) => e.includes(p))),
139
+ 'error must enumerate valid protocols'
140
+ );
141
+ });
142
+
143
+ // ---------------------------------------------------------------------------
144
+ // 7. getConnectionStatus — default 'unknown' for new binding
145
+ // ---------------------------------------------------------------------------
146
+
147
+ test('getConnectionStatus returns default "unknown" for new binding', () => {
148
+ const controller = createAgentTransportBindingController();
149
+ const binding = makeBinding('new-binding');
150
+ const status = controller.getConnectionStatus(binding);
151
+
152
+ assert.ok(status, 'getConnectionStatus must return a value');
153
+ assert.equal(status.connectionStatus, 'unknown', 'default connection status must be "unknown"');
154
+ assert.equal(status.bindingName, binding.metadata.name, 'result must carry the binding name');
155
+ });
156
+
157
+ // ---------------------------------------------------------------------------
158
+ // 8. getReconnectPolicy — returns policy from spec with defaults
159
+ // ---------------------------------------------------------------------------
160
+
161
+ test('getReconnectPolicy returns policy from spec with defaults', () => {
162
+ const controller = createAgentTransportBindingController();
163
+ const binding = makeBinding('policy-binding', {
164
+ reconnectPolicy: {
165
+ maxRetries: 5,
166
+ backoffMs: 500,
167
+ maxBackoffMs: 30000
168
+ }
169
+ });
170
+ const policy = controller.getReconnectPolicy(binding);
171
+
172
+ assert.ok(policy, 'getReconnectPolicy must return a value');
173
+ assert.equal(policy.maxRetries, 5, 'maxRetries must match spec');
174
+ assert.equal(policy.backoffMs, 500, 'backoffMs must match spec');
175
+ assert.equal(policy.maxBackoffMs, 30000, 'maxBackoffMs must match spec');
176
+ });
177
+
178
+ // ---------------------------------------------------------------------------
179
+ // 9. getReconnectPolicy — returns defaults when no policy in spec
180
+ // ---------------------------------------------------------------------------
181
+
182
+ test('getReconnectPolicy returns defaults when no policy in spec', () => {
183
+ const controller = createAgentTransportBindingController();
184
+ const binding = makeBinding('no-policy-binding');
185
+ // no reconnectPolicy in spec
186
+ const policy = controller.getReconnectPolicy(binding);
187
+
188
+ assert.ok(policy, 'getReconnectPolicy must return a value');
189
+ assert.ok(typeof policy.maxRetries === 'number', 'maxRetries must default to a number');
190
+ assert.ok(typeof policy.backoffMs === 'number', 'backoffMs must default to a number');
191
+ assert.ok(typeof policy.maxBackoffMs === 'number', 'maxBackoffMs must default to a number');
192
+ assert.ok(policy.maxRetries >= 0, 'maxRetries default must be non-negative');
193
+ assert.ok(policy.backoffMs >= 0, 'backoffMs default must be non-negative');
194
+ assert.ok(policy.maxBackoffMs >= policy.backoffMs, 'maxBackoffMs must be >= backoffMs');
195
+ });
196
+
197
+ // ---------------------------------------------------------------------------
198
+ // 10. validate — rejects null resource
199
+ // ---------------------------------------------------------------------------
200
+
201
+ test('validate rejects null resource', () => {
202
+ const controller = createAgentTransportBindingController();
203
+ const result = controller.validate(null);
204
+
205
+ assert.equal(result.valid, false, 'null resource must fail validation');
206
+ assert.ok(result.errors.length > 0, 'errors array must not be empty');
207
+ assert.ok(
208
+ result.errors.some((e) => /null|undefined/i.test(e)),
209
+ 'error must mention null or undefined'
210
+ );
211
+ });
212
+
213
+ // ---------------------------------------------------------------------------
214
+ // 11. BOUNDARY object exported with correct role
215
+ // ---------------------------------------------------------------------------
216
+
217
+ test('AGENT_TRANSPORT_BINDING_CONTROLLER_BOUNDARY is exported and has correct role', () => {
218
+ assert.ok(AGENT_TRANSPORT_BINDING_CONTROLLER_BOUNDARY, 'BOUNDARY must be exported');
219
+ assert.equal(
220
+ AGENT_TRANSPORT_BINDING_CONTROLLER_BOUNDARY.role,
221
+ 'agent-transport-binding-controller',
222
+ 'BOUNDARY role must be "agent-transport-binding-controller"'
223
+ );
224
+ assert.ok(
225
+ Array.isArray(AGENT_TRANSPORT_BINDING_CONTROLLER_BOUNDARY.owns),
226
+ 'BOUNDARY must declare owned concerns'
227
+ );
228
+ });
229
+
230
+ // ---------------------------------------------------------------------------
231
+ // 12. validate — all valid protocols are accepted
232
+ // ---------------------------------------------------------------------------
233
+
234
+ test('validate accepts each valid protocol (stdio, http, websocket, unix)', () => {
235
+ const controller = createAgentTransportBindingController();
236
+ for (const protocol of VALID_PROTOCOLS) {
237
+ const binding = makeBinding(`binding-${protocol}`, { protocol });
238
+ const result = controller.validate(binding);
239
+ assert.equal(result.valid, true, `protocol "${protocol}" must be accepted`);
240
+ assert.equal(result.errors.length, 0, `no errors expected for protocol "${protocol}"`);
241
+ }
242
+ });
243
+
244
+ // ---------------------------------------------------------------------------
245
+ // 13. getConnectionStatus — reads from status.connectionStatus when present
246
+ // ---------------------------------------------------------------------------
247
+
248
+ test('getConnectionStatus reads connectionStatus from resource status field when present', () => {
249
+ const controller = createAgentTransportBindingController();
250
+ const binding = makeBinding('connected-binding');
251
+ binding.status = { connectionStatus: 'connected' };
252
+ const status = controller.getConnectionStatus(binding);
253
+
254
+ assert.equal(status.connectionStatus, 'connected', 'must reflect status from resource status field');
255
+ });
256
+
257
+ // ---------------------------------------------------------------------------
258
+ // 14. validate accumulates errors for multiple missing fields
259
+ // ---------------------------------------------------------------------------
260
+
261
+ test('validate accumulates errors when both adapterRef and endpoint are missing', () => {
262
+ const controller = createAgentTransportBindingController();
263
+ const binding = makeBinding('double-missing-binding');
264
+ delete binding.spec.adapterRef;
265
+ delete binding.spec.endpoint;
266
+ const result = controller.validate(binding);
267
+
268
+ assert.equal(result.valid, false, 'binding with multiple missing fields must fail validation');
269
+ assert.ok(
270
+ result.errors.some((e) => /adapterRef/i.test(e)),
271
+ 'errors must include adapterRef error'
272
+ );
273
+ assert.ok(
274
+ result.errors.some((e) => /endpoint/i.test(e)),
275
+ 'errors must include endpoint error'
276
+ );
277
+ assert.ok(result.errors.length >= 2, 'must accumulate at least two errors');
278
+ });
279
+
280
+ // ---------------------------------------------------------------------------
281
+ // 15. validateAgentTransportBinding standalone export
282
+ // ---------------------------------------------------------------------------
283
+
284
+ test('validateAgentTransportBinding is exported and validates correctly', () => {
285
+ assert.equal(typeof validateAgentTransportBinding, 'function', 'validateAgentTransportBinding must be a named export');
286
+
287
+ const binding = makeBinding('standalone-validate-binding');
288
+ const result = validateAgentTransportBinding(binding);
289
+
290
+ assert.ok(result, 'validateAgentTransportBinding must return a result');
291
+ assert.ok('valid' in result, 'result must have a valid property');
292
+ assert.ok(Array.isArray(result.errors), 'result must have an errors array');
293
+ assert.equal(result.valid, true, 'a fully-specified binding must pass standalone validation');
294
+ });
@@ -0,0 +1,211 @@
1
+ import assert from 'node:assert/strict';
2
+ import test from 'node:test';
3
+ import { createAgentTriggerController, createResource } from '../src/index.js';
4
+
5
+ function makeTriggerRule(name, spec = {}) {
6
+ return createResource('AgentTriggerRule', { name, namespace: 'krate-org-default' }, {
7
+ organizationRef: 'default',
8
+ sources: ['ci-failure'],
9
+ agentStack: 'debug-stack',
10
+ taskKind: 'diagnostic',
11
+ ...spec
12
+ });
13
+ }
14
+
15
+ function makeEvent(overrides = {}) {
16
+ return {
17
+ type: 'ci-failure',
18
+ repository: 'myapp',
19
+ ref: 'main',
20
+ actor: 'alice',
21
+ source: { kind: 'Pipeline', name: 'pipeline-123' },
22
+ ...overrides
23
+ };
24
+ }
25
+
26
+ function makeExecution(ruleName, eventUid, phase = 'Dispatched') {
27
+ const exec = createResource('AgentTriggerExecution', { name: `exec-${ruleName}-1`, namespace: 'krate-org-default' }, {
28
+ organizationRef: 'default',
29
+ triggerRule: ruleName,
30
+ sourceEvent: eventUid,
31
+ decision: phase
32
+ });
33
+ exec.status = { phase };
34
+ return exec;
35
+ }
36
+
37
+ // 1. matchRule: event type matches
38
+ test('matchRule: event type matches rule sources', () => {
39
+ const controller = createAgentTriggerController();
40
+ const rule = makeTriggerRule('rule-1', { sources: ['ci-failure'] });
41
+ const event = makeEvent({ type: 'ci-failure' });
42
+
43
+ const result = controller.matchRule(rule, event);
44
+
45
+ assert.equal(result.matches, true);
46
+ assert.equal(result.reason, 'All conditions met');
47
+ });
48
+
49
+ // 2. matchRule: event type doesn't match
50
+ test('matchRule: event type does not match rule sources', () => {
51
+ const controller = createAgentTriggerController();
52
+ const rule = makeTriggerRule('rule-2', { sources: ['ci-failure'] });
53
+ const event = makeEvent({ type: 'webhook' });
54
+
55
+ const result = controller.matchRule(rule, event);
56
+
57
+ assert.equal(result.matches, false);
58
+ assert.ok(result.reason.includes("'webhook'"), 'Reason should mention the event type');
59
+ assert.ok(result.reason.includes('ci-failure'), 'Reason should mention the expected source');
60
+ });
61
+
62
+ // 3. matchRule: repository scope match
63
+ test('matchRule: repository scope matches', () => {
64
+ const controller = createAgentTriggerController();
65
+ const rule = makeTriggerRule('rule-3', { sources: ['ci-failure'], repository: 'myapp' });
66
+ const event = makeEvent({ type: 'ci-failure', repository: 'myapp' });
67
+
68
+ const result = controller.matchRule(rule, event);
69
+
70
+ assert.equal(result.matches, true);
71
+ assert.equal(result.reason, 'All conditions met');
72
+ });
73
+
74
+ // 4. matchRule: repository scope mismatch
75
+ test('matchRule: repository scope mismatch', () => {
76
+ const controller = createAgentTriggerController();
77
+ const rule = makeTriggerRule('rule-4', { sources: ['ci-failure'], repository: 'myapp' });
78
+ const event = makeEvent({ type: 'ci-failure', repository: 'other' });
79
+
80
+ const result = controller.matchRule(rule, event);
81
+
82
+ assert.equal(result.matches, false);
83
+ assert.ok(result.reason.includes("'other'"), 'Reason should mention the event repository');
84
+ assert.ok(result.reason.includes("'myapp'"), 'Reason should mention the rule scope');
85
+ });
86
+
87
+ // 5. matchRule: actor filter
88
+ test('matchRule: actor not in allowedActors', () => {
89
+ const controller = createAgentTriggerController();
90
+ const rule = makeTriggerRule('rule-5', { sources: ['ci-failure'], allowedActors: ['alice'] });
91
+ const event = makeEvent({ type: 'ci-failure', actor: 'bob' });
92
+
93
+ const result = controller.matchRule(rule, event);
94
+
95
+ assert.equal(result.matches, false);
96
+ assert.ok(result.reason.includes("'bob'"), 'Reason should mention the rejected actor');
97
+ });
98
+
99
+ // 6. evaluateEvent: deduplication
100
+ test('evaluateEvent: marks duplicate when existing execution matches', () => {
101
+ const controller = createAgentTriggerController();
102
+ const rule = makeTriggerRule('rule-dedup');
103
+ const event = makeEvent();
104
+ const eventUid = `${event.type}:${event.source.kind}:${event.source.name}`;
105
+ const existingExecution = makeExecution('rule-dedup', eventUid, 'Dispatched');
106
+
107
+ const resources = {
108
+ AgentTriggerRule: [rule],
109
+ AgentTriggerExecution: [existingExecution]
110
+ };
111
+
112
+ const evaluations = controller.evaluateEvent({ event, resources });
113
+
114
+ assert.equal(evaluations.length, 1);
115
+ assert.equal(evaluations[0].matches, true);
116
+ assert.equal(evaluations[0].isDuplicate, true);
117
+ });
118
+
119
+ // 7. processEvent: dispatches matching rules
120
+ test('processEvent: dispatches matching rules via dispatch controller', async () => {
121
+ let dispatchCalled = false;
122
+ const mockDispatchController = {
123
+ async createManualDispatch(input) {
124
+ dispatchCalled = true;
125
+ const run = createResource('AgentDispatchRun', { name: `dispatch-${Date.now()}`, namespace: input.namespace }, {
126
+ organizationRef: input.organizationRef,
127
+ repository: input.repository,
128
+ sourceRefs: input.sourceRefs,
129
+ agentStack: input.agentStack,
130
+ taskKind: input.taskKind
131
+ });
132
+ run.status = { phase: 'Queued' };
133
+ return { error: false, run };
134
+ }
135
+ };
136
+ const controller = createAgentTriggerController({ dispatchController: mockDispatchController });
137
+ const rule = makeTriggerRule('rule-dispatch');
138
+ const event = makeEvent();
139
+ const resources = { AgentTriggerRule: [rule], AgentTriggerExecution: [] };
140
+
141
+ const result = await controller.processEvent({ event, resources, namespace: 'krate-org-default', organizationRef: 'default' });
142
+
143
+ assert.equal(dispatchCalled, true, 'Dispatch controller should be called');
144
+ assert.equal(result.dispatched, 1);
145
+ assert.equal(result.skipped, 0);
146
+ assert.equal(result.processed, 1);
147
+ assert.equal(result.executions.length, 1);
148
+ assert.equal(result.executions[0].status.phase, 'Dispatched');
149
+ assert.ok(result.executions[0].status.dispatchRunRef, 'Execution should reference the dispatch run');
150
+ });
151
+
152
+ // 8. processEvent: skips non-matching
153
+ test('processEvent: skips non-matching rules', async () => {
154
+ const controller = createAgentTriggerController();
155
+ const rule = makeTriggerRule('rule-skip', { sources: ['ci-failure'] });
156
+ const event = makeEvent({ type: 'webhook' });
157
+ const resources = { AgentTriggerRule: [rule], AgentTriggerExecution: [] };
158
+
159
+ const result = await controller.processEvent({ event, resources });
160
+
161
+ assert.equal(result.dispatched, 0);
162
+ assert.equal(result.skipped, 1);
163
+ assert.equal(result.processed, 1);
164
+ assert.equal(result.executions[0].status.phase, 'Skipped');
165
+ });
166
+
167
+ // 9. processEvent: multiple rules match same event
168
+ test('processEvent: multiple rules match same event', async () => {
169
+ let dispatchCount = 0;
170
+ const mockDispatchController = {
171
+ async createManualDispatch(input) {
172
+ dispatchCount++;
173
+ const run = createResource('AgentDispatchRun', { name: `dispatch-${Date.now()}-${dispatchCount}`, namespace: input.namespace }, {
174
+ organizationRef: input.organizationRef,
175
+ repository: input.repository,
176
+ sourceRefs: input.sourceRefs,
177
+ agentStack: input.agentStack,
178
+ taskKind: input.taskKind
179
+ });
180
+ run.status = { phase: 'Queued' };
181
+ return { error: false, run };
182
+ }
183
+ };
184
+ const controller = createAgentTriggerController({ dispatchController: mockDispatchController });
185
+ const rule1 = makeTriggerRule('rule-multi-1', { sources: ['ci-failure'], agentStack: 'stack-a' });
186
+ const rule2 = makeTriggerRule('rule-multi-2', { sources: ['ci-failure'], agentStack: 'stack-b' });
187
+ const event = makeEvent();
188
+ const resources = { AgentTriggerRule: [rule1, rule2], AgentTriggerExecution: [] };
189
+
190
+ const result = await controller.processEvent({ event, resources, namespace: 'krate-org-default', organizationRef: 'default' });
191
+
192
+ assert.equal(dispatchCount, 2, 'Dispatch should be called twice');
193
+ assert.equal(result.dispatched, 2);
194
+ assert.equal(result.skipped, 0);
195
+ assert.equal(result.processed, 2);
196
+ assert.equal(result.executions.length, 2);
197
+ });
198
+
199
+ // 10. processEvent: no rules
200
+ test('processEvent: empty rules array', async () => {
201
+ const controller = createAgentTriggerController();
202
+ const event = makeEvent();
203
+ const resources = { AgentTriggerRule: [], AgentTriggerExecution: [] };
204
+
205
+ const result = await controller.processEvent({ event, resources });
206
+
207
+ assert.equal(result.processed, 0);
208
+ assert.equal(result.dispatched, 0);
209
+ assert.equal(result.skipped, 0);
210
+ assert.equal(result.executions.length, 0);
211
+ });