@a5c-ai/kradle 5.0.1-staging.3abdf9534c25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (295) hide show
  1. package/Dockerfile +31 -0
  2. package/README.md +187 -0
  3. package/bin/kradle-demo.mjs +23 -0
  4. package/bin/kradle-server.mjs +14 -0
  5. package/dist/kradle-controller-ui.json +3482 -0
  6. package/dist/kradle-lifecycle.json +201 -0
  7. package/dist/kradle-runtime-snapshot.json +3125 -0
  8. package/dist/kradle-summary.json +724 -0
  9. package/docs/README.md +61 -0
  10. package/docs/agents/README.md +83 -0
  11. package/docs/agents/acceptance-test-matrix.md +193 -0
  12. package/docs/agents/agent-mux-adapter-contract.md +167 -0
  13. package/docs/agents/agent-mux-source-map.md +310 -0
  14. package/docs/agents/agent-run-memory-import-spec.md +256 -0
  15. package/docs/agents/agent-stack-management-spec.md +421 -0
  16. package/docs/agents/api-contract-spec.md +309 -0
  17. package/docs/agents/artifacts-writeback-spec.md +145 -0
  18. package/docs/agents/chart-packaging-spec.md +128 -0
  19. package/docs/agents/ci-orchestration-spec.md +140 -0
  20. package/docs/agents/context-assembly-spec.md +219 -0
  21. package/docs/agents/controller-reconciliation-spec.md +255 -0
  22. package/docs/agents/crd-schema-spec.md +315 -0
  23. package/docs/agents/decision-log-open-questions.md +169 -0
  24. package/docs/agents/developer-implementation-checklist.md +329 -0
  25. package/docs/agents/dispatching-design.md +262 -0
  26. package/docs/agents/gaps-agent-mux-to-kradle-crds.md +298 -0
  27. package/docs/agents/glossary.md +66 -0
  28. package/docs/agents/implementation-blueprint.md +324 -0
  29. package/docs/agents/implementation-rollout-slices.md +251 -0
  30. package/docs/agents/memory-context-integration-spec.md +194 -0
  31. package/docs/agents/memory-ontology-schema-spec.md +253 -0
  32. package/docs/agents/memory-operations-runbook.md +121 -0
  33. package/docs/agents/mvp-vertical-slice-spec.md +146 -0
  34. package/docs/agents/observability-audit-spec.md +265 -0
  35. package/docs/agents/operator-runbook.md +174 -0
  36. package/docs/agents/org-memory-api-payload-examples.md +333 -0
  37. package/docs/agents/org-memory-controller-sequence-spec.md +181 -0
  38. package/docs/agents/org-memory-e2e-fixture-plan.md +161 -0
  39. package/docs/agents/org-memory-ui-implementation-map.md +114 -0
  40. package/docs/agents/org-memory-vertical-slice-spec.md +168 -0
  41. package/docs/agents/org-resource-model-delta-spec.md +111 -0
  42. package/docs/agents/org-route-resource-model-spec.md +183 -0
  43. package/docs/agents/org-scoping-namespace-spec.md +114 -0
  44. package/docs/agents/rbac-secrets-management-spec.md +406 -0
  45. package/docs/agents/repository-page-integration-spec.md +255 -0
  46. package/docs/agents/resource-contract-examples.md +808 -0
  47. package/docs/agents/resource-relationship-map.md +190 -0
  48. package/docs/agents/security-threat-model.md +188 -0
  49. package/docs/agents/shared-memory-company-brain-spec.md +358 -0
  50. package/docs/agents/storage-migration-spec.md +168 -0
  51. package/docs/agents/subagent-orchestration-spec.md +152 -0
  52. package/docs/agents/system-overview.md +88 -0
  53. package/docs/agents/tools-mcp-skills-spec.md +189 -0
  54. package/docs/agents/traceability-matrix.md +79 -0
  55. package/docs/agents/ui-flow-spec.md +211 -0
  56. package/docs/agents/ui-ux-system-spec.md +426 -0
  57. package/docs/agents/workspace-lifecycle-spec.md +166 -0
  58. package/docs/architecture-spec.md +78 -0
  59. package/docs/architecture-v2.md +2759 -0
  60. package/docs/components/control-plane.md +78 -0
  61. package/docs/components/data-plane.md +69 -0
  62. package/docs/components/hooks-events.md +67 -0
  63. package/docs/components/identity-rbac-policy.md +73 -0
  64. package/docs/components/kubevela-oam.md +70 -0
  65. package/docs/components/operations-publishing.md +81 -0
  66. package/docs/components/runners-ci.md +66 -0
  67. package/docs/components/web-ui.md +94 -0
  68. package/docs/crd-behaviors-and-relationships.md +3926 -0
  69. package/docs/external/README.md +47 -0
  70. package/docs/external/bidirectional-sync-design.md +134 -0
  71. package/docs/external/cicd-interface.md +64 -0
  72. package/docs/external/external-backend-controllers.md +170 -0
  73. package/docs/external/external-backend-crds.md +234 -0
  74. package/docs/external/external-backend-ui-spec.md +151 -0
  75. package/docs/external/external-backend-ux-flows.md +115 -0
  76. package/docs/external/external-object-mapping.md +125 -0
  77. package/docs/external/git-forge-interface.md +68 -0
  78. package/docs/external/github-integration-design.md +151 -0
  79. package/docs/external/issue-tracking-interface.md +66 -0
  80. package/docs/external/provider-capability-manifests.md +204 -0
  81. package/docs/external/provider-catalog.md +139 -0
  82. package/docs/external/provider-rollout-testing.md +78 -0
  83. package/docs/external/research-results.md +48 -0
  84. package/docs/external/security-auth-permissions.md +81 -0
  85. package/docs/external/sync-state-machines.md +108 -0
  86. package/docs/external/unified-external-backend-model.md +107 -0
  87. package/docs/external/user-facing-changes.md +67 -0
  88. package/docs/gaps.md +161 -0
  89. package/docs/install.md +94 -0
  90. package/docs/integration-and-design-decisions.md +1530 -0
  91. package/docs/kradle-design.md +334 -0
  92. package/docs/local-minikube.md +55 -0
  93. package/docs/ontology/README.md +32 -0
  94. package/docs/ontology/bounded-contexts.md +29 -0
  95. package/docs/ontology/events-and-hooks.md +32 -0
  96. package/docs/ontology/oam-kubevela.md +32 -0
  97. package/docs/ontology/operations-and-release.md +25 -0
  98. package/docs/ontology/personas-and-actors.md +32 -0
  99. package/docs/ontology/policies-and-invariants.md +33 -0
  100. package/docs/ontology/problem-space.md +30 -0
  101. package/docs/ontology/resource-contracts.md +40 -0
  102. package/docs/ontology/resource-taxonomy.md +42 -0
  103. package/docs/ontology/runners-and-ci.md +29 -0
  104. package/docs/ontology/solution-space.md +24 -0
  105. package/docs/ontology/storage-and-data-boundaries.md +29 -0
  106. package/docs/ontology/validation-matrix.md +24 -0
  107. package/docs/ontology/web-ui-excellent-flows.md +32 -0
  108. package/docs/ontology/workflows.md +39 -0
  109. package/docs/ontology/world.md +35 -0
  110. package/docs/openapi.yaml +1291 -0
  111. package/docs/product-requirements.md +62 -0
  112. package/docs/requirements-v2.md +235 -0
  113. package/docs/roadmap-mvp.md +87 -0
  114. package/docs/sdk-api-reference.md +1108 -0
  115. package/docs/system-requirements.md +90 -0
  116. package/docs/system-spec-v2.md +1230 -0
  117. package/docs/tests/README.md +53 -0
  118. package/docs/tests/agent-qa-plan.md +63 -0
  119. package/docs/tests/browser-ui-tests.md +62 -0
  120. package/docs/tests/ci-quality-gates.md +48 -0
  121. package/docs/tests/coverage-model.md +64 -0
  122. package/docs/tests/e2e-scenario-tests.md +53 -0
  123. package/docs/tests/fixtures-test-data.md +63 -0
  124. package/docs/tests/observability-reliability-tests.md +54 -0
  125. package/docs/tests/product-test-matrix.md +145 -0
  126. package/docs/tests/qa-adoption-roadmap.md +130 -0
  127. package/docs/tests/qa-automation-plan.md +101 -0
  128. package/docs/tests/security-compliance-tests.md +57 -0
  129. package/docs/tests/test-framework-tools.md +88 -0
  130. package/docs/tests/test-suite-layout.md +121 -0
  131. package/docs/tests/unit-integration-tests.md +48 -0
  132. package/docs/todo-kyverno +714 -0
  133. package/docs/todos.md +4 -0
  134. package/docs/user-stories.md +78 -0
  135. package/docs/web-console-spec.md +533 -0
  136. package/examples/minikube-demo.yaml +190 -0
  137. package/examples/oam-application.yaml +23 -0
  138. package/examples/policy-kyverno-pr-title.yaml +18 -0
  139. package/package.json +66 -0
  140. package/scripts/build.mjs +29 -0
  141. package/scripts/setup-minikube.mjs +65 -0
  142. package/scripts/smoke.mjs +37 -0
  143. package/scripts/validate-doc-coverage.mjs +152 -0
  144. package/scripts/validate-package.mjs +95 -0
  145. package/scripts/validate-ui.mjs +305 -0
  146. package/src/agent-adapter-controller.js +169 -0
  147. package/src/agent-approval-controller.js +170 -0
  148. package/src/agent-context-bundles.js +242 -0
  149. package/src/agent-dispatch-controller.js +549 -0
  150. package/src/agent-gateway-config-controller.js +147 -0
  151. package/src/agent-identity-migration.js +115 -0
  152. package/src/agent-memory-controller.js +357 -0
  153. package/src/agent-memory-import.js +327 -0
  154. package/src/agent-memory-query.js +292 -0
  155. package/src/agent-memory-repository-source-controller.js +255 -0
  156. package/src/agent-mux-client.js +589 -0
  157. package/src/agent-permission-review.js +250 -0
  158. package/src/agent-persona-controller.js +135 -0
  159. package/src/agent-project-controller.js +117 -0
  160. package/src/agent-prompt-composition.js +55 -0
  161. package/src/agent-provider-config-controller.js +151 -0
  162. package/src/agent-secret-config-grant-controller.js +282 -0
  163. package/src/agent-session-transcript-controller.js +189 -0
  164. package/src/agent-stack-controller.js +421 -0
  165. package/src/agent-subagent-controller.js +160 -0
  166. package/src/agent-transport-binding-controller.js +121 -0
  167. package/src/agent-trigger-controller.js +387 -0
  168. package/src/agent-workspace-controller.js +702 -0
  169. package/src/agent-writeback-controller.js +302 -0
  170. package/src/api-controller.js +621 -0
  171. package/src/argocd-gitops.js +43 -0
  172. package/src/artifact-registry-controller.js +542 -0
  173. package/src/assistant-runtime.js +284 -0
  174. package/src/async-controller.js +207 -0
  175. package/src/audit-controller.js +191 -0
  176. package/src/auth.js +310 -0
  177. package/src/component-catalog.js +41 -0
  178. package/src/control-plane.js +136 -0
  179. package/src/controller-client.js +112 -0
  180. package/src/controller-ui.js +620 -0
  181. package/src/data-plane.js +179 -0
  182. package/src/event-bus.js +397 -0
  183. package/src/external/conflict-controller.js +225 -0
  184. package/src/external/github/auth.js +96 -0
  185. package/src/external/github/cicd.js +180 -0
  186. package/src/external/github/git-forge.js +240 -0
  187. package/src/external/github/index.js +144 -0
  188. package/src/external/github/issue-tracking.js +163 -0
  189. package/src/external/provider-adapter.js +161 -0
  190. package/src/external/provider-resource-factory.js +221 -0
  191. package/src/external/sync-controller.js +235 -0
  192. package/src/external/webhook-controller.js +144 -0
  193. package/src/external/write-controller.js +283 -0
  194. package/src/gitea-backend.js +131 -0
  195. package/src/gitea-service.js +173 -0
  196. package/src/handoff.js +98 -0
  197. package/src/health-probes.js +134 -0
  198. package/src/hooks-events.js +63 -0
  199. package/src/hooks-lifecycle.js +117 -0
  200. package/src/http-server.js +409 -0
  201. package/src/identity-policy.js +86 -0
  202. package/src/index.js +71 -0
  203. package/src/jitsi-agent-bridge.js +141 -0
  204. package/src/jitsi-meeting-controller.js +291 -0
  205. package/src/jitsi-sync-controller.js +198 -0
  206. package/src/kradle-inference-service-controller.js +246 -0
  207. package/src/kubernetes-controller-async.js +531 -0
  208. package/src/kubernetes-controller.js +904 -0
  209. package/src/kubernetes-resource-gateway.js +48 -0
  210. package/src/model-route-controller.js +364 -0
  211. package/src/notification-controller.js +178 -0
  212. package/src/operations.js +112 -0
  213. package/src/org-scoping.js +5 -0
  214. package/src/resource-model.js +282 -0
  215. package/src/runner-controller.js +272 -0
  216. package/src/runners-ci.js +48 -0
  217. package/src/runtime.js +196 -0
  218. package/src/snapshot-cache.js +157 -0
  219. package/src/virtual-model-controller.js +538 -0
  220. package/src/virtual-model-hook-bridge.js +200 -0
  221. package/src/web-ui.js +40 -0
  222. package/tests/agent-adapter-controller.test.js +361 -0
  223. package/tests/agent-approval-controller.test.js +173 -0
  224. package/tests/agent-context-bundles.test.js +278 -0
  225. package/tests/agent-dispatch-controller.test.js +679 -0
  226. package/tests/agent-gateway-config-controller.test.js +386 -0
  227. package/tests/agent-identity-migration.test.js +87 -0
  228. package/tests/agent-memory-controller.test.js +461 -0
  229. package/tests/agent-memory-import-snapshot.test.js +477 -0
  230. package/tests/agent-memory-query.test.js +404 -0
  231. package/tests/agent-memory-repository-source.test.js +514 -0
  232. package/tests/agent-mux-client.test.js +389 -0
  233. package/tests/agent-mux-integration.test.js +971 -0
  234. package/tests/agent-permission-review-v2.test.js +317 -0
  235. package/tests/agent-permission-review.test.js +209 -0
  236. package/tests/agent-persona-controller.test.js +127 -0
  237. package/tests/agent-project-controller.test.js +302 -0
  238. package/tests/agent-prompt-composition.test.js +76 -0
  239. package/tests/agent-provider-config-controller.test.js +376 -0
  240. package/tests/agent-resources.test.js +303 -0
  241. package/tests/agent-secret-config-grant.test.js +231 -0
  242. package/tests/agent-session-transcript-controller.test.js +499 -0
  243. package/tests/agent-stack-controller.test.js +283 -0
  244. package/tests/agent-subagent-controller.test.js +201 -0
  245. package/tests/agent-transport-binding-controller.test.js +294 -0
  246. package/tests/agent-trigger-controller.test.js +271 -0
  247. package/tests/agent-trigger-routes.test.js +190 -0
  248. package/tests/agent-trigger-sources.test.js +245 -0
  249. package/tests/agent-workspace-controller.test.js +181 -0
  250. package/tests/agent-writeback.test.js +292 -0
  251. package/tests/approval-persistence.test.js +171 -0
  252. package/tests/artifact-registry.test.js +511 -0
  253. package/tests/assistant-runtime.test.js +506 -0
  254. package/tests/async-controller.test.js +252 -0
  255. package/tests/audit-controller.test.js +227 -0
  256. package/tests/codespace-controller.test.js +318 -0
  257. package/tests/controller-client.test.js +133 -0
  258. package/tests/deployment.test.js +527 -0
  259. package/tests/e2e/lifecycle.test.js +120 -0
  260. package/tests/event-bus-integration.test.js +355 -0
  261. package/tests/external-github-forge.test.js +560 -0
  262. package/tests/external-github-issues-cicd.test.js +520 -0
  263. package/tests/external-integration.test.js +470 -0
  264. package/tests/external-persistence.test.js +415 -0
  265. package/tests/external-provider-adapter.test.js +365 -0
  266. package/tests/external-resource-model.test.js +223 -0
  267. package/tests/external-webhook-sync.test.js +287 -0
  268. package/tests/external-write-conflict.test.js +353 -0
  269. package/tests/gitea-service.test.js +253 -0
  270. package/tests/health-check-real.test.js +165 -0
  271. package/tests/health-probes.test.js +90 -0
  272. package/tests/hooks-lifecycle.test.js +364 -0
  273. package/tests/integration/full-flow.test.js +266 -0
  274. package/tests/jitsi-agent-bridge.test.js +119 -0
  275. package/tests/jitsi-helm-integration.test.js +77 -0
  276. package/tests/jitsi-meeting-controller.test.js +170 -0
  277. package/tests/jitsi-resource-model.test.js +73 -0
  278. package/tests/jitsi-sync-controller.test.js +112 -0
  279. package/tests/kradle-inference-service.test.js +689 -0
  280. package/tests/kradle.test.js +779 -0
  281. package/tests/memory-search-wiring.test.js +270 -0
  282. package/tests/model-route-controller.test.js +733 -0
  283. package/tests/notification-controller.test.js +196 -0
  284. package/tests/notification-integration.test.js +179 -0
  285. package/tests/org-scoping.test.js +687 -0
  286. package/tests/runner-controller.test.js +327 -0
  287. package/tests/runner-integration.test.js +231 -0
  288. package/tests/session-cookie-hmac.test.js +151 -0
  289. package/tests/snapshot-performance.test.js +315 -0
  290. package/tests/sse-events.test.js +107 -0
  291. package/tests/virtual-model-controller.test.js +877 -0
  292. package/tests/virtual-model-hook-bridge.test.js +384 -0
  293. package/tests/webhook-trigger.test.js +198 -0
  294. package/tests/workspace-volumes.test.js +312 -0
  295. package/tests/writeback-persistence.test.js +207 -0
@@ -0,0 +1,461 @@
1
+ import assert from 'node:assert/strict';
2
+ import test from 'node:test';
3
+ import { createKradleApiController, createResource } from '../src/index.js';
4
+ import { createAgentMemoryController } from '../src/agent-memory-controller.js';
5
+
6
+ function makeRecords() {
7
+ return [
8
+ {
9
+ id: 'service/auth-api',
10
+ nodeKind: 'Service',
11
+ attributes: { name: 'auth-api', language: 'typescript', team: 'platform' },
12
+ edges: [
13
+ { target: 'service/user-db', kind: 'depends-on' },
14
+ { target: 'team/platform', kind: 'owned-by' },
15
+ ],
16
+ },
17
+ {
18
+ id: 'service/user-db',
19
+ nodeKind: 'Service',
20
+ attributes: { name: 'user-db', language: 'go', team: 'data' },
21
+ edges: [
22
+ { target: 'infra/postgres-cluster', kind: 'depends-on' },
23
+ ],
24
+ },
25
+ {
26
+ id: 'team/platform',
27
+ nodeKind: 'Team',
28
+ attributes: { name: 'platform', lead: 'alice' },
29
+ edges: [],
30
+ },
31
+ {
32
+ id: 'infra/postgres-cluster',
33
+ nodeKind: 'Infrastructure',
34
+ attributes: { name: 'postgres-cluster', provider: 'aws' },
35
+ edges: [],
36
+ },
37
+ {
38
+ id: 'runbook/deploy-auth',
39
+ nodeKind: 'Runbook',
40
+ attributes: { name: 'deploy-auth', service: 'auth-api' },
41
+ edges: [
42
+ { target: 'service/auth-api', kind: 'references' },
43
+ ],
44
+ },
45
+ ];
46
+ }
47
+
48
+ function makeDocuments() {
49
+ return [
50
+ { path: 'docs/architecture.md', content: 'The auth-api service handles authentication.\nIt uses JWT tokens for session management.\nThe service connects to user-db for persistence.' },
51
+ { path: 'docs/runbooks/deploy.md', content: 'Step 1: Run the deploy script.\nStep 2: Verify health checks.\nStep 3: Monitor error rates.' },
52
+ { path: 'src/auth/handler.ts', content: 'export function handleAuth(req) {\n const token = req.headers.authorization;\n return validateToken(token);\n}' },
53
+ { path: 'configs/prod.yaml', content: 'database:\n host: prod-db.internal\n port: 5432\n password: [redacted]' },
54
+ ];
55
+ }
56
+
57
+ function createRecordingGateway({ snapshotResources = {}, failKinds = new Set() } = {}) {
58
+ const applied = [];
59
+ return {
60
+ namespace: 'kradle-system',
61
+ resourceDefinitions: {},
62
+ applied,
63
+ async snapshot() {
64
+ return { resources: snapshotResources, namespace: 'kradle-system' };
65
+ },
66
+ async list() {
67
+ return { items: [] };
68
+ },
69
+ async get() {
70
+ return null;
71
+ },
72
+ async apply(resource) {
73
+ if (failKinds.has(resource.kind)) {
74
+ throw new Error(`apply failed for ${resource.kind}`);
75
+ }
76
+ applied.push(JSON.parse(JSON.stringify(resource)));
77
+ return { operation: 'apply', resource };
78
+ },
79
+ async delete() {
80
+ return { operation: 'delete' };
81
+ },
82
+ watch() {
83
+ return { close: () => {} };
84
+ },
85
+ };
86
+ }
87
+
88
+ function makeDispatchResources() {
89
+ return {
90
+ AgentStack: [
91
+ createResource('AgentStack', { name: 'mem-stack', namespace: 'kradle-org-acme' }, {
92
+ organizationRef: 'acme',
93
+ baseAgent: 'claude-code',
94
+ adapter: 'claude-code',
95
+ provider: 'anthropic',
96
+ runtimeIdentity: { serviceAccountRef: 'sa-default' },
97
+ }),
98
+ ],
99
+ AgentServiceAccount: [
100
+ createResource('AgentServiceAccount', { name: 'sa-default', namespace: 'kradle-org-acme' }, {
101
+ organizationRef: 'acme',
102
+ namespace: 'kradle-org-acme',
103
+ serviceAccountName: 'sa-default',
104
+ }),
105
+ ],
106
+ AgentRoleBinding: [
107
+ createResource('AgentRoleBinding', { name: 'rb-default', namespace: 'kradle-org-acme' }, {
108
+ organizationRef: 'acme',
109
+ subject: 'sa-default',
110
+ roleRef: 'agent-developer',
111
+ scope: 'namespace',
112
+ }),
113
+ ],
114
+ AgentSecretGrant: [
115
+ createResource('AgentSecretGrant', { name: 'sg-model', namespace: 'kradle-org-acme' }, {
116
+ organizationRef: 'acme',
117
+ subject: 'sa-default',
118
+ secretRef: 'model-secret',
119
+ purpose: 'model-provider',
120
+ }),
121
+ ],
122
+ AgentMemoryRepository: [
123
+ createResource('AgentMemoryRepository', { name: 'company-brain', namespace: 'kradle-org-acme' }, {
124
+ organizationRef: 'acme',
125
+ repositoryRef: 'memory-repo',
126
+ defaultBranch: 'main',
127
+ layoutProfile: 'standard',
128
+ }),
129
+ ],
130
+ };
131
+ }
132
+
133
+ test('createMemorySnapshot creates valid resource with digests', () => {
134
+ const controller = createAgentMemoryController();
135
+ const records = makeRecords();
136
+ const documents = makeDocuments();
137
+
138
+ const snapshot = controller.createMemorySnapshot({
139
+ memoryRepository: 'company-brain',
140
+ requestedRef: 'main',
141
+ resolvedCommit: 'abc123def456',
142
+ queryManifest: { include: ['Service', 'Team'] },
143
+ selectedRecords: records,
144
+ selectedDocuments: documents,
145
+ ontologyDigest: 'onto-digest-1',
146
+ namespace: 'kradle-org-default',
147
+ organizationRef: 'acme',
148
+ });
149
+
150
+ assert.equal(snapshot.kind, 'AgentMemorySnapshot');
151
+ assert.ok(snapshot.metadata.name.startsWith('memsnapshot-'));
152
+ assert.equal(snapshot.spec.memoryRepository, 'company-brain');
153
+ assert.equal(snapshot.spec.requestedRef, 'main');
154
+ assert.equal(snapshot.spec.resolvedCommit, 'abc123def456');
155
+ assert.ok(snapshot.spec.queryManifestDigest, 'Should have queryManifestDigest');
156
+ assert.ok(snapshot.spec.selectedRecordsDigest, 'Should have selectedRecordsDigest');
157
+ assert.ok(snapshot.spec.selectedDocumentsDigest, 'Should have selectedDocumentsDigest');
158
+ assert.equal(snapshot.spec.ontologyDigest, 'onto-digest-1');
159
+ assert.equal(snapshot.spec.recordCount, records.length);
160
+ assert.equal(snapshot.spec.documentCount, documents.length);
161
+ assert.equal(snapshot.status.phase, 'Pinned');
162
+ });
163
+
164
+ test('searchGraph filters by node kind', () => {
165
+ const controller = createAgentMemoryController();
166
+ const records = makeRecords();
167
+
168
+ const result = controller.searchGraph({ records, kinds: ['Service'], query: '' });
169
+
170
+ assert.equal(result.totalMatches, 2, 'Should match exactly 2 Service records');
171
+ assert.ok(result.matches.every(m => m.record.nodeKind === 'Service'));
172
+ });
173
+
174
+ test('searchGraph matches query text', () => {
175
+ const controller = createAgentMemoryController();
176
+ const records = makeRecords();
177
+
178
+ const result = controller.searchGraph({ records, kinds: [], query: 'auth' });
179
+
180
+ assert.ok(result.totalMatches >= 1, 'Should match at least auth-api');
181
+ const authMatch = result.matches.find(m => m.record.id === 'service/auth-api');
182
+ assert.ok(authMatch, 'Should include auth-api in matches');
183
+ assert.equal(authMatch.score, 2, 'ID match should score 2');
184
+ });
185
+
186
+ test('searchGraph respects edgeDepth', () => {
187
+ const controller = createAgentMemoryController();
188
+ const records = makeRecords();
189
+
190
+ // edgeDepth=1: from auth-api should reach user-db and team/platform but NOT postgres-cluster
191
+ const depth1 = controller.searchGraph({ records, kinds: ['Service'], query: 'auth-api', edgeDepth: 1 });
192
+ const authMatch1 = depth1.matches.find(m => m.record.id === 'service/auth-api');
193
+ assert.ok(authMatch1, 'Should find auth-api');
194
+ const depth1Targets = authMatch1.edges.map(e => e.target);
195
+ assert.ok(depth1Targets.includes('service/user-db'), 'Depth 1 should include user-db');
196
+ assert.ok(!depth1Targets.includes('infra/postgres-cluster'), 'Depth 1 should NOT include postgres-cluster');
197
+
198
+ // edgeDepth=2: should also reach postgres-cluster
199
+ const depth2 = controller.searchGraph({ records, kinds: ['Service'], query: 'auth-api', edgeDepth: 2 });
200
+ const authMatch2 = depth2.matches.find(m => m.record.id === 'service/auth-api');
201
+ const depth2Targets = authMatch2.edges.map(e => e.target);
202
+ assert.ok(depth2Targets.includes('infra/postgres-cluster'), 'Depth 2 should include postgres-cluster');
203
+ });
204
+
205
+ test('searchGrep finds pattern matches', () => {
206
+ const controller = createAgentMemoryController();
207
+ const documents = makeDocuments();
208
+
209
+ const result = controller.searchGrep({ documents, pattern: 'auth', maxMatches: 25 });
210
+
211
+ assert.ok(result.totalMatches >= 1, 'Should find auth pattern');
212
+ assert.ok(result.excerpts.some(e => e.path === 'docs/architecture.md'), 'Should match in architecture.md');
213
+ assert.ok(result.excerpts.every(e => e.lineNumber > 0), 'Every excerpt should have a lineNumber');
214
+ assert.ok(result.excerpts.every(e => typeof e.context === 'string'), 'Every excerpt should have context');
215
+ });
216
+
217
+ test('searchGrep caps at maxMatches', () => {
218
+ const controller = createAgentMemoryController();
219
+ const documents = makeDocuments();
220
+
221
+ const result = controller.searchGrep({ documents, pattern: 'the', maxMatches: 2 });
222
+
223
+ assert.ok(result.totalMatches <= 2, 'Should not exceed maxMatches');
224
+ assert.ok(result.excerpts.length <= 2, 'Excerpts length should not exceed maxMatches');
225
+ });
226
+
227
+ test('searchGrep filters by path patterns', () => {
228
+ const controller = createAgentMemoryController();
229
+ const documents = makeDocuments();
230
+
231
+ const result = controller.searchGrep({ documents, paths: ['docs/*'], pattern: 'deploy' });
232
+
233
+ assert.ok(result.totalMatches >= 1, 'Should find deploy in docs');
234
+ assert.ok(result.excerpts.every(e => e.path.startsWith('docs/')), 'All matches should be in docs/');
235
+ });
236
+
237
+ test('resolveTimeTravel mode=current returns latest commit', () => {
238
+ const controller = createAgentMemoryController();
239
+ const commits = [
240
+ { sha: 'latest-sha', timestamp: '2026-05-10T10:00:00Z' },
241
+ { sha: 'older-sha', timestamp: '2026-05-09T10:00:00Z' },
242
+ ];
243
+
244
+ const result = controller.resolveTimeTravel({ mode: 'current', commits });
245
+
246
+ assert.equal(result.resolvedCommit, 'latest-sha');
247
+ assert.equal(result.mode, 'current');
248
+ assert.ok(result.resolvedAt);
249
+ });
250
+
251
+ test('resolveTimeTravel mode=ref-at-time finds correct commit', () => {
252
+ const controller = createAgentMemoryController();
253
+ const commits = [
254
+ { sha: 'c3', timestamp: '2026-05-10T10:00:00Z' },
255
+ { sha: 'c2', timestamp: '2026-05-08T10:00:00Z' },
256
+ { sha: 'c1', timestamp: '2026-05-05T10:00:00Z' },
257
+ ];
258
+
259
+ const result = controller.resolveTimeTravel({
260
+ mode: 'ref-at-time',
261
+ requestedTime: '2026-05-09T00:00:00Z',
262
+ commits,
263
+ });
264
+
265
+ assert.equal(result.resolvedCommit, 'c2', 'Should resolve to c2 (latest before requested time)');
266
+ assert.equal(result.mode, 'ref-at-time');
267
+ assert.ok(result.staleBy !== null, 'staleBy should be set');
268
+ });
269
+
270
+ test('scanForRedaction catches API keys', () => {
271
+ const controller = createAgentMemoryController();
272
+ const content = 'API_KEY = sk-test1234567890abcdefghij\nSome normal text\nSECRET_KEY=my-super-secret-value';
273
+
274
+ const result = controller.scanForRedaction(content);
275
+
276
+ assert.equal(result.clean, false, 'Content should not be clean');
277
+ assert.ok(result.redactionCount > 0, 'Should have redactions');
278
+ assert.ok(result.redactionsByKind['secret-key'] > 0, 'Should catch secret-key pattern');
279
+ assert.ok(result.redactedContent.includes('[REDACTED:'), 'Redacted content should have markers');
280
+ assert.ok(!result.redactedContent.includes('my-super-secret-value'), 'Secret should be redacted');
281
+ });
282
+
283
+ test('scanForRedaction catches provider tokens', () => {
284
+ const controller = createAgentMemoryController();
285
+ const content = 'token: ghp_abcdefghijklmnopqrstuvwxyz1234567890\nslack: xoxb-123-456-abcdefghij';
286
+
287
+ const result = controller.scanForRedaction(content);
288
+
289
+ assert.equal(result.clean, false);
290
+ assert.ok(result.redactionsByKind['provider-token'] > 0, 'Should catch provider-token pattern');
291
+ });
292
+
293
+ test('validateOntology catches missing required fields', () => {
294
+ const controller = createAgentMemoryController();
295
+ const records = [
296
+ { id: 'svc/a', nodeKind: 'Service', attributes: { name: 'a' }, edges: [] },
297
+ { id: 'svc/b', nodeKind: 'Service', attributes: { name: 'b', language: 'go' }, edges: [] },
298
+ ];
299
+ const ontology = {
300
+ requiredFields: { Service: ['name', 'language', 'team'] },
301
+ allowedEdgeKinds: ['depends-on', 'owned-by'],
302
+ };
303
+
304
+ const result = controller.validateOntology({ records, ontology });
305
+
306
+ assert.equal(result.valid, false, 'Should be invalid due to missing fields');
307
+ assert.ok(result.errors.length >= 2, 'Should have errors for missing language and team on svc/a, and team on svc/b');
308
+ const missingLang = result.errors.find(e => e.record === 'svc/a' && e.field === 'language');
309
+ assert.ok(missingLang, 'Should report missing language on svc/a');
310
+ const missingTeam = result.errors.find(e => e.record === 'svc/a' && e.field === 'team');
311
+ assert.ok(missingTeam, 'Should report missing team on svc/a');
312
+ });
313
+
314
+ test('validateOntology passes valid records', () => {
315
+ const controller = createAgentMemoryController();
316
+ const records = [
317
+ { id: 'svc/a', nodeKind: 'Service', attributes: { name: 'a', language: 'ts' }, edges: [{ target: 'svc/b', kind: 'depends-on' }] },
318
+ { id: 'svc/b', nodeKind: 'Service', attributes: { name: 'b', language: 'go' }, edges: [] },
319
+ ];
320
+ const ontology = {
321
+ requiredFields: { Service: ['name', 'language'] },
322
+ allowedEdgeKinds: ['depends-on', 'owned-by'],
323
+ };
324
+
325
+ const result = controller.validateOntology({ records, ontology });
326
+
327
+ assert.equal(result.valid, true, 'Should be valid');
328
+ assert.equal(result.errors.length, 0);
329
+ });
330
+
331
+ test('createImport sets phase=Pending', () => {
332
+ const controller = createAgentMemoryController();
333
+
334
+ const importResource = controller.createImport({
335
+ organizationRef: 'acme',
336
+ memoryRepository: 'company-brain',
337
+ source: 'babysitter-run/run-123',
338
+ include: { kinds: ['decision', 'learning'] },
339
+ validationPolicy: 'strict',
340
+ namespace: 'kradle-org-default',
341
+ });
342
+
343
+ assert.equal(importResource.kind, 'AgentRunMemoryImport');
344
+ assert.ok(importResource.metadata.name.startsWith('memimport-'));
345
+ assert.equal(importResource.status.phase, 'Pending');
346
+ assert.equal(importResource.spec.memoryRepository, 'company-brain');
347
+ assert.equal(importResource.spec.source, 'babysitter-run/run-123');
348
+ assert.equal(importResource.spec.validationPolicy, 'strict');
349
+ });
350
+
351
+ test('api-controller createMemoryImport applies returned AgentRunMemoryImport in org namespace', async () => {
352
+ const gateway = createRecordingGateway();
353
+ const controller = createKradleApiController({ resourceGateway: gateway });
354
+
355
+ const result = await controller.createMemoryImport({
356
+ organizationRef: 'acme',
357
+ memoryRepository: 'company-brain',
358
+ source: 'babysitter-run/run-123',
359
+ include: { kinds: ['decision'] },
360
+ validationPolicy: 'strict',
361
+ });
362
+
363
+ const appliedImport = gateway.applied.find((resource) => resource.kind === 'AgentRunMemoryImport');
364
+ assert.ok(appliedImport, 'AgentRunMemoryImport must be applied durably');
365
+ assert.equal(appliedImport.metadata.namespace, 'kradle-org-acme');
366
+ assert.equal(appliedImport.spec.organizationRef, 'acme');
367
+ assert.equal(result.importResource?.metadata?.name || result.metadata?.name, appliedImport.metadata.name);
368
+ assert.ok(result.applyResult, 'result should expose the apply result');
369
+ });
370
+
371
+ test('api-controller createMemoryImport propagates apply failures', async () => {
372
+ const gateway = createRecordingGateway({ failKinds: new Set(['AgentRunMemoryImport']) });
373
+ const controller = createKradleApiController({ resourceGateway: gateway });
374
+
375
+ await assert.rejects(
376
+ () => controller.createMemoryImport({
377
+ organizationRef: 'acme',
378
+ memoryRepository: 'company-brain',
379
+ source: 'babysitter-run/run-123',
380
+ include: { kinds: ['decision'] },
381
+ }),
382
+ /apply failed for AgentRunMemoryImport/
383
+ );
384
+ });
385
+
386
+ test('api-controller createMemoryUpdate applies returned AgentMemoryUpdate in org namespace', async () => {
387
+ const gateway = createRecordingGateway();
388
+ const controller = createKradleApiController({ resourceGateway: gateway });
389
+
390
+ assert.equal(typeof controller.createMemoryUpdate, 'function', 'controller must expose createMemoryUpdate');
391
+ const result = await controller.createMemoryUpdate({
392
+ organizationRef: 'acme',
393
+ memoryRepository: 'company-brain',
394
+ sourceRun: 'dispatch-123',
395
+ changes: [{ op: 'add', path: 'records/service-api' }],
396
+ });
397
+
398
+ const appliedUpdate = gateway.applied.find((resource) => resource.kind === 'AgentMemoryUpdate');
399
+ assert.ok(appliedUpdate, 'AgentMemoryUpdate must be applied durably');
400
+ assert.equal(appliedUpdate.metadata.namespace, 'kradle-org-acme');
401
+ assert.equal(appliedUpdate.spec.organizationRef, 'acme');
402
+ assert.equal(result.update?.metadata?.name || result.metadata?.name, appliedUpdate.metadata.name);
403
+ });
404
+
405
+ test('api-controller dispatchAgent applies returned AgentMemorySnapshot in org namespace', async () => {
406
+ const gateway = createRecordingGateway({ snapshotResources: makeDispatchResources() });
407
+ const controller = createKradleApiController({ resourceGateway: gateway });
408
+
409
+ const result = await controller.dispatchAgent({
410
+ agentStack: 'mem-stack',
411
+ repository: 'repo',
412
+ ref: 'main',
413
+ taskKind: 'diagnostic',
414
+ actor: 'owner',
415
+ organizationRef: 'acme',
416
+ namespace: 'kradle-org-acme',
417
+ });
418
+
419
+ assert.equal(result.error, false);
420
+ const appliedSnapshot = gateway.applied.find((resource) => resource.kind === 'AgentMemorySnapshot');
421
+ assert.ok(appliedSnapshot, 'AgentMemorySnapshot returned by dispatch must be applied durably');
422
+ assert.equal(appliedSnapshot.metadata.namespace, 'kradle-org-acme');
423
+ assert.equal(appliedSnapshot.spec.organizationRef, 'acme');
424
+ assert.equal(result.memorySnapshot.metadata.name, appliedSnapshot.metadata.name);
425
+ });
426
+
427
+ test('processImport advances phases', () => {
428
+ const controller = createAgentMemoryController();
429
+
430
+ let importResource = controller.createImport({
431
+ organizationRef: 'acme',
432
+ memoryRepository: 'company-brain',
433
+ source: 'run-1',
434
+ include: { kinds: ['all'] },
435
+ namespace: 'kradle-org-default',
436
+ });
437
+
438
+ assert.equal(importResource.status.phase, 'Pending');
439
+
440
+ // Pending -> Collecting
441
+ importResource = controller.processImport({ importResource, content: 'some content' });
442
+ assert.equal(importResource.status.phase, 'Collecting');
443
+
444
+ // Collecting -> Redacting (triggers scanForRedaction)
445
+ importResource = controller.processImport({ importResource, content: 'API_KEY=secret123' });
446
+ assert.equal(importResource.status.phase, 'Redacting');
447
+ assert.ok(importResource.status.redactionScan, 'Should have redaction scan result');
448
+ assert.equal(importResource.status.redactionScan.clean, false, 'Should detect secret');
449
+
450
+ // Redacting -> Normalizing
451
+ importResource = controller.processImport({ importResource, content: 'normalized' });
452
+ assert.equal(importResource.status.phase, 'Normalizing');
453
+
454
+ // Normalizing -> Validating
455
+ importResource = controller.processImport({ importResource, content: 'validated' });
456
+ assert.equal(importResource.status.phase, 'Validating');
457
+
458
+ // Validating -> AwaitingReview
459
+ importResource = controller.processImport({ importResource, content: '' });
460
+ assert.equal(importResource.status.phase, 'AwaitingReview');
461
+ });