@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,421 @@
1
+ import { createPermissionReviewer } from './agent-permission-review.js';
2
+ import { clone } from './resource-model.js';
3
+
4
+ export const AGENT_STACK_CONTROLLER_BOUNDARY = {
5
+ role: 'agent-stack-controller',
6
+ scope: 'Stack readiness reconciliation with capability resolution and condition management',
7
+ owns: ['capability resolution', 'stack conditions', 'readiness computation', 'mcp health checks'],
8
+ delegatesTo: ['agent-permission-review', 'resource-model'],
9
+ mustNotOwn: ['secret values', 'dispatch execution', 'Agent Mux sessions']
10
+ };
11
+
12
+ const MCP_HEALTH_TIMEOUT_MS = 3000;
13
+ const JITSI_ROLES = new Set(['observer', 'participant', 'moderator']);
14
+ const JITSI_TOOLS = new Set([
15
+ 'kradle_send_chat_message',
16
+ 'kradle_get_meeting_transcript',
17
+ 'kradle_get_participant_list',
18
+ 'kradle_raise_hand',
19
+ 'kradle_share_screen',
20
+ 'kradle_invite_to_meeting',
21
+ 'kradle_start_recording',
22
+ 'kradle_react',
23
+ ]);
24
+
25
+ /**
26
+ * Perform an HTTP health check for an MCP server endpoint.
27
+ * @param {string} url
28
+ * @param {Function|null} fetchFn
29
+ * @returns {Promise<{ status: string, latencyMs: number, error?: string }>}
30
+ */
31
+ async function performMcpHealthCheck(url, fetchFn) {
32
+ const fn = fetchFn || globalThis.fetch;
33
+ const start = Date.now();
34
+ try {
35
+ const controller = new AbortController();
36
+ const timer = setTimeout(() => controller.abort(), MCP_HEALTH_TIMEOUT_MS);
37
+ let response;
38
+ try {
39
+ response = await fn(url, { signal: controller.signal });
40
+ } finally {
41
+ clearTimeout(timer);
42
+ }
43
+ const latencyMs = Date.now() - start;
44
+ if (response.ok) {
45
+ return { status: 'healthy', latencyMs };
46
+ }
47
+ return { status: 'unhealthy', latencyMs, error: `HTTP ${response.status}` };
48
+ } catch (err) {
49
+ const latencyMs = Date.now() - start;
50
+ return { status: 'unhealthy', latencyMs, error: err.message || String(err) };
51
+ }
52
+ }
53
+
54
+ export function createAgentStackController(options = {}) {
55
+ const permissionReviewer = options.permissionReviewer || createPermissionReviewer();
56
+ const fetchFn = options.fetch || null;
57
+
58
+ return {
59
+ role: 'agent-stack-controller',
60
+
61
+ reconcileStack(stack, resources = {}) {
62
+ const spec = stack?.spec || {};
63
+ const conditions = [];
64
+ const missing = [];
65
+
66
+ // --- Resolve capability refs from stack spec ---
67
+ const resolvedTools = [];
68
+ const resolvedMcpServers = [];
69
+ const resolvedSkills = [];
70
+ const resolvedSubagents = [];
71
+ const resolvedContextLabels = [];
72
+
73
+ // toolPolicyRef
74
+ const toolPolicyRef = spec.toolPolicy || spec.toolPolicyRef || null;
75
+ let toolPolicyFound = true;
76
+ if (toolPolicyRef) {
77
+ const profiles = resources.AgentToolProfile || [];
78
+ const profile = profiles.find((p) => p.metadata?.name === toolPolicyRef);
79
+ if (profile) {
80
+ resolvedTools.push(profile.metadata.name);
81
+ } else {
82
+ toolPolicyFound = false;
83
+ missing.push(`AgentToolProfile/${toolPolicyRef}`);
84
+ }
85
+ }
86
+
87
+ // mcpServerRefs — support both flat spec.mcpServerRefs and structured spec.externalTools.mcpServerRefs
88
+ const mcpServerRefs = [
89
+ ...(spec.mcpServerRefs || []),
90
+ ...(spec.externalTools?.mcpServerRefs || [])
91
+ ].filter((v, i, a) => a.indexOf(v) === i); // dedupe
92
+ let allMcpFound = true;
93
+ for (const ref of mcpServerRefs) {
94
+ const servers = resources.AgentMcpServer || [];
95
+ const server = servers.find((s) => s.metadata?.name === ref);
96
+ if (server) {
97
+ resolvedMcpServers.push(server.metadata.name);
98
+ } else {
99
+ allMcpFound = false;
100
+ missing.push(`AgentMcpServer/${ref}`);
101
+ }
102
+ }
103
+
104
+ // memoryRepositoryRefs — resolve memory repository associations
105
+ const memoryRepositoryRefs = spec.memoryRepositoryRefs || [];
106
+ const resolvedMemoryRepos = [];
107
+ let allMemoryReposFound = true;
108
+ for (const ref of memoryRepositoryRefs) {
109
+ const repos = resources.AgentMemoryRepository || [];
110
+ const repo = repos.find((r) => r.metadata?.name === ref);
111
+ if (repo) {
112
+ resolvedMemoryRepos.push(repo.metadata.name);
113
+ } else {
114
+ allMemoryReposFound = false;
115
+ missing.push(`AgentMemoryRepository/${ref}`);
116
+ }
117
+ }
118
+
119
+ // skillRefs
120
+ const skillRefs = spec.skillRefs || [];
121
+ let allSkillsFound = true;
122
+ let allSkillsValid = true;
123
+ for (const ref of skillRefs) {
124
+ const skills = resources.AgentSkill || [];
125
+ const skill = skills.find((s) => s.metadata?.name === ref);
126
+ if (skill) {
127
+ resolvedSkills.push(skill.metadata.name);
128
+ if (!skill.spec?.format || !skill.spec?.sourceRef) {
129
+ allSkillsValid = false;
130
+ }
131
+ } else {
132
+ allSkillsFound = false;
133
+ allSkillsValid = false;
134
+ missing.push(`AgentSkill/${ref}`);
135
+ }
136
+ }
137
+
138
+ // subagentRefs
139
+ const subagentRefs = spec.subagentRefs || [];
140
+ let allSubagentsFound = true;
141
+ let allSubagentsValid = true;
142
+ for (const ref of subagentRefs) {
143
+ const subagents = resources.AgentSubagent || [];
144
+ const subagent = subagents.find((s) => s.metadata?.name === ref);
145
+ if (subagent) {
146
+ resolvedSubagents.push(subagent.metadata.name);
147
+ if (!subagent.spec?.taskKinds || subagent.spec.taskKinds.length === 0) {
148
+ allSubagentsValid = false;
149
+ }
150
+ } else {
151
+ allSubagentsFound = false;
152
+ allSubagentsValid = false;
153
+ missing.push(`AgentSubagent/${ref}`);
154
+ }
155
+ }
156
+
157
+ // contextLabelRefs
158
+ const contextLabelRefs = spec.contextLabelRefs || [];
159
+ let allContextLabelsFound = true;
160
+ for (const ref of contextLabelRefs) {
161
+ const labels = resources.AgentContextLabel || [];
162
+ const label = labels.find((l) => l.metadata?.name === ref);
163
+ if (label) {
164
+ resolvedContextLabels.push(label.metadata.name);
165
+ } else {
166
+ allContextLabelsFound = false;
167
+ missing.push(`AgentContextLabel/${ref}`);
168
+ }
169
+ }
170
+
171
+ // --- Build conditions ---
172
+ const allRefsFound = missing.length === 0;
173
+ conditions.push({
174
+ type: 'CapabilitiesResolved',
175
+ status: allRefsFound ? 'True' : 'False',
176
+ reason: allRefsFound ? 'AllRefsResolved' : 'MissingRefs',
177
+ message: allRefsFound ? 'All capability references resolved' : `Missing: ${missing.join(', ')}`
178
+ });
179
+
180
+ conditions.push({
181
+ type: 'ToolsAdmitted',
182
+ status: (toolPolicyFound || !toolPolicyRef) ? 'True' : 'False',
183
+ reason: !toolPolicyRef ? 'NoToolPolicyRef' : toolPolicyFound ? 'ToolPolicyResolved' : 'ToolPolicyMissing',
184
+ message: !toolPolicyRef ? 'No tool policy reference set' : toolPolicyFound ? 'Tool policy resolved' : `AgentToolProfile/${toolPolicyRef} not found`
185
+ });
186
+
187
+ conditions.push({
188
+ type: 'McpHealthy',
189
+ status: allMcpFound ? 'True' : 'False',
190
+ reason: allMcpFound ? 'AllMcpServersExist' : 'MissingMcpServers',
191
+ message: allMcpFound ? 'All MCP servers exist (health check deferred)' : `Missing MCP servers: ${mcpServerRefs.filter((ref) => !resolvedMcpServers.includes(ref)).join(', ')}`
192
+ });
193
+
194
+ conditions.push({
195
+ type: 'SkillsValidated',
196
+ status: (allSkillsFound && allSkillsValid) ? 'True' : 'False',
197
+ reason: !allSkillsFound ? 'MissingSkills' : !allSkillsValid ? 'InvalidSkillFormat' : 'AllSkillsValid',
198
+ message: !allSkillsFound ? `Missing skills: ${skillRefs.filter((ref) => !resolvedSkills.includes(ref)).join(', ')}` : !allSkillsValid ? 'Some skills have invalid format or missing sourceRef' : 'All skills validated'
199
+ });
200
+
201
+ conditions.push({
202
+ type: 'SubagentsValid',
203
+ status: (allSubagentsFound && allSubagentsValid) ? 'True' : 'False',
204
+ reason: !allSubagentsFound ? 'MissingSubagents' : !allSubagentsValid ? 'InvalidSubagentTaskKinds' : 'AllSubagentsValid',
205
+ message: !allSubagentsFound ? `Missing subagents: ${subagentRefs.filter((ref) => !resolvedSubagents.includes(ref)).join(', ')}` : !allSubagentsValid ? 'Some subagents have invalid or empty taskKinds' : 'All subagents validated'
206
+ });
207
+
208
+ conditions.push({
209
+ type: 'ContextLabelsValid',
210
+ status: allContextLabelsFound ? 'True' : 'False',
211
+ reason: allContextLabelsFound ? 'AllContextLabelsExist' : 'MissingContextLabels',
212
+ message: allContextLabelsFound ? 'All context labels exist' : `Missing context labels: ${contextLabelRefs.filter((ref) => !resolvedContextLabels.includes(ref)).join(', ')}`
213
+ });
214
+
215
+ const memoryBound = memoryRepositoryRefs.length === 0 || allMemoryReposFound;
216
+ conditions.push({
217
+ type: 'MemoryBound',
218
+ status: memoryBound ? 'True' : 'False',
219
+ reason: memoryRepositoryRefs.length === 0 ? 'NoMemoryRefsConfigured' : allMemoryReposFound ? 'AllMemoryReposResolved' : 'MissingMemoryRepos',
220
+ message: memoryRepositoryRefs.length === 0 ? 'No memory repository references configured' : allMemoryReposFound ? 'All memory repositories resolved' : `Missing memory repositories: ${memoryRepositoryRefs.filter((ref) => !resolvedMemoryRepos.includes(ref)).join(', ')}`
221
+ });
222
+
223
+ if (spec.jitsiCapability === true) {
224
+ const jitsiConfig = spec.jitsiConfig || {};
225
+ const role = jitsiConfig.role || 'observer';
226
+ const tools = jitsiConfig.tools || [];
227
+ const invalidTools = tools.filter((tool) => !JITSI_TOOLS.has(tool));
228
+ const providerRef = spec.jitsiMeetingProviderRef;
229
+ let providerFound = true;
230
+ if (providerRef) {
231
+ providerFound = (resources.JitsiMeetProvider || []).some((provider) => provider.metadata?.name === providerRef);
232
+ if (!providerFound) missing.push(`JitsiMeetProvider/${providerRef}`);
233
+ }
234
+ const observerCanSpeak = role === 'observer' && ['speak', 'both'].includes(jitsiConfig.capabilities?.audio);
235
+ const valid = Boolean(providerRef) && providerFound && JITSI_ROLES.has(role) && invalidTools.length === 0 && !observerCanSpeak;
236
+ conditions.push({
237
+ type: 'JitsiCapabilityReady',
238
+ status: valid ? 'True' : 'False',
239
+ reason: valid ? 'JitsiCapabilityValid' : 'InvalidJitsiCapability',
240
+ message: valid
241
+ ? 'Jitsi meeting capability is valid'
242
+ : [
243
+ !providerRef ? 'jitsiMeetingProviderRef is required' : null,
244
+ providerRef && !providerFound ? `JitsiMeetProvider/${providerRef} not found` : null,
245
+ !JITSI_ROLES.has(role) ? `Invalid role: ${role}` : null,
246
+ observerCanSpeak ? 'observer role cannot use speak or both audio modes' : null,
247
+ invalidTools.length ? `Invalid Jitsi tools: ${invalidTools.join(', ')}` : null,
248
+ ].filter(Boolean).join('; ')
249
+ });
250
+ } else {
251
+ conditions.push({
252
+ type: 'JitsiCapabilityReady',
253
+ status: 'True',
254
+ reason: 'JitsiCapabilityDisabled',
255
+ message: 'No Jitsi meeting capability configured'
256
+ });
257
+ }
258
+
259
+ // --- Permission review conditions via permissionReviewer ---
260
+ const serviceAccountRef = spec.runtimeIdentity?.serviceAccountRef || spec.runtimeIdentity;
261
+ const serviceAccounts = resources.AgentServiceAccount || [];
262
+ const serviceAccount = serviceAccounts.find((sa) => sa.metadata?.name === serviceAccountRef);
263
+ const runtimeIdentityReady = Boolean(serviceAccount);
264
+
265
+ conditions.push({
266
+ type: 'RuntimeIdentityReady',
267
+ status: runtimeIdentityReady ? 'True' : 'False',
268
+ reason: runtimeIdentityReady ? 'ServiceAccountBound' : 'MissingServiceAccount',
269
+ message: runtimeIdentityReady ? `AgentServiceAccount ${serviceAccountRef} bound` : `AgentServiceAccount ${serviceAccountRef || 'undefined'} not found`
270
+ });
271
+
272
+ // Run permission review for roles, secrets, config
273
+ const permissionReview = permissionReviewer.reviewPermissions({
274
+ repository: stack?.metadata?.labels?.repository || 'unknown',
275
+ ref: stack?.metadata?.labels?.ref || 'main',
276
+ actor: stack?.metadata?.labels?.actor || 'system',
277
+ agentStack: stack?.metadata?.name,
278
+ triggerSource: 'reconciliation',
279
+ taskKind: spec.taskKind || 'general',
280
+ resources
281
+ });
282
+
283
+ const rolesAdmitted = !permissionReview.reasons.some((r) => r.severity === 'error' && r.message.includes('AgentRoleBinding'));
284
+ const secretsAdmitted = !permissionReview.reasons.some((r) => r.severity === 'error' && r.message.includes('AgentSecretGrant'));
285
+ const configAdmitted = !permissionReview.reasons.some((r) => r.severity === 'error' && r.message.includes('AgentConfigGrant'));
286
+
287
+ conditions.push({
288
+ type: 'RolesAdmitted',
289
+ status: rolesAdmitted ? 'True' : 'False',
290
+ reason: rolesAdmitted ? 'RoleBindingsResolved' : 'MissingRoleBindings',
291
+ message: rolesAdmitted ? 'Role bindings satisfied' : 'Missing required AgentRoleBinding resources'
292
+ });
293
+
294
+ conditions.push({
295
+ type: 'SecretsAdmitted',
296
+ status: secretsAdmitted ? 'True' : 'False',
297
+ reason: secretsAdmitted ? 'SecretGrantsResolved' : 'MissingSecretGrants',
298
+ message: secretsAdmitted ? 'Secret grants satisfied' : 'Missing required AgentSecretGrant resources'
299
+ });
300
+
301
+ conditions.push({
302
+ type: 'ConfigAdmitted',
303
+ status: configAdmitted ? 'True' : 'False',
304
+ reason: configAdmitted ? 'ConfigGrantsResolved' : 'MissingConfigGrants',
305
+ message: configAdmitted ? 'Config grants satisfied' : 'Missing required AgentConfigGrant resources'
306
+ });
307
+
308
+ // --- Ready condition: true only if ALL other conditions are true ---
309
+ const allTrue = conditions.every((c) => c.status === 'True');
310
+ const hasErrors = conditions.some((c) => c.status === 'False');
311
+
312
+ conditions.push({
313
+ type: 'Ready',
314
+ status: allTrue ? 'True' : 'False',
315
+ reason: allTrue ? 'StackReady' : 'StackNotReady',
316
+ message: allTrue ? 'All conditions met' : `Failing conditions: ${conditions.filter((c) => c.status === 'False').map((c) => c.type).join(', ')}`
317
+ });
318
+
319
+ return {
320
+ conditions: clone(conditions),
321
+ capabilities: {
322
+ tools: clone(resolvedTools),
323
+ mcpServers: clone(resolvedMcpServers),
324
+ skills: clone(resolvedSkills),
325
+ subagents: clone(resolvedSubagents),
326
+ contextLabels: clone(resolvedContextLabels),
327
+ memoryRepos: clone(resolvedMemoryRepos)
328
+ },
329
+ validation: allTrue ? 'valid' : hasErrors ? 'invalid' : 'warning',
330
+ permissionDecision: permissionReview.decision
331
+ };
332
+ },
333
+
334
+ listStackCapabilities(stack, resources = {}) {
335
+ const spec = stack?.spec || {};
336
+ const capabilities = [];
337
+
338
+ // Tools
339
+ const toolPolicyRef = spec.toolPolicy || spec.toolPolicyRef || null;
340
+ if (toolPolicyRef) {
341
+ const profiles = resources.AgentToolProfile || [];
342
+ const profile = profiles.find((p) => p.metadata?.name === toolPolicyRef);
343
+ capabilities.push({
344
+ kind: 'tool',
345
+ name: toolPolicyRef,
346
+ status: profile ? 'resolved' : 'missing',
347
+ ref: toolPolicyRef
348
+ });
349
+ }
350
+
351
+ // MCP Servers
352
+ for (const ref of spec.mcpServerRefs || []) {
353
+ const servers = resources.AgentMcpServer || [];
354
+ const server = servers.find((s) => s.metadata?.name === ref);
355
+ capabilities.push({
356
+ kind: 'mcp',
357
+ name: ref,
358
+ status: server ? 'resolved' : 'missing',
359
+ ref
360
+ });
361
+ }
362
+
363
+ // Skills
364
+ for (const ref of spec.skillRefs || []) {
365
+ const skills = resources.AgentSkill || [];
366
+ const skill = skills.find((s) => s.metadata?.name === ref);
367
+ capabilities.push({
368
+ kind: 'skill',
369
+ name: ref,
370
+ status: skill ? 'resolved' : 'missing',
371
+ ref
372
+ });
373
+ }
374
+
375
+ // Subagents
376
+ for (const ref of spec.subagentRefs || []) {
377
+ const subagents = resources.AgentSubagent || [];
378
+ const subagent = subagents.find((s) => s.metadata?.name === ref);
379
+ capabilities.push({
380
+ kind: 'subagent',
381
+ name: ref,
382
+ status: subagent ? 'resolved' : 'missing',
383
+ ref
384
+ });
385
+ }
386
+
387
+ // Context Labels
388
+ for (const ref of spec.contextLabelRefs || []) {
389
+ const labels = resources.AgentContextLabel || [];
390
+ const label = labels.find((l) => l.metadata?.name === ref);
391
+ capabilities.push({
392
+ kind: 'contextLabel',
393
+ name: ref,
394
+ status: label ? 'resolved' : 'missing',
395
+ ref
396
+ });
397
+ }
398
+
399
+ return capabilities;
400
+ },
401
+
402
+ /**
403
+ * Perform a health check for an AgentMcpServer resource.
404
+ * If no endpoint is configured in spec, returns { status: 'unknown', reason: 'no-endpoint' }.
405
+ * Otherwise performs a real HTTP GET with a 3s timeout.
406
+ * @param {object} mcpServer
407
+ * @returns {Promise<{ serverName: string, status: string, latencyMs?: number, reason?: string, error?: string }>}
408
+ */
409
+ async checkMcpHealth(mcpServer) {
410
+ const serverName = mcpServer?.metadata?.name;
411
+ const endpoint = mcpServer?.spec?.endpoint;
412
+
413
+ if (!endpoint) {
414
+ return { serverName, status: 'unknown', reason: 'no-endpoint' };
415
+ }
416
+
417
+ const checkResult = await performMcpHealthCheck(endpoint, fetchFn);
418
+ return { serverName, ...checkResult };
419
+ }
420
+ };
421
+ }
@@ -0,0 +1,160 @@
1
+ import { createResource, clone } from './resource-model.js';
2
+
3
+ export const AGENT_SUBAGENT_CONTROLLER_BOUNDARY = {
4
+ role: 'agent-subagent-controller',
5
+ scope: 'Subagent dispatch orchestration with tool scoping, role-based routing, and supervision protocol',
6
+ owns: ['subagent validation', 'dispatch record creation', 'tool scope resolution', 'task routing', 'supervision config'],
7
+ delegatesTo: ['resource-model'],
8
+ mustNotOwn: ['secret values', 'Agent Mux sessions', 'parent session lifecycle']
9
+ };
10
+
11
+ const DEFAULT_SUPERVISION = {
12
+ monitorInterval: 60,
13
+ maxDuration: 7200,
14
+ autoTerminate: false
15
+ };
16
+
17
+ export function createAgentSubagentController() {
18
+ return {
19
+ role: 'agent-subagent-controller',
20
+
21
+ /**
22
+ * Validate an AgentSubagent resource.
23
+ * Checks for required fields beyond what the CRD spec.requiredSpec covers:
24
+ * - metadata.name
25
+ * - spec.parentStackRef
26
+ * - spec.role
27
+ */
28
+ validate(subagent) {
29
+ const errors = [];
30
+
31
+ if (!subagent?.metadata?.name) {
32
+ errors.push('metadata.name is required');
33
+ }
34
+
35
+ const spec = subagent?.spec || {};
36
+
37
+ if (!spec.parentStackRef) {
38
+ errors.push('spec.parentStackRef is required');
39
+ }
40
+
41
+ if (!spec.role) {
42
+ errors.push('spec.role is required');
43
+ }
44
+
45
+ return {
46
+ valid: errors.length === 0,
47
+ errors
48
+ };
49
+ },
50
+
51
+ /**
52
+ * Get the tool scope for a subagent.
53
+ * Returns { unrestricted: true, allowed: [], denied: [] } when no toolScope is set.
54
+ * Returns { unrestricted: false, allowed: [...], denied: [...] } when toolScope is configured.
55
+ */
56
+ getToolScope(subagent) {
57
+ const toolScope = subagent?.spec?.toolScope;
58
+ if (!toolScope) {
59
+ return {
60
+ unrestricted: true,
61
+ allowed: [],
62
+ denied: []
63
+ };
64
+ }
65
+ return {
66
+ unrestricted: false,
67
+ allowed: clone(toolScope.allowed || []),
68
+ denied: clone(toolScope.denied || [])
69
+ };
70
+ },
71
+
72
+ /**
73
+ * Get the list of explicitly denied tools for a subagent.
74
+ */
75
+ getDeniedTools(subagent) {
76
+ const toolScope = subagent?.spec?.toolScope;
77
+ return clone(toolScope?.denied || []);
78
+ },
79
+
80
+ /**
81
+ * Dispatch a subagent — creates a dispatch record linking the subagent to
82
+ * a parent session. Requires parentSessionRef to be provided.
83
+ */
84
+ dispatchSubagent({ subagent, parentSessionRef, taskKind, namespace = 'default', organizationRef = 'default', resources = {} }) {
85
+ if (!parentSessionRef) {
86
+ return {
87
+ error: true,
88
+ reason: 'parentSessionRef-required',
89
+ message: 'parentSessionRef is required to dispatch a subagent'
90
+ };
91
+ }
92
+
93
+ const subagentName = subagent?.metadata?.name;
94
+ const parentStackRef = subagent?.spec?.parentStackRef;
95
+ const role = subagent?.spec?.role;
96
+ const recordName = `subagent-dispatch-${subagentName}-${Date.now()}`;
97
+
98
+ const dispatchRecord = createResource(
99
+ 'AgentDispatchRun',
100
+ { name: recordName, namespace },
101
+ {
102
+ organizationRef,
103
+ repository: resources.AgentStack?.[0]?.spec?.repositoryRef || 'unknown',
104
+ sourceRefs: [],
105
+ agentStack: parentStackRef || 'unknown',
106
+ taskKind: taskKind || 'general',
107
+ parentSessionRef,
108
+ subagentRef: subagentName,
109
+ subagentRole: role
110
+ }
111
+ );
112
+
113
+ dispatchRecord.status = { phase: 'Queued', queuedAt: new Date().toISOString() };
114
+
115
+ return {
116
+ dispatchRecord: clone(dispatchRecord)
117
+ };
118
+ },
119
+
120
+ /**
121
+ * Get supervision configuration for the subagent.
122
+ * Returns configured values or defaults when supervision is not set.
123
+ */
124
+ getSupervisionConfig(subagent) {
125
+ const supervision = subagent?.spec?.supervision;
126
+ if (!supervision) {
127
+ return { ...DEFAULT_SUPERVISION };
128
+ }
129
+ return {
130
+ monitorInterval: supervision.monitorInterval !== undefined ? supervision.monitorInterval : DEFAULT_SUPERVISION.monitorInterval,
131
+ maxDuration: supervision.maxDuration !== undefined ? supervision.maxDuration : DEFAULT_SUPERVISION.maxDuration,
132
+ autoTerminate: supervision.autoTerminate !== undefined ? supervision.autoTerminate : DEFAULT_SUPERVISION.autoTerminate
133
+ };
134
+ },
135
+
136
+ /**
137
+ * Validate task routing: checks that the requested role maps to an available subagent.
138
+ */
139
+ validateTaskRouting({ role, taskKind, subagents = [] }) {
140
+ const match = subagents.find(s => s?.spec?.role === role);
141
+ if (!match) {
142
+ return {
143
+ valid: false,
144
+ error: `No subagent found for role '${role}'`
145
+ };
146
+ }
147
+ return {
148
+ valid: true,
149
+ matchedSubagent: clone(match)
150
+ };
151
+ },
152
+
153
+ /**
154
+ * Get current status of a subagent from its status field.
155
+ */
156
+ getSubagentStatus(subagent) {
157
+ return clone(subagent?.status || { phase: 'idle' });
158
+ }
159
+ };
160
+ }