@a5c-ai/krate 5.0.1-staging.04a3db697

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 (246) 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 +3067 -0
  6. package/dist/krate-lifecycle.json +201 -0
  7. package/dist/krate-runtime-snapshot.json +2955 -0
  8. package/dist/krate-summary.json +722 -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/product-requirements.md +62 -0
  108. package/docs/roadmap-mvp.md +87 -0
  109. package/docs/system-requirements.md +90 -0
  110. package/docs/tests/README.md +53 -0
  111. package/docs/tests/agent-qa-plan.md +63 -0
  112. package/docs/tests/browser-ui-tests.md +62 -0
  113. package/docs/tests/ci-quality-gates.md +48 -0
  114. package/docs/tests/coverage-model.md +64 -0
  115. package/docs/tests/e2e-scenario-tests.md +53 -0
  116. package/docs/tests/fixtures-test-data.md +63 -0
  117. package/docs/tests/observability-reliability-tests.md +54 -0
  118. package/docs/tests/product-test-matrix.md +145 -0
  119. package/docs/tests/qa-adoption-roadmap.md +130 -0
  120. package/docs/tests/qa-automation-plan.md +101 -0
  121. package/docs/tests/security-compliance-tests.md +57 -0
  122. package/docs/tests/test-framework-tools.md +88 -0
  123. package/docs/tests/test-suite-layout.md +121 -0
  124. package/docs/tests/unit-integration-tests.md +48 -0
  125. package/docs/todo-kyverno +714 -0
  126. package/docs/todos.md +4 -0
  127. package/docs/user-stories.md +78 -0
  128. package/examples/minikube-demo.yaml +190 -0
  129. package/examples/oam-application.yaml +23 -0
  130. package/examples/policy-kyverno-pr-title.yaml +18 -0
  131. package/package.json +63 -0
  132. package/scripts/build.mjs +29 -0
  133. package/scripts/setup-minikube.mjs +65 -0
  134. package/scripts/smoke.mjs +37 -0
  135. package/scripts/validate-doc-coverage.mjs +152 -0
  136. package/scripts/validate-package.mjs +93 -0
  137. package/scripts/validate-ui.mjs +236 -0
  138. package/src/agent-adapter-controller.js +169 -0
  139. package/src/agent-approval-controller.js +170 -0
  140. package/src/agent-context-bundles.js +242 -0
  141. package/src/agent-dispatch-controller.js +209 -0
  142. package/src/agent-gateway-config-controller.js +147 -0
  143. package/src/agent-memory-controller.js +357 -0
  144. package/src/agent-memory-import.js +327 -0
  145. package/src/agent-memory-query.js +292 -0
  146. package/src/agent-memory-repository-source-controller.js +255 -0
  147. package/src/agent-mux-client.js +280 -0
  148. package/src/agent-permission-review.js +250 -0
  149. package/src/agent-project-controller.js +117 -0
  150. package/src/agent-provider-config-controller.js +150 -0
  151. package/src/agent-secret-config-grant-controller.js +282 -0
  152. package/src/agent-session-transcript-controller.js +189 -0
  153. package/src/agent-stack-controller.js +347 -0
  154. package/src/agent-subagent-controller.js +160 -0
  155. package/src/agent-transport-binding-controller.js +121 -0
  156. package/src/agent-trigger-controller.js +321 -0
  157. package/src/agent-workspace-controller.js +447 -0
  158. package/src/agent-writeback-controller.js +302 -0
  159. package/src/api-controller.js +541 -0
  160. package/src/argocd-gitops.js +43 -0
  161. package/src/async-controller.js +207 -0
  162. package/src/audit-controller.js +191 -0
  163. package/src/auth.js +307 -0
  164. package/src/component-catalog.js +41 -0
  165. package/src/control-plane.js +136 -0
  166. package/src/controller-client.js +50 -0
  167. package/src/controller-ui.js +551 -0
  168. package/src/data-plane.js +178 -0
  169. package/src/event-bus.js +61 -0
  170. package/src/external/conflict-controller.js +225 -0
  171. package/src/external/github/auth.js +96 -0
  172. package/src/external/github/cicd.js +180 -0
  173. package/src/external/github/git-forge.js +240 -0
  174. package/src/external/github/index.js +144 -0
  175. package/src/external/github/issue-tracking.js +163 -0
  176. package/src/external/provider-adapter.js +161 -0
  177. package/src/external/provider-resource-factory.js +161 -0
  178. package/src/external/sync-controller.js +235 -0
  179. package/src/external/webhook-controller.js +144 -0
  180. package/src/external/write-controller.js +283 -0
  181. package/src/gitea-backend.js +95 -0
  182. package/src/gitea-service.js +173 -0
  183. package/src/handoff.js +98 -0
  184. package/src/hooks-events.js +63 -0
  185. package/src/http-server.js +377 -0
  186. package/src/identity-policy.js +86 -0
  187. package/src/index.js +55 -0
  188. package/src/kubernetes-controller-async.js +511 -0
  189. package/src/kubernetes-controller.js +878 -0
  190. package/src/kubernetes-resource-gateway.js +48 -0
  191. package/src/operations.js +112 -0
  192. package/src/org-scoping.js +5 -0
  193. package/src/resource-model.js +221 -0
  194. package/src/runners-ci.js +48 -0
  195. package/src/runtime.js +196 -0
  196. package/src/snapshot-cache.js +157 -0
  197. package/src/web-ui.js +40 -0
  198. package/tests/agent-adapter-controller.test.js +361 -0
  199. package/tests/agent-approval-controller.test.js +173 -0
  200. package/tests/agent-context-bundles.test.js +278 -0
  201. package/tests/agent-dispatch-controller.test.js +315 -0
  202. package/tests/agent-gateway-config-controller.test.js +386 -0
  203. package/tests/agent-memory-controller.test.js +308 -0
  204. package/tests/agent-memory-import-snapshot.test.js +477 -0
  205. package/tests/agent-memory-query.test.js +404 -0
  206. package/tests/agent-memory-repository-source.test.js +514 -0
  207. package/tests/agent-mux-client.test.js +204 -0
  208. package/tests/agent-permission-review-v2.test.js +317 -0
  209. package/tests/agent-permission-review.test.js +209 -0
  210. package/tests/agent-project-controller.test.js +302 -0
  211. package/tests/agent-provider-config-controller.test.js +376 -0
  212. package/tests/agent-resources.test.js +228 -0
  213. package/tests/agent-secret-config-grant.test.js +231 -0
  214. package/tests/agent-session-transcript-controller.test.js +499 -0
  215. package/tests/agent-stack-controller.test.js +221 -0
  216. package/tests/agent-subagent-controller.test.js +201 -0
  217. package/tests/agent-transport-binding-controller.test.js +294 -0
  218. package/tests/agent-trigger-controller.test.js +211 -0
  219. package/tests/agent-trigger-routes.test.js +190 -0
  220. package/tests/agent-trigger-sources.test.js +245 -0
  221. package/tests/agent-workspace-controller.test.js +181 -0
  222. package/tests/agent-writeback.test.js +292 -0
  223. package/tests/approval-persistence.test.js +171 -0
  224. package/tests/async-controller.test.js +252 -0
  225. package/tests/audit-controller.test.js +227 -0
  226. package/tests/deployment.test.js +396 -0
  227. package/tests/e2e/lifecycle.test.js +117 -0
  228. package/tests/external-github-forge.test.js +560 -0
  229. package/tests/external-github-issues-cicd.test.js +520 -0
  230. package/tests/external-integration.test.js +470 -0
  231. package/tests/external-persistence.test.js +340 -0
  232. package/tests/external-provider-adapter.test.js +365 -0
  233. package/tests/external-resource-model.test.js +215 -0
  234. package/tests/external-webhook-sync.test.js +287 -0
  235. package/tests/external-write-conflict.test.js +353 -0
  236. package/tests/gitea-service.test.js +253 -0
  237. package/tests/health-check-real.test.js +165 -0
  238. package/tests/integration/full-flow.test.js +266 -0
  239. package/tests/krate.test.js +727 -0
  240. package/tests/memory-search-wiring.test.js +270 -0
  241. package/tests/org-scoping.test.js +687 -0
  242. package/tests/session-cookie-hmac.test.js +151 -0
  243. package/tests/snapshot-performance.test.js +247 -0
  244. package/tests/sse-events.test.js +107 -0
  245. package/tests/workspace-volumes.test.js +312 -0
  246. package/tests/writeback-persistence.test.js +207 -0
@@ -0,0 +1,282 @@
1
+ // Agent Secret/Config Grant Controller — Secret & ConfigMap grant lifecycle
2
+ //
3
+ // Provides:
4
+ // - createAgentSecretGrantController() — validates AgentSecretGrant resources
5
+ // - createAgentConfigGrantController() — validates AgentConfigGrant resources
6
+ // - listGrantsForAgent(agentRef) — filter grants by target agent
7
+ // - revokeGrant(grantName) — mark a grant as revoked
8
+
9
+ import { createResource, clone } from './resource-model.js';
10
+
11
+ export const AGENT_SECRET_GRANT_CONTROLLER_BOUNDARY = {
12
+ role: 'agent-secret-grant-controller',
13
+ scope: 'AgentSecretGrant lifecycle — creation, listing, revocation for agent-to-secret access grants',
14
+ owns: ['grant creation', 'grant listing', 'grant revocation', 'input validation'],
15
+ delegatesTo: ['resource-model'],
16
+ mustNotOwn: ['Kubernetes Secret storage', 'agent execution', 'secret value decryption', 'UI rendering']
17
+ };
18
+
19
+ export const AGENT_CONFIG_GRANT_CONTROLLER_BOUNDARY = {
20
+ role: 'agent-config-grant-controller',
21
+ scope: 'AgentConfigGrant lifecycle — creation, listing, revocation for agent-to-configmap access grants',
22
+ owns: ['grant creation', 'grant listing', 'grant revocation', 'input validation'],
23
+ delegatesTo: ['resource-model'],
24
+ mustNotOwn: ['Kubernetes ConfigMap storage', 'agent execution', 'UI rendering']
25
+ };
26
+
27
+ const VALID_PERMISSIONS = new Set(['read', 'use', 'mount']);
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // Validation helpers
31
+ // ---------------------------------------------------------------------------
32
+
33
+ /**
34
+ * Validate an AgentSecretGrant input.
35
+ *
36
+ * @param {object} input
37
+ * @returns {{ valid: boolean, errors: string[] }}
38
+ */
39
+ export function validateAgentSecretGrant(input) {
40
+ const errors = [];
41
+ if (!input) return { valid: false, errors: ['input must not be null or undefined'] };
42
+ if (!input.name || typeof input.name !== 'string') errors.push('name is required and must be a non-empty string');
43
+ if (!input.orgRef || typeof input.orgRef !== 'string') errors.push('orgRef is required and must be a non-empty string');
44
+ if (!input.secretName || typeof input.secretName !== 'string') errors.push('secretName is required and must be a non-empty string');
45
+ if (!input.grantedTo || typeof input.grantedTo !== 'string') errors.push('grantedTo is required and must be a non-empty string');
46
+ if (!Array.isArray(input.permissions) || input.permissions.length === 0) {
47
+ errors.push('permissions is required and must be a non-empty array');
48
+ } else {
49
+ const invalid = input.permissions.filter((p) => !VALID_PERMISSIONS.has(p));
50
+ if (invalid.length > 0) errors.push(`invalid permissions: ${invalid.join(', ')}. Valid values: ${[...VALID_PERMISSIONS].join(', ')}`);
51
+ }
52
+ return { valid: errors.length === 0, errors };
53
+ }
54
+
55
+ /**
56
+ * Validate an AgentConfigGrant input.
57
+ *
58
+ * @param {object} input
59
+ * @returns {{ valid: boolean, errors: string[] }}
60
+ */
61
+ export function validateAgentConfigGrant(input) {
62
+ const errors = [];
63
+ if (!input) return { valid: false, errors: ['input must not be null or undefined'] };
64
+ if (!input.name || typeof input.name !== 'string') errors.push('name is required and must be a non-empty string');
65
+ if (!input.orgRef || typeof input.orgRef !== 'string') errors.push('orgRef is required and must be a non-empty string');
66
+ if (!input.configMapName || typeof input.configMapName !== 'string') errors.push('configMapName is required and must be a non-empty string');
67
+ if (!input.grantedTo || typeof input.grantedTo !== 'string') errors.push('grantedTo is required and must be a non-empty string');
68
+ return { valid: errors.length === 0, errors };
69
+ }
70
+
71
+ // ---------------------------------------------------------------------------
72
+ // listGrantsForAgent — shared utility
73
+ // ---------------------------------------------------------------------------
74
+
75
+ /**
76
+ * Filter grants by the grantedTo (agent ref) field from a collection of grant resources.
77
+ *
78
+ * @param {object[]} grants Array of AgentSecretGrant or AgentConfigGrant resources
79
+ * @param {string} agentRef The agent identifier to filter by
80
+ * @returns {object[]}
81
+ */
82
+ export function listGrantsForAgent(grants, agentRef) {
83
+ if (!agentRef) return [];
84
+ return grants.filter((g) => g.spec?.grantedTo === agentRef || g.spec?.subject === agentRef);
85
+ }
86
+
87
+ // ---------------------------------------------------------------------------
88
+ // revokeGrant — shared utility
89
+ // ---------------------------------------------------------------------------
90
+
91
+ /**
92
+ * Mark a grant as revoked by returning an updated copy of the resource.
93
+ *
94
+ * @param {object[]} grants Array of grant resources
95
+ * @param {string} grantName Name of the grant to revoke
96
+ * @returns {{ grant: object } | { error: true, reason: string, message: string }}
97
+ */
98
+ export function revokeGrant(grants, grantName) {
99
+ if (!grantName) return { error: true, reason: 'missing-name', message: 'grantName is required' };
100
+
101
+ const found = grants.find((g) => g.metadata?.name === grantName);
102
+ if (!found) return { error: true, reason: 'not-found', message: `Grant not found: ${grantName}` };
103
+
104
+ if (found.status?.phase === 'Revoked') {
105
+ return { error: true, reason: 'already-revoked', message: `Grant "${grantName}" is already revoked` };
106
+ }
107
+
108
+ const updated = clone(found);
109
+ updated.status = {
110
+ ...updated.status,
111
+ phase: 'Revoked',
112
+ revokedAt: new Date().toISOString()
113
+ };
114
+
115
+ return { grant: updated };
116
+ }
117
+
118
+ // ---------------------------------------------------------------------------
119
+ // createAgentSecretGrantController
120
+ // ---------------------------------------------------------------------------
121
+
122
+ /**
123
+ * Create a controller for AgentSecretGrant resources.
124
+ *
125
+ * @param {{ persistFn?: (resource: object) => Promise<any> }} [opts]
126
+ * @returns {object}
127
+ */
128
+ export function createAgentSecretGrantController({ persistFn } = {}) {
129
+ function persist(resource) {
130
+ if (typeof persistFn === 'function') {
131
+ Promise.resolve(persistFn(resource)).catch(() => {});
132
+ }
133
+ }
134
+
135
+ return {
136
+ role: 'agent-secret-grant-controller',
137
+
138
+ /**
139
+ * Create an AgentSecretGrant resource.
140
+ *
141
+ * @param {{ name, orgRef, secretName, grantedTo, permissions, namespace?, keys? }} input
142
+ * @returns {{ grant: object } | { error: true, message: string }}
143
+ */
144
+ createSecretGrant({
145
+ name,
146
+ orgRef,
147
+ secretName,
148
+ grantedTo,
149
+ permissions = ['read'],
150
+ namespace = 'default',
151
+ keys = []
152
+ }) {
153
+ const validation = validateAgentSecretGrant({ name, orgRef, secretName, grantedTo, permissions });
154
+ if (!validation.valid) {
155
+ return { error: true, message: validation.errors.join('; ') };
156
+ }
157
+
158
+ const now = new Date().toISOString();
159
+ const grant = createResource('AgentSecretGrant', { name, namespace }, {
160
+ organizationRef: orgRef,
161
+ orgRef,
162
+ secretName,
163
+ secretRef: secretName,
164
+ grantedTo,
165
+ subject: grantedTo,
166
+ permissions,
167
+ keys,
168
+ purpose: permissions.join(',')
169
+ });
170
+ grant.status = { phase: 'Active', createdAt: now };
171
+
172
+ persist(grant);
173
+ return { grant };
174
+ },
175
+
176
+ /**
177
+ * List all grants for a specific agent.
178
+ *
179
+ * @param {string} agentRef
180
+ * @param {object[]} grants
181
+ * @returns {object[]}
182
+ */
183
+ listGrantsForAgent(agentRef, grants = []) {
184
+ return listGrantsForAgent(grants, agentRef);
185
+ },
186
+
187
+ /**
188
+ * Revoke a grant by name.
189
+ *
190
+ * @param {string} grantName
191
+ * @param {object[]} grants
192
+ * @returns {{ grant: object } | { error: true, reason: string, message: string }}
193
+ */
194
+ revokeGrant(grantName, grants = []) {
195
+ const result = revokeGrant(grants, grantName);
196
+ if (!result.error && result.grant) persist(result.grant);
197
+ return result;
198
+ }
199
+ };
200
+ }
201
+
202
+ // ---------------------------------------------------------------------------
203
+ // createAgentConfigGrantController
204
+ // ---------------------------------------------------------------------------
205
+
206
+ /**
207
+ * Create a controller for AgentConfigGrant resources.
208
+ *
209
+ * @param {{ persistFn?: (resource: object) => Promise<any> }} [opts]
210
+ * @returns {object}
211
+ */
212
+ export function createAgentConfigGrantController({ persistFn } = {}) {
213
+ function persist(resource) {
214
+ if (typeof persistFn === 'function') {
215
+ Promise.resolve(persistFn(resource)).catch(() => {});
216
+ }
217
+ }
218
+
219
+ return {
220
+ role: 'agent-config-grant-controller',
221
+
222
+ /**
223
+ * Create an AgentConfigGrant resource.
224
+ *
225
+ * @param {{ name, orgRef, configMapName, grantedTo, namespace?, keys? }} input
226
+ * @returns {{ grant: object } | { error: true, message: string }}
227
+ */
228
+ createConfigGrant({
229
+ name,
230
+ orgRef,
231
+ configMapName,
232
+ grantedTo,
233
+ namespace = 'default',
234
+ keys = []
235
+ }) {
236
+ const validation = validateAgentConfigGrant({ name, orgRef, configMapName, grantedTo });
237
+ if (!validation.valid) {
238
+ return { error: true, message: validation.errors.join('; ') };
239
+ }
240
+
241
+ const now = new Date().toISOString();
242
+ const grant = createResource('AgentConfigGrant', { name, namespace }, {
243
+ organizationRef: orgRef,
244
+ orgRef,
245
+ configMapName,
246
+ configMapRef: configMapName,
247
+ grantedTo,
248
+ subject: grantedTo,
249
+ keys,
250
+ purpose: 'read'
251
+ });
252
+ grant.status = { phase: 'Active', createdAt: now };
253
+
254
+ persist(grant);
255
+ return { grant };
256
+ },
257
+
258
+ /**
259
+ * List all grants for a specific agent.
260
+ *
261
+ * @param {string} agentRef
262
+ * @param {object[]} grants
263
+ * @returns {object[]}
264
+ */
265
+ listGrantsForAgent(agentRef, grants = []) {
266
+ return listGrantsForAgent(grants, agentRef);
267
+ },
268
+
269
+ /**
270
+ * Revoke a grant by name.
271
+ *
272
+ * @param {string} grantName
273
+ * @param {object[]} grants
274
+ * @returns {{ grant: object } | { error: true, reason: string, message: string }}
275
+ */
276
+ revokeGrant(grantName, grants = []) {
277
+ const result = revokeGrant(grants, grantName);
278
+ if (!result.error && result.grant) persist(result.grant);
279
+ return result;
280
+ }
281
+ };
282
+ }
@@ -0,0 +1,189 @@
1
+ // Agent Session Transcript Controller — Slice 1.2e
2
+ // Manages AgentSessionTranscript resources: durable transcript storage,
3
+ // message indexing, and pagination support.
4
+
5
+ export const AGENT_SESSION_TRANSCRIPT_CONTROLLER_BOUNDARY = {
6
+ role: 'agent-session-transcript-controller',
7
+ scope: 'AgentSessionTranscript lifecycle: validation, message indexing, pagination, role/tool filtering',
8
+ owns: ['transcript validation', 'message indexing', 'pagination', 'role filter', 'tool name filter'],
9
+ delegatesTo: ['resource-model'],
10
+ mustNotOwn: ['session lifecycle', 'dispatch execution', 'Agent Mux sessions', 'secret values']
11
+ };
12
+
13
+ const VALID_ROLES = ['user', 'assistant', 'tool', 'system'];
14
+
15
+ /**
16
+ * Validate an AgentSessionTranscript resource. Returns { valid, errors }.
17
+ * @param {object} resource
18
+ * @returns {{ valid: boolean, errors: string[] }}
19
+ */
20
+ export function validateAgentSessionTranscript(resource) {
21
+ const errors = [];
22
+
23
+ // Guard against null/undefined resource
24
+ if (resource == null) {
25
+ errors.push('resource must not be null or undefined');
26
+ return { valid: false, errors };
27
+ }
28
+
29
+ // Validate metadata.name
30
+ if (!resource?.metadata?.name) {
31
+ errors.push('metadata.name is required');
32
+ }
33
+
34
+ const spec = resource?.spec || {};
35
+
36
+ // Validate organizationRef
37
+ if (!spec.organizationRef) {
38
+ errors.push('spec.organizationRef is required');
39
+ }
40
+
41
+ // Validate sessionRef
42
+ if (!spec.sessionRef) {
43
+ errors.push('spec.sessionRef is required; provide the AgentSession ID this transcript is linked to');
44
+ }
45
+
46
+ // Validate messages — must be an array (can be empty for a new transcript)
47
+ const messages = spec.messages;
48
+ if (!Array.isArray(messages)) {
49
+ errors.push('spec.messages must be an array');
50
+ } else {
51
+ // Validate each message shape
52
+ for (let i = 0; i < messages.length; i++) {
53
+ const msg = messages[i];
54
+ if (msg == null || typeof msg !== 'object') {
55
+ errors.push(`spec.messages[${i}] must be an object`);
56
+ continue;
57
+ }
58
+ if (!msg.role) {
59
+ errors.push(`spec.messages[${i}].role is required`);
60
+ } else if (!VALID_ROLES.includes(msg.role)) {
61
+ errors.push(`spec.messages[${i}].role "${msg.role}" is not valid; valid roles are: ${VALID_ROLES.join(', ')}`);
62
+ }
63
+ if (msg.content == null) {
64
+ errors.push(`spec.messages[${i}].content is required`);
65
+ }
66
+ }
67
+ }
68
+
69
+ // Validate pageSize if explicitly set
70
+ const pageSize = spec.pageSize;
71
+ if (pageSize != null && (!Number.isInteger(pageSize) || pageSize < 1)) {
72
+ errors.push('spec.pageSize must be a positive integer when specified');
73
+ }
74
+
75
+ return { valid: errors.length === 0, errors };
76
+ }
77
+
78
+ /**
79
+ * Factory that returns an AgentSessionTranscript controller instance.
80
+ */
81
+ export function createAgentSessionTranscriptController() {
82
+ return {
83
+ role: 'agent-session-transcript-controller',
84
+
85
+ /**
86
+ * Validate an AgentSessionTranscript resource.
87
+ * @param {object} resource
88
+ * @returns {{ valid: boolean, errors: string[] }}
89
+ */
90
+ validate(resource) {
91
+ return validateAgentSessionTranscript(resource);
92
+ },
93
+
94
+ /**
95
+ * Return all messages in the transcript, in order.
96
+ * @param {object} resource
97
+ * @returns {Array<{ role: string, content: string, timestamp?: string, toolCalls?: object[] }>}
98
+ */
99
+ getMessages(resource) {
100
+ if (resource == null) {
101
+ throw new Error('resource must not be null or undefined');
102
+ }
103
+ const messages = resource?.spec?.messages;
104
+ if (!Array.isArray(messages)) {
105
+ return [];
106
+ }
107
+ return [...messages];
108
+ },
109
+
110
+ /**
111
+ * Return the total number of messages in the transcript.
112
+ * @param {object} resource
113
+ * @returns {number}
114
+ */
115
+ getTotalMessages(resource) {
116
+ if (resource == null) {
117
+ throw new Error('resource must not be null or undefined');
118
+ }
119
+ const messages = resource?.spec?.messages;
120
+ return Array.isArray(messages) ? messages.length : 0;
121
+ },
122
+
123
+ /**
124
+ * Return a page of messages from the transcript.
125
+ * @param {object} resource
126
+ * @param {number} pageIndex - zero-based page index
127
+ * @param {number} [pageSizeOverride] - override spec.pageSize (defaults to spec.pageSize or 20)
128
+ * @returns {{ messages: object[], pageIndex: number, pageSize: number, totalMessages: number, totalPages: number }}
129
+ */
130
+ getPage(resource, pageIndex, pageSizeOverride) {
131
+ if (resource == null) {
132
+ throw new Error('resource must not be null or undefined');
133
+ }
134
+ const messages = Array.isArray(resource?.spec?.messages) ? resource.spec.messages : [];
135
+ const pageSize = pageSizeOverride ?? resource?.spec?.pageSize ?? 20;
136
+ const totalMessages = messages.length;
137
+ const totalPages = Math.max(1, Math.ceil(totalMessages / pageSize));
138
+ const safePageIndex = Math.max(0, Math.min(pageIndex, totalPages - 1));
139
+ const start = safePageIndex * pageSize;
140
+ const end = start + pageSize;
141
+ return {
142
+ messages: messages.slice(start, end),
143
+ pageIndex: safePageIndex,
144
+ pageSize,
145
+ totalMessages,
146
+ totalPages
147
+ };
148
+ },
149
+
150
+ /**
151
+ * Return all messages filtered by role.
152
+ * @param {object} resource
153
+ * @param {string} role - one of: user, assistant, tool, system
154
+ * @returns {Array<object>}
155
+ */
156
+ getMessagesByRole(resource, role) {
157
+ if (resource == null) {
158
+ throw new Error('resource must not be null or undefined');
159
+ }
160
+ const messages = Array.isArray(resource?.spec?.messages) ? resource.spec.messages : [];
161
+ return messages.filter((m) => m.role === role);
162
+ },
163
+
164
+ /**
165
+ * Return all messages that contain a tool call with the given tool name.
166
+ * @param {object} resource
167
+ * @param {string} toolName
168
+ * @returns {Array<object>}
169
+ */
170
+ getMessagesByToolName(resource, toolName) {
171
+ if (resource == null) {
172
+ throw new Error('resource must not be null or undefined');
173
+ }
174
+ const messages = Array.isArray(resource?.spec?.messages) ? resource.spec.messages : [];
175
+ return messages.filter((m) => {
176
+ if (!Array.isArray(m.toolCalls)) return false;
177
+ return m.toolCalls.some((tc) => tc.name === toolName);
178
+ });
179
+ },
180
+
181
+ /**
182
+ * Return the list of valid message roles.
183
+ * @returns {string[]}
184
+ */
185
+ getValidRoles() {
186
+ return [...VALID_ROLES];
187
+ }
188
+ };
189
+ }