@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,620 @@
1
+ import { giteaIssueSyncPlan, githubProjectIssueSyncPlan } from './gitea-backend.js';
2
+ import { resourceToYaml } from './resource-model.js';
3
+ import { KRADLE_ORG_LABEL, KRADLE_ORG_NAMESPACE_LABEL, KRADLE_RESOURCES, apiResourceName, createKradleKubernetesReconciler, orgNamespaceName } from './kubernetes-controller.js';
4
+
5
+ const controllerEndpoints = [
6
+ { method: 'GET', path: '/healthz', purpose: 'container and load-balancer health' },
7
+ { method: 'GET', path: '/api/controller?org=:org', purpose: 'live Kradle org model' },
8
+ { method: 'GET', path: '/api/orgs/:org/resources', purpose: 'org-scoped Kradle resource listing' },
9
+ { method: 'POST', path: '/api/orgs/:org/resources', purpose: 'org-scoped Kradle resource apply flow' },
10
+ { method: 'GET', path: '/api/orgs/:org/resources/:kind/:name', purpose: 'org-scoped Kradle resource detail' },
11
+ { method: 'DELETE', path: '/api/orgs/:org/resources/:kind/:name', purpose: 'org-scoped Kradle resource delete flow' },
12
+ { method: 'GET', path: '/api/orgs/:org/repositories', purpose: 'list org repositories through Kradle' },
13
+ { method: 'POST', path: '/api/orgs/:org/repositories', purpose: 'create org repositories through Kradle' },
14
+ { method: 'GET', path: '/api/orgs/:org/repositories/:name', purpose: 'get org repository details through Kradle' },
15
+ { method: 'DELETE', path: '/api/orgs/:org/repositories/:name', purpose: 'delete org repositories through Kradle' },
16
+ { method: 'GET', path: '/api/orgs/:org/policies', purpose: 'list Kradle policy profiles, templates, bindings, Kyverno health, and exception requests' },
17
+ { method: 'POST', path: '/api/orgs/:org/policies', purpose: 'create org policy bindings in audit or enforce mode' },
18
+ { method: 'GET', path: '/api/orgs/:org/policy-reports', purpose: 'list normalized Kyverno policy report results' },
19
+ { method: 'GET', path: '/api/orgs/:org/policy-exception-requests', purpose: 'list pending and approved Kradle policy exception requests' },
20
+ { method: 'POST', path: '/api/orgs/:org/policy-exception-requests', purpose: 'request a temporary policy exception through Kradle' },
21
+ { method: 'GET', path: '/api/watch/orgs/:org/*', purpose: 'org-scoped Kradle live event stream bridged to browser updates' },
22
+ { method: 'POST', path: '/api/git-proxy', purpose: 'repository streaming proxy when configured' },
23
+ { method: 'GET', path: '/api/orgs/:org/agents/stacks', purpose: 'list agent stacks and capability status' },
24
+ { method: 'GET', path: '/api/orgs/:org/agents/runs', purpose: 'list agent dispatch runs with queue and status' },
25
+ { method: 'GET', path: '/api/orgs/:org/agents/rules', purpose: 'list trigger rules and delivery status' },
26
+ { method: 'GET', path: '/api/orgs/:org/agents/sessions', purpose: 'list agent sessions with lifecycle state' },
27
+ { method: 'GET', path: '/api/orgs/:org/agents/workspaces', purpose: 'list agent workspaces with lifecycle state' },
28
+ { method: 'GET', path: '/api/orgs/:org/agents/approvals', purpose: 'list pending and resolved agent approvals' },
29
+ { method: 'GET', path: '/api/orgs/:org/agents/permissions/review', purpose: 'explainable permission check for agent dispatch' },
30
+ { method: 'GET', path: '/api/orgs/:org/agents/adapters', purpose: 'list agent adapters and transport bindings' },
31
+ { method: 'GET', path: '/api/orgs/:org/agents/providers', purpose: 'list model provider configurations' },
32
+ { method: 'GET', path: '/api/orgs/:org/agents/projects', purpose: 'list agent projects with board config' },
33
+ { method: 'POST', path: '/api/orgs/:org/agents/dispatch', purpose: 'create manual agent dispatch run' },
34
+ { method: 'POST', path: '/api/orgs/:org/agents/approvals/:name/decide', purpose: 'approve or deny a pending agent approval request' },
35
+ { method: 'POST', path: '/api/orgs/:org/agents/triggers/process', purpose: 'evaluate an event against trigger rules and dispatch matching agents' },
36
+ { method: 'POST', path: '/api/orgs/:org/agents/workspaces', purpose: 'provision a new agent workspace with worktree and runtime' },
37
+ { method: 'POST', path: '/api/orgs/:org/agents/workspaces/:name/archive', purpose: 'archive an agent workspace and mark it for cleanup' },
38
+ { method: 'POST', path: '/api/orgs/:org/agents/workspaces/:name/link', purpose: 'link a work item to an agent workspace' },
39
+ { method: 'POST', path: '/api/orgs/:org/agents/memory/query', purpose: 'query Company Brain memory with graph and grep search' },
40
+ { method: 'POST', path: '/api/orgs/:org/agents/memory/imports', purpose: 'create a memory import from a babysitter run' },
41
+ { method: 'GET', path: '/api/orgs/:org/agents/memory/snapshots', purpose: 'list memory snapshots for an organization' },
42
+ { method: 'GET', path: '/api/orgs/:org/agents/memory/repositories', purpose: 'list memory repositories for an organization' }
43
+ ];
44
+
45
+ const runtimeComponents = [
46
+ { id: 'identity-access', title: 'Identity and access', area: 'identity', resources: ['User', 'Team', 'Invite', 'IdentityMapping', 'AuthProvider'], docs: 'src/auth.js' },
47
+ { id: 'api-controller', title: 'Kradle API controller', area: 'api', resources: ['Repository', 'KradleProject', 'PullRequest', 'Issue', 'Pipeline'], docs: 'src/api-controller.js' },
48
+ { id: 'kradle-resource-client', title: 'Kradle resource client', area: 'control-plane', resources: ['Repository', 'BranchProtection', 'RefPolicy'], docs: 'src/kubernetes-controller.js' },
49
+ { id: 'repository-service', title: 'Repository service', area: 'data-plane', resources: ['Repository', 'BranchProtection', 'RefPolicy'], docs: 'src/data-plane.js' },
50
+ { id: 'runners-ci', title: 'Runner scheduler', area: 'ci', resources: ['RunnerPool', 'Pipeline', 'Job'], docs: 'src/kubernetes-controller.js' },
51
+ { id: 'hooks-events', title: 'Webhook bus', area: 'events', resources: ['WebhookSubscription', 'WebhookDelivery'], docs: 'src/kubernetes-controller.js' },
52
+ { id: 'policy-engine', title: 'Kyverno policy engine', area: 'policy', resources: ['PolicyProfile', 'PolicyTemplate', 'PolicyBinding', 'PolicyExceptionRequest'], docs: 'docs/todo-kyverno' },
53
+ { id: 'agent-orchestration', title: 'Agent orchestration', area: 'agents', resources: ['AgentStack', 'AgentDispatchRun', 'AgentTriggerRule', 'AgentSession', 'KradleWorkspace', 'AgentApproval', 'AgentAdapter', 'AgentProviderConfig', 'KradleProject'], docs: 'docs/agents/' }
54
+ ];
55
+
56
+ export function createControllerUiModel(source, options = {}) {
57
+ const snapshot = normalizeSnapshot(source);
58
+ const organizations = ensureOrganizations(snapshot.resources.Organization || [], snapshot.namespace);
59
+ const requestedOrg = options.organization || options.org || process.env.KRADLE_ORG || '';
60
+ const activeOrg = organizations.find((org) => org.slug === requestedOrg || org.name === requestedOrg) || organizations[0];
61
+ const resources = KRADLE_RESOURCES.map((definition) => {
62
+ const rawItems = snapshot.resources[definition.kind] || [];
63
+ const items = filterResourceItemsForOrg(definition, rawItems, activeOrg?.slug);
64
+ return {
65
+ kind: definition.kind,
66
+ plural: definition.plural,
67
+ apiResource: apiResourceName(definition),
68
+ count: items.length,
69
+ names: items.map((item) => item.metadata?.name).filter(Boolean),
70
+ items,
71
+ phases: summarizePhases(items),
72
+ storage: definition.storage,
73
+ yaml: items[0] ? resourceToYaml(items[0]) : null,
74
+ action: snapshot.commands.find((command) => command.kind === definition.kind) || {
75
+ list: `Open ${definition.kind} records in ${activeOrg?.namespace || snapshot.namespace}`,
76
+ watch: `Watch ${definition.kind} updates in ${activeOrg?.namespace || snapshot.namespace}`,
77
+ apply: 'Save resource changes',
78
+ delete: `Delete ${definition.kind} in ${activeOrg?.namespace || snapshot.namespace}`
79
+ }
80
+ };
81
+ });
82
+ const users = filterByOrg(snapshot.resources.User || [], activeOrg?.slug);
83
+ const teams = filterByOrg(snapshot.resources.Team || [], activeOrg?.slug);
84
+ const invites = filterByOrg(snapshot.resources.Invite || [], activeOrg?.slug);
85
+ const identityMappings = filterByOrg(snapshot.resources.IdentityMapping || [], activeOrg?.slug);
86
+ const authProviders = filterByOrg(snapshot.resources.AuthProvider || [], activeOrg?.slug);
87
+ const repositoryPermissions = filterByOrg(snapshot.resources.RepositoryPermission || [], activeOrg?.slug);
88
+ const sshKeys = filterByOrg(snapshot.resources.SSHKey || [], activeOrg?.slug);
89
+ const identityReconciliation = createKradleKubernetesReconciler({ namespace: snapshot.namespace }).reconcileIdentityAccessResources({ User: users, Team: teams, Invite: invites, IdentityMapping: identityMappings, RepositoryPermission: repositoryPermissions, SSHKey: sshKeys });
90
+ const identityView = createIdentityView({ users, teams, invites, identityMappings, authProviders, permissions: repositoryPermissions, sshKeys, reconciliation: identityReconciliation });
91
+ identityView.org = activeOrg?.slug;
92
+ const repositories = filterByOrg(snapshot.resources.Repository || [], activeOrg?.slug);
93
+ const pullRequests = filterByOrg(snapshot.resources.PullRequest || [], activeOrg?.slug);
94
+ const issues = filterByOrg(snapshot.resources.Issue || [], activeOrg?.slug);
95
+ const pipelines = filterByOrg(snapshot.resources.Pipeline || [], activeOrg?.slug);
96
+ const jobs = filterByOrg(snapshot.resources.Job || [], activeOrg?.slug);
97
+ const runnerPools = filterByOrg(snapshot.resources.RunnerPool || [], activeOrg?.slug);
98
+ const webhookSubscriptions = filterByOrg(snapshot.resources.WebhookSubscription || [], activeOrg?.slug);
99
+ const webhookDeliveries = filterByOrg(snapshot.resources.WebhookDelivery || [], activeOrg?.slug);
100
+ const policyProfiles = filterByOrg(snapshot.resources.PolicyProfile || [], activeOrg?.slug);
101
+ const policyTemplates = filterByOrg(snapshot.resources.PolicyTemplate || [], activeOrg?.slug).concat((snapshot.resources.PolicyTemplate || []).filter((item) => !item.spec?.organizationRef));
102
+ const policyBindings = filterByOrg(snapshot.resources.PolicyBinding || [], activeOrg?.slug);
103
+ const policyExceptionRequests = filterByOrg(snapshot.resources.PolicyExceptionRequest || [], activeOrg?.slug);
104
+ const policyEngine = createPolicyEngineView({ kyverno: snapshot.kyverno, policyProfiles, policyTemplates, policyBindings, policyExceptionRequests, org: activeOrg?.slug, namespace: activeOrg?.namespace || snapshot.namespace });
105
+ const agentStacks = filterByOrg(snapshot.resources.AgentStack || [], activeOrg?.slug);
106
+ const agentDispatchRuns = filterByOrg(snapshot.resources.AgentDispatchRun || [], activeOrg?.slug);
107
+ const agentTriggerRules = filterByOrg(snapshot.resources.AgentTriggerRule || [], activeOrg?.slug);
108
+ const agentSessions = filterByOrg(snapshot.resources.AgentSession || [], activeOrg?.slug);
109
+ const agentWorkspaces = filterByOrg(snapshot.resources.KradleWorkspace || [], activeOrg?.slug);
110
+ const agentApprovals = filterByOrg(snapshot.resources.AgentApproval || [], activeOrg?.slug);
111
+ const agentAdapters = filterByOrg(snapshot.resources.AgentAdapter || [], activeOrg?.slug);
112
+ const agentProviders = filterByOrg(snapshot.resources.AgentProviderConfig || [], activeOrg?.slug);
113
+ const agentProjects = filterByOrg(snapshot.resources.KradleProject || [], activeOrg?.slug);
114
+ const agentGateway = filterByOrg(snapshot.resources.AgentGatewayConfig || [], activeOrg?.slug);
115
+ const agentTranscripts = filterByOrg(snapshot.resources.AgentSessionTranscript || [], activeOrg?.slug);
116
+ const jitsiMeetings = filterByOrg(snapshot.resources.JitsiMeeting || [], activeOrg?.slug);
117
+ const memoryRepositories = filterByOrg(snapshot.resources.AgentMemoryRepository || [], activeOrg?.slug);
118
+ const memorySnapshots = filterByOrg(snapshot.resources.AgentMemorySnapshot || [], activeOrg?.slug);
119
+ const memoryImports = filterByOrg(snapshot.resources.AgentRunMemoryImport || [], activeOrg?.slug);
120
+
121
+ const agentView = {
122
+ org: activeOrg?.slug,
123
+ stacks: { count: agentStacks.length, items: agentStacks },
124
+ runs: { count: agentDispatchRuns.length, items: agentDispatchRuns, active: agentDispatchRuns.filter(r => r.status?.phase && r.status.phase !== 'Completed' && r.status.phase !== 'Failed') },
125
+ rules: { count: agentTriggerRules.length, items: agentTriggerRules },
126
+ sessions: { count: agentSessions.length, items: agentSessions },
127
+ workspaces: { count: agentWorkspaces.length, items: agentWorkspaces },
128
+ approvals: { count: agentApprovals.length, items: agentApprovals, pending: agentApprovals.filter(a => !a.status?.phase || a.status.phase === 'Pending') },
129
+ adapters: { count: agentAdapters.length, items: agentAdapters },
130
+ providers: { count: agentProviders.length, items: agentProviders },
131
+ projects: { count: agentProjects.length, items: agentProjects },
132
+ gateway: agentGateway[0] || null,
133
+ transcripts: { count: agentTranscripts.length, items: agentTranscripts },
134
+ meetings: { count: jitsiMeetings.length, items: jitsiMeetings, active: jitsiMeetings.filter((meeting) => meeting.status?.phase === 'Active') },
135
+ memoryRepositories: { count: memoryRepositories.length, items: memoryRepositories },
136
+ memorySnapshots: { count: memorySnapshots.length, items: memorySnapshots },
137
+ memoryImports: { count: memoryImports.length, items: memoryImports, pending: memoryImports.filter(i => !i.status?.phase || i.status.phase === 'Pending' || i.status.phase === 'AwaitingReview') },
138
+ };
139
+ const deploymentApplications = filterByOrg(snapshot.resources.KubeVelaApplication || [], activeOrg?.slug);
140
+ const deploymentReleases = filterByOrg(snapshot.resources.KubeVelaApplicationRevision || [], activeOrg?.slug);
141
+ const deploymentComponents = snapshot.resources.KubeVelaComponentDefinition || [];
142
+ const deploymentWorkloads = snapshot.resources.KubeVelaWorkloadDefinition || [];
143
+ const deploymentTraits = snapshot.resources.KubeVelaTraitDefinition || [];
144
+ const deploymentScopes = snapshot.resources.KubeVelaScopeDefinition || [];
145
+ const deploymentPolicyDefinitions = snapshot.resources.KubeVelaPolicyDefinition || [];
146
+ const deploymentPolicies = filterByOrg(snapshot.resources.KubeVelaPolicy || [], activeOrg?.slug);
147
+ const deploymentAutomationSteps = snapshot.resources.KubeVelaWorkflowStepDefinition || [];
148
+ const deploymentAutomations = filterByOrg(snapshot.resources.KubeVelaWorkflow || [], activeOrg?.slug);
149
+ const deploymentManagedResources = filterByOrg(snapshot.resources.KubeVelaResourceTracker || [], activeOrg?.slug);
150
+ const events = snapshot.events || [];
151
+ const workspaceConnected = Boolean(snapshot.kubectl?.available);
152
+ const apiInstalled = resources.some((resource) => resource.count > 0) || Boolean(snapshot.apiService) || snapshot.crds?.length > 0;
153
+ const validation = [
154
+ { name: 'Kradle workspace is connected', passed: workspaceConnected, evidence: snapshot.kubectl?.context || snapshot.kubectl?.errors?.[0] || 'Kradle workspace unavailable' },
155
+ { name: 'Kradle API surface is discoverable', passed: apiInstalled, evidence: snapshot.apiService?.metadata?.name || `${snapshot.crds?.length || 0} Kradle resources discovered` },
156
+ { name: 'Repository management uses org-scoped Kradle actions', passed: true, evidence: '/api/orgs/:org/repositories and /api/orgs/:org/resources call src/kubernetes-controller.js' },
157
+ { name: 'Kradle API is separated from delivery execution', passed: true, evidence: 'src/api-controller.js delegates resource operations through the Kradle gateway' },
158
+ { name: 'Live streams use Kradle watch', passed: true, evidence: '/api/watch/orgs/:org/* uses Kradle live updates' },
159
+ { name: 'UI renders live and empty states without demo data', passed: true, evidence: 'apps/web Server Components consume fetchControllerUiModel() only' }
160
+ ];
161
+
162
+ return {
163
+ product: 'Kradle',
164
+ status: workspaceConnected && apiInstalled ? 'ready' : 'degraded',
165
+ namespace: activeOrg?.namespace || snapshot.namespace,
166
+ platformNamespace: snapshot.namespace,
167
+ org: activeOrg,
168
+ orgs: organizations,
169
+ generatedAt: snapshot.generatedAt || new Date().toISOString(),
170
+ correlationId: snapshot.correlationId,
171
+ controller: {
172
+ mode: 'kradle-workspace',
173
+ endpoints: controllerEndpoints,
174
+ architecture: snapshot.architecture || defaultArchitecture(snapshot.namespace),
175
+ storage: snapshot.storage,
176
+ connection: { available: workspaceConnected, context: snapshot.kubectl?.context || null, errors: snapshot.kubectl?.errors || [] },
177
+ apiService: snapshot.apiService ? snapshot.apiService.metadata?.name : null,
178
+ commands: snapshot.commands
179
+ },
180
+ metrics: {
181
+ components: runtimeComponents.length,
182
+ resources: resources.reduce((total, resource) => total + resource.count, 0),
183
+ events: events.length,
184
+ auditEntries: 0,
185
+ users: users.length,
186
+ teams: teams.length,
187
+ invites: invites.length,
188
+ repositories: repositories.length,
189
+ pullRequests: pullRequests.length,
190
+ issues: issues.length,
191
+ projects: agentProjects.length,
192
+ pipelines: pipelines.length,
193
+ jobs: jobs.length,
194
+ runnerPools: runnerPools.length,
195
+ webhookDeliveries: webhookDeliveries.length,
196
+ policyViolations: policyEngine.violations.length,
197
+ policyBindings: policyBindings.length,
198
+ deployments: deploymentApplications.length,
199
+ releases: deploymentReleases.length,
200
+ agentStacks: agentStacks.length,
201
+ agentRuns: agentDispatchRuns.length,
202
+ agentSessions: agentSessions.length,
203
+ greenChecks: validation.filter((item) => item.passed).length,
204
+ totalChecks: validation.length
205
+ },
206
+ components: runtimeComponents,
207
+ resources,
208
+ events: events.slice(-8).map((event) => ({
209
+ type: event.type || event.reason || 'KradleEvent',
210
+ storage: 'kubernetes',
211
+ resource: event.involvedObject ? `${event.involvedObject.kind}/${event.involvedObject.namespace || snapshot.namespace}/${event.involvedObject.name}` : event.metadata?.name,
212
+ actor: event.reportingController || event.source?.component || 'kubernetes',
213
+ allowed: true,
214
+ message: event.message || event.note || ''
215
+ })),
216
+ auditLog: [],
217
+ delivery: createDeliveryView({ applications: deploymentApplications, releases: deploymentReleases, components: deploymentComponents, workloads: deploymentWorkloads, traits: deploymentTraits, scopes: deploymentScopes, policyDefinitions: deploymentPolicyDefinitions, policies: deploymentPolicies, automationSteps: deploymentAutomationSteps, automations: deploymentAutomations, managedResources: deploymentManagedResources }),
218
+ policyEngine,
219
+ agents: agentView,
220
+ identity: identityView,
221
+ validation,
222
+ permissions: snapshot.permissions || [],
223
+ views: {
224
+ dashboard: {
225
+ repositories,
226
+ projects: agentProjects,
227
+ issues,
228
+ issueSync: issueSyncView({ org: activeOrg?.slug, projects: agentProjects, repositories, issues }),
229
+ excellentFlows: ['Create or import a repository', 'Browse code and copy clone commands', 'Review and merge a pull request', 'Debug a failing pipeline run', 'Edit runner pool capacity', 'Inspect and replay webhook deliveries', 'Save a triage View'],
230
+ cards: dashboardCards({ repositories, projects: agentProjects, pullRequests, issues, pipelines, runnerPools, webhookDeliveries })
231
+ },
232
+ pullRequestReview: pullRequests[0] ? pullRequestReview(pullRequests[0], pipelines, jobs) : null,
233
+ failingRun: pipelines.find((pipeline) => pipeline.status?.phase === 'Failed') ? failingRun(pipelines.find((pipeline) => pipeline.status?.phase === 'Failed'), jobs) : null,
234
+ runnerPoolEditor: runnerPools[0] ? runnerPoolEditor(runnerPools[0]) : null,
235
+ webhookInspector: webhookSubscriptions[0] ? webhookInspector(webhookSubscriptions[0], webhookDeliveries) : null,
236
+ triageView: (snapshot.resources.View || [])[0] ? triageView((snapshot.resources.View || [])[0]) : null,
237
+ identityAdmin: identityView,
238
+ policyCenter: policyEngine
239
+ },
240
+ operations: {
241
+ image: 'ghcr.io/<owner>/<repo>/kradle-controller',
242
+ chart: 'charts/kradle',
243
+ installCommands: ['Install Kradle release package', 'Apply the demo workspace configuration'],
244
+ releaseGates: ['npm run check', 'docker build', 'helm package charts/kradle', 'npm pack --json']
245
+ }
246
+ };
247
+ }
248
+
249
+
250
+ function ensureOrganizations(organizations = [], platformNamespace = 'kradle-system') {
251
+ if (organizations.length) return organizations.map((org) => {
252
+ const slug = org.spec?.slug || org.metadata?.name;
253
+ const namespace = org.spec?.namespaceName || org.metadata?.labels?.[KRADLE_ORG_NAMESPACE_LABEL] || orgNamespaceName(slug);
254
+ return { name: org.metadata?.name, slug, displayName: org.spec?.displayName || slug, namespace, platformNamespace: org.metadata?.namespace || platformNamespace };
255
+ });
256
+ const slug = 'default';
257
+ return [{ name: slug, slug, displayName: 'Default org', namespace: orgNamespaceName(slug), platformNamespace }];
258
+ }
259
+
260
+
261
+ function filterResourceItemsForOrg(definition, items = [], org) {
262
+ if (!org) return [];
263
+ if (definition.kind === 'Organization') return items.filter((item) => (item.spec?.slug || item.metadata?.name) === org);
264
+ if (definition.kind === 'OrgNamespaceBinding') return filterByOrg(items, org);
265
+ if (definition.namespace && definition.namespace !== orgNamespaceName(org)) return items;
266
+ return filterByOrg(items, org);
267
+ }
268
+
269
+ function filterByOrg(items = [], org) {
270
+ if (!org) return items;
271
+ const orgNamespace = orgNamespaceName(org);
272
+ return items.filter((item) => {
273
+ const itemOrg = item.spec?.organizationRef || item.metadata?.labels?.[KRADLE_ORG_LABEL];
274
+ return itemOrg === org || item.metadata?.namespace === orgNamespace;
275
+ });
276
+ }
277
+
278
+ function normalizeSnapshot(source = {}) {
279
+ const raw = typeof source.snapshot === 'function' ? source.snapshot() : source;
280
+ if (raw?.source === 'kubernetes') return { commands: [], crds: [], events: [], permissions: [], storage: {}, ...raw, resources: raw.resources || {} };
281
+ const resources = raw?.resources || {};
282
+ return {
283
+ source: 'runtime-snapshot',
284
+ namespace: raw?.namespace || 'kradle-system',
285
+ generatedAt: new Date().toISOString(),
286
+ correlationId: null,
287
+ kubectl: { available: false, context: null, errors: ['runtime snapshot supplied outside the Kradle workspace path'] },
288
+ apiService: null,
289
+ crds: [],
290
+ resources,
291
+ events: raw?.events || [],
292
+ permissions: [],
293
+ architecture: raw?.architecture || defaultArchitecture(raw?.namespace || 'kradle-system'),
294
+ storage: raw?.storage || {},
295
+ commands: []
296
+ };
297
+ }
298
+
299
+ function summarizePhases(items) {
300
+ return items.reduce((summary, item) => {
301
+ const phase = item.status?.phase || (item.status?.ready === true ? 'Ready' : item.status?.conditions?.[0]?.type) || 'Unspecified';
302
+ summary[phase] = (summary[phase] || 0) + 1;
303
+ return summary;
304
+ }, {});
305
+ }
306
+
307
+ function dashboardCards({ repositories, projects = [], pullRequests, issues = [], pipelines, runnerPools, webhookDeliveries }) {
308
+ return [
309
+ { label: 'Repositories', value: repositories.length, href: '/repositories' },
310
+ { label: 'Projects', value: projects.length, href: '/agents/projects' },
311
+ { label: 'Issues', value: issues.length, href: '/inbox' },
312
+ { label: 'Pull requests', value: pullRequests.length, href: '/inbox' },
313
+ { label: 'Runs', value: pipelines.length, href: '/runs' },
314
+ { label: 'Runner pools', value: runnerPools.length, href: '/runners-ci' },
315
+ { label: 'Webhook deliveries', value: webhookDeliveries.length, href: '/hooks-events' }
316
+ ];
317
+ }
318
+
319
+ function defaultArchitecture(namespace) {
320
+ return {
321
+ apiController: { role: 'kradle-api-controller', scope: 'HTTP route orchestration, request validation, API errors, and workflow affordances; never owns delivery reconciliation loops', owns: ['/api/controller', '/api/orgs/:org/resources', '/api/orgs/:org/repositories', '/api/watch/orgs/:org/*'], delegatesTo: ['kradle-resource-gateway', 'repository-service'] },
322
+ resourceGateway: { role: 'kradle-resource-gateway', scope: 'Narrow application port translating API controller intent into Kradle resource-client calls', namespace, delegatesTo: ['kradle-resource-client'] },
323
+ resourceClient: { role: 'kradle-resource-client', scope: 'Kradle resource operations and live streams; no UI flow ownership', namespace, owns: ['Kradle resources', 'aggregated API resources'] },
324
+ deliveryReconciler: { role: 'kradle-delivery-reconciler', scope: 'Repository status projection, repository hosting intent, policy projection, and data-plane sync intent; never owns HTTP routes or browser flows', namespace, delegatesTo: ['kradle-resource-gateway', 'repository-service'] },
325
+ repositoryService: { role: 'repository-service', scope: 'repository streaming and SSH hosting, object storage, and search indexing', boundary: process.env.KRADLE_GITEA_HTTP_URL || 'repository service not configured' }
326
+ };
327
+ }
328
+
329
+ export function issueRepositoryRefs(issue = {}) {
330
+ return uniqueStrings([
331
+ issue.spec?.repository,
332
+ issue.spec?.repoRef,
333
+ issue.spec?.repositoryRef,
334
+ issue.metadata?.labels?.repository,
335
+ issue.metadata?.labels?.['kradle.a5c.ai/repository'],
336
+ issue.metadata?.annotations?.['kradle.a5c.ai/repository'],
337
+ issue.metadata?.annotations?.['kradle.a5c.ai/repositories'],
338
+ issue.status?.repository,
339
+ issue.status?.repositoryRef,
340
+ ...(issue.spec?.repositories || []),
341
+ ...(issue.spec?.repositoryRefs || []),
342
+ ...(issue.status?.repositories || []),
343
+ ...(issue.status?.repositoryRefs || [])
344
+ ]);
345
+ }
346
+
347
+ export function issueProjectRefs(issue = {}) {
348
+ return uniqueStrings([
349
+ issue.spec?.project,
350
+ issue.spec?.projectRef,
351
+ issue.spec?.kradleProject,
352
+ issue.spec?.kradleProjectRef,
353
+ issue.metadata?.labels?.project,
354
+ issue.metadata?.labels?.['kradle.a5c.ai/project'],
355
+ issue.metadata?.labels?.['kradle.a5c.ai/kradle-project'],
356
+ issue.metadata?.annotations?.['kradle.a5c.ai/project'],
357
+ issue.metadata?.annotations?.['kradle.a5c.ai/projects'],
358
+ issue.status?.project,
359
+ issue.status?.projectRef,
360
+ ...(issue.spec?.projects || []),
361
+ ...(issue.spec?.projectRefs || []),
362
+ ...(issue.status?.projects || []),
363
+ ...(issue.status?.projectRefs || [])
364
+ ]);
365
+ }
366
+
367
+ function uniqueStrings(values = []) {
368
+ return [...new Set(values.flatMap(refNames).filter(Boolean).map(String))];
369
+ }
370
+
371
+ function refNames(value) {
372
+ if (value === undefined || value === null || value === '') return [];
373
+ if (Array.isArray(value)) return value.flatMap(refNames);
374
+ if (typeof value === 'string') return value.split(',').map((part) => part.trim()).filter(Boolean);
375
+ if (typeof value === 'object') return refNames(value.name || value.repository || value.repo || value.project || value.kradleProject || value.metadata?.name || value.ref || value.slug);
376
+ return [String(value)];
377
+ }
378
+
379
+ function issueSyncView({ org = 'default', projects = [], repositories = [], issues = [] }) {
380
+ return {
381
+ gitea: giteaIssueSyncPlan({ org, project: projects[0]?.metadata?.name || null, issue: issues[0] || null, repositories: repositories.map((repo) => repo.metadata?.name).filter(Boolean) }),
382
+ github: githubProjectIssueSyncPlan({ org, project: projects[0]?.metadata?.name || null, issue: issues[0] || null, repositories: repositories.map((repo) => repo.metadata?.name).filter(Boolean) })
383
+ };
384
+ }
385
+
386
+ function pullRequestReview(pullRequest, pipelines, jobs) {
387
+ const pipelineRuns = pipelines.filter((pipeline) => pipeline.spec?.pullRequest === pullRequest.metadata?.name || pipeline.metadata?.labels?.pullrequest === pullRequest.metadata?.name);
388
+ return {
389
+ pullRequest,
390
+ changedFiles: pullRequest.status?.changedFiles || [],
391
+ pipelineRuns,
392
+ jobs: jobs.filter((job) => pipelineRuns.some((pipeline) => job.spec?.pipeline === pipeline.metadata?.name)),
393
+ yaml: resourceToYaml(pullRequest),
394
+ keyboardShortcuts: ['j/k navigate files', 'a approve', 'm merge when allowed']
395
+ };
396
+ }
397
+
398
+ function failingRun(pipeline, jobs) {
399
+ return {
400
+ pipeline,
401
+ jobs: jobs.filter((job) => job.spec?.pipeline === pipeline.metadata?.name),
402
+ stream: `/api/watch/orgs/${pipeline.spec?.organizationRef || pipeline.metadata?.labels?.['kradle.a5c.ai/org'] || 'default'}/pipelines/${pipeline.metadata?.name}`,
403
+ actions: ['rerun from step', 'open run events', 'create incident', 'copy diagnostic summary']
404
+ };
405
+ }
406
+
407
+ function runnerPoolEditor(resource) {
408
+ return {
409
+ resource,
410
+ fields: ['image', 'resources', 'warmReplicas', 'maxReplicas', 'trustTier', 'cache'],
411
+ saveModes: ['save capacity', 'open review'],
412
+ yaml: resourceToYaml(resource)
413
+ };
414
+ }
415
+
416
+ function webhookInspector(subscription, deliveries) {
417
+ return {
418
+ subscription,
419
+ deliveries: deliveries.filter((delivery) => delivery.spec?.subscription === subscription.metadata?.name || delivery.metadata?.labels?.subscription === subscription.metadata?.name),
420
+ columns: ['phase', 'signature', 'attempts', 'response', 'latency'],
421
+ actions: ['replay', 'disable subscription', 'copy curl']
422
+ };
423
+ }
424
+
425
+ function triageView(view) {
426
+ return { resource: view, yaml: resourceToYaml(view), selector: view.spec?.selector || {} };
427
+ }
428
+
429
+
430
+
431
+ function createIdentityView({ users, teams, invites, identityMappings, authProviders, permissions, sshKeys, reconciliation }) {
432
+ const reconciliationStatuses = reconciliation?.desiredStatuses || [];
433
+ const reconciliationPhases = reconciliationStatuses.reduce((summary, item) => ({ ...summary, [item.phase]: (summary[item.phase] || 0) + 1 }), {});
434
+ return {
435
+ counts: {
436
+ users: users.length,
437
+ teams: teams.length,
438
+ pendingInvites: invites.filter((invite) => (invite.status?.phase || 'Pending') === 'Pending').length,
439
+ mappings: identityMappings.length,
440
+ repositoryGrants: permissions.length,
441
+ sshKeys: sshKeys.length
442
+ },
443
+ providers: authProviders.map((provider) => ({
444
+ name: provider.metadata?.name,
445
+ label: provider.spec?.label || provider.metadata?.name,
446
+ type: provider.spec?.type || 'oidc',
447
+ enabled: provider.spec?.enabled !== false,
448
+ phase: provider.status?.phase || 'Unknown'
449
+ })),
450
+ users: users.map((user) => ({
451
+ name: user.metadata?.name,
452
+ displayName: user.spec?.displayName || user.metadata?.name,
453
+ email: user.spec?.email || '',
454
+ teams: user.spec?.teams || [],
455
+ admin: Boolean(user.spec?.admin),
456
+ disabled: Boolean(user.spec?.disabled),
457
+ phase: user.status?.phase || 'Unknown'
458
+ })),
459
+ teams: teams.map((team) => ({
460
+ name: team.metadata?.name,
461
+ displayName: team.spec?.displayName || team.metadata?.name,
462
+ members: team.spec?.members || [],
463
+ maintainers: team.spec?.maintainers || [],
464
+ repositoryGrants: team.spec?.repositoryGrants || []
465
+ })),
466
+ invites: invites.map((invite) => ({
467
+ name: invite.metadata?.name,
468
+ email: invite.spec?.email,
469
+ role: invite.spec?.role || 'member',
470
+ teams: invite.spec?.teams || [],
471
+ phase: invite.status?.phase || 'Pending',
472
+ expiresAt: invite.spec?.expiresAt || ''
473
+ })),
474
+ mappings: identityMappings.map((mapping) => ({
475
+ name: mapping.metadata?.name,
476
+ user: mapping.spec?.user,
477
+ provider: mapping.spec?.provider,
478
+ workspaceIdentity: mapping.spec?.workspaceIdentity?.name || mapping.spec?.subject,
479
+ repositoryIdentity: mapping.spec?.repositoryIdentity?.username || mapping.spec?.user,
480
+ phase: mapping.status?.phase || 'Unknown'
481
+ })),
482
+ permissions: permissions.map((permission) => ({
483
+ name: permission.metadata?.name,
484
+ repository: permission.spec?.repository,
485
+ subject: permission.spec?.subject,
486
+ subjectKind: permission.spec?.subjectKind || 'user',
487
+ permission: permission.spec?.permission || 'read',
488
+ revoked: Boolean(permission.spec?.revoked || permission.status?.phase === 'Revoked'),
489
+ phase: permission.status?.phase || 'Unknown'
490
+ })),
491
+ sshKeys: sshKeys.map((key) => ({
492
+ name: key.metadata?.name,
493
+ title: key.spec?.title || key.metadata?.name,
494
+ owner: key.spec?.owner || key.spec?.user || '',
495
+ scope: key.spec?.scope || 'user',
496
+ repository: key.spec?.repository || '',
497
+ revoked: Boolean(key.spec?.revoked || key.status?.phase === 'Revoked'),
498
+ phase: key.status?.phase || 'Unknown'
499
+ })),
500
+ reconciliation: {
501
+ counts: reconciliation?.counts || {},
502
+ phases: reconciliationPhases,
503
+ statuses: reconciliationStatuses.map((item) => ({
504
+ kind: item.kind,
505
+ name: item.name,
506
+ phase: item.phase,
507
+ checks: (item.conditions || []).map((condition) => readableCondition(condition))
508
+ })),
509
+ nextActions: (reconciliation?.syncIntents || []).map((intent) => readableIntent(intent))
510
+ }
511
+ };
512
+ }
513
+
514
+
515
+ function readableCondition(condition = {}) {
516
+ const subject = String(condition.type || 'AccessCheck').replace(/([a-z])([A-Z])/g, '$1 $2');
517
+ return `${subject}: ${condition.status === 'False' ? 'needs attention' : 'ready'}`;
518
+ }
519
+
520
+ function readableIntent(intent = {}) {
521
+ const action = String(intent.action || 'sync-access').replace(/-/g, ' ');
522
+ const subject = intent.user || intent.team || intent.subject || intent.owner || intent.name || intent.repository || 'workspace access';
523
+ return `${action}: ${subject}`;
524
+ }
525
+
526
+ function createPolicyEngineView({ kyverno = {}, policyProfiles = [], policyTemplates = [], policyBindings = [], policyExceptionRequests = [], org = 'default', namespace = 'kradle-system' }) {
527
+ const reports = kyverno.reports || { results: [], violations: [] };
528
+ const violations = reports.violations || [];
529
+ const detected = Boolean(kyverno.detected);
530
+ const mode = kyverno.mode || 'disabled';
531
+ const health = mode === 'disabled' ? 'disabled' : detected && !(kyverno.degraded || []).length ? 'ready' : 'degraded';
532
+ return {
533
+ engine: 'kyverno',
534
+ mode,
535
+ health,
536
+ detected,
537
+ namespace: kyverno.namespace || 'kyverno',
538
+ policyNamespace: kyverno.policyNamespace || namespace,
539
+ requireForEnforceMode: kyverno.requireForEnforceMode !== false,
540
+ org,
541
+ profiles: policyProfiles.map(policySummary),
542
+ templates: policyTemplates.map(policySummary),
543
+ bindings: policyBindings.map(policySummary),
544
+ exceptionRequests: policyExceptionRequests.map(policySummary),
545
+ kyvernoResources: Object.fromEntries(Object.entries(kyverno.resources || {}).map(([kind, items]) => [kind, Array.isArray(items) ? items.length : 0])),
546
+ controllers: kyverno.controllers || [],
547
+ permissions: kyverno.permissions || [],
548
+ reports: {
549
+ policyReports: reports.policyReports?.length || 0,
550
+ clusterPolicyReports: reports.clusterPolicyReports?.length || 0,
551
+ results: reports.results || []
552
+ },
553
+ violations,
554
+ degraded: kyverno.degraded || [],
555
+ emptyState: mode === 'disabled' ? 'Kyverno integration is disabled. Kradle native RefPolicy and BranchProtection remain available.' : detected ? '' : 'Kyverno is not installed or is not readable by Kradle.'
556
+ };
557
+ }
558
+
559
+ function policySummary(resource = {}) {
560
+ return {
561
+ kind: resource.kind,
562
+ name: resource.metadata?.name,
563
+ namespace: resource.metadata?.namespace,
564
+ mode: resource.spec?.mode || resource.status?.mode || 'audit',
565
+ phase: resource.status?.phase || 'Pending',
566
+ displayName: resource.spec?.displayName || resource.metadata?.name,
567
+ targetKinds: resource.spec?.targetKinds || resource.spec?.match?.resourceKinds || [],
568
+ templateRef: resource.spec?.templateRef || '',
569
+ policyRef: resource.status?.policyRef || resource.spec?.policyRef || null,
570
+ violationCount: resource.status?.lastViolationCount || 0
571
+ };
572
+ }
573
+
574
+ function createDeliveryView({ applications, releases, components, workloads, traits, scopes, policyDefinitions, policies, automationSteps, automations, managedResources }) {
575
+ return {
576
+ installed: applications.length + components.length + workloads.length + traits.length + scopes.length + policyDefinitions.length + automationSteps.length > 0,
577
+ specVersion: 'v0.3.0',
578
+ counts: {
579
+ applications: applications.length,
580
+ releases: releases.length,
581
+ components: components.length,
582
+ workloads: workloads.length,
583
+ traits: traits.length,
584
+ scopes: scopes.length,
585
+ policyDefinitions: policyDefinitions.length,
586
+ policies: policies.length,
587
+ automationSteps: automationSteps.length,
588
+ automations: automations.length,
589
+ managedResources: managedResources.length
590
+ },
591
+ capabilityCatalog: {
592
+ components: components.map((item) => item.metadata?.name).filter(Boolean),
593
+ workloads: workloads.map((item) => item.metadata?.name).filter(Boolean),
594
+ traits: traits.map((item) => item.metadata?.name).filter(Boolean),
595
+ scopes: scopes.map((item) => item.metadata?.name).filter(Boolean),
596
+ policyDefinitions: policyDefinitions.map((item) => item.metadata?.name).filter(Boolean),
597
+ policies: policies.map((item) => item.metadata?.name).filter(Boolean),
598
+ automationSteps: automationSteps.map((item) => item.metadata?.name).filter(Boolean)
599
+ },
600
+ applications: applications.map((application) => ({
601
+ name: application.metadata?.name,
602
+ namespace: application.metadata?.namespace,
603
+ healthy: application.status?.services?.every((service) => service.healthy !== false) || false,
604
+ message: application.status?.services?.map((service) => service.message).filter(Boolean).join('; ') || '',
605
+ status: application.status?.status || application.status?.phase || application.status?.conditions?.[0]?.type || 'Unknown',
606
+ appliedResources: (application.status?.appliedResources || []).map((resource) => ({ apiVersion: resource.apiVersion, kind: resource.kind, namespace: resource.namespace || '', name: resource.name })),
607
+ services: (application.status?.services || []).map((service) => ({ name: service.name, namespace: service.namespace, healthy: service.healthy !== false, message: service.message || '', traits: service.traits || [], workloadDefinition: service.workloadDefinition || null })),
608
+ workflow: application.status?.workflow ? { status: application.status.workflow.status || 'Unknown', finished: Boolean(application.status.workflow.finished), appRevision: application.status.workflow.appRevision || '', steps: (application.status.workflow.steps || []).map((step) => ({ name: step.name, type: step.type, phase: step.phase || 'Unknown' })) } : null,
609
+ releases: releases.filter((release) => release.metadata?.labels?.['app.oam.dev/name'] === application.metadata?.name || release.metadata?.name?.startsWith(application.metadata?.name + '-')).map((release) => ({ name: release.metadata?.name, succeeded: release.status?.succeeded, publishVersion: release.status?.publishVersion || '' })),
610
+ managedResources: managedResources.filter((resource) => resource.metadata?.labels?.['app.oam.dev/name'] === application.metadata?.name || resource.spec?.application?.name === application.metadata?.name || resource.metadata?.name?.startsWith(application.metadata?.name + '-')).map((resource) => resource.metadata?.name).filter(Boolean),
611
+ yaml: resourceToYaml(application)
612
+ })),
613
+ runtime: {
614
+ releases: releases.map((item) => ({ name: item.metadata?.name, application: item.metadata?.labels?.['app.oam.dev/name'] || item.spec?.application?.name || '', succeeded: item.status?.succeeded || false })),
615
+ automations: automations.map((item) => ({ name: item.metadata?.name, phase: item.status?.phase || item.status?.status || 'Unknown' })),
616
+ policies: policies.map((item) => ({ name: item.metadata?.name, type: item.type || item.spec?.type || item.metadata?.labels?.['policy.oam.dev/type'] || '' })),
617
+ managedResources: managedResources.map((item) => ({ name: item.metadata?.name, type: item.spec?.type || item.metadata?.labels?.['app.oam.dev/resource-tracker-type'] || '' }))
618
+ }
619
+ };
620
+ }