@a5c-ai/kradle 5.0.1-staging.3abdf9534c25

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 (295) hide show
  1. package/Dockerfile +31 -0
  2. package/README.md +187 -0
  3. package/bin/kradle-demo.mjs +23 -0
  4. package/bin/kradle-server.mjs +14 -0
  5. package/dist/kradle-controller-ui.json +3482 -0
  6. package/dist/kradle-lifecycle.json +201 -0
  7. package/dist/kradle-runtime-snapshot.json +3125 -0
  8. package/dist/kradle-summary.json +724 -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-kradle-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/architecture-v2.md +2759 -0
  60. package/docs/components/control-plane.md +78 -0
  61. package/docs/components/data-plane.md +69 -0
  62. package/docs/components/hooks-events.md +67 -0
  63. package/docs/components/identity-rbac-policy.md +73 -0
  64. package/docs/components/kubevela-oam.md +70 -0
  65. package/docs/components/operations-publishing.md +81 -0
  66. package/docs/components/runners-ci.md +66 -0
  67. package/docs/components/web-ui.md +94 -0
  68. package/docs/crd-behaviors-and-relationships.md +3926 -0
  69. package/docs/external/README.md +47 -0
  70. package/docs/external/bidirectional-sync-design.md +134 -0
  71. package/docs/external/cicd-interface.md +64 -0
  72. package/docs/external/external-backend-controllers.md +170 -0
  73. package/docs/external/external-backend-crds.md +234 -0
  74. package/docs/external/external-backend-ui-spec.md +151 -0
  75. package/docs/external/external-backend-ux-flows.md +115 -0
  76. package/docs/external/external-object-mapping.md +125 -0
  77. package/docs/external/git-forge-interface.md +68 -0
  78. package/docs/external/github-integration-design.md +151 -0
  79. package/docs/external/issue-tracking-interface.md +66 -0
  80. package/docs/external/provider-capability-manifests.md +204 -0
  81. package/docs/external/provider-catalog.md +139 -0
  82. package/docs/external/provider-rollout-testing.md +78 -0
  83. package/docs/external/research-results.md +48 -0
  84. package/docs/external/security-auth-permissions.md +81 -0
  85. package/docs/external/sync-state-machines.md +108 -0
  86. package/docs/external/unified-external-backend-model.md +107 -0
  87. package/docs/external/user-facing-changes.md +67 -0
  88. package/docs/gaps.md +161 -0
  89. package/docs/install.md +94 -0
  90. package/docs/integration-and-design-decisions.md +1530 -0
  91. package/docs/kradle-design.md +334 -0
  92. package/docs/local-minikube.md +55 -0
  93. package/docs/ontology/README.md +32 -0
  94. package/docs/ontology/bounded-contexts.md +29 -0
  95. package/docs/ontology/events-and-hooks.md +32 -0
  96. package/docs/ontology/oam-kubevela.md +32 -0
  97. package/docs/ontology/operations-and-release.md +25 -0
  98. package/docs/ontology/personas-and-actors.md +32 -0
  99. package/docs/ontology/policies-and-invariants.md +33 -0
  100. package/docs/ontology/problem-space.md +30 -0
  101. package/docs/ontology/resource-contracts.md +40 -0
  102. package/docs/ontology/resource-taxonomy.md +42 -0
  103. package/docs/ontology/runners-and-ci.md +29 -0
  104. package/docs/ontology/solution-space.md +24 -0
  105. package/docs/ontology/storage-and-data-boundaries.md +29 -0
  106. package/docs/ontology/validation-matrix.md +24 -0
  107. package/docs/ontology/web-ui-excellent-flows.md +32 -0
  108. package/docs/ontology/workflows.md +39 -0
  109. package/docs/ontology/world.md +35 -0
  110. package/docs/openapi.yaml +1291 -0
  111. package/docs/product-requirements.md +62 -0
  112. package/docs/requirements-v2.md +235 -0
  113. package/docs/roadmap-mvp.md +87 -0
  114. package/docs/sdk-api-reference.md +1108 -0
  115. package/docs/system-requirements.md +90 -0
  116. package/docs/system-spec-v2.md +1230 -0
  117. package/docs/tests/README.md +53 -0
  118. package/docs/tests/agent-qa-plan.md +63 -0
  119. package/docs/tests/browser-ui-tests.md +62 -0
  120. package/docs/tests/ci-quality-gates.md +48 -0
  121. package/docs/tests/coverage-model.md +64 -0
  122. package/docs/tests/e2e-scenario-tests.md +53 -0
  123. package/docs/tests/fixtures-test-data.md +63 -0
  124. package/docs/tests/observability-reliability-tests.md +54 -0
  125. package/docs/tests/product-test-matrix.md +145 -0
  126. package/docs/tests/qa-adoption-roadmap.md +130 -0
  127. package/docs/tests/qa-automation-plan.md +101 -0
  128. package/docs/tests/security-compliance-tests.md +57 -0
  129. package/docs/tests/test-framework-tools.md +88 -0
  130. package/docs/tests/test-suite-layout.md +121 -0
  131. package/docs/tests/unit-integration-tests.md +48 -0
  132. package/docs/todo-kyverno +714 -0
  133. package/docs/todos.md +4 -0
  134. package/docs/user-stories.md +78 -0
  135. package/docs/web-console-spec.md +533 -0
  136. package/examples/minikube-demo.yaml +190 -0
  137. package/examples/oam-application.yaml +23 -0
  138. package/examples/policy-kyverno-pr-title.yaml +18 -0
  139. package/package.json +66 -0
  140. package/scripts/build.mjs +29 -0
  141. package/scripts/setup-minikube.mjs +65 -0
  142. package/scripts/smoke.mjs +37 -0
  143. package/scripts/validate-doc-coverage.mjs +152 -0
  144. package/scripts/validate-package.mjs +95 -0
  145. package/scripts/validate-ui.mjs +305 -0
  146. package/src/agent-adapter-controller.js +169 -0
  147. package/src/agent-approval-controller.js +170 -0
  148. package/src/agent-context-bundles.js +242 -0
  149. package/src/agent-dispatch-controller.js +549 -0
  150. package/src/agent-gateway-config-controller.js +147 -0
  151. package/src/agent-identity-migration.js +115 -0
  152. package/src/agent-memory-controller.js +357 -0
  153. package/src/agent-memory-import.js +327 -0
  154. package/src/agent-memory-query.js +292 -0
  155. package/src/agent-memory-repository-source-controller.js +255 -0
  156. package/src/agent-mux-client.js +589 -0
  157. package/src/agent-permission-review.js +250 -0
  158. package/src/agent-persona-controller.js +135 -0
  159. package/src/agent-project-controller.js +117 -0
  160. package/src/agent-prompt-composition.js +55 -0
  161. package/src/agent-provider-config-controller.js +151 -0
  162. package/src/agent-secret-config-grant-controller.js +282 -0
  163. package/src/agent-session-transcript-controller.js +189 -0
  164. package/src/agent-stack-controller.js +421 -0
  165. package/src/agent-subagent-controller.js +160 -0
  166. package/src/agent-transport-binding-controller.js +121 -0
  167. package/src/agent-trigger-controller.js +387 -0
  168. package/src/agent-workspace-controller.js +702 -0
  169. package/src/agent-writeback-controller.js +302 -0
  170. package/src/api-controller.js +621 -0
  171. package/src/argocd-gitops.js +43 -0
  172. package/src/artifact-registry-controller.js +542 -0
  173. package/src/assistant-runtime.js +284 -0
  174. package/src/async-controller.js +207 -0
  175. package/src/audit-controller.js +191 -0
  176. package/src/auth.js +310 -0
  177. package/src/component-catalog.js +41 -0
  178. package/src/control-plane.js +136 -0
  179. package/src/controller-client.js +112 -0
  180. package/src/controller-ui.js +620 -0
  181. package/src/data-plane.js +179 -0
  182. package/src/event-bus.js +397 -0
  183. package/src/external/conflict-controller.js +225 -0
  184. package/src/external/github/auth.js +96 -0
  185. package/src/external/github/cicd.js +180 -0
  186. package/src/external/github/git-forge.js +240 -0
  187. package/src/external/github/index.js +144 -0
  188. package/src/external/github/issue-tracking.js +163 -0
  189. package/src/external/provider-adapter.js +161 -0
  190. package/src/external/provider-resource-factory.js +221 -0
  191. package/src/external/sync-controller.js +235 -0
  192. package/src/external/webhook-controller.js +144 -0
  193. package/src/external/write-controller.js +283 -0
  194. package/src/gitea-backend.js +131 -0
  195. package/src/gitea-service.js +173 -0
  196. package/src/handoff.js +98 -0
  197. package/src/health-probes.js +134 -0
  198. package/src/hooks-events.js +63 -0
  199. package/src/hooks-lifecycle.js +117 -0
  200. package/src/http-server.js +409 -0
  201. package/src/identity-policy.js +86 -0
  202. package/src/index.js +71 -0
  203. package/src/jitsi-agent-bridge.js +141 -0
  204. package/src/jitsi-meeting-controller.js +291 -0
  205. package/src/jitsi-sync-controller.js +198 -0
  206. package/src/kradle-inference-service-controller.js +246 -0
  207. package/src/kubernetes-controller-async.js +531 -0
  208. package/src/kubernetes-controller.js +904 -0
  209. package/src/kubernetes-resource-gateway.js +48 -0
  210. package/src/model-route-controller.js +364 -0
  211. package/src/notification-controller.js +178 -0
  212. package/src/operations.js +112 -0
  213. package/src/org-scoping.js +5 -0
  214. package/src/resource-model.js +282 -0
  215. package/src/runner-controller.js +272 -0
  216. package/src/runners-ci.js +48 -0
  217. package/src/runtime.js +196 -0
  218. package/src/snapshot-cache.js +157 -0
  219. package/src/virtual-model-controller.js +538 -0
  220. package/src/virtual-model-hook-bridge.js +200 -0
  221. package/src/web-ui.js +40 -0
  222. package/tests/agent-adapter-controller.test.js +361 -0
  223. package/tests/agent-approval-controller.test.js +173 -0
  224. package/tests/agent-context-bundles.test.js +278 -0
  225. package/tests/agent-dispatch-controller.test.js +679 -0
  226. package/tests/agent-gateway-config-controller.test.js +386 -0
  227. package/tests/agent-identity-migration.test.js +87 -0
  228. package/tests/agent-memory-controller.test.js +461 -0
  229. package/tests/agent-memory-import-snapshot.test.js +477 -0
  230. package/tests/agent-memory-query.test.js +404 -0
  231. package/tests/agent-memory-repository-source.test.js +514 -0
  232. package/tests/agent-mux-client.test.js +389 -0
  233. package/tests/agent-mux-integration.test.js +971 -0
  234. package/tests/agent-permission-review-v2.test.js +317 -0
  235. package/tests/agent-permission-review.test.js +209 -0
  236. package/tests/agent-persona-controller.test.js +127 -0
  237. package/tests/agent-project-controller.test.js +302 -0
  238. package/tests/agent-prompt-composition.test.js +76 -0
  239. package/tests/agent-provider-config-controller.test.js +376 -0
  240. package/tests/agent-resources.test.js +303 -0
  241. package/tests/agent-secret-config-grant.test.js +231 -0
  242. package/tests/agent-session-transcript-controller.test.js +499 -0
  243. package/tests/agent-stack-controller.test.js +283 -0
  244. package/tests/agent-subagent-controller.test.js +201 -0
  245. package/tests/agent-transport-binding-controller.test.js +294 -0
  246. package/tests/agent-trigger-controller.test.js +271 -0
  247. package/tests/agent-trigger-routes.test.js +190 -0
  248. package/tests/agent-trigger-sources.test.js +245 -0
  249. package/tests/agent-workspace-controller.test.js +181 -0
  250. package/tests/agent-writeback.test.js +292 -0
  251. package/tests/approval-persistence.test.js +171 -0
  252. package/tests/artifact-registry.test.js +511 -0
  253. package/tests/assistant-runtime.test.js +506 -0
  254. package/tests/async-controller.test.js +252 -0
  255. package/tests/audit-controller.test.js +227 -0
  256. package/tests/codespace-controller.test.js +318 -0
  257. package/tests/controller-client.test.js +133 -0
  258. package/tests/deployment.test.js +527 -0
  259. package/tests/e2e/lifecycle.test.js +120 -0
  260. package/tests/event-bus-integration.test.js +355 -0
  261. package/tests/external-github-forge.test.js +560 -0
  262. package/tests/external-github-issues-cicd.test.js +520 -0
  263. package/tests/external-integration.test.js +470 -0
  264. package/tests/external-persistence.test.js +415 -0
  265. package/tests/external-provider-adapter.test.js +365 -0
  266. package/tests/external-resource-model.test.js +223 -0
  267. package/tests/external-webhook-sync.test.js +287 -0
  268. package/tests/external-write-conflict.test.js +353 -0
  269. package/tests/gitea-service.test.js +253 -0
  270. package/tests/health-check-real.test.js +165 -0
  271. package/tests/health-probes.test.js +90 -0
  272. package/tests/hooks-lifecycle.test.js +364 -0
  273. package/tests/integration/full-flow.test.js +266 -0
  274. package/tests/jitsi-agent-bridge.test.js +119 -0
  275. package/tests/jitsi-helm-integration.test.js +77 -0
  276. package/tests/jitsi-meeting-controller.test.js +170 -0
  277. package/tests/jitsi-resource-model.test.js +73 -0
  278. package/tests/jitsi-sync-controller.test.js +112 -0
  279. package/tests/kradle-inference-service.test.js +689 -0
  280. package/tests/kradle.test.js +779 -0
  281. package/tests/memory-search-wiring.test.js +270 -0
  282. package/tests/model-route-controller.test.js +733 -0
  283. package/tests/notification-controller.test.js +196 -0
  284. package/tests/notification-integration.test.js +179 -0
  285. package/tests/org-scoping.test.js +687 -0
  286. package/tests/runner-controller.test.js +327 -0
  287. package/tests/runner-integration.test.js +231 -0
  288. package/tests/session-cookie-hmac.test.js +151 -0
  289. package/tests/snapshot-performance.test.js +315 -0
  290. package/tests/sse-events.test.js +107 -0
  291. package/tests/virtual-model-controller.test.js +877 -0
  292. package/tests/virtual-model-hook-bridge.test.js +384 -0
  293. package/tests/webhook-trigger.test.js +198 -0
  294. package/tests/workspace-volumes.test.js +312 -0
  295. package/tests/writeback-persistence.test.js +207 -0
@@ -0,0 +1,200 @@
1
+ // Virtual Model Hook Bridge
2
+ // Bridges KradleVirtualModel CRD hooks to the agent-mux hook dispatcher.
3
+
4
+ import { createVirtualModelController } from './virtual-model-controller.js';
5
+
6
+ export const VIRTUAL_MODEL_HOOK_BRIDGE_BOUNDARY = {
7
+ role: 'virtual-model-hook-bridge',
8
+ scope: 'Bridge between KradleVirtualModel CRD hooks and agent-mux hook dispatcher',
9
+ owns: ['virtual model matching', 'hook type mapping', 'result format conversion'],
10
+ delegatesTo: ['virtual-model-controller', 'resource-model'],
11
+ mustNotOwn: ['hook dispatch orchestration', 'model invocation']
12
+ };
13
+
14
+ export const VIRTUAL_MODEL_HOOK_TYPES = [
15
+ 'VirtualModel.PreModelResolution',
16
+ 'VirtualModel.PreCompletion',
17
+ 'VirtualModel.PostCompletion',
18
+ 'VirtualModel.PreToolUse',
19
+ 'VirtualModel.PostToolUse',
20
+ 'VirtualModel.TurnEnd',
21
+ 'VirtualModel.SessionStart',
22
+ 'VirtualModel.SessionEnd',
23
+ 'VirtualModel.UserPromptSubmit',
24
+ 'VirtualModel.OnError',
25
+ 'VirtualModel.OnCompact',
26
+ ];
27
+
28
+ /**
29
+ * Find a virtual model whose spec.modelName matches the given modelName.
30
+ * @param {string} modelName
31
+ * @param {object[]} virtualModels
32
+ * @returns {object|null}
33
+ */
34
+ function matchVirtualModel(modelName, virtualModels) {
35
+ if (!modelName || !Array.isArray(virtualModels)) return null;
36
+ for (const vm of virtualModels) {
37
+ if (vm?.spec?.modelName === modelName) return vm;
38
+ }
39
+ return null;
40
+ }
41
+
42
+ /**
43
+ * Handle a hook invocation by mapping it to the appropriate controller method
44
+ * and converting the result to UnifiedHookResult format.
45
+ *
46
+ * @param {string} hookType - One of VIRTUAL_MODEL_HOOK_TYPES
47
+ * @param {object} payload - The hook payload data
48
+ * @param {object|null} virtualModel - The matched KradleVirtualModel resource
49
+ * @param {object} controller - The virtual model controller instance
50
+ * @returns {{ decision: string, modifiedInput?: object, message?: string }}
51
+ */
52
+ function handleHook(hookType, payload, virtualModel, controller) {
53
+ if (!virtualModel) {
54
+ return { decision: 'allow' };
55
+ }
56
+
57
+ switch (hookType) {
58
+ case 'VirtualModel.PreModelResolution': {
59
+ const requestContext = payload?.data?.requestContext || payload?.data || {};
60
+ const resources = payload?.data?.resources || [];
61
+ const result = controller.resolveRoute(virtualModel, requestContext, resources);
62
+ if (result.routeRef) {
63
+ return { decision: 'modify', modifiedInput: { modelId: result.routeRef } };
64
+ }
65
+ return { decision: 'allow' };
66
+ }
67
+
68
+ case 'VirtualModel.PreCompletion': {
69
+ const request = payload?.data?.request || {};
70
+ const context = payload?.data?.context || {};
71
+ const transformed = controller.transformRequest(virtualModel, request, context);
72
+ if (transformed !== request) {
73
+ return { decision: 'modify', modifiedInput: { request: transformed } };
74
+ }
75
+ return { decision: 'allow' };
76
+ }
77
+
78
+ case 'VirtualModel.PostCompletion': {
79
+ const response = payload?.data?.response || {};
80
+ const context = payload?.data?.context || {};
81
+ const transformed = controller.transformResponse(virtualModel, response, context);
82
+ if (transformed !== response) {
83
+ return { decision: 'modify', modifiedInput: { response: transformed } };
84
+ }
85
+ return { decision: 'allow' };
86
+ }
87
+
88
+ case 'VirtualModel.PreToolUse': {
89
+ const toolCall = payload?.data?.toolCall || payload?.data?.tool_input || {};
90
+ const session = payload?.data?.session || {};
91
+ const result = controller.firePreToolUse(virtualModel, toolCall, session);
92
+ return { decision: result.allow ? 'allow' : 'deny' };
93
+ }
94
+
95
+ case 'VirtualModel.PostToolUse': {
96
+ const toolCall = payload?.data?.toolCall || {};
97
+ const toolResult = payload?.data?.toolResult || payload?.data?.tool_output || {};
98
+ const session = payload?.data?.session || {};
99
+ const result = controller.firePostToolUse(virtualModel, toolCall, toolResult, session);
100
+ if (result.modified) {
101
+ return { decision: 'modify', modifiedInput: { result: result.modified } };
102
+ }
103
+ return { decision: 'allow' };
104
+ }
105
+
106
+ case 'VirtualModel.TurnEnd': {
107
+ const turn = payload?.data?.turn || {};
108
+ const session = payload?.data?.session || {};
109
+ const result = controller.fireTurnEnd(virtualModel, turn, session);
110
+ return { decision: 'allow', modifiedInput: { action: result.action } };
111
+ }
112
+
113
+ case 'VirtualModel.SessionStart': {
114
+ const session = payload?.data?.session || {};
115
+ controller.fireSessionStart(virtualModel, session);
116
+ return { decision: 'allow' };
117
+ }
118
+
119
+ case 'VirtualModel.SessionEnd': {
120
+ const session = payload?.data?.session || {};
121
+ controller.fireSessionEnd(virtualModel, session);
122
+ return { decision: 'allow' };
123
+ }
124
+
125
+ case 'VirtualModel.UserPromptSubmit': {
126
+ const prompt = payload?.data?.prompt || payload?.data?.message || '';
127
+ const session = payload?.data?.session || {};
128
+ const result = controller.fireUserPromptSubmit(virtualModel, prompt, session);
129
+ if (result.block) {
130
+ return { decision: 'deny', message: 'Blocked by virtual model hook' };
131
+ }
132
+ if (result.modified) {
133
+ return { decision: 'modify', modifiedInput: { prompt: result.modified } };
134
+ }
135
+ return { decision: 'allow' };
136
+ }
137
+
138
+ case 'VirtualModel.OnError': {
139
+ const error = payload?.data?.error || {};
140
+ const session = payload?.data?.session || {};
141
+ const result = controller.fireError(virtualModel, error, session);
142
+ return { decision: 'modify', modifiedInput: { retry: result.retry, fallbackRoute: result.fallbackRoute } };
143
+ }
144
+
145
+ case 'VirtualModel.OnCompact': {
146
+ const summary = payload?.data?.summary || {};
147
+ const session = payload?.data?.session || {};
148
+ const result = controller.fireCompact(virtualModel, summary, session);
149
+ if (result.modified) {
150
+ return { decision: 'modify', modifiedInput: { summary: result.modified } };
151
+ }
152
+ return { decision: 'allow' };
153
+ }
154
+
155
+ default:
156
+ return { decision: 'allow' };
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Create a virtual model hook bridge instance.
162
+ *
163
+ * @param {object} [options]
164
+ * @param {object} [options.controller] - Virtual model controller (created if not supplied)
165
+ * @param {object} [options.eventBus] - Event bus for the controller
166
+ * @returns {{ matchVirtualModel: Function, handleHook: Function, hookTypes: string[] }}
167
+ */
168
+ export function createVirtualModelHookBridge(options = {}) {
169
+ const controller = options.controller || createVirtualModelController({
170
+ eventBus: options.eventBus
171
+ });
172
+
173
+ return {
174
+ role: 'virtual-model-hook-bridge',
175
+
176
+ /**
177
+ * Find a virtual model by modelName.
178
+ * @param {string} modelName
179
+ * @param {object[]} virtualModels
180
+ * @returns {object|null}
181
+ */
182
+ matchVirtualModel(modelName, virtualModels) {
183
+ return matchVirtualModel(modelName, virtualModels);
184
+ },
185
+
186
+ /**
187
+ * Handle a hook invocation.
188
+ * @param {string} hookType
189
+ * @param {object} payload
190
+ * @param {object|null} virtualModel
191
+ * @returns {{ decision: string, modifiedInput?: object, message?: string }}
192
+ */
193
+ handleHook(hookType, payload, virtualModel) {
194
+ return handleHook(hookType, payload, virtualModel, controller);
195
+ },
196
+
197
+ /** All supported hook types. */
198
+ hookTypes: VIRTUAL_MODEL_HOOK_TYPES,
199
+ };
200
+ }
package/src/web-ui.js ADDED
@@ -0,0 +1,40 @@
1
+ import { createResource } from './resource-model.js';
2
+ import { toResourceYaml } from './identity-policy.js';
3
+
4
+ export function createPullRequestReviewModel({ pullRequest, changedFiles = [], pipelineRuns = [] }) {
5
+ return { layout: 'three-pane-review', panes: ['file-tree', 'diff-and-comments', 'conversation-ci'], keyboardShortcuts: ['j/k file navigation', 'n/p comment navigation', 'a add suggestion', 'm merge'], pullRequest, changedFiles, pipelineRuns, yaml: toResourceYaml(pullRequest) };
6
+ }
7
+
8
+ export function createFailingRunModel({ pipeline, jobs }) {
9
+ const failedJobs = jobs.filter((job) => job.status.phase === 'Failed');
10
+ return { layout: 'live-run-debugger', stream: 'sse', pipeline, failedJobs, actions: ['copy failure', 'find similar runs', 'rerun from step'], similarRunSelector: failedJobs.map((job) => job.metadata.labels).filter(Boolean) };
11
+ }
12
+
13
+ export function createRunnerPoolEditor(pool) {
14
+ return { layout: 'split-form-yaml', fields: ['image', 'resources', 'nodeSelector', 'warmReplicas', 'maxReplicas', 'trustTier', 'cache'], resource: pool, yaml: toResourceYaml(pool), saveModes: ['apply', 'copy kubectl', 'open platform-config PR'] };
15
+ }
16
+
17
+ export function createWebhookInspector({ subscription, deliveries }) {
18
+ return { layout: 'webhook-inspector', subscription, deliveries, columns: ['phase', 'latency', 'attempts', 'response', 'signature'], actions: ['send test delivery', 'inspect headers/body/response', 'replay'] };
19
+ }
20
+
21
+ export function createPolicyRolloutModel(policy) {
22
+ return { layout: 'policy-authoring', modes: ['template', 'CEL/raw'], rollout: ['preview', 'audit', 'enforce'], policy, yaml: toResourceYaml(policy) };
23
+ }
24
+
25
+ export function createTriageView({ name, namespace = 'kradle-org-default', organizationRef = 'default', selector }) {
26
+ return createResource('View', { name, namespace, labels: { purpose: 'triage' } }, { organizationRef, selector, columns: ['kind', 'repository', 'priority', 'assignee', 'status'], shareable: true }, { saved: true });
27
+ }
28
+
29
+ export function createDashboard({ repositories, pullRequests, pipelines, runnerPools, webhookDeliveries }) {
30
+ return {
31
+ product: 'Kradle',
32
+ principles: ['Kubernetes is the backend', 'CRDs are contracts', 'GitOps transparency'],
33
+ repositories,
34
+ pullRequests,
35
+ pipelines,
36
+ runnerPools,
37
+ webhookDeliveries,
38
+ excellentFlows: ['Open and review a PR', 'Debug a failing run', 'Configure a runner pool', 'Add a webhook and verify it works', 'Write a PR policy with audit-to-enforce rollout', 'Cross-repo triage with saved filters']
39
+ };
40
+ }
@@ -0,0 +1,361 @@
1
+ import assert from 'node:assert/strict';
2
+ import test from 'node:test';
3
+ import { createAgentAdapterController, validateAgentAdapter, createResource, AGENT_ADAPTER_CONTROLLER_BOUNDARY } from '../src/index.js';
4
+
5
+ // ---------------------------------------------------------------------------
6
+ // Acceptance criteria: Slice 1.2a — Agent Adapter Controller
7
+ //
8
+ // An AgentAdapter defines how an agent connects to a model provider.
9
+ // It specifies adapter name/type, supported transport protocols
10
+ // (stdio, http, websocket), a capabilities matrix, and a health check
11
+ // endpoint/method.
12
+ //
13
+ // All tests in this file are expected to FAIL until the controller is
14
+ // implemented and exported from src/index.js.
15
+ // ---------------------------------------------------------------------------
16
+
17
+ const VALID_TRANSPORTS = ['stdio', 'http', 'websocket', 'unix'];
18
+ const VALID_ADAPTER_TYPES = ['subprocess', 'remote', 'programmatic'];
19
+
20
+ function makeAdapter(name, overrides = {}) {
21
+ return createResource('AgentAdapter', { name, namespace: 'kradle-org-default' }, {
22
+ organizationRef: 'default',
23
+ adapterType: 'subprocess',
24
+ transport: 'stdio',
25
+ capabilities: ['tool-use', 'streaming'],
26
+ ...overrides
27
+ });
28
+ }
29
+
30
+ // ---------------------------------------------------------------------------
31
+ // 1. Factory and shape
32
+ // ---------------------------------------------------------------------------
33
+
34
+ test('createAgentAdapterController returns a controller with validate method', () => {
35
+ const controller = createAgentAdapterController();
36
+ assert.ok(controller, 'controller must be truthy');
37
+ assert.equal(typeof controller.validate, 'function', 'controller must expose a validate method');
38
+ assert.equal(controller.role, 'agent-adapter-controller', 'controller must declare its role');
39
+ });
40
+
41
+ // ---------------------------------------------------------------------------
42
+ // 2. validate — happy path
43
+ // ---------------------------------------------------------------------------
44
+
45
+ test('validate accepts a valid adapter with name, type, and transport', () => {
46
+ const controller = createAgentAdapterController();
47
+ const adapter = makeAdapter('claude-code-adapter');
48
+ const result = controller.validate(adapter);
49
+
50
+ assert.equal(result.valid, true, 'valid adapter must pass validation');
51
+ assert.ok(Array.isArray(result.errors), 'result must contain an errors array');
52
+ assert.equal(result.errors.length, 0, 'errors array must be empty for a valid adapter');
53
+ });
54
+
55
+ // ---------------------------------------------------------------------------
56
+ // 3. validate — missing name
57
+ // ---------------------------------------------------------------------------
58
+
59
+ test('validate rejects adapter with missing name', () => {
60
+ const controller = createAgentAdapterController();
61
+ // Build a raw object without metadata.name to bypass createResource guard
62
+ const adapter = {
63
+ apiVersion: 'kradle.a5c.ai/v1alpha1',
64
+ kind: 'AgentAdapter',
65
+ metadata: { namespace: 'kradle-org-default', labels: {}, annotations: {} },
66
+ spec: { organizationRef: 'default', adapterType: 'subprocess', transport: 'stdio', capabilities: ['tool-use'] },
67
+ status: {}
68
+ };
69
+ const result = controller.validate(adapter);
70
+
71
+ assert.equal(result.valid, false, 'adapter without a name must fail validation');
72
+ assert.ok(result.errors.length > 0, 'errors array must not be empty');
73
+ assert.ok(
74
+ result.errors.some((e) => /name/i.test(e)),
75
+ 'at least one error must mention "name"'
76
+ );
77
+ });
78
+
79
+ // ---------------------------------------------------------------------------
80
+ // 4. validate — invalid transport type
81
+ // ---------------------------------------------------------------------------
82
+
83
+ test('validate rejects adapter with invalid transport type', () => {
84
+ const controller = createAgentAdapterController();
85
+ const adapter = makeAdapter('bad-transport-adapter', { transport: 'grpc' });
86
+ const result = controller.validate(adapter);
87
+
88
+ assert.equal(result.valid, false, 'adapter with unsupported transport must fail validation');
89
+ assert.ok(result.errors.length > 0, 'errors array must not be empty');
90
+ assert.ok(
91
+ result.errors.some((e) => /transport/i.test(e)),
92
+ 'at least one error must mention "transport"'
93
+ );
94
+ // Confirm that valid transports are enumerated in the error message
95
+ assert.ok(
96
+ result.errors.some((e) => VALID_TRANSPORTS.some((t) => e.includes(t))),
97
+ 'error must enumerate valid transport types'
98
+ );
99
+ });
100
+
101
+ // ---------------------------------------------------------------------------
102
+ // 5. validate — empty capabilities array
103
+ // ---------------------------------------------------------------------------
104
+
105
+ test('validate rejects adapter with empty capabilities array', () => {
106
+ const controller = createAgentAdapterController();
107
+ const adapter = makeAdapter('no-caps-adapter', { capabilities: [] });
108
+ const result = controller.validate(adapter);
109
+
110
+ assert.equal(result.valid, false, 'adapter with empty capabilities must fail validation');
111
+ assert.ok(result.errors.length > 0, 'errors array must not be empty');
112
+ assert.ok(
113
+ result.errors.some((e) => /capabilities/i.test(e)),
114
+ 'at least one error must mention "capabilities"'
115
+ );
116
+ });
117
+
118
+ // ---------------------------------------------------------------------------
119
+ // 6. getCapabilities — capabilities matrix
120
+ // ---------------------------------------------------------------------------
121
+
122
+ test('getCapabilities returns the capabilities matrix for an adapter', () => {
123
+ const controller = createAgentAdapterController();
124
+ const adapter = makeAdapter('full-adapter', {
125
+ capabilities: ['tool-use', 'streaming', 'file-access']
126
+ });
127
+ const matrix = controller.getCapabilities(adapter);
128
+
129
+ assert.ok(matrix, 'getCapabilities must return a value');
130
+ assert.ok(Array.isArray(matrix.supported), 'matrix must have a supported array');
131
+ assert.ok(matrix.supported.includes('tool-use'), 'supported must include tool-use');
132
+ assert.ok(matrix.supported.includes('streaming'), 'supported must include streaming');
133
+ assert.ok(matrix.supported.includes('file-access'), 'supported must include file-access');
134
+ assert.equal(matrix.adapterName, adapter.metadata.name, 'matrix must carry the adapter name');
135
+ });
136
+
137
+ // ---------------------------------------------------------------------------
138
+ // 7. getSupportedTransports — transport enumeration
139
+ // ---------------------------------------------------------------------------
140
+
141
+ test('getSupportedTransports returns the transport types', () => {
142
+ const controller = createAgentAdapterController();
143
+ const transports = controller.getSupportedTransports();
144
+
145
+ assert.ok(Array.isArray(transports), 'getSupportedTransports must return an array');
146
+ assert.equal(transports.length, 4, 'exactly four transport types must be supported');
147
+ assert.ok(transports.includes('stdio'), 'transports must include stdio');
148
+ assert.ok(transports.includes('http'), 'transports must include http');
149
+ assert.ok(transports.includes('websocket'), 'transports must include websocket');
150
+ assert.ok(transports.includes('unix'), 'transports must include unix');
151
+ });
152
+
153
+ // ---------------------------------------------------------------------------
154
+ // 8. healthCheck — stub result when no endpoint configured
155
+ // ---------------------------------------------------------------------------
156
+
157
+ test('healthCheck returns a result with status "unknown" when no endpoint configured', async () => {
158
+ const controller = createAgentAdapterController();
159
+ const adapter = makeAdapter('no-endpoint-adapter');
160
+ // Adapter has no healthEndpoint in spec
161
+ const result = await controller.healthCheck(adapter);
162
+
163
+ assert.ok(result, 'healthCheck must return a result');
164
+ assert.equal(result.status, 'unknown', 'status must be "unknown" when no endpoint is configured');
165
+ assert.equal(result.adapterName, adapter.metadata.name, 'result must carry the adapter name');
166
+ assert.equal(result.reason, 'no-endpoint', 'reason must be "no-endpoint"');
167
+ });
168
+
169
+ // ---------------------------------------------------------------------------
170
+ // 9. validateAgentAdapter — standalone export follows existing pattern
171
+ // ---------------------------------------------------------------------------
172
+
173
+ test('validateAgentAdapter exported from controller follows existing pattern', () => {
174
+ assert.equal(typeof validateAgentAdapter, 'function', 'validateAgentAdapter must be a named export');
175
+
176
+ const adapter = makeAdapter('standalone-validate-adapter');
177
+ const result = validateAgentAdapter(adapter);
178
+
179
+ assert.ok(result, 'validateAgentAdapter must return a result');
180
+ assert.ok('valid' in result, 'result must have a valid property');
181
+ assert.ok(Array.isArray(result.errors), 'result must have an errors array');
182
+ assert.equal(result.valid, true, 'a fully-specified adapter must pass standalone validation');
183
+ });
184
+
185
+ // ---------------------------------------------------------------------------
186
+ // 10. validate — missing adapterType
187
+ // ---------------------------------------------------------------------------
188
+
189
+ test('validate rejects adapter with missing adapterType', () => {
190
+ const controller = createAgentAdapterController();
191
+ const adapter = makeAdapter('no-type-adapter', { adapterType: undefined });
192
+ // Remove adapterType from spec entirely
193
+ delete adapter.spec.adapterType;
194
+ const result = controller.validate(adapter);
195
+
196
+ assert.equal(result.valid, false, 'adapter without adapterType must fail validation');
197
+ assert.ok(result.errors.length > 0, 'errors array must not be empty');
198
+ assert.ok(
199
+ result.errors.some((e) => /adapterType/i.test(e)),
200
+ 'at least one error must mention "adapterType"'
201
+ );
202
+ });
203
+
204
+ // ---------------------------------------------------------------------------
205
+ // 11. validate — invalid adapterType ('garbage')
206
+ // ---------------------------------------------------------------------------
207
+
208
+ test('validate rejects adapter with invalid adapterType', () => {
209
+ const controller = createAgentAdapterController();
210
+ const adapter = makeAdapter('bad-type-adapter', { adapterType: 'garbage' });
211
+ const result = controller.validate(adapter);
212
+
213
+ assert.equal(result.valid, false, 'adapter with unsupported adapterType must fail validation');
214
+ assert.ok(result.errors.length > 0, 'errors array must not be empty');
215
+ assert.ok(
216
+ result.errors.some((e) => /adapterType/i.test(e)),
217
+ 'at least one error must mention "adapterType"'
218
+ );
219
+ assert.ok(
220
+ result.errors.some((e) => VALID_ADAPTER_TYPES.some((t) => e.includes(t))),
221
+ 'error must enumerate valid adapter types'
222
+ );
223
+ });
224
+
225
+ // ---------------------------------------------------------------------------
226
+ // 12. validate — unix transport is valid
227
+ // ---------------------------------------------------------------------------
228
+
229
+ test('validate accepts adapter with unix transport', () => {
230
+ const controller = createAgentAdapterController();
231
+ const adapter = makeAdapter('unix-transport-adapter', { transport: 'unix' });
232
+ const result = controller.validate(adapter);
233
+
234
+ assert.equal(result.valid, true, 'adapter with unix transport must pass validation');
235
+ assert.equal(result.errors.length, 0, 'errors array must be empty');
236
+ });
237
+
238
+ // ---------------------------------------------------------------------------
239
+ // 13. healthCheck returns 'not-implemented' when healthEndpoint IS configured
240
+ // ---------------------------------------------------------------------------
241
+
242
+ test('healthCheck performs real HTTP check when healthEndpoint is configured', async () => {
243
+ // Use a mock fetch to avoid real network calls in unit tests
244
+ const mockFetch = async () => ({ ok: true, status: 200 });
245
+ const controller = createAgentAdapterController({ fetch: mockFetch });
246
+ const adapter = makeAdapter('endpoint-adapter', { healthEndpoint: 'http://localhost:9090/health' });
247
+ const result = await controller.healthCheck(adapter);
248
+
249
+ assert.ok(result, 'healthCheck must return a result');
250
+ assert.equal(result.status, 'healthy', 'status must be "healthy" when endpoint fetch succeeds');
251
+ assert.equal(result.adapterName, adapter.metadata.name, 'result must carry the adapter name');
252
+ assert.ok(typeof result.latencyMs === 'number', 'result must include latencyMs');
253
+ });
254
+
255
+ // ---------------------------------------------------------------------------
256
+ // 14. validate / getCapabilities / healthCheck reject null resource
257
+ // ---------------------------------------------------------------------------
258
+
259
+ test('validate rejects null resource with a clear error', () => {
260
+ const controller = createAgentAdapterController();
261
+ const result = controller.validate(null);
262
+
263
+ assert.equal(result.valid, false, 'null resource must fail validation');
264
+ assert.ok(result.errors.length > 0, 'errors array must not be empty');
265
+ assert.ok(
266
+ result.errors.some((e) => /null|undefined/i.test(e)),
267
+ 'error must mention null or undefined'
268
+ );
269
+ });
270
+
271
+ test('getCapabilities throws on null resource', () => {
272
+ const controller = createAgentAdapterController();
273
+ assert.throws(
274
+ () => controller.getCapabilities(null),
275
+ /null|undefined/i,
276
+ 'getCapabilities must throw on null resource'
277
+ );
278
+ });
279
+
280
+ test('healthCheck rejects on null resource', async () => {
281
+ const controller = createAgentAdapterController();
282
+ await assert.rejects(
283
+ () => controller.healthCheck(null),
284
+ /null|undefined/i,
285
+ 'healthCheck must reject on null resource'
286
+ );
287
+ });
288
+
289
+ // ---------------------------------------------------------------------------
290
+ // 15. getSupportedAdapterTypes — adapter type enumeration
291
+ // ---------------------------------------------------------------------------
292
+
293
+ test('getSupportedAdapterTypes returns the valid adapter types array', () => {
294
+ const controller = createAgentAdapterController();
295
+ const types = controller.getSupportedAdapterTypes();
296
+
297
+ assert.ok(Array.isArray(types), 'getSupportedAdapterTypes must return an array');
298
+ assert.equal(types.length, VALID_ADAPTER_TYPES.length, 'must return all valid adapter types');
299
+ assert.ok(types.includes('subprocess'), 'adapter types must include subprocess');
300
+ assert.ok(types.includes('remote'), 'adapter types must include remote');
301
+ assert.ok(types.includes('programmatic'), 'adapter types must include programmatic');
302
+ });
303
+
304
+ // ---------------------------------------------------------------------------
305
+ // 16. getCapabilities with undefined spec (resource with no spec key)
306
+ // ---------------------------------------------------------------------------
307
+
308
+ test('getCapabilities handles resource with no spec key gracefully', () => {
309
+ const controller = createAgentAdapterController();
310
+ const resource = {
311
+ apiVersion: 'kradle.a5c.ai/v1alpha1',
312
+ kind: 'AgentAdapter',
313
+ metadata: { name: 'spec-less-adapter', namespace: 'kradle-org-default', labels: {}, annotations: {} },
314
+ status: {}
315
+ // no spec key at all
316
+ };
317
+ const matrix = controller.getCapabilities(resource);
318
+
319
+ assert.ok(matrix, 'getCapabilities must return a value even with no spec');
320
+ assert.ok(Array.isArray(matrix.supported), 'supported must be an array');
321
+ assert.equal(matrix.supported.length, 0, 'supported must be empty when spec is absent');
322
+ assert.equal(matrix.adapterName, 'spec-less-adapter', 'adapterName must still be populated');
323
+ });
324
+
325
+ // ---------------------------------------------------------------------------
326
+ // 17. validate — simultaneous adapterType AND transport errors accumulate
327
+ // ---------------------------------------------------------------------------
328
+
329
+ test('validate accumulates all errors when both adapterType and transport are invalid', () => {
330
+ const controller = createAgentAdapterController();
331
+ const adapter = makeAdapter('double-bad-adapter', { adapterType: 'bad-type', transport: 'grpc' });
332
+ const result = controller.validate(adapter);
333
+
334
+ assert.equal(result.valid, false, 'adapter with multiple invalid fields must fail validation');
335
+ assert.ok(
336
+ result.errors.some((e) => /adapterType/i.test(e)),
337
+ 'errors must include an adapterType error'
338
+ );
339
+ assert.ok(
340
+ result.errors.some((e) => /transport/i.test(e)),
341
+ 'errors must include a transport error'
342
+ );
343
+ assert.ok(result.errors.length >= 2, 'must accumulate at least two errors');
344
+ });
345
+
346
+ // ---------------------------------------------------------------------------
347
+ // 18. BOUNDARY object export
348
+ // ---------------------------------------------------------------------------
349
+
350
+ test('AGENT_ADAPTER_CONTROLLER_BOUNDARY is exported and has correct role', () => {
351
+ assert.ok(AGENT_ADAPTER_CONTROLLER_BOUNDARY, 'BOUNDARY must be exported');
352
+ assert.equal(
353
+ AGENT_ADAPTER_CONTROLLER_BOUNDARY.role,
354
+ 'agent-adapter-controller',
355
+ 'BOUNDARY role must be "agent-adapter-controller"'
356
+ );
357
+ assert.ok(
358
+ Array.isArray(AGENT_ADAPTER_CONTROLLER_BOUNDARY.owns),
359
+ 'BOUNDARY must declare owned concerns'
360
+ );
361
+ });