@a5c-ai/krate 5.0.1-staging.00fa5317c

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 (256) hide show
  1. package/Dockerfile +31 -0
  2. package/README.md +183 -0
  3. package/bin/krate-demo.mjs +23 -0
  4. package/bin/krate-server.mjs +14 -0
  5. package/dist/krate-controller-ui.json +3205 -0
  6. package/dist/krate-lifecycle.json +201 -0
  7. package/dist/krate-runtime-snapshot.json +3125 -0
  8. package/dist/krate-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-krate-crds.md +298 -0
  27. package/docs/agents/glossary.md +66 -0
  28. package/docs/agents/implementation-blueprint.md +324 -0
  29. package/docs/agents/implementation-rollout-slices.md +251 -0
  30. package/docs/agents/memory-context-integration-spec.md +194 -0
  31. package/docs/agents/memory-ontology-schema-spec.md +253 -0
  32. package/docs/agents/memory-operations-runbook.md +121 -0
  33. package/docs/agents/mvp-vertical-slice-spec.md +146 -0
  34. package/docs/agents/observability-audit-spec.md +265 -0
  35. package/docs/agents/operator-runbook.md +174 -0
  36. package/docs/agents/org-memory-api-payload-examples.md +333 -0
  37. package/docs/agents/org-memory-controller-sequence-spec.md +181 -0
  38. package/docs/agents/org-memory-e2e-fixture-plan.md +161 -0
  39. package/docs/agents/org-memory-ui-implementation-map.md +114 -0
  40. package/docs/agents/org-memory-vertical-slice-spec.md +168 -0
  41. package/docs/agents/org-resource-model-delta-spec.md +111 -0
  42. package/docs/agents/org-route-resource-model-spec.md +183 -0
  43. package/docs/agents/org-scoping-namespace-spec.md +114 -0
  44. package/docs/agents/rbac-secrets-management-spec.md +406 -0
  45. package/docs/agents/repository-page-integration-spec.md +255 -0
  46. package/docs/agents/resource-contract-examples.md +808 -0
  47. package/docs/agents/resource-relationship-map.md +190 -0
  48. package/docs/agents/security-threat-model.md +188 -0
  49. package/docs/agents/shared-memory-company-brain-spec.md +358 -0
  50. package/docs/agents/storage-migration-spec.md +168 -0
  51. package/docs/agents/subagent-orchestration-spec.md +152 -0
  52. package/docs/agents/system-overview.md +88 -0
  53. package/docs/agents/tools-mcp-skills-spec.md +189 -0
  54. package/docs/agents/traceability-matrix.md +79 -0
  55. package/docs/agents/ui-flow-spec.md +211 -0
  56. package/docs/agents/ui-ux-system-spec.md +426 -0
  57. package/docs/agents/workspace-lifecycle-spec.md +166 -0
  58. package/docs/architecture-spec.md +78 -0
  59. package/docs/components/control-plane.md +78 -0
  60. package/docs/components/data-plane.md +69 -0
  61. package/docs/components/hooks-events.md +67 -0
  62. package/docs/components/identity-rbac-policy.md +73 -0
  63. package/docs/components/kubevela-oam.md +70 -0
  64. package/docs/components/operations-publishing.md +81 -0
  65. package/docs/components/runners-ci.md +66 -0
  66. package/docs/components/web-ui.md +94 -0
  67. package/docs/external/README.md +47 -0
  68. package/docs/external/bidirectional-sync-design.md +134 -0
  69. package/docs/external/cicd-interface.md +64 -0
  70. package/docs/external/external-backend-controllers.md +170 -0
  71. package/docs/external/external-backend-crds.md +234 -0
  72. package/docs/external/external-backend-ui-spec.md +151 -0
  73. package/docs/external/external-backend-ux-flows.md +115 -0
  74. package/docs/external/external-object-mapping.md +125 -0
  75. package/docs/external/git-forge-interface.md +68 -0
  76. package/docs/external/github-integration-design.md +151 -0
  77. package/docs/external/issue-tracking-interface.md +66 -0
  78. package/docs/external/provider-capability-manifests.md +204 -0
  79. package/docs/external/provider-catalog.md +139 -0
  80. package/docs/external/provider-rollout-testing.md +78 -0
  81. package/docs/external/research-results.md +48 -0
  82. package/docs/external/security-auth-permissions.md +81 -0
  83. package/docs/external/sync-state-machines.md +108 -0
  84. package/docs/external/unified-external-backend-model.md +107 -0
  85. package/docs/external/user-facing-changes.md +67 -0
  86. package/docs/gaps.md +161 -0
  87. package/docs/install.md +94 -0
  88. package/docs/krate-design.md +334 -0
  89. package/docs/local-minikube.md +55 -0
  90. package/docs/ontology/README.md +32 -0
  91. package/docs/ontology/bounded-contexts.md +29 -0
  92. package/docs/ontology/events-and-hooks.md +32 -0
  93. package/docs/ontology/oam-kubevela.md +32 -0
  94. package/docs/ontology/operations-and-release.md +25 -0
  95. package/docs/ontology/personas-and-actors.md +32 -0
  96. package/docs/ontology/policies-and-invariants.md +33 -0
  97. package/docs/ontology/problem-space.md +30 -0
  98. package/docs/ontology/resource-contracts.md +40 -0
  99. package/docs/ontology/resource-taxonomy.md +42 -0
  100. package/docs/ontology/runners-and-ci.md +29 -0
  101. package/docs/ontology/solution-space.md +24 -0
  102. package/docs/ontology/storage-and-data-boundaries.md +29 -0
  103. package/docs/ontology/validation-matrix.md +24 -0
  104. package/docs/ontology/web-ui-excellent-flows.md +32 -0
  105. package/docs/ontology/workflows.md +39 -0
  106. package/docs/ontology/world.md +35 -0
  107. package/docs/openapi.yaml +1275 -0
  108. package/docs/product-requirements.md +62 -0
  109. package/docs/roadmap-mvp.md +87 -0
  110. package/docs/system-requirements.md +90 -0
  111. package/docs/tests/README.md +53 -0
  112. package/docs/tests/agent-qa-plan.md +63 -0
  113. package/docs/tests/browser-ui-tests.md +62 -0
  114. package/docs/tests/ci-quality-gates.md +48 -0
  115. package/docs/tests/coverage-model.md +64 -0
  116. package/docs/tests/e2e-scenario-tests.md +53 -0
  117. package/docs/tests/fixtures-test-data.md +63 -0
  118. package/docs/tests/observability-reliability-tests.md +54 -0
  119. package/docs/tests/product-test-matrix.md +145 -0
  120. package/docs/tests/qa-adoption-roadmap.md +130 -0
  121. package/docs/tests/qa-automation-plan.md +101 -0
  122. package/docs/tests/security-compliance-tests.md +57 -0
  123. package/docs/tests/test-framework-tools.md +88 -0
  124. package/docs/tests/test-suite-layout.md +121 -0
  125. package/docs/tests/unit-integration-tests.md +48 -0
  126. package/docs/todo-kyverno +714 -0
  127. package/docs/todos.md +4 -0
  128. package/docs/user-stories.md +78 -0
  129. package/examples/minikube-demo.yaml +190 -0
  130. package/examples/oam-application.yaml +23 -0
  131. package/examples/policy-kyverno-pr-title.yaml +18 -0
  132. package/package.json +63 -0
  133. package/scripts/build.mjs +29 -0
  134. package/scripts/setup-minikube.mjs +65 -0
  135. package/scripts/smoke.mjs +37 -0
  136. package/scripts/validate-doc-coverage.mjs +152 -0
  137. package/scripts/validate-package.mjs +93 -0
  138. package/scripts/validate-ui.mjs +278 -0
  139. package/src/agent-adapter-controller.js +169 -0
  140. package/src/agent-approval-controller.js +170 -0
  141. package/src/agent-context-bundles.js +242 -0
  142. package/src/agent-dispatch-controller.js +209 -0
  143. package/src/agent-gateway-config-controller.js +147 -0
  144. package/src/agent-memory-controller.js +357 -0
  145. package/src/agent-memory-import.js +327 -0
  146. package/src/agent-memory-query.js +292 -0
  147. package/src/agent-memory-repository-source-controller.js +255 -0
  148. package/src/agent-mux-client.js +280 -0
  149. package/src/agent-permission-review.js +250 -0
  150. package/src/agent-project-controller.js +117 -0
  151. package/src/agent-provider-config-controller.js +150 -0
  152. package/src/agent-secret-config-grant-controller.js +282 -0
  153. package/src/agent-session-transcript-controller.js +189 -0
  154. package/src/agent-stack-controller.js +347 -0
  155. package/src/agent-subagent-controller.js +160 -0
  156. package/src/agent-transport-binding-controller.js +121 -0
  157. package/src/agent-trigger-controller.js +381 -0
  158. package/src/agent-workspace-controller.js +702 -0
  159. package/src/agent-writeback-controller.js +302 -0
  160. package/src/api-controller.js +541 -0
  161. package/src/argocd-gitops.js +43 -0
  162. package/src/async-controller.js +207 -0
  163. package/src/audit-controller.js +191 -0
  164. package/src/auth.js +307 -0
  165. package/src/component-catalog.js +41 -0
  166. package/src/control-plane.js +136 -0
  167. package/src/controller-client.js +72 -0
  168. package/src/controller-ui.js +617 -0
  169. package/src/data-plane.js +179 -0
  170. package/src/event-bus.js +61 -0
  171. package/src/external/conflict-controller.js +225 -0
  172. package/src/external/github/auth.js +96 -0
  173. package/src/external/github/cicd.js +180 -0
  174. package/src/external/github/git-forge.js +240 -0
  175. package/src/external/github/index.js +144 -0
  176. package/src/external/github/issue-tracking.js +163 -0
  177. package/src/external/provider-adapter.js +161 -0
  178. package/src/external/provider-resource-factory.js +161 -0
  179. package/src/external/sync-controller.js +235 -0
  180. package/src/external/webhook-controller.js +144 -0
  181. package/src/external/write-controller.js +283 -0
  182. package/src/gitea-backend.js +131 -0
  183. package/src/gitea-service.js +173 -0
  184. package/src/handoff.js +98 -0
  185. package/src/hooks-events.js +63 -0
  186. package/src/http-server.js +377 -0
  187. package/src/identity-policy.js +86 -0
  188. package/src/index.js +57 -0
  189. package/src/kubernetes-controller-async.js +511 -0
  190. package/src/kubernetes-controller.js +878 -0
  191. package/src/kubernetes-resource-gateway.js +48 -0
  192. package/src/notification-controller.js +178 -0
  193. package/src/operations.js +112 -0
  194. package/src/org-scoping.js +5 -0
  195. package/src/resource-model.js +221 -0
  196. package/src/runner-controller.js +272 -0
  197. package/src/runners-ci.js +48 -0
  198. package/src/runtime.js +196 -0
  199. package/src/snapshot-cache.js +157 -0
  200. package/src/web-ui.js +40 -0
  201. package/tests/agent-adapter-controller.test.js +361 -0
  202. package/tests/agent-approval-controller.test.js +173 -0
  203. package/tests/agent-context-bundles.test.js +278 -0
  204. package/tests/agent-dispatch-controller.test.js +315 -0
  205. package/tests/agent-gateway-config-controller.test.js +386 -0
  206. package/tests/agent-memory-controller.test.js +308 -0
  207. package/tests/agent-memory-import-snapshot.test.js +477 -0
  208. package/tests/agent-memory-query.test.js +404 -0
  209. package/tests/agent-memory-repository-source.test.js +514 -0
  210. package/tests/agent-mux-client.test.js +204 -0
  211. package/tests/agent-permission-review-v2.test.js +317 -0
  212. package/tests/agent-permission-review.test.js +209 -0
  213. package/tests/agent-project-controller.test.js +302 -0
  214. package/tests/agent-provider-config-controller.test.js +376 -0
  215. package/tests/agent-resources.test.js +228 -0
  216. package/tests/agent-secret-config-grant.test.js +231 -0
  217. package/tests/agent-session-transcript-controller.test.js +499 -0
  218. package/tests/agent-stack-controller.test.js +221 -0
  219. package/tests/agent-subagent-controller.test.js +201 -0
  220. package/tests/agent-transport-binding-controller.test.js +294 -0
  221. package/tests/agent-trigger-controller.test.js +211 -0
  222. package/tests/agent-trigger-routes.test.js +190 -0
  223. package/tests/agent-trigger-sources.test.js +245 -0
  224. package/tests/agent-workspace-controller.test.js +181 -0
  225. package/tests/agent-writeback.test.js +292 -0
  226. package/tests/approval-persistence.test.js +171 -0
  227. package/tests/async-controller.test.js +252 -0
  228. package/tests/audit-controller.test.js +227 -0
  229. package/tests/codespace-controller.test.js +318 -0
  230. package/tests/deployment.test.js +407 -0
  231. package/tests/e2e/lifecycle.test.js +117 -0
  232. package/tests/event-bus-integration.test.js +190 -0
  233. package/tests/external-github-forge.test.js +560 -0
  234. package/tests/external-github-issues-cicd.test.js +520 -0
  235. package/tests/external-integration.test.js +470 -0
  236. package/tests/external-persistence.test.js +340 -0
  237. package/tests/external-provider-adapter.test.js +365 -0
  238. package/tests/external-resource-model.test.js +215 -0
  239. package/tests/external-webhook-sync.test.js +287 -0
  240. package/tests/external-write-conflict.test.js +353 -0
  241. package/tests/gitea-service.test.js +253 -0
  242. package/tests/health-check-real.test.js +165 -0
  243. package/tests/integration/full-flow.test.js +266 -0
  244. package/tests/krate.test.js +756 -0
  245. package/tests/memory-search-wiring.test.js +270 -0
  246. package/tests/notification-controller.test.js +196 -0
  247. package/tests/notification-integration.test.js +179 -0
  248. package/tests/org-scoping.test.js +687 -0
  249. package/tests/runner-controller.test.js +327 -0
  250. package/tests/runner-integration.test.js +231 -0
  251. package/tests/session-cookie-hmac.test.js +151 -0
  252. package/tests/snapshot-performance.test.js +247 -0
  253. package/tests/sse-events.test.js +107 -0
  254. package/tests/webhook-trigger.test.js +198 -0
  255. package/tests/workspace-volumes.test.js +312 -0
  256. package/tests/writeback-persistence.test.js +207 -0
@@ -0,0 +1,48 @@
1
+ import { createKubernetesResourceClient, repositoryManifest } from './kubernetes-controller.js';
2
+
3
+ export const KUBERNETES_RESOURCE_GATEWAY_BOUNDARY = {
4
+ role: 'kubernetes-resource-gateway',
5
+ scope: 'Application port translating API controller intent into Kubernetes resource-client operations',
6
+ owns: ['resource definitions', 'list/get/apply/delete/watch delegation', 'Repository manifest application', 'namespace scoping'],
7
+ delegatesTo: ['kubernetes-resource-client'],
8
+ mustNotOwn: ['HTTP routes', 'Next.js page flow decisions', 'forge DTO composition', 'Kubernetes reconciliation scheduling']
9
+ };
10
+
11
+ export function createKubernetesResourceGateway(options = {}) {
12
+ const resourceClient = options.resourceClient || options.kubernetesClient || createKubernetesResourceClient(options);
13
+ const namespace = options.namespace || resourceClient.namespace || process.env.KRATE_NAMESPACE || 'krate-system';
14
+
15
+ return {
16
+ ...KUBERNETES_RESOURCE_GATEWAY_BOUNDARY,
17
+ namespace,
18
+ resourceDefinitions: resourceClient.resourceDefinitions,
19
+ async snapshot() {
20
+ return resourceClient.snapshot();
21
+ },
22
+ async list(kindOrPlural) {
23
+ return resourceClient.listResource(kindOrPlural);
24
+ },
25
+ async get(kindOrPlural, name) {
26
+ return resourceClient.getResource(kindOrPlural, name);
27
+ },
28
+ async apply(resource) {
29
+ return resourceClient.applyResource(resource);
30
+ },
31
+ async delete(kindOrPlural, name) {
32
+ return resourceClient.deleteResource(kindOrPlural, name);
33
+ },
34
+ async createRepository(input) {
35
+ return resourceClient.applyResource(repositoryManifest(input, namespace));
36
+ },
37
+ async createOrganization(input) {
38
+ return resourceClient.createOrganization(input);
39
+ },
40
+ watch(resourcePath, handlers = {}) {
41
+ return resourceClient.watchResource(resourcePath, handlers);
42
+ }
43
+ };
44
+ }
45
+
46
+
47
+
48
+
@@ -0,0 +1,178 @@
1
+ import { randomUUID } from 'node:crypto';
2
+
3
+ export const NOTIFICATION_CONTROLLER_BOUNDARY = {
4
+ role: 'notification-controller',
5
+ scope: 'User notification lifecycle: creation from events, querying, read state, and preferences',
6
+ owns: ['notification creation', 'notification listing', 'read state', 'user preferences'],
7
+ delegatesTo: [],
8
+ mustNotOwn: ['event dispatch', 'UI rendering', 'push delivery']
9
+ };
10
+
11
+ const NOTIFICATION_TYPES = {
12
+ 'run-complete': { severity: 'info' },
13
+ 'approval-needed': { severity: 'warning' },
14
+ 'conflict-detected': { severity: 'warning' },
15
+ 'workspace-ready': { severity: 'info' },
16
+ 'system': { severity: 'info' },
17
+ };
18
+
19
+ const DEFAULT_PREFERENCES = {
20
+ runs: true,
21
+ approvals: true,
22
+ conflicts: true,
23
+ workspaces: true,
24
+ sound: false,
25
+ desktop: false,
26
+ };
27
+
28
+ export function createNotificationController() {
29
+ // Map of org -> notifications[]
30
+ const store = new Map();
31
+ // Map of userId -> preferences
32
+ const prefsStore = new Map();
33
+
34
+ function getOrgNotifications(org) {
35
+ if (!store.has(org)) store.set(org, []);
36
+ return store.get(org);
37
+ }
38
+
39
+ function mapEventToNotification(event) {
40
+ const { type, status, name, action, resourceRef, org = 'default' } = event || {};
41
+
42
+ let notifType = 'system';
43
+ let title = 'System event';
44
+ let message = '';
45
+ let severity = 'info';
46
+
47
+ if (type === 'AgentDispatchRun') {
48
+ notifType = 'run-complete';
49
+ const runName = name || event?.metadata?.name || 'Unknown';
50
+ if (status === 'completed') {
51
+ title = `Run ${runName} completed`;
52
+ message = `Agent dispatch run "${runName}" completed successfully.`;
53
+ severity = 'info';
54
+ } else if (status === 'failed') {
55
+ title = `Run ${runName} failed`;
56
+ message = `Agent dispatch run "${runName}" failed.`;
57
+ severity = 'error';
58
+ } else {
59
+ title = `Run ${runName} updated`;
60
+ message = `Agent dispatch run "${runName}" status: ${status || 'unknown'}.`;
61
+ severity = 'info';
62
+ }
63
+ } else if (type === 'AgentApproval' && status === 'pending') {
64
+ notifType = 'approval-needed';
65
+ const actionLabel = action || event?.spec?.action || 'unknown action';
66
+ title = `Approval needed for ${actionLabel}`;
67
+ message = `An agent is requesting approval for: ${actionLabel}.`;
68
+ severity = 'warning';
69
+ } else if (type === 'ExternalSyncConflict') {
70
+ notifType = 'conflict-detected';
71
+ const resource = resourceRef || event?.spec?.resourceRef || name || 'unknown resource';
72
+ title = `Conflict in ${resource}`;
73
+ message = `A sync conflict was detected in resource: ${resource}.`;
74
+ severity = 'warning';
75
+ } else if (type === 'KrateWorkspace' && event?.claimed) {
76
+ notifType = 'workspace-ready';
77
+ const wsName = name || event?.metadata?.name || 'Unknown';
78
+ const runRef = event?.claimedBy || event?.spec?.claimedBy || 'a run';
79
+ title = `Workspace ${wsName} claimed by ${runRef}`;
80
+ message = `Workspace "${wsName}" has been claimed by "${runRef}".`;
81
+ severity = 'info';
82
+ }
83
+
84
+ return { notifType, title, message, severity, org };
85
+ }
86
+
87
+ return {
88
+ role: 'notification-controller',
89
+
90
+ createNotification(event) {
91
+ const { notifType, title, message, severity, org } = mapEventToNotification(event);
92
+
93
+ const notification = {
94
+ id: randomUUID(),
95
+ type: notifType,
96
+ title,
97
+ message,
98
+ severity,
99
+ resourceRef: event?.resourceRef || null,
100
+ createdAt: new Date().toISOString(),
101
+ read: false,
102
+ org,
103
+ };
104
+
105
+ const notifications = getOrgNotifications(org);
106
+ notifications.push(notification);
107
+
108
+ return notification;
109
+ },
110
+
111
+ listNotifications(org, opts = {}) {
112
+ const { unreadOnly = false, limit = 20, since = null } = opts;
113
+ let notifications = [...getOrgNotifications(org)];
114
+
115
+ // Filter by read state
116
+ if (unreadOnly) {
117
+ notifications = notifications.filter((n) => !n.read);
118
+ }
119
+
120
+ // Filter by since timestamp
121
+ if (since) {
122
+ const sinceDate = new Date(since).getTime();
123
+ notifications = notifications.filter((n) => new Date(n.createdAt).getTime() > sinceDate);
124
+ }
125
+
126
+ // Newest first
127
+ notifications.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
128
+
129
+ // Cap to limit
130
+ if (limit > 0) {
131
+ notifications = notifications.slice(0, limit);
132
+ }
133
+
134
+ return notifications;
135
+ },
136
+
137
+ markAsRead(notificationId) {
138
+ for (const notifications of store.values()) {
139
+ const notification = notifications.find((n) => n.id === notificationId);
140
+ if (notification) {
141
+ notification.read = true;
142
+ return true;
143
+ }
144
+ }
145
+ return false;
146
+ },
147
+
148
+ markAllAsRead(org) {
149
+ const notifications = getOrgNotifications(org);
150
+ let count = 0;
151
+ for (const notification of notifications) {
152
+ if (!notification.read) {
153
+ notification.read = true;
154
+ count++;
155
+ }
156
+ }
157
+ return count;
158
+ },
159
+
160
+ getUnreadCount(org) {
161
+ return getOrgNotifications(org).filter((n) => !n.read).length;
162
+ },
163
+
164
+ getPreferences(userId) {
165
+ if (!prefsStore.has(userId)) {
166
+ return { ...DEFAULT_PREFERENCES };
167
+ }
168
+ return { ...DEFAULT_PREFERENCES, ...prefsStore.get(userId) };
169
+ },
170
+
171
+ updatePreferences(userId, prefs) {
172
+ const existing = prefsStore.get(userId) || {};
173
+ const merged = { ...DEFAULT_PREFERENCES, ...existing, ...prefs };
174
+ prefsStore.set(userId, merged);
175
+ return merged;
176
+ },
177
+ };
178
+ }
@@ -0,0 +1,112 @@
1
+ import { ControlPlane } from './control-plane.js';
2
+ import { GiteaGitService, GiteaRepositoryStore } from './data-plane.js';
3
+ import { WebhookBus } from './hooks-events.js';
4
+ import { RunnerScheduler } from './runners-ci.js';
5
+ import { createAdmissionPolicy, mapOidcIdentity, toResourceYaml } from './identity-policy.js';
6
+ import { createResource } from './resource-model.js';
7
+ import { createDashboard, createPullRequestReviewModel, createRunnerPoolEditor, createTriageView, createWebhookInspector } from './web-ui.js';
8
+ import { createKrateComponentCatalog, createKrateLifecycleSnapshot } from './component-catalog.js';
9
+
10
+ export function chartPackageSurface() {
11
+ return {
12
+ chart: 'charts/krate',
13
+ values: 'charts/krate/values.yaml',
14
+ crds: ['Repository', 'BranchProtection', 'RefPolicy', 'RunnerPool', 'WebhookSubscription', 'View', 'Selector'],
15
+ templates: ['CRDs', 'optional APIService', 'ServiceAccount', 'ClusterRole', 'ClusterRoleBinding', 'Deployment', 'Service', 'NetworkPolicy', 'Gitea backend', 'Argo CD Application'],
16
+ examples: ['examples/minikube-demo.yaml', 'examples/policy-kyverno-pr-title.yaml'],
17
+ validation: ['npm run e2e', 'npm run package:check', 'npm run setup:minikube -- --dry-run']
18
+ };
19
+ }
20
+
21
+ export function localSetupPlan() {
22
+ return {
23
+ script: 'scripts/setup-minikube.mjs',
24
+ defaultMode: 'dry-run',
25
+ applyMode: 'npm run setup:minikube -- --apply',
26
+ requiredTools: ['minikube', 'kubectl', 'helm', 'node', 'npm'],
27
+ defaultProfile: 'krate',
28
+ namespace: 'krate-system'
29
+ };
30
+ }
31
+
32
+ export function generateInstallManifests({ namespace = 'krate-system' } = {}) {
33
+ const manifests = [
34
+ { apiVersion: 'apiregistration.k8s.io/v1', kind: 'APIService', metadata: { name: 'v1alpha1.krate.a5c.ai' }, spec: { group: 'krate.a5c.ai', version: 'v1alpha1', service: { namespace, name: 'krate-api' } } },
35
+ { apiVersion: 'apps/v1', kind: 'Deployment', metadata: { name: 'krate-api', namespace }, spec: { replicas: 2, template: { spec: { containers: [{ name: 'api', image: 'krate/api:dev' }] } } } },
36
+ { apiVersion: 'apps/v1', kind: 'Deployment', metadata: { name: 'krate-gitea', namespace, labels: { 'app.kubernetes.io/component': 'gitea-backend' } }, spec: { replicas: 1, template: { spec: { containers: [{ name: 'gitea', image: 'gitea/gitea:1.22-rootless' }] } } } },
37
+ { apiVersion: 'argoproj.io/v1alpha1', kind: 'Application', metadata: { name: 'krate', namespace: 'argocd' }, spec: { source: { repoURL: 'https://gitea-http.krate-system.svc.cluster.local/krate/platform-config.git', path: 'charts/krate', targetRevision: 'main' }, destination: { namespace, server: 'https://kubernetes.default.svc' }, syncPolicy: { automated: { prune: true, selfHeal: true } } } },
38
+ { apiVersion: 'apps/v1', kind: 'Deployment', metadata: { name: 'krate-web', namespace }, spec: { replicas: 1 } }
39
+ ];
40
+ return manifests.map((manifest) => `---\n${toResourceYaml(manifest)}`).join('\n');
41
+ }
42
+
43
+ export function backupPlan() {
44
+ return { resources: ['CRDs and low-cardinality config', 'Postgres aggregated records', 'repository storage', 'object storage'], restoreOrder: ['API/config', 'Postgres', 'repository data', 'objects', 'controllers'], validation: ['list resources', 'read Gitea repository refs', 'open PR', 'replay webhook delivery'] };
45
+ }
46
+
47
+ export function observabilityModel() {
48
+ return {
49
+ metrics: ['api_request_latency', 'postgres_aggregate_lag', 'gitea_receive_pack_latency', 'runner_queue_depth', 'webhook_delivery_phase', 'admission_denials'],
50
+ logs: ['control-plane audit', 'gitea access', 'runner job', 'webhook dispatcher', 'controller reconcile'],
51
+ alerts: ['APIService unavailable', 'Postgres unavailable', 'repository service unavailable', 'runner saturation', 'webhook failure burst', 'backup validation failed']
52
+ };
53
+ }
54
+
55
+ export function releaseGates() {
56
+ return ['build', 'docs and ontology coverage', 'unit acceptance tests', 'e2e package lifecycle tests', 'package validation', 'minikube dry-run setup', 'smoke flow', 'backup restore validation', 'known limitations reviewed'];
57
+ }
58
+
59
+ export function createKrateMvpDemo() {
60
+ const platformEngineer = mapOidcIdentity({ subject: 'user:platform', email: 'platform@example.com', groups: ['krate:platform-engineers'] });
61
+ const developer = mapOidcIdentity({ subject: 'user:dev', email: 'dev@example.com', groups: ['krate:developers'] });
62
+ const repoAdmin = mapOidcIdentity({ subject: 'user:admin', email: 'admin@example.com', groups: ['krate:repo-admins'] });
63
+ const organizationRef = 'default';
64
+ const namespace = 'krate-org-default';
65
+ const controlPlane = new ControlPlane();
66
+ controlPlane.addAdmissionPolicy(createAdmissionPolicy({ name: 'pr-title-required', mode: 'enforce', match: ({ resource }) => resource.kind === 'PullRequest', validate: ({ resource }) => Boolean(resource.spec.title && resource.spec.title.length >= 8), message: 'PullRequest spec.title must be descriptive' }));
67
+ const git = new GiteaGitService({ controlPlane, stores: [new GiteaRepositoryStore({ name: 'gitea-primary', receivePackReady: true })] });
68
+ const runners = new RunnerScheduler({ controlPlane });
69
+ const webhooks = new WebhookBus({ controlPlane });
70
+ const repository = git.createRepository({ name: 'krate-demo', namespace, organizationRef }, repoAdmin);
71
+ const branchProtection = controlPlane.create(createResource('BranchProtection', { name: 'main-protection', namespace }, { organizationRef, refs: ['refs/heads/main'], requirePullRequest: true }), repoAdmin);
72
+ const refPolicy = controlPlane.create(createResource('RefPolicy', { name: 'deny-internal-refs', namespace }, { organizationRef, deny: ['refs/internal/'] }), repoAdmin);
73
+ const runnerPool = runners.createRunnerPool({ name: 'trusted-linux', namespace, organizationRef, warmReplicas: 1, maxReplicas: 4 }, platformEngineer);
74
+ const subscription = webhooks.subscribe({ name: 'chatops', namespace, organizationRef, url: 'https://hooks.example.test/krate', events: ['pullrequest.created'] }, repoAdmin);
75
+ const pullRequest = controlPlane.create(createResource('PullRequest', { name: 'pr-1', namespace, labels: { repository: 'krate-demo' } }, { organizationRef, repository: 'krate-demo', sourceRef: 'refs/heads/feature', targetRef: 'refs/heads/main', title: 'Add Kubernetes-native forge smoke path' }, { phase: 'Open' }), developer);
76
+ const pipelineRun = runners.startPipeline({ name: 'pipeline-pr-1', namespace, organizationRef, repository: 'krate-demo', ref: 'refs/pull/1/head', actor: developer, fork: false }, developer);
77
+ const delivery = webhooks.deliver({ subscriptionName: 'chatops', namespace, organizationRef, eventType: 'pullrequest.created', payload: { pullRequest: pullRequest.metadata.name, repository: 'krate-demo' } }, repoAdmin);
78
+ const replay = webhooks.replay(delivery, repoAdmin);
79
+ const triageView = controlPlane.create(createTriageView({ name: 'priority-triage', namespace, organizationRef, selector: { labels: { priority: 'high' } } }), repoAdmin);
80
+ const demo = {
81
+ users: { platformEngineer, developer, repoAdmin }, controlPlane, git, runners, webhooks,
82
+ resources: { repository, branchProtection, refPolicy, runnerPool, subscription, pullRequest, pipelineRun, delivery, replay, triageView },
83
+ ui: { dashboard: createDashboard({ repositories: [repository], pullRequests: [pullRequest], pipelines: [pipelineRun.pipeline], runnerPools: [runnerPool], webhookDeliveries: [delivery, replay] }), review: createPullRequestReviewModel({ pullRequest, changedFiles: ['src/index.js'], pipelineRuns: [pipelineRun.pipeline] }), runnerPoolEditor: createRunnerPoolEditor(runnerPool), webhookInspector: createWebhookInspector({ subscription, deliveries: [delivery, replay] }) },
84
+ operations: { installManifests: generateInstallManifests(), chartPackage: chartPackageSurface(), localSetup: localSetupPlan(), backupPlan: backupPlan(), observability: observabilityModel(), releaseGates: releaseGates() }
85
+ };
86
+ demo.components = createKrateComponentCatalog(demo);
87
+ demo.lifecycle = createKrateLifecycleSnapshot(demo);
88
+ return demo;
89
+ }
90
+
91
+ export function runSmokeAssertions(demo = createKrateMvpDemo()) {
92
+ const storage = demo.controlPlane.storageReport();
93
+ const assertions = [
94
+ ['Repository stored as CRD/etcd config', storage.etcd.includes('Repository')],
95
+ ['PullRequest stored as aggregated/Postgres record', storage.postgres.includes('PullRequest')],
96
+ ['RunnerPool stored as CRD/etcd configuration', storage.etcd.includes('RunnerPool')],
97
+ ['WebhookDelivery stored as aggregated/Postgres record', storage.postgres.includes('WebhookDelivery')],
98
+ ['Gitea receive path is ready', demo.git.route('krate-demo').backend === 'gitea' && demo.git.route('krate-demo').receivePackReady],
99
+ ['Repository exposes Gitea integration plan', demo.resources.repository.spec.gitHosting.backend === 'gitea' && demo.resources.repository.spec.gitHosting.integrationPlan.backend === 'gitea'],
100
+ ['UI exposes YAML for PR review', demo.ui.review.yaml.includes('kind: PullRequest')],
101
+ ['Install manifests expose Kubernetes API discovery', demo.operations.installManifests.includes('kind: APIService') || demo.operations.chartPackage.crds.includes('Repository')],
102
+ ['Chart package exposes CRDs', demo.operations.chartPackage.crds.includes('Repository')],
103
+ ['Local setup defaults to dry-run minikube flow', demo.operations.localSetup.defaultMode === 'dry-run'],
104
+ ['Release gates include docs and ontology coverage', demo.operations.releaseGates.includes('docs and ontology coverage')],
105
+ ['Release gates include e2e package lifecycle tests', demo.operations.releaseGates.includes('e2e package lifecycle tests')],
106
+ ['Observability tracks webhook delivery phase', demo.operations.observability.metrics.includes('webhook_delivery_phase')],
107
+ ['Component catalog covers every implementation area', demo.components.every((component) => component.implemented)],
108
+ ['Lifecycle snapshot is ready for local development', demo.lifecycle.status === 'ready-for-local-development']
109
+ ];
110
+ return { ok: assertions.every(([, passed]) => passed), assertions };
111
+ }
112
+
@@ -0,0 +1,5 @@
1
+ // Dedicated org-scoping module — re-exports the org namespace derivation
2
+ // function so callers can depend on it without pulling in the full
3
+ // kubernetes-controller surface.
4
+
5
+ export { orgNamespaceName, normalizeOrgSlug, resolveResourceOrg, withOrgScope } from './kubernetes-controller.js';
@@ -0,0 +1,221 @@
1
+ export const CONFIG_KINDS = new Set(['Organization', 'OrgNamespaceBinding', 'User', 'Team', 'Invite', 'IdentityMapping', 'AuthProvider', 'Repository', 'SSHKey', 'RepositoryPermission', 'WebhookSubscription', 'RefPolicy', 'BranchProtection', 'PolicyProfile', 'PolicyTemplate', 'PolicyBinding', 'PolicyExceptionRequest', 'RunnerPool', 'View', 'Selector', 'AgentStack', 'AgentSubagent', 'AgentToolProfile', 'AgentMcpServer', 'AgentSkill', 'AgentTriggerRule', 'AgentContextLabel', 'KrateWorkspacePolicy', 'AgentServiceAccount', 'AgentRoleBinding', 'AgentSecretGrant', 'AgentConfigGrant', 'AgentAdapter', 'AgentTransportBinding', 'AgentProviderConfig', 'KrateProject', 'AgentGatewayConfig', 'AgentMemoryRepository', 'AgentMemorySource', 'AgentMemoryOntology', 'AgentMemoryAssociation', 'KrateWorkspace', 'ExternalBackendProvider', 'ExternalBackendBinding', 'ExternalBackendSyncPolicy', 'ExternalProviderCapabilityManifest']);
2
+ export const AGGREGATED_KINDS = new Set(['PullRequest', 'Issue', 'Review', 'Pipeline', 'Job', 'WebhookDelivery', 'AgentDispatchRun', 'AgentDispatchAttempt', 'AgentSession', 'AgentContextBundle', 'KrateArtifact', 'AgentApproval', 'AgentTriggerExecution', 'AgentCapabilityRequirement', 'WorkItemSessionLink', 'WorkItemWorkspaceLink', 'AgentSessionTranscript', 'AgentSessionAttachment', 'KrateWorkspaceRuntime', 'AgentMemorySnapshot', 'AgentMemoryQuery', 'AgentMemoryUpdate', 'AgentRunMemoryImport', 'ExternalWebhookDelivery', 'ExternalSyncEvent', 'ExternalSyncState', 'ExternalWriteIntent', 'ExternalSyncConflict', 'ExternalObjectLink']);
3
+ export const ALL_KINDS = new Set([...CONFIG_KINDS, ...AGGREGATED_KINDS]);
4
+
5
+ export const RESOURCE_DEFINITIONS = Object.freeze({
6
+ Organization: { storage: 'etcd', context: 'identity', plural: 'organizations', purpose: 'Krate organization identity in the platform namespace with a bound tenant namespace', requiredSpec: ['displayName', 'namespaceName'] },
7
+ OrgNamespaceBinding: { storage: 'etcd', context: 'identity', plural: 'orgnamespacebindings', purpose: 'Binding from one organization to exactly one tenant namespace for resources and side effects', requiredSpec: ['organizationRef', 'namespace'] },
8
+ User: { storage: 'etcd', context: 'identity', plural: 'users', purpose: 'Human account profile, sign-in state, admin flag, and linked identities', requiredSpec: ['organizationRef', 'displayName', 'email'] },
9
+ Team: { storage: 'etcd', context: 'identity', plural: 'teams', purpose: 'Team membership, maintainers, and repository permission grants', requiredSpec: ['organizationRef', 'displayName'] },
10
+ Invite: { storage: 'etcd', context: 'identity', plural: 'invites', purpose: 'Pending user invitation with requested teams and expiry', requiredSpec: ['organizationRef', 'email', 'role'] },
11
+ IdentityMapping: { storage: 'etcd', context: 'identity', plural: 'identitymappings', purpose: 'Mapping between Krate users, sign-in subjects, workspace identities, and repository accounts', requiredSpec: ['organizationRef', 'user', 'provider', 'subject'] },
12
+ AuthProvider: { storage: 'etcd', context: 'identity', plural: 'authproviders', purpose: 'Installation sign-in provider visibility and delegated identity settings', requiredSpec: ['organizationRef', 'type'] },
13
+ Repository: { storage: 'etcd', context: 'data-plane', plural: 'repositories', purpose: 'Repository identity, visibility, repository hosting integration, object storage, and search settings', requiredSpec: ['organizationRef', 'visibility'] },
14
+ SSHKey: { storage: 'etcd', context: 'data-plane', plural: 'sshkeys', purpose: 'User, deploy, and automation SSH keys reconciled into repository key APIs', requiredSpec: ['organizationRef', 'scope', 'key'] },
15
+ RepositoryPermission: { storage: 'etcd', context: 'data-plane', plural: 'repositorypermissions', purpose: 'Repository collaborators and teams synced with repository permissions', requiredSpec: ['organizationRef', 'repository', 'subject', 'permission'] },
16
+ WebhookSubscription: { storage: 'etcd', context: 'hooks-events', plural: 'webhooksubscriptions', purpose: 'Endpoint, event filters, signing reference, delivery mode, and retry policy', requiredSpec: ['organizationRef', 'url', 'events'] },
17
+ RefPolicy: { storage: 'etcd', context: 'data-plane', plural: 'refpolicies', purpose: 'Reference deny rules, force-push policy, signing policy, and future custom hook gates', requiredSpec: ['organizationRef'] },
18
+ BranchProtection: { storage: 'etcd', context: 'control-plane', plural: 'branchprotections', purpose: 'Protected ref rules such as pull-request requirements', requiredSpec: ['organizationRef', 'refs'] },
19
+ PolicyProfile: { storage: 'etcd', context: 'policy', plural: 'policyprofiles', purpose: 'Organization policy posture, default templates, rollout mode, and exception approval rules', requiredSpec: ['organizationRef', 'displayName', 'mode'] },
20
+ PolicyTemplate: { storage: 'etcd', context: 'policy', plural: 'policytemplates', purpose: 'Curated Kyverno policy template metadata, parameters, rollout defaults, and remediation guidance', requiredSpec: ['displayName', 'targetKinds', 'kyverno'] },
21
+ PolicyBinding: { storage: 'etcd', context: 'policy', plural: 'policybindings', purpose: 'Binding from a policy template to org, repository, environment, or resource selectors with audit/enforce rollout state', requiredSpec: ['organizationRef', 'templateRef', 'mode'] },
22
+ PolicyExceptionRequest: { storage: 'etcd', context: 'policy', plural: 'policyexceptionrequests', purpose: 'Auditable request and approval workflow for temporary Kyverno PolicyException resources', requiredSpec: ['organizationRef', 'policyRef', 'justification', 'expiresAt'] },
23
+ View: { storage: 'etcd', context: 'web-ui', plural: 'views', purpose: 'Saved triage and dashboard view backed by resource selectors', requiredSpec: ['organizationRef', 'selector'] },
24
+ Selector: { storage: 'etcd', context: 'web-ui', plural: 'selectors', purpose: 'Reusable label/query selector for workflows and views', requiredSpec: ['organizationRef'] },
25
+ PullRequest: { storage: 'postgres', context: 'control-plane', plural: 'pullrequests', purpose: 'Review unit with source/target refs, title, checks, and merge lifecycle', requiredSpec: ['organizationRef', 'repository', 'title'] },
26
+ Issue: { storage: 'postgres', context: 'control-plane', plural: 'issues', purpose: 'Project-scoped work item with labels, comments, backend sync metadata, and zero-or-more repository associations', requiredSpec: ['organizationRef', 'title'] },
27
+ Review: { storage: 'postgres', context: 'control-plane', plural: 'reviews', purpose: 'Approval, comment, or change-request record for a pull request', requiredSpec: ['organizationRef', 'pullRequest'] },
28
+ Pipeline: { storage: 'postgres', context: 'runners-ci', plural: 'pipelines', purpose: 'CI pipeline run state, trust tier, steps, and resume point', requiredSpec: ['organizationRef', 'repository', 'ref'] },
29
+ Job: { storage: 'postgres', context: 'runners-ci', plural: 'jobs', purpose: 'Executable CI step with service-account scope and isolation metadata', requiredSpec: ['organizationRef', 'pipeline', 'step'] },
30
+ RunnerPool: { storage: 'etcd', context: 'runners-ci', plural: 'runnerpools', purpose: 'Runner capacity, warm/max replicas, cache policy, and trust boundary', requiredSpec: ['organizationRef', 'warmReplicas', 'maxReplicas'] },
31
+ WebhookDelivery: { storage: 'postgres', context: 'hooks-events', plural: 'webhookdeliveries', purpose: 'Durable outbound webhook delivery attempt with signature, phase, response, and replay metadata', requiredSpec: ['organizationRef', 'subscription', 'eventType', 'signature'] },
32
+ AgentStack: { storage: 'etcd', context: 'agents', plural: 'agentstacks', purpose: 'Reusable agent definition with model, prompt, tools, MCP servers, skills, subagents, approval mode, and runner policy', requiredSpec: ['organizationRef', 'baseAgent', 'adapter', 'runtimeIdentity'] },
33
+ AgentSubagent: { storage: 'etcd', context: 'agents', plural: 'agentsubagents', purpose: 'Named child-agent definition with role, task kinds, tool subset, and workspace scope', requiredSpec: ['organizationRef', 'rolePrompt', 'taskKinds'] },
34
+ AgentToolProfile: { storage: 'etcd', context: 'agents', plural: 'agenttoolprofiles', purpose: 'Native tool policy for filesystem, network, shell, and approval gates', requiredSpec: ['organizationRef', 'filesystemPolicy', 'approvalPolicyByTool'] },
35
+ AgentMcpServer: { storage: 'etcd', context: 'agents', plural: 'agentmcpservers', purpose: 'Managed MCP endpoint with transport, discovery, health, and secret/config refs', requiredSpec: ['organizationRef', 'transport', 'scope'] },
36
+ AgentSkill: { storage: 'etcd', context: 'agents', plural: 'agentskills', purpose: 'Reusable runbook/procedure bundle with prompt fragments, tool deps, and output contracts', requiredSpec: ['organizationRef', 'format', 'sourceRef'] },
37
+ AgentTriggerRule: { storage: 'etcd', context: 'agents', plural: 'agenttriggerrules', purpose: 'Event-to-stack routing for CI failures, webhooks, comments, labels, schedules, and manual dispatch', requiredSpec: ['organizationRef', 'sources', 'agentStack', 'taskKind'] },
38
+ AgentContextLabel: { storage: 'etcd', context: 'agents', plural: 'agentcontextlabels', purpose: 'Reviewed prompt fragment with provenance and allowlisted sources', requiredSpec: ['organizationRef', 'promptFragment', 'allowedSources'] },
39
+ KrateWorkspacePolicy: { storage: 'etcd', context: 'agents', plural: 'krateworkspacepolicies', purpose: 'Git worktree provisioning, cleanup, retention, and trust tier policies', requiredSpec: ['organizationRef', 'mode', 'retentionPolicy'] },
40
+ AgentServiceAccount: { storage: 'etcd', context: 'identity', plural: 'agentserviceaccounts', purpose: 'Kubernetes ServiceAccount wrapper for agent/runner identity binding', requiredSpec: ['organizationRef', 'namespace', 'serviceAccountName'] },
41
+ AgentRoleBinding: { storage: 'etcd', context: 'identity', plural: 'agentrolebindings', purpose: 'Managed projection to native Kubernetes RBAC for agent identity', requiredSpec: ['organizationRef', 'subject', 'roleRef', 'scope'] },
42
+ AgentSecretGrant: { storage: 'etcd', context: 'identity', plural: 'agentsecretgrants', purpose: 'Explicit permission for subject to access Secret keys with purpose scope', requiredSpec: ['organizationRef', 'subject', 'secretRef', 'purpose'] },
43
+ AgentConfigGrant: { storage: 'etcd', context: 'identity', plural: 'agentconfiggrants', purpose: 'Explicit permission for subject to access ConfigMap keys with purpose scope', requiredSpec: ['organizationRef', 'subject', 'configMapRef', 'purpose'] },
44
+ AgentDispatchRun: { storage: 'postgres', context: 'agents', plural: 'agentdispatchruns', purpose: 'Logical CI-like run visible beside Pipeline/Job records with queue, status, workspace, and cost', requiredSpec: ['organizationRef', 'repository', 'sourceRefs', 'agentStack', 'taskKind'] },
45
+ AgentDispatchAttempt: { storage: 'postgres', context: 'agents', plural: 'agentdispatchattempts', purpose: 'Concrete execution attempt with reason, stack snapshot, and runtime state', requiredSpec: ['organizationRef', 'agentDispatchRun', 'attemptReason', 'agentStackSnapshot'] },
46
+ AgentSession: { storage: 'postgres', context: 'agents', plural: 'agentsessions', purpose: 'Krate projection of Agent Mux chat/session with lifecycle state', requiredSpec: ['organizationRef', 'agentMuxSessionId', 'dispatchRun'] },
47
+ AgentContextBundle: { storage: 'postgres', context: 'agents', plural: 'agentcontextbundles', purpose: 'Immutable prompt/context snapshot with digest, provenance, and redaction manifest', requiredSpec: ['organizationRef', 'dispatchRun', 'digest', 'sources'] },
48
+ KrateArtifact: { storage: 'postgres', context: 'agents', plural: 'krateartifacts', purpose: 'Durable agent output with kind, digest, and retention policy', requiredSpec: ['organizationRef', 'dispatchRun', 'kind', 'digest'] },
49
+ AgentApproval: { storage: 'postgres', context: 'agents', plural: 'agentapprovals', purpose: 'Human gate for tools, secrets, write-back, and release actions', requiredSpec: ['organizationRef', 'dispatchRun', 'action', 'requestedBy'] },
50
+ KrateWorkspace: { storage: 'etcd', context: 'workspaces', plural: 'krateworkspaces', purpose: 'Volume-backed git workspace with PVC lifecycle, repo binding, and runner mount spec', requiredSpec: ['organizationRef', 'repository', 'volumeSpec'] },
51
+ AgentTriggerExecution: { storage: 'postgres', context: 'agents', plural: 'agenttriggerexecutions', purpose: 'Durable trigger evaluation record with dedupe, coalescing, and rejection reason', requiredSpec: ['organizationRef', 'triggerRule', 'sourceEvent', 'decision'] },
52
+ AgentCapabilityRequirement: { storage: 'postgres', context: 'agents', plural: 'agentcapabilityrequirements', purpose: 'Computed dependency record from tools, MCP, skills, models, and subagents', requiredSpec: ['organizationRef', 'ownerRef', 'requiredRoles'] },
53
+ WorkItemSessionLink: { storage: 'postgres', context: 'agents', plural: 'workitemsessionlinks', purpose: 'Association between issues/PRs and agent sessions', requiredSpec: ['organizationRef', 'workItemRef', 'agentSession'] },
54
+ WorkItemWorkspaceLink: { storage: 'postgres', context: 'agents', plural: 'workitemworkspacelinks', purpose: 'Association between issues/PRs and agent workspaces', requiredSpec: ['organizationRef', 'workItemRef', 'workspace'] },
55
+ AgentAdapter: { storage: 'etcd', context: 'agents', plural: 'agentadapters', purpose: 'Agent adapter definition with transport type, capabilities matrix, auth requirements, and installation method', requiredSpec: ['organizationRef', 'adapterType', 'transport'] },
56
+ AgentTransportBinding: { storage: 'etcd', context: 'agents', plural: 'agenttransportbindings', purpose: 'Connection configuration for an adapter instance with endpoint, protocol, auth, health check, and reconnect policy', requiredSpec: ['organizationRef', 'adapterRef', 'endpoint', 'protocol'] },
57
+ AgentProviderConfig: { storage: 'etcd', context: 'agents', plural: 'agentproviderconfigs', purpose: 'Model provider configuration with API base, auth type, default model, model translations, and rate limits', requiredSpec: ['organizationRef', 'provider', 'authType'] },
58
+ KrateProject: { storage: 'etcd', context: 'agents', plural: 'krateprojects', purpose: 'Org project grouping issues, linked repositories, kanban board config, default workflow, and backend sync refs', requiredSpec: ['organizationRef', 'displayName'] },
59
+ AgentGatewayConfig: { storage: 'etcd', context: 'agents', plural: 'agentgatewayconfigs', purpose: 'Runtime Agent Mux gateway connection settings with URL, auth, reconnect policy, and feature flags', requiredSpec: ['organizationRef', 'gatewayUrl'] },
60
+ AgentSessionTranscript: { storage: 'postgres', context: 'agents', plural: 'agentsessiontranscripts', purpose: 'Durable chat transcript with message nodes, pagination support, and cost per turn', requiredSpec: ['organizationRef', 'sessionRef', 'messages'] },
61
+ AgentSessionAttachment: { storage: 'postgres', context: 'agents', plural: 'agentsessionattachments', purpose: 'File attached to a session message with source type, MIME type, digest, and redaction status', requiredSpec: ['organizationRef', 'sessionRef', 'sourceType', 'digest'] },
62
+ KrateWorkspaceRuntime: { storage: 'postgres', context: 'agents', plural: 'krateworkspaceruntimes', purpose: 'Workspace runtime surface state with cwd, environment variables, process status, and preview URL', requiredSpec: ['organizationRef', 'workspaceRef', 'status'] },
63
+ AgentMemoryRepository: { storage: 'etcd', context: 'agents', plural: 'agentmemoryrepositories', purpose: 'Org-level Git repository pointer for shared agent memory with layout profile and index policy', requiredSpec: ['organizationRef', 'repositoryRef', 'defaultBranch', 'layoutProfile'] },
64
+ AgentMemorySource: { storage: 'etcd', context: 'agents', plural: 'agentmemorysources', purpose: 'Read policy for memory paths and kinds per repository, team, stack, or trigger', requiredSpec: ['organizationRef', 'repositoryRef', 'appliesTo', 'include'] },
65
+ AgentMemoryOntology: { storage: 'etcd', context: 'agents', plural: 'agentmemoryontologies', purpose: 'Ontology policy pointer with required fields, edge kinds, and controlled vocabulary', requiredSpec: ['organizationRef', 'memoryRepository', 'ontologyPath'] },
66
+ AgentMemoryAssociation: { storage: 'etcd', context: 'agents', plural: 'agentmemoryassociations', purpose: 'Bridge record linking memory content to Krate resources by relationship type', requiredSpec: ['organizationRef', 'memoryRef', 'targetRef', 'relationship'] },
67
+ AgentMemorySnapshot: { storage: 'postgres', context: 'agents', plural: 'agentmemorysnapshots', purpose: 'Immutable dispatch-time memory pin with resolved commit, query manifest digest, and selected records digest', requiredSpec: ['organizationRef', 'memoryRepository', 'requestedRef', 'resolvedCommit'] },
68
+ AgentMemoryQuery: { storage: 'postgres', context: 'agents', plural: 'agentmemoryqueries', purpose: 'Graph and grep retrieval record with query parameters, result digests, and ranking metadata', requiredSpec: ['organizationRef', 'snapshotRef', 'requester', 'query'] },
69
+ AgentMemoryUpdate: { storage: 'postgres', context: 'agents', plural: 'agentmemoryupdates', purpose: 'Reviewable proposed memory mutation with branch, changes, and validation status', requiredSpec: ['organizationRef', 'memoryRepository', 'sourceRun', 'changes'] },
70
+ AgentRunMemoryImport: { storage: 'postgres', context: 'agents', plural: 'agentrunmemoryimports', purpose: 'Import curated babysitter run metadata into org company brain with redaction and review', requiredSpec: ['organizationRef', 'memoryRepository', 'source', 'include'] },
71
+ ExternalBackendProvider: { storage: 'etcd', context: 'external-backends', plural: 'externalbackendproviders', purpose: 'External backend provider registration with type, endpoint, auth configuration, and capability discovery settings', requiredSpec: ['organizationRef', 'providerType', 'endpoint'] },
72
+ ExternalBackendBinding: { storage: 'etcd', context: 'external-backends', plural: 'externalbackendbindings', purpose: 'Binding of an external backend provider to an organization with credential reference and sync scope', requiredSpec: ['organizationRef', 'providerRef', 'credentialRef'] },
73
+ ExternalBackendSyncPolicy: { storage: 'etcd', context: 'external-backends', plural: 'externalbackendsyncpolicies', purpose: 'Sync interval, conflict resolution mode, field mapping overrides, and retry policy for an external backend provider', requiredSpec: ['organizationRef', 'providerRef', 'syncInterval'] },
74
+ ExternalProviderCapabilityManifest: { storage: 'etcd', context: 'external-backends', plural: 'externalprovidercapabilitymanifests', purpose: 'Discovered capability surface of an external backend provider including supported resource kinds and API features', requiredSpec: ['organizationRef', 'providerRef', 'capabilities'] },
75
+ ExternalWebhookDelivery: { storage: 'postgres', context: 'external-backends', plural: 'externalwebhookdeliveries', purpose: 'Inbound webhook delivery from an external backend provider with event type, payload, and processing state', requiredSpec: ['organizationRef', 'providerRef', 'eventType', 'payload'] },
76
+ ExternalSyncEvent: { storage: 'postgres', context: 'external-backends', plural: 'externalsyncevents', purpose: 'Discrete sync event record from an external backend for a specific resource kind with dedupe and ordering metadata', requiredSpec: ['organizationRef', 'providerRef', 'eventKind', 'resourceRef'] },
77
+ ExternalSyncState: { storage: 'postgres', context: 'external-backends', plural: 'externalsyncstates', purpose: 'Current sync phase, last successful sync timestamp, and error details for an external resource binding', requiredSpec: ['organizationRef', 'providerRef', 'resourceRef', 'phase'] },
78
+ ExternalWriteIntent: { storage: 'postgres', context: 'external-backends', plural: 'externalwriteintents', purpose: 'Queued write-back intent to an external backend with operation, payload snapshot, and approval state', requiredSpec: ['organizationRef', 'providerRef', 'resourceRef', 'operation'] },
79
+ ExternalSyncConflict: { storage: 'postgres', context: 'external-backends', plural: 'externalsyncconflicts', purpose: 'Detected conflict between local and external state with conflict kind, diff, and resolution outcome', requiredSpec: ['organizationRef', 'providerRef', 'resourceRef', 'conflictKind'] },
80
+ ExternalObjectLink: { storage: 'postgres', context: 'external-backends', plural: 'externalobjectlinks', purpose: 'Stable mapping between a Krate local resource and its external backend counterpart by external ID', requiredSpec: ['organizationRef', 'providerRef', 'externalId', 'localRef'] }
81
+ });
82
+
83
+ export function listResourceDefinitions() {
84
+ return Object.entries(RESOURCE_DEFINITIONS).map(([kind, definition]) => ({ kind, ...clone(definition) }));
85
+ }
86
+
87
+ export function resourceDefinitionForKind(kind) {
88
+ const definition = RESOURCE_DEFINITIONS[kind];
89
+ if (!definition) throw new Error(`Unknown Krate resource kind: ${kind}`);
90
+ return { kind, ...clone(definition) };
91
+ }
92
+
93
+ export function resourceSchemaForKind(kind) {
94
+ const definition = resourceDefinitionForKind(kind);
95
+ return {
96
+ apiVersion: 'krate.a5c.ai/v1alpha1',
97
+ kind: definition.kind,
98
+ plural: definition.plural,
99
+ storage: definition.storage,
100
+ context: definition.context,
101
+ required: {
102
+ metadata: ['name'],
103
+ spec: [...definition.requiredSpec]
104
+ },
105
+ status: ['storage', 'phase', 'conditions']
106
+ };
107
+ }
108
+
109
+ export function storageClassForKind(kind) {
110
+ return resourceDefinitionForKind(kind).storage;
111
+ }
112
+
113
+ export function resourceKey(resource) {
114
+ const namespace = resource.metadata?.namespace || 'default';
115
+ const name = resource.metadata?.name;
116
+ if (!name) throw new Error('resource metadata.name is required');
117
+ return `${resource.kind}/${namespace}/${name}`;
118
+ }
119
+
120
+ export function clone(value) {
121
+ return value === undefined ? undefined : JSON.parse(JSON.stringify(value));
122
+ }
123
+
124
+ export function createResource(kind, metadata, spec = {}, status = {}) {
125
+ if (!ALL_KINDS.has(kind)) throw new Error(`Unknown Krate resource kind: ${kind}`);
126
+ if (!metadata?.name) throw new Error(`${kind} requires metadata.name`);
127
+ return {
128
+ apiVersion: 'krate.a5c.ai/v1alpha1',
129
+ kind,
130
+ metadata: { namespace: metadata.namespace || 'default', labels: {}, annotations: {}, ...metadata },
131
+ spec: clone(spec),
132
+ status: clone(status)
133
+ };
134
+ }
135
+
136
+ export function validateResource(resource) {
137
+ if (!resource || typeof resource !== 'object') throw new Error('resource must be an object');
138
+ const definition = resourceDefinitionForKind(resource.kind);
139
+ if (!resource.metadata?.name) throw new Error(`${resource.kind} metadata.name is required`);
140
+ if (!resource.spec || typeof resource.spec !== 'object') resource.spec = {};
141
+ if (!resource.status || typeof resource.status !== 'object') resource.status = {};
142
+ resource.metadata.namespace ||= 'default';
143
+ resource.metadata.labels ||= {};
144
+ resource.metadata.annotations ||= {};
145
+ for (const field of definition.requiredSpec) {
146
+ if (resource.spec[field] === undefined || resource.spec[field] === null || resource.spec[field] === '') {
147
+ throw new Error(`${resource.kind} spec.${field} is required`);
148
+ }
149
+ }
150
+ return resource;
151
+ }
152
+
153
+ export function toKubernetesList(kind, items) {
154
+ return { apiVersion: 'krate.a5c.ai/v1alpha1', kind: `${kind}List`, items: items.map(clone) };
155
+ }
156
+
157
+ export function matchLabels(resource, selector = {}) {
158
+ const labels = resource.metadata?.labels || {};
159
+ return Object.entries(selector).every(([key, value]) => labels[key] === value);
160
+ }
161
+
162
+ export function createSelector({ name, namespace = 'krate-org-default', organizationRef = 'default', labels = {}, query = '' }) {
163
+ return createResource('Selector', { name, namespace }, { organizationRef, labels, query });
164
+ }
165
+
166
+ export function createView({ name, namespace = 'krate-org-default', organizationRef = 'default', selector, columns = [], sort = [] }) {
167
+ return createResource('View', { name, namespace }, { organizationRef, selector, columns, sort });
168
+ }
169
+
170
+ export function resourceToYaml(resource) {
171
+ const lines = [];
172
+ const scalar = (value) => {
173
+ if (value === null) return 'null';
174
+ if (value === undefined) return 'null';
175
+ if (typeof value === 'string') return value.includes(': ') || value.startsWith('{') || value.startsWith('[') ? JSON.stringify(value) : value;
176
+ return String(value);
177
+ };
178
+ const writeValue = (key, value, indent = 0) => {
179
+ const pad = ' '.repeat(indent);
180
+ if (Array.isArray(value)) {
181
+ lines.push(`${pad}${key}:`);
182
+ writeArray(value, indent + 2);
183
+ } else if (value && typeof value === 'object') {
184
+ lines.push(`${pad}${key}:`);
185
+ writeObject(value, indent + 2);
186
+ } else {
187
+ lines.push(`${pad}${key}: ${scalar(value)}`);
188
+ }
189
+ };
190
+ const writeObject = (value, indent) => {
191
+ for (const [childKey, childValue] of Object.entries(value)) writeValue(childKey, childValue, indent);
192
+ };
193
+ const writeArray = (value, indent) => {
194
+ const pad = ' '.repeat(indent);
195
+ for (const item of value) {
196
+ if (Array.isArray(item)) {
197
+ lines.push(`${pad}-`);
198
+ writeArray(item, indent + 2);
199
+ } else if (item && typeof item === 'object') {
200
+ const entries = Object.entries(item);
201
+ if (entries.length === 0) {
202
+ lines.push(`${pad}- {}`);
203
+ } else {
204
+ const [[firstKey, firstValue], ...rest] = entries;
205
+ if (firstValue && typeof firstValue === 'object') {
206
+ lines.push(`${pad}- ${firstKey}:`);
207
+ if (Array.isArray(firstValue)) writeArray(firstValue, indent + 4);
208
+ else writeObject(firstValue, indent + 4);
209
+ } else {
210
+ lines.push(`${pad}- ${firstKey}: ${scalar(firstValue)}`);
211
+ }
212
+ for (const [childKey, childValue] of rest) writeValue(childKey, childValue, indent + 2);
213
+ }
214
+ } else {
215
+ lines.push(`${pad}- ${scalar(item)}`);
216
+ }
217
+ }
218
+ };
219
+ writeObject(resource, 0);
220
+ return lines.join('\n') + '\n';
221
+ }