@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,621 @@
1
+ import { createKubernetesResourceGateway } from './kubernetes-resource-gateway.js';
2
+ import { clearSnapshotCache } from './snapshot-cache.js';
3
+ import { createPermissionReviewer } from './agent-permission-review.js';
4
+ import { createAgentDispatchController } from './agent-dispatch-controller.js';
5
+ import { createAgentMuxClient } from './agent-mux-client.js';
6
+ import { createAgentApprovalController } from './agent-approval-controller.js';
7
+ import { createAgentTriggerController } from './agent-trigger-controller.js';
8
+ import { createAgentWorkspaceController } from './agent-workspace-controller.js';
9
+ import { createAgentMemoryController } from './agent-memory-controller.js';
10
+ import { orgNamespaceName, normalizeOrgSlug } from './org-scoping.js';
11
+ import { globalEventBus } from './event-bus.js';
12
+ import { createSyncController } from './external/sync-controller.js';
13
+ import { createWebhookController } from './external/webhook-controller.js';
14
+ import { createWriteController } from './external/write-controller.js';
15
+ import { createConflictController } from './external/conflict-controller.js';
16
+ import { createModelRouteController } from './model-route-controller.js';
17
+
18
+ export const KRADLE_API_CONTROLLER_BOUNDARY = {
19
+ role: 'kradle-api-controller',
20
+ scope: 'HTTP/application facade for validation, request orchestration, user-facing DTOs, API errors, and workflow affordances',
21
+ owns: ['input validation', 'forge DTOs', 'API errors', 'workflow affordances', 'controller UI snapshots'],
22
+ delegatesTo: ['kubernetes-resource-gateway', 'git-data-plane'],
23
+ mustNotOwn: ['kubectl process execution', 'Kubernetes reconciliation loops', 'watch stream internals', 'repository storage internals']
24
+ };
25
+
26
+ export function createKradleApiController(options = {}) {
27
+ const resourceGateway = options.resourceGateway || createKubernetesResourceGateway(options);
28
+ const namespace = options.namespace || resourceGateway.namespace || process.env.KRADLE_NAMESPACE || 'kradle-system';
29
+ const onAuditEvent = typeof options.onAuditEvent === 'function' ? options.onAuditEvent : null;
30
+
31
+ function emitAuditEvent(resource, operation) {
32
+ if (!onAuditEvent) return;
33
+ try {
34
+ const org = resource.spec?.organizationRef || resource.metadata?.labels?.['kradle.a5c.ai/org'] || '';
35
+ onAuditEvent({
36
+ operation,
37
+ org,
38
+ namespace: org ? orgNamespaceName(org) : (resource.metadata?.namespace || namespace),
39
+ kind: resource.kind,
40
+ name: resource.metadata?.name,
41
+ timestamp: new Date().toISOString()
42
+ });
43
+ } catch {
44
+ // Audit failures must not crash apply operations
45
+ }
46
+ }
47
+
48
+ return {
49
+ role: 'kradle-api-controller',
50
+ namespace,
51
+ resourceGateway,
52
+ resourceDefinitions: resourceGateway.resourceDefinitions,
53
+ async snapshot() {
54
+ return withArchitecture(await resourceGateway.snapshot(), namespace);
55
+ },
56
+ async listRepositoriesForForge() {
57
+ const resources = await resourceGateway.list('Repository');
58
+ return normalizeResourceList(resources).map((resource) => repositoryForgeSummary(resource, namespace));
59
+ },
60
+ async getRepositoryForgeView(name) {
61
+ const resource = await resourceGateway.get('Repository', name);
62
+ const repository = resource?.resource || resource;
63
+ return repositoryForgeView(repository, namespace);
64
+ },
65
+ async listResource(kindOrPlural) {
66
+ return resourceGateway.list(kindOrPlural);
67
+ },
68
+ async listResourceForOrg(org, kindOrPlural) {
69
+ const orgNs = orgNamespaceName(normalizeOrgSlug(org));
70
+ // Client-side filtering is used because the resource gateway's list()
71
+ // method does not currently support namespace-scoped listing. The
72
+ // gateway aggregates resources across namespaces at snapshot time, so
73
+ // filtering here is both correct and consistent with the gateway API.
74
+ const result = await resourceGateway.list(kindOrPlural);
75
+ const items = normalizeResourceList(result).filter(
76
+ (item) => item.metadata?.namespace === orgNs
77
+ );
78
+ return { ...result, items };
79
+ },
80
+ async getResource(kindOrPlural, name) {
81
+ return resourceGateway.get(kindOrPlural, name);
82
+ },
83
+ async applyResource(resource) {
84
+ // Cross-org admission check: if the resource has an organizationRef,
85
+ // ensure the namespace matches the org's derived namespace.
86
+ const resourceOrg = resource.spec?.organizationRef;
87
+ const resourceNs = resource.metadata?.namespace;
88
+ if (resourceOrg) {
89
+ const expectedNs = orgNamespaceName(resourceOrg);
90
+ if (resourceNs && resourceNs !== expectedNs) {
91
+ // Explicit namespace does not match the org — reject
92
+ throw new Error(
93
+ `Cross-org namespace mismatch: resource organizationRef "${resourceOrg}" expects namespace "${expectedNs}" but got "${resourceNs}"`
94
+ );
95
+ }
96
+ if (!resourceNs) {
97
+ // organizationRef present but no namespace — auto-assign
98
+ resource = {
99
+ ...resource,
100
+ metadata: { ...resource.metadata, namespace: expectedNs }
101
+ };
102
+ }
103
+ }
104
+ const result = await resourceGateway.apply(resource);
105
+ clearSnapshotCache();
106
+ const appliedResource = result.resource || resource;
107
+ emitAuditEvent(appliedResource, result.operation || 'apply');
108
+ globalEventBus.emitResourceChange(
109
+ appliedResource.kind || resource.kind || 'Unknown',
110
+ appliedResource.metadata?.name || resource.metadata?.name || 'unknown',
111
+ result.operation || 'apply'
112
+ );
113
+ return result;
114
+ },
115
+ async applyResourceForOrg(orgSlug, resource) {
116
+ const slug = normalizeOrgSlug(orgSlug);
117
+ const orgNs = orgNamespaceName(slug);
118
+ const resourceOrg = resource.spec?.organizationRef;
119
+ if (resourceOrg && normalizeOrgSlug(resourceOrg) !== slug) {
120
+ throw new Error(
121
+ `Org mismatch: resource organizationRef "${resourceOrg}" does not match target org "${slug}"`
122
+ );
123
+ }
124
+ const scopedResource = {
125
+ ...resource,
126
+ metadata: { ...resource.metadata, namespace: orgNs },
127
+ spec: { ...resource.spec, organizationRef: slug }
128
+ };
129
+ const result = await resourceGateway.apply(scopedResource);
130
+ clearSnapshotCache();
131
+ const appliedResource = result.resource || scopedResource;
132
+ emitAuditEvent(appliedResource, result.operation || 'apply');
133
+ globalEventBus.emitResourceChange(
134
+ appliedResource.kind || scopedResource.kind || 'Unknown',
135
+ appliedResource.metadata?.name || scopedResource.metadata?.name || 'unknown',
136
+ result.operation || 'apply'
137
+ );
138
+ return { ...result, resource: appliedResource };
139
+ },
140
+ async deleteResource(kindOrPlural, name) {
141
+ const result = await resourceGateway.delete(kindOrPlural, name);
142
+ clearSnapshotCache();
143
+ emitAuditEvent(
144
+ { kind: kindOrPlural, metadata: { name, namespace }, spec: {} },
145
+ 'delete'
146
+ );
147
+ globalEventBus.emitResourceChange(kindOrPlural, name, 'delete');
148
+ return result;
149
+ },
150
+ async deleteResourceForOrg(orgSlug, kindOrPlural, name) {
151
+ const slug = normalizeOrgSlug(orgSlug);
152
+ const orgNs = orgNamespaceName(slug);
153
+ // Verify the resource exists and belongs to the org before deleting
154
+ const existing = await resourceGateway.get(kindOrPlural, name);
155
+ const resource = existing?.resource || existing;
156
+ if (resource) {
157
+ const resourceNs = resource.metadata?.namespace;
158
+ if (!resourceNs || resourceNs !== orgNs) {
159
+ throw new Error(
160
+ `Cross-org denial: resource "${name}" is in namespace "${resourceNs || '(none)'}" which does not match org "${slug}" namespace "${orgNs}"`
161
+ );
162
+ }
163
+ }
164
+ const result = await resourceGateway.delete(kindOrPlural, name);
165
+ clearSnapshotCache();
166
+ emitAuditEvent(
167
+ { kind: kindOrPlural, metadata: { name, namespace: orgNs }, spec: { organizationRef: slug } },
168
+ 'delete'
169
+ );
170
+ globalEventBus.emitResourceChange(kindOrPlural, name, 'delete');
171
+ return result;
172
+ },
173
+ async getResourceForOrg(orgSlug, kindOrPlural, name) {
174
+ const slug = normalizeOrgSlug(orgSlug);
175
+ const orgNs = orgNamespaceName(slug);
176
+ const existing = await resourceGateway.get(kindOrPlural, name);
177
+ const resource = existing?.resource || existing;
178
+ if (resource) {
179
+ const resourceNs = resource.metadata?.namespace;
180
+ if (!resourceNs || resourceNs !== orgNs) {
181
+ throw new Error(
182
+ `Cross-org denial: resource "${name}" is in namespace "${resourceNs || '(none)'}" which does not match org "${slug}" namespace "${orgNs}"`
183
+ );
184
+ }
185
+ }
186
+ return existing;
187
+ },
188
+ async createRepository(input) {
189
+ const created = await resourceGateway.createRepository(input);
190
+ const repository = created?.resource || created;
191
+ emitAuditEvent(
192
+ repository?.kind ? repository : { kind: 'Repository', metadata: repository?.metadata || { name: input.name || input.metadata?.name }, spec: repository?.spec || input.spec || input },
193
+ 'create-repository'
194
+ );
195
+ return {
196
+ operation: created?.operation || 'create-repository',
197
+ command: created?.command || 'kubectl apply -f -',
198
+ repository: repositoryForgeSummary(repository, namespace),
199
+ resource: repository
200
+ };
201
+ },
202
+ async createOrganization(input) {
203
+ const result = await resourceGateway.createOrganization(input);
204
+ const orgResource = result?.organization || result?.resource || result;
205
+ emitAuditEvent(
206
+ orgResource?.kind ? orgResource : { kind: 'Organization', metadata: orgResource?.metadata || { name: input.slug || input.name || input.metadata?.name }, spec: orgResource?.spec || input.spec || input },
207
+ 'create-organization'
208
+ );
209
+ return result;
210
+ },
211
+ watchResource(resourcePath, handlers = {}) {
212
+ return resourceGateway.watch(resourcePath, handlers);
213
+ },
214
+ async reviewAgentPermissions(input) {
215
+ const reviewer = createPermissionReviewer();
216
+ const snapshot = await this.snapshot();
217
+ return reviewer.reviewPermissions({
218
+ ...input,
219
+ resources: snapshot.resources
220
+ });
221
+ },
222
+ async dispatchAgent(input) {
223
+ const snapshot = await this.snapshot();
224
+ const controllerOptions = input.controllerOptions || {};
225
+ const controller = createAgentDispatchController({
226
+ ...controllerOptions,
227
+ agentMuxClient: controllerOptions.agentMuxClient || createAgentMuxClient({ resourceGateway }),
228
+ });
229
+ const result = await controller.createManualDispatch({
230
+ ...input,
231
+ resources: snapshot.resources
232
+ });
233
+ if (result?.memorySnapshot) {
234
+ result.memorySnapshotApplyResult = await this.applyResourceForOrg(
235
+ input.organizationRef || result.memorySnapshot.spec?.organizationRef || 'default',
236
+ result.memorySnapshot
237
+ );
238
+ }
239
+ return result;
240
+ },
241
+ async approveAgentAction(input) {
242
+ const snapshot = await this.snapshot();
243
+ const approvalController = createAgentApprovalController();
244
+ return approvalController.recordDecision({
245
+ ...input,
246
+ decision: 'approve',
247
+ resources: snapshot.resources
248
+ });
249
+ },
250
+ async denyAgentAction(input) {
251
+ const snapshot = await this.snapshot();
252
+ const approvalController = createAgentApprovalController();
253
+ return approvalController.recordDecision({
254
+ ...input,
255
+ decision: 'deny',
256
+ resources: snapshot.resources
257
+ });
258
+ },
259
+ async processWebhookEvent(input) {
260
+ const snapshot = await this.snapshot();
261
+ const controllerOptions = input.controllerOptions || {};
262
+ const dispatchController = createAgentDispatchController({
263
+ ...controllerOptions,
264
+ agentMuxClient: controllerOptions.agentMuxClient || createAgentMuxClient({ resourceGateway }),
265
+ });
266
+ const triggerController = createAgentTriggerController({ dispatchController });
267
+ return triggerController.processEvent({
268
+ event: input.event,
269
+ resources: snapshot.resources,
270
+ namespace: input.namespace || namespace,
271
+ organizationRef: input.organizationRef || 'default',
272
+ });
273
+ },
274
+ async provisionAgentWorkspace(input) {
275
+ const workspaceController = createAgentWorkspaceController();
276
+ return workspaceController.provisionWorkspace({
277
+ ...input,
278
+ namespace: input.namespace || namespace,
279
+ organizationRef: input.organizationRef || 'default'
280
+ });
281
+ },
282
+ async archiveAgentWorkspace(input) {
283
+ const snapshot = await this.snapshot();
284
+ const workspaceController = createAgentWorkspaceController();
285
+ return workspaceController.archiveWorkspace({
286
+ ...input,
287
+ resources: snapshot.resources
288
+ });
289
+ },
290
+ async linkWorkItem(input) {
291
+ const workspaceController = createAgentWorkspaceController();
292
+ return workspaceController.linkWorkItem({
293
+ ...input,
294
+ namespace: input.namespace || namespace,
295
+ organizationRef: input.organizationRef || 'default'
296
+ });
297
+ },
298
+ async queryAgentMemory(input) {
299
+ const memoryController = createAgentMemoryController();
300
+ return memoryController.queryMemory({
301
+ ...input,
302
+ namespace: input.namespace || namespace,
303
+ organizationRef: input.organizationRef || 'default'
304
+ });
305
+ },
306
+ async createMemoryImport(input) {
307
+ const memoryController = createAgentMemoryController();
308
+ const importResource = memoryController.createImport({
309
+ ...input,
310
+ namespace: input.namespace || namespace,
311
+ organizationRef: input.organizationRef || 'default'
312
+ });
313
+ const applyResult = await this.applyResourceForOrg(
314
+ importResource.spec?.organizationRef || input.organizationRef || 'default',
315
+ importResource
316
+ );
317
+ return { ...importResource, importResource, applyResult };
318
+ },
319
+ async createMemoryUpdate(input) {
320
+ const memoryController = createAgentMemoryController();
321
+ const update = memoryController.createMemoryUpdate({
322
+ ...input,
323
+ namespace: input.namespace || namespace,
324
+ organizationRef: input.organizationRef || 'default'
325
+ });
326
+ const applyResult = await this.applyResourceForOrg(
327
+ update.spec?.organizationRef || input.organizationRef || 'default',
328
+ update
329
+ );
330
+ return { ...update, update, applyResult };
331
+ },
332
+ async applyModelRoute(route, options = {}) {
333
+ const routeController = createModelRouteController();
334
+ const validation = routeController.validate(route);
335
+ if (!validation.valid) {
336
+ throw new Error(`Invalid KradleModelRoute: ${validation.errors.join('; ')}`);
337
+ }
338
+
339
+ const routeResult = await this.applyResource(route);
340
+ const appliedRoute = routeResult.resource || route;
341
+ const snapshot = options.resources
342
+ ? { resources: options.resources }
343
+ : await resourceGateway.snapshot();
344
+ const allResources = normalizeResourceList(snapshot?.resources || snapshot?.items || snapshot || []);
345
+ const resolvedEndpoint = routeController.resolveRoute(appliedRoute, allResources);
346
+ if (!resolvedEndpoint.endpoint) {
347
+ throw new Error(`Could not resolve endpoint for KradleModelRoute "${appliedRoute.metadata?.name || 'unknown'}"`);
348
+ }
349
+ const gatewayRoute = routeController.generateEnvoyRouteManifest(appliedRoute, resolvedEndpoint);
350
+ const gatewayRouteResult = await this.applyResource(gatewayRoute);
351
+ return { route: appliedRoute, routeResult, gatewayRoute, gatewayRouteResult };
352
+ },
353
+
354
+ // ---------------------------------------------------------------------------
355
+ // Model Route catalog
356
+ // ---------------------------------------------------------------------------
357
+
358
+ /**
359
+ * List the unified model catalog for an organization by resolving all
360
+ * KradleModelRoute resources through the model-route-controller.
361
+ *
362
+ * @param {string} org - organization slug
363
+ * @returns {Promise<Array<{ name: string, provider: string, type: string, status: string, endpoint: string, protocol: string }>>}
364
+ */
365
+ async listModelCatalog(org) {
366
+ const slug = normalizeOrgSlug(org);
367
+ const orgNs = orgNamespaceName(slug);
368
+ const result = await resourceGateway.list('KradleModelRoute');
369
+ const routes = normalizeResourceList(result).filter(
370
+ (item) => item.metadata?.namespace === orgNs
371
+ );
372
+ const snapshot = await resourceGateway.snapshot();
373
+ const allResources = normalizeResourceList(snapshot?.resources || snapshot?.items || snapshot || []);
374
+ const routeController = createModelRouteController();
375
+ return routeController.listModelCatalog(routes, allResources);
376
+ },
377
+
378
+ // ---------------------------------------------------------------------------
379
+ // External controller integration
380
+ // ---------------------------------------------------------------------------
381
+
382
+ /**
383
+ * Sync an external resource into the Kradle resource store.
384
+ * Creates a SyncController with a persistFn that calls applyResource, then
385
+ * upserts the resource and optionally advances the watermark.
386
+ *
387
+ * @param {string} bindingName
388
+ * @param {{ kind, localName, namespace?, spec, externalEnvelope, watermark? }} options
389
+ */
390
+ async syncExternalBinding(bindingName, options = {}) {
391
+ const self = this;
392
+ const syncController = createSyncController({
393
+ persistFn: async (resource) => {
394
+ await resourceGateway.apply(resource);
395
+ }
396
+ });
397
+
398
+ const {
399
+ kind,
400
+ localName,
401
+ namespace: resourceNamespace = 'default',
402
+ spec = {},
403
+ externalEnvelope,
404
+ watermark
405
+ } = options;
406
+
407
+ const resource = syncController.upsertResource({
408
+ kind,
409
+ localName,
410
+ namespace: resourceNamespace,
411
+ spec,
412
+ externalEnvelope
413
+ });
414
+
415
+ if (watermark) {
416
+ syncController.updateWatermark(bindingName, watermark);
417
+ }
418
+
419
+ // Keep a reference to the sync controller so getExternalSyncStatus can read it
420
+ if (!self._syncControllers) self._syncControllers = new Map();
421
+ self._syncControllers.set(bindingName, syncController);
422
+
423
+ return { resource, bindingName };
424
+ },
425
+
426
+ /**
427
+ * Create a write intent for an external operation.
428
+ *
429
+ * @param {{ interfaceKey, operation, payload?, resourceRef, requiresApproval?,
430
+ * maxRetries?, namespace?, organizationRef? }} input
431
+ */
432
+ async createExternalWriteIntent(input) {
433
+ const writeController = createWriteController({
434
+ persistFn: async (resource) => {
435
+ await resourceGateway.apply(resource);
436
+ }
437
+ });
438
+ return writeController.createWriteIntent(input);
439
+ },
440
+
441
+ /**
442
+ * Approve a PendingApproval ExternalWriteIntent.
443
+ *
444
+ * @param {{ intentName, approvedBy, resources? }} opts
445
+ */
446
+ async approveExternalWriteIntent(opts) {
447
+ const writeController = createWriteController({
448
+ persistFn: async (resource) => {
449
+ await resourceGateway.apply(resource);
450
+ }
451
+ });
452
+ return writeController.approveWriteIntent(opts);
453
+ },
454
+
455
+ /**
456
+ * Cancel (reject) an ExternalWriteIntent.
457
+ *
458
+ * @param {{ intentName, cancelledBy, resources? }} opts
459
+ */
460
+ async cancelExternalWriteIntent({ intentName, cancelledBy, resources } = {}) {
461
+ const writeController = createWriteController({
462
+ persistFn: async (resource) => {
463
+ await resourceGateway.apply(resource);
464
+ }
465
+ });
466
+ return writeController.rejectWriteIntent({
467
+ intentName,
468
+ rejectedBy: cancelledBy,
469
+ reason: 'cancelled',
470
+ resources
471
+ });
472
+ },
473
+
474
+ /**
475
+ * Detect a conflict between local and external field values.
476
+ *
477
+ * @param {{ resourceRef, fieldPath, localValue, externalValue, namespace?, organizationRef? }} input
478
+ */
479
+ async detectExternalConflict(input) {
480
+ const conflictController = createConflictController({
481
+ persistFn: async (resource) => {
482
+ await resourceGateway.apply(resource);
483
+ }
484
+ });
485
+ return conflictController.detectConflict(input);
486
+ },
487
+
488
+ /**
489
+ * Resolve an Open ExternalSyncConflict using the specified strategy.
490
+ *
491
+ * @param {{ conflictName, strategy, resolvedValue?, resources? }} opts
492
+ */
493
+ async resolveExternalConflict(opts) {
494
+ const conflictController = createConflictController({
495
+ persistFn: async (resource) => {
496
+ await resourceGateway.apply(resource);
497
+ }
498
+ });
499
+ return conflictController.resolveConflict(opts);
500
+ },
501
+
502
+ /**
503
+ * Return the current sync state for a binding (watermark, etc.).
504
+ *
505
+ * @param {string} bindingName
506
+ * @returns {{ bindingName: string, watermark: string|null }}
507
+ */
508
+ async getExternalSyncStatus(bindingName) {
509
+ const syncController = this._syncControllers?.get(bindingName);
510
+ const watermark = syncController ? syncController.getWatermark(bindingName) : null;
511
+ return { bindingName, watermark };
512
+ },
513
+
514
+ /**
515
+ * Process an inbound external webhook payload.
516
+ * Creates a WebhookController, processes the delivery, and emits events.
517
+ *
518
+ * @param {{ deliveryId, eventType, payload, rawBody, providerType?, secret? }} params
519
+ */
520
+ async processExternalWebhook({ deliveryId, eventType, payload, rawBody, providerType, secret } = {}) {
521
+ const webhookController = createWebhookController({ secret: secret || '' });
522
+ return webhookController.processDelivery({ deliveryId, eventType, payload, rawBody });
523
+ }
524
+ };
525
+ }
526
+
527
+ export function withArchitecture(snapshot, namespace = snapshot?.namespace || 'default') {
528
+ return {
529
+ ...snapshot,
530
+ architecture: {
531
+ apiController: {
532
+ ...KRADLE_API_CONTROLLER_BOUNDARY,
533
+ owns: [...KRADLE_API_CONTROLLER_BOUNDARY.owns, '/api/controller', '/api/orgs/:org/resources', '/api/orgs/:org/repositories', '/api/watch/orgs/:org/*'],
534
+ scope: `${KRADLE_API_CONTROLLER_BOUNDARY.scope}; never owns Kubernetes reconciliation loops`
535
+ },
536
+ resourceGateway: {
537
+ role: 'kubernetes-resource-gateway',
538
+ scope: 'Narrow application port translating API controller intent into Kubernetes resource-client calls',
539
+ namespace,
540
+ delegatesTo: ['kubernetes-resource-client']
541
+ },
542
+ kubernetesClient: {
543
+ role: 'kubernetes-resource-client',
544
+ scope: 'kubectl-backed Kubernetes API discovery, SubjectAccessReview checks, list/get/apply/delete/watch; no UI flow or product workflow ownership',
545
+ namespace,
546
+ owns: ['Kradle CRDs', 'aggregated API resources', 'Kubernetes watch streams']
547
+ },
548
+ kubernetesReconciler: {
549
+ role: 'kradle-kubernetes-reconciler',
550
+ scope: 'Repository status projection, repository hosting intent, policy projection, and data-plane sync intent; never owns HTTP routes or browser flows',
551
+ namespace,
552
+ delegatesTo: ['kubernetes-resource-gateway', 'git-data-plane']
553
+ },
554
+ dataPlane: {
555
+ role: 'git-data-plane',
556
+ scope: 'Repository streaming, SSH hosting, object storage, search indexing, and warm receive-pack paths',
557
+ boundary: process.env.KRADLE_GITEA_HTTP_URL || 'repository service not configured'
558
+ }
559
+ }
560
+ };
561
+ }
562
+
563
+ export function repositoryForgeSummary(resource, namespace = 'kradle-system') {
564
+ const metadata = resource?.metadata || {};
565
+ const spec = resource?.spec || {};
566
+ const name = metadata.name || 'unknown-repository';
567
+ const repositoryNamespace = metadata.namespace || namespace;
568
+ const org = spec.organizationRef || metadata.labels?.['kradle.a5c.ai/org'] || 'default';
569
+ const repoPath = `/orgs/${encodeURIComponent(org)}/repositories/${encodeURIComponent(name)}`;
570
+ return {
571
+ kind: 'Repository',
572
+ name,
573
+ org,
574
+ namespace: repositoryNamespace,
575
+ visibility: spec.visibility || 'internal',
576
+ defaultBranch: spec.defaultBranch || 'main',
577
+ phase: resource?.status?.phase || (resource ? 'Ready' : 'Unknown'),
578
+ href: `${repoPath}/code`,
579
+ cloneUrl: spec.gitHosting?.httpUrl || `<kradle-repository-service>/${encodeURIComponent(org)}/${name}.git`,
580
+ actions: {
581
+ code: `${repoPath}/code`,
582
+ pullRequests: `${repoPath}/pull-requests`,
583
+ issues: `${repoPath}/issues`,
584
+ runs: `${repoPath}/runs`,
585
+ pipelines: `${repoPath}/runs`,
586
+ hooks: `${repoPath}/hooks`,
587
+ settings: `${repoPath}/settings`,
588
+ yaml: `/orgs/${encodeURIComponent(org)}/advanced-plans?kind=Repository&name=${encodeURIComponent(name)}`
589
+ },
590
+ kubectl: {
591
+ get: `kubectl get repositories.kradle.a5c.ai ${name} -n ${repositoryNamespace} -o yaml`,
592
+ delete: `kubectl delete repositories.kradle.a5c.ai ${name} -n ${repositoryNamespace}`
593
+ }
594
+ };
595
+ }
596
+
597
+ export function repositoryForgeView(resource, namespace = 'default') {
598
+ const summary = repositoryForgeSummary(resource, namespace);
599
+ return {
600
+ ...summary,
601
+ primaryFlow: 'browse-code-open-pr-review-merge',
602
+ emptyState: resource ? null : 'Repository resource is not available from the Kubernetes resource gateway.',
603
+ sections: [
604
+ { id: 'code', label: 'Code', href: summary.actions.code, state: 'branch-and-path-aware' },
605
+ { id: 'pull-requests', label: 'Pull requests', href: summary.actions.pullRequests, state: 'review-merge-checks' },
606
+ { id: 'issues', label: 'Issues', href: summary.actions.issues, state: 'triage-policy-aware' },
607
+ { id: 'runs', label: 'Runs', href: summary.actions.runs, state: 'runner-and-job-aware' },
608
+ { id: 'hooks', label: 'Hooks', href: summary.actions.hooks, state: 'delivery-replay-aware' },
609
+ { id: 'settings', label: 'Settings', href: summary.actions.settings, state: 'branch-protection-rbac-danger-actions' }
610
+ ]
611
+ };
612
+ }
613
+
614
+ function normalizeResourceList(result) {
615
+ if (Array.isArray(result)) return result;
616
+ if (Array.isArray(result?.items)) return result.items;
617
+ if (Array.isArray(result?.resources)) return result.resources;
618
+ if (result?.resource) return [result.resource];
619
+ return [];
620
+ }
621
+
@@ -0,0 +1,43 @@
1
+ export function createArgoCdApplication({
2
+ name = 'kradle',
3
+ namespace = 'argocd',
4
+ project = 'default',
5
+ repoURL,
6
+ path = 'charts/kradle',
7
+ targetRevision = 'HEAD',
8
+ destinationNamespace = 'kradle-system',
9
+ destinationServer = 'https://kubernetes.default.svc',
10
+ automated = true
11
+ } = {}) {
12
+ if (!repoURL) throw new Error('Argo CD Application requires repoURL');
13
+ return {
14
+ apiVersion: 'argoproj.io/v1alpha1',
15
+ kind: 'Application',
16
+ metadata: {
17
+ name,
18
+ namespace,
19
+ labels: {
20
+ 'app.kubernetes.io/part-of': 'kradle',
21
+ 'kradle.a5c.ai/gitops-engine': 'argocd'
22
+ }
23
+ },
24
+ spec: {
25
+ project,
26
+ source: { repoURL, targetRevision, path },
27
+ destination: { server: destinationServer, namespace: destinationNamespace },
28
+ syncPolicy: automated ? {
29
+ automated: { prune: true, selfHeal: true },
30
+ syncOptions: ['CreateNamespace=true']
31
+ } : { syncOptions: ['CreateNamespace=true'] }
32
+ }
33
+ };
34
+ }
35
+
36
+ export function createKradleGitOpsPlan({ repoURL, namespace = 'kradle-system', applicationName = 'kradle' }) {
37
+ return {
38
+ engine: 'argocd',
39
+ application: createArgoCdApplication({ name: applicationName, repoURL, destinationNamespace: namespace }),
40
+ requiredClusterResources: ['Application.argoproj.io', 'Namespace', 'ServiceAccount', 'RBAC', 'APIService', 'Kradle CRDs'],
41
+ syncGuarantees: ['automated prune', 'automated selfHeal', 'namespace creation']
42
+ };
43
+ }