@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,250 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { clone } from './resource-model.js';
3
+
4
+ export const AGENT_PERMISSION_REVIEW_BOUNDARY = {
5
+ role: 'agent-permission-review',
6
+ scope: 'Deterministic permission review for agent dispatch decisions',
7
+ owns: ['capability expansion', 'grant resolution', 'permission snapshot creation'],
8
+ delegatesTo: ['resource-model'],
9
+ mustNotOwn: ['secret values', 'native K8s API calls', 'runtime execution']
10
+ };
11
+
12
+ const VALID_APPROVAL_MODES = new Set(['yolo', 'prompt', 'deny']);
13
+
14
+ export function createPermissionReviewer(options = {}) {
15
+ return {
16
+ role: 'agent-permission-review',
17
+
18
+ reviewPermissions({ repository, ref, actor, agentStack, triggerSource, taskKind, runnerPool, toolRefs = [], skillRefs = [], mcpServerRefs = [], contextLabelRefs = [], workspacePolicyRef, isFork = false, resources = {} }) {
19
+ const reasons = [];
20
+ const grants = [];
21
+ const crossOrgDenials = [];
22
+ const untrustedForkWarnings = [];
23
+
24
+ // Step 1 — Resolve AgentStack
25
+ const stacks = resources.AgentStack || [];
26
+ const stack = stacks.find((s) => s.metadata?.name === agentStack);
27
+ if (!stack) {
28
+ return buildDecision({ decision: 'denied', reasons: [{ severity: 'error', message: `AgentStack not found: ${agentStack}` }], grants, capabilities: {}, actor, repository, ref, agentStack, taskKind, crossOrgDenials, untrustedForkWarnings });
29
+ }
30
+
31
+ // Step 1a — Validate approvalMode
32
+ const approvalMode = stack.spec?.approvalMode;
33
+ if (approvalMode !== undefined && !VALID_APPROVAL_MODES.has(approvalMode)) {
34
+ reasons.push({ severity: 'error', message: `Invalid approvalMode '${approvalMode}': must be one of ${[...VALID_APPROVAL_MODES].join(', ')}` });
35
+ }
36
+
37
+ // Step 1b — Deny mode blocks everything immediately
38
+ if (approvalMode === 'deny') {
39
+ reasons.push({ severity: 'error', message: `approvalMode is 'deny': all requests are blocked by policy` });
40
+ return buildDecision({ decision: 'denied', reasons, grants, capabilities: {}, actor, repository, ref, agentStack, taskKind, approvalMode, crossOrgDenials, untrustedForkWarnings });
41
+ }
42
+
43
+ // Step 2 — Cross-org denial check
44
+ const agentOrg = stack.spec?.organizationRef;
45
+ if (agentOrg && repository) {
46
+ // repository format: '<org>/<repo>' or just '<repo>'
47
+ const repoParts = repository.split('/');
48
+ const repoOrg = repoParts.length >= 2 ? repoParts[0] : null;
49
+ if (repoOrg && repoOrg !== agentOrg) {
50
+ crossOrgDenials.push({ agentOrg, resourceOrg: repoOrg, resource: repository });
51
+ reasons.push({ severity: 'error', message: `Cross-org access denied: agent org '${agentOrg}' cannot access repository in org '${repoOrg}'` });
52
+ }
53
+ }
54
+
55
+ // Step 3 — Expand capabilities from stack spec
56
+ const capabilities = {
57
+ toolRefs: clone(stack.spec?.toolPolicy ? [stack.spec.toolPolicy] : toolRefs),
58
+ mcpServerRefs: clone(stack.spec?.mcpServerRefs || mcpServerRefs),
59
+ skillRefs: clone(stack.spec?.skillRefs || skillRefs),
60
+ subagentRefs: clone(stack.spec?.subagentRefs || [])
61
+ };
62
+
63
+ // Step 4 — Untrusted fork detection
64
+ const isForkRef = isFork || /^refs\/pull\/\d+\//.test(ref);
65
+ if (isForkRef) {
66
+ const blockedKinds = ['AgentServiceAccount', 'AgentSecretGrant'];
67
+ untrustedForkWarnings.push({
68
+ ref,
69
+ isFork: true,
70
+ blockedKinds,
71
+ message: `Untrusted fork detected for ref '${ref}': privileged grants restricted`
72
+ });
73
+ reasons.push({ severity: 'warning', message: `Untrusted fork detected for ref '${ref}': privileged grants (${blockedKinds.join(', ')}) are not auto-approved` });
74
+ }
75
+
76
+ // Step 5 — Check runtime identity (AgentServiceAccount)
77
+ const serviceAccountRef = stack.spec?.runtimeIdentity?.serviceAccountRef || stack.spec?.runtimeIdentity;
78
+ const serviceAccounts = resources.AgentServiceAccount || [];
79
+ const serviceAccount = serviceAccounts.find((sa) => sa.metadata?.name === serviceAccountRef);
80
+ if (!serviceAccount) {
81
+ reasons.push({ severity: 'error', message: `Missing AgentServiceAccount: ${serviceAccountRef}` });
82
+ } else {
83
+ const saGrant = { kind: 'AgentServiceAccount', name: serviceAccount.metadata.name, status: 'bound' };
84
+ if (isForkRef) {
85
+ saGrant.status = 'fork-restricted';
86
+ }
87
+ grants.push(saGrant);
88
+ }
89
+
90
+ // Step 6 — Check role bindings
91
+ const roleBindings = resources.AgentRoleBinding || [];
92
+ const matchedBindings = roleBindings.filter((rb) => rb.spec?.subject === serviceAccountRef || rb.spec?.subject === agentStack);
93
+ for (const binding of matchedBindings) {
94
+ grants.push({ kind: 'AgentRoleBinding', name: binding.metadata.name, roleRef: binding.spec?.roleRef, scope: binding.spec?.scope, status: 'bound' });
95
+ }
96
+ if (matchedBindings.length === 0 && serviceAccount) {
97
+ reasons.push({ severity: 'warning', message: `No AgentRoleBinding found for subject: ${serviceAccountRef}` });
98
+ }
99
+
100
+ // Step 7 — Check secret grants
101
+ const secretGrants = resources.AgentSecretGrant || [];
102
+ const neededSecrets = collectSecretNeeds(stack, capabilities, resources);
103
+ for (const need of neededSecrets) {
104
+ const match = secretGrants.find((sg) => {
105
+ if (sg.spec?.subject !== serviceAccountRef && sg.spec?.subject !== agentStack) return false;
106
+ if (need.purpose && sg.spec?.purpose !== need.purpose) return false;
107
+ if (sg.spec?.allowedRepositories && sg.spec.allowedRepositories.length > 0 && !sg.spec.allowedRepositories.includes(repository)) return false;
108
+ if (sg.spec?.allowedRefs && sg.spec.allowedRefs.length > 0 && !sg.spec.allowedRefs.includes(ref)) return false;
109
+ return true;
110
+ });
111
+ if (!match) {
112
+ reasons.push({ severity: 'error', message: `Missing AgentSecretGrant for ${need.description} (purpose: ${need.purpose})` });
113
+ } else {
114
+ const grantEntry = { kind: 'AgentSecretGrant', name: match.metadata.name, purpose: match.spec?.purpose, status: 'granted' };
115
+ if (isForkRef) {
116
+ grantEntry.status = 'fork-restricted';
117
+ } else if (match.spec?.requiredApproval) {
118
+ grantEntry.status = 'requires-approval';
119
+ grantEntry.requiredApproval = match.spec.requiredApproval;
120
+ reasons.push({ severity: 'info', message: `AgentSecretGrant ${match.metadata.name} requires approval: ${match.spec.requiredApproval}` });
121
+ }
122
+ grants.push(grantEntry);
123
+ }
124
+ }
125
+
126
+ // Step 8 — Check config grants
127
+ const configGrants = resources.AgentConfigGrant || [];
128
+ const neededConfigs = collectConfigNeeds(stack, capabilities, resources);
129
+ for (const need of neededConfigs) {
130
+ const match = configGrants.find((cg) => {
131
+ if (cg.spec?.subject !== serviceAccountRef && cg.spec?.subject !== agentStack) return false;
132
+ if (need.purpose && cg.spec?.purpose !== need.purpose) return false;
133
+ return true;
134
+ });
135
+ if (!match) {
136
+ reasons.push({ severity: 'error', message: `Missing AgentConfigGrant for ${need.description} (purpose: ${need.purpose})` });
137
+ } else {
138
+ grants.push({ kind: 'AgentConfigGrant', name: match.metadata.name, purpose: match.spec?.purpose, status: 'granted' });
139
+ }
140
+ }
141
+
142
+ // Step 9 — Workspace policy enforcement
143
+ if (workspacePolicyRef) {
144
+ const policies = resources.KrateWorkspacePolicy || [];
145
+ const policy = policies.find((p) => p.metadata?.name === workspacePolicyRef);
146
+ if (policy) {
147
+ // Check maxConcurrentSessions
148
+ if (policy.spec?.maxConcurrentSessions === 0) {
149
+ reasons.push({ severity: 'error', message: `Workspace policy '${workspacePolicyRef}' maxConcurrentSessions is 0: no sessions allowed` });
150
+ }
151
+ // Check deniedTools
152
+ const deniedTools = policy.spec?.deniedTools || [];
153
+ const requestedTools = capabilities.toolRefs;
154
+ for (const tool of requestedTools) {
155
+ if (deniedTools.includes(tool)) {
156
+ reasons.push({ severity: 'error', message: `Tool '${tool}' is denied by workspace policy '${workspacePolicyRef}'` });
157
+ }
158
+ }
159
+ // Check allowedTools (if specified, only those tools are permitted)
160
+ const allowedTools = policy.spec?.allowedTools;
161
+ if (allowedTools && allowedTools.length > 0) {
162
+ for (const tool of requestedTools) {
163
+ if (!allowedTools.includes(tool)) {
164
+ reasons.push({ severity: 'error', message: `Tool '${tool}' is not in allowedTools for workspace policy '${workspacePolicyRef}'` });
165
+ }
166
+ }
167
+ }
168
+ }
169
+ }
170
+
171
+ // Step 10 — Decision
172
+ const hasErrors = reasons.some((r) => r.severity === 'error');
173
+ const hasApprovals = grants.some((g) => g.status === 'requires-approval');
174
+ let decision;
175
+ if (hasErrors) {
176
+ decision = 'denied';
177
+ } else if (hasApprovals) {
178
+ decision = 'requires-approval';
179
+ } else {
180
+ decision = 'allowed';
181
+ }
182
+
183
+ return buildDecision({ decision, reasons, grants, capabilities, actor, repository, ref, agentStack, taskKind, approvalMode, crossOrgDenials, untrustedForkWarnings });
184
+ },
185
+
186
+ createPermissionSnapshot(reviewResult) {
187
+ const snapshot = clone(reviewResult);
188
+ snapshot.snapshotAt = new Date().toISOString();
189
+ snapshot.frozen = true;
190
+ snapshot.digest = reviewResult.digest;
191
+ return Object.freeze(snapshot);
192
+ }
193
+ };
194
+ }
195
+
196
+ function collectSecretNeeds(stack, capabilities, resources) {
197
+ const needs = [];
198
+ if (stack.spec?.adapter) {
199
+ needs.push({ description: `model provider for adapter ${stack.spec.adapter}`, purpose: 'model-provider' });
200
+ }
201
+ const mcpServers = resources.AgentMcpServer || [];
202
+ for (const ref of capabilities.mcpServerRefs) {
203
+ const server = mcpServers.find((s) => s.metadata?.name === ref);
204
+ if (server?.spec?.secretRef) {
205
+ needs.push({ description: `MCP server ${ref} secret`, purpose: `mcp-server:${ref}` });
206
+ }
207
+ }
208
+ return needs;
209
+ }
210
+
211
+ function collectConfigNeeds(stack, capabilities, resources) {
212
+ const needs = [];
213
+ const mcpServers = resources.AgentMcpServer || [];
214
+ for (const ref of capabilities.mcpServerRefs) {
215
+ const server = mcpServers.find((s) => s.metadata?.name === ref);
216
+ if (server?.spec?.configMapRef) {
217
+ needs.push({ description: `MCP server ${ref} config`, purpose: `mcp-server:${ref}` });
218
+ }
219
+ }
220
+ return needs;
221
+ }
222
+
223
+ function buildDecision({ decision, reasons, grants, capabilities, actor, repository, ref, agentStack, taskKind, approvalMode, crossOrgDenials = [], untrustedForkWarnings = [] }) {
224
+ const result = {
225
+ decision,
226
+ actor,
227
+ repository,
228
+ ref,
229
+ agentStack,
230
+ taskKind,
231
+ capabilities: clone(capabilities),
232
+ grants: clone(grants),
233
+ reasons: clone(reasons),
234
+ crossOrgDenials: clone(crossOrgDenials),
235
+ untrustedForkWarnings: clone(untrustedForkWarnings),
236
+ reviewedAt: new Date().toISOString()
237
+ };
238
+ if (approvalMode !== undefined) {
239
+ result.approvalMode = approvalMode;
240
+ }
241
+ result.digest = computeDigest(result);
242
+ return result;
243
+ }
244
+
245
+ function computeDigest(result) {
246
+ const keys = Object.keys(result).filter((k) => k !== 'digest' && k !== 'reviewedAt').sort();
247
+ const canonical = {};
248
+ for (const key of keys) canonical[key] = result[key];
249
+ return createHash('sha256').update(JSON.stringify(canonical)).digest('hex');
250
+ }
@@ -0,0 +1,117 @@
1
+ // Agent Project Controller — Slice 1.2d
2
+ // Manages KrateProject resources: validation, workflow columns, board state, and issue assignment.
3
+
4
+ export const AGENT_PROJECT_CONTROLLER_BOUNDARY = {
5
+ role: 'agent-project-controller',
6
+ scope: 'KrateProject lifecycle: validation, workflow column definitions, board state management, issue assignment tracking',
7
+ owns: ['project validation', 'workflow columns', 'board state', 'default column resolution', 'issue assignment tracking'],
8
+ delegatesTo: ['resource-model'],
9
+ mustNotOwn: ['issue content', 'PR lifecycle', 'dispatch execution', 'Agent Mux sessions']
10
+ };
11
+
12
+ const VALID_BOARD_STATES = ['active', 'archived'];
13
+
14
+ /**
15
+ * Validate a KrateProject resource. Returns { valid, errors }.
16
+ * @param {object} resource
17
+ * @returns {{ valid: boolean, errors: string[] }}
18
+ */
19
+ export function validateAgentProject(resource) {
20
+ const errors = [];
21
+
22
+ // Guard against null/undefined resource
23
+ if (resource == null) {
24
+ errors.push('resource must not be null or undefined');
25
+ return { valid: false, errors };
26
+ }
27
+
28
+ // Validate metadata.name
29
+ if (!resource?.metadata?.name) {
30
+ errors.push('metadata.name is required');
31
+ }
32
+
33
+ const spec = resource?.spec || {};
34
+
35
+ // Validate organizationRef
36
+ if (!spec.organizationRef) {
37
+ errors.push('spec.organizationRef is required');
38
+ }
39
+
40
+ // Validate workflowColumns — must be a non-empty array
41
+ const cols = spec.workflowColumns;
42
+ if (!Array.isArray(cols) || cols.length === 0) {
43
+ errors.push('spec.workflowColumns must be a non-empty array');
44
+ } else {
45
+ // Check for duplicate column IDs
46
+ const seen = new Set();
47
+ for (const col of cols) {
48
+ if (seen.has(col.id)) {
49
+ errors.push(`spec.workflowColumns contains duplicate column ID: "${col.id}"`);
50
+ break;
51
+ }
52
+ seen.add(col.id);
53
+ }
54
+ }
55
+
56
+ // Validate boardState if explicitly set
57
+ const boardState = spec.boardState;
58
+ if (boardState != null && !VALID_BOARD_STATES.includes(boardState)) {
59
+ errors.push(`spec.boardState "${boardState}" is not supported; valid states are: ${VALID_BOARD_STATES.join(', ')}`);
60
+ }
61
+
62
+ return { valid: errors.length === 0, errors };
63
+ }
64
+
65
+ /**
66
+ * Factory that returns a KrateProject controller instance.
67
+ */
68
+ export function createAgentProjectController() {
69
+ return {
70
+ role: 'agent-project-controller',
71
+
72
+ /**
73
+ * Validate a KrateProject resource.
74
+ * @param {object} resource
75
+ * @returns {{ valid: boolean, errors: string[] }}
76
+ */
77
+ validate(resource) {
78
+ return validateAgentProject(resource);
79
+ },
80
+
81
+ /**
82
+ * Return the workflow columns for a project, in order.
83
+ * @param {object} resource
84
+ * @returns {Array<{ id: string, displayName: string, color: string, default?: boolean }>}
85
+ */
86
+ getWorkflowColumns(resource) {
87
+ const cols = resource?.spec?.workflowColumns;
88
+ if (!Array.isArray(cols)) {
89
+ return [];
90
+ }
91
+ return [...cols];
92
+ },
93
+
94
+ /**
95
+ * Return the default column — the one marked `default: true`, or the first column.
96
+ * @param {object} resource
97
+ * @returns {{ id: string, displayName: string, color: string, default?: boolean } | undefined}
98
+ */
99
+ getDefaultColumn(resource) {
100
+ const cols = Array.isArray(resource?.spec?.workflowColumns)
101
+ ? resource.spec.workflowColumns
102
+ : [];
103
+ const marked = cols.find((c) => c.default === true);
104
+ return marked ?? cols[0];
105
+ },
106
+
107
+ /**
108
+ * Return the board state for a project.
109
+ * Defaults to 'active' when spec.boardState is not set.
110
+ * @param {object} resource
111
+ * @returns {string}
112
+ */
113
+ getBoardState(resource) {
114
+ return resource?.spec?.boardState ?? 'active';
115
+ }
116
+ };
117
+ }
@@ -0,0 +1,150 @@
1
+ // Agent Provider Config Controller — Slice 1.2c
2
+ // Manages AgentProviderConfig resources: model provider config validation,
3
+ // endpoint resolution, credential ref validation, and feature flag management.
4
+
5
+ export const AGENT_PROVIDER_CONFIG_CONTROLLER_BOUNDARY = {
6
+ role: 'agent-provider-config-controller',
7
+ scope: 'AgentProviderConfig lifecycle: validation, endpoint resolution, credential ref validation, feature flags',
8
+ owns: ['provider config validation', 'endpoint resolution', 'credential ref validation', 'feature flag defaults', 'rate limit defaults'],
9
+ delegatesTo: ['resource-model'],
10
+ mustNotOwn: ['secret values', 'dispatch execution', 'Agent Mux sessions', 'adapter implementation']
11
+ };
12
+
13
+ const VALID_PROVIDER_TYPES = ['anthropic', 'openai', 'azure-openai', 'google-vertex', 'foundry', 'custom'];
14
+
15
+ const DEFAULT_ENDPOINTS = Object.freeze({
16
+ anthropic: 'https://api.anthropic.com/v1',
17
+ openai: 'https://api.openai.com/v1',
18
+ 'azure-openai': null, // requires explicit endpoint (tenant-specific)
19
+ 'google-vertex': 'https://us-central1-aiplatform.googleapis.com/v1',
20
+ foundry: null, // requires explicit endpoint
21
+ custom: null // requires explicit endpoint
22
+ });
23
+
24
+ const DEFAULT_FEATURE_FLAGS = Object.freeze({
25
+ streaming: true,
26
+ tool_use: true,
27
+ vision: false
28
+ });
29
+
30
+ const DEFAULT_RATE_LIMITS = Object.freeze({
31
+ requestsPerMinute: 60,
32
+ tokensPerMinute: 100000
33
+ });
34
+
35
+ /**
36
+ * Validate an AgentProviderConfig resource. Returns { valid, errors }.
37
+ * @param {object} resource
38
+ * @returns {{ valid: boolean, errors: string[] }}
39
+ */
40
+ export function validateAgentProviderConfig(resource) {
41
+ const errors = [];
42
+
43
+ // Guard against null/undefined resource
44
+ if (resource == null) {
45
+ errors.push('resource must not be null or undefined');
46
+ return { valid: false, errors };
47
+ }
48
+
49
+ // Validate metadata.name
50
+ if (!resource?.metadata?.name) {
51
+ errors.push('metadata.name is required');
52
+ }
53
+
54
+ const spec = resource?.spec || {};
55
+
56
+ // Validate providerType
57
+ const providerType = spec.providerType;
58
+ if (!providerType) {
59
+ errors.push(`spec.providerType is required; valid types are: ${VALID_PROVIDER_TYPES.join(', ')}`);
60
+ } else if (!VALID_PROVIDER_TYPES.includes(providerType)) {
61
+ errors.push(`spec.providerType "${providerType}" is not supported; valid types are: ${VALID_PROVIDER_TYPES.join(', ')}`);
62
+ }
63
+
64
+ // Validate credentialRef — always required for security
65
+ if (!spec.credentialRef) {
66
+ errors.push('spec.credentialRef is required; provide a Kubernetes Secret name for the API key');
67
+ }
68
+
69
+ return { valid: errors.length === 0, errors };
70
+ }
71
+
72
+ /**
73
+ * Factory that returns an AgentProviderConfig controller instance.
74
+ */
75
+ export function createAgentProviderConfigController() {
76
+ return {
77
+ role: 'agent-provider-config-controller',
78
+
79
+ /**
80
+ * Validate an AgentProviderConfig resource.
81
+ * @param {object} resource
82
+ * @returns {{ valid: boolean, errors: string[] }}
83
+ */
84
+ validate(resource) {
85
+ return validateAgentProviderConfig(resource);
86
+ },
87
+
88
+ /**
89
+ * Resolve the effective API endpoint for a provider config.
90
+ * Returns the explicit spec.endpoint if set, otherwise falls back to
91
+ * the well-known default for the provider type.
92
+ * @param {object} resource
93
+ * @returns {string|null}
94
+ */
95
+ resolveEndpoint(resource) {
96
+ if (resource == null) {
97
+ throw new Error('resource must not be null or undefined');
98
+ }
99
+ const spec = resource?.spec || {};
100
+
101
+ // If an explicit endpoint is set in spec, prefer it
102
+ if (spec.endpoint) {
103
+ return spec.endpoint;
104
+ }
105
+
106
+ // Fall back to the known default for the provider type
107
+ const providerType = spec.providerType;
108
+ return DEFAULT_ENDPOINTS[providerType] ?? null;
109
+ },
110
+
111
+ /**
112
+ * Return the effective feature flags for a provider config.
113
+ * Merges spec.featureFlags with defaults; spec values take precedence.
114
+ * @param {object} resource
115
+ * @returns {{ streaming: boolean, tool_use: boolean, vision: boolean, [key: string]: boolean }}
116
+ */
117
+ getFeatureFlags(resource) {
118
+ if (resource == null) {
119
+ throw new Error('resource must not be null or undefined');
120
+ }
121
+ const specFlags = resource?.spec?.featureFlags ?? {};
122
+ return { ...DEFAULT_FEATURE_FLAGS, ...specFlags };
123
+ },
124
+
125
+ /**
126
+ * Return the effective rate limit configuration for a provider config.
127
+ * Merges spec.rateLimits with defaults; spec values take precedence.
128
+ * @param {object} resource
129
+ * @returns {{ requestsPerMinute: number, tokensPerMinute: number }}
130
+ */
131
+ getRateLimits(resource) {
132
+ if (resource == null) {
133
+ throw new Error('resource must not be null or undefined');
134
+ }
135
+ const specLimits = resource?.spec?.rateLimits ?? {};
136
+ return {
137
+ requestsPerMinute: specLimits.requestsPerMinute ?? DEFAULT_RATE_LIMITS.requestsPerMinute,
138
+ tokensPerMinute: specLimits.tokensPerMinute ?? DEFAULT_RATE_LIMITS.tokensPerMinute
139
+ };
140
+ },
141
+
142
+ /**
143
+ * Return the list of supported provider types.
144
+ * @returns {string[]}
145
+ */
146
+ getSupportedProviderTypes() {
147
+ return [...VALID_PROVIDER_TYPES];
148
+ }
149
+ };
150
+ }