@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,477 @@
1
+ /**
2
+ * Tests for agent-memory-import.js — Slice 2.3c
3
+ *
4
+ * Covers:
5
+ * - parseJournalForImport: key event extraction, summary shape, effect results, empty journal,
6
+ * summary-only mode (no raw task content)
7
+ * - validateMemoryImport: valid import, missing runId
8
+ * - createMemorySnapshot: basic snapshot with timestamp and record refs, query criteria filtering
9
+ * - validateMemorySnapshot: missing sessionRef
10
+ * - validateOntology (standalone): valid ontology, empty nodeKinds, duplicate nodeKind names
11
+ * - getOntologyNodeKinds: returns nodeKinds from spec
12
+ * - getOntologyEdgeKinds: returns edgeKinds from spec
13
+ */
14
+
15
+ import assert from 'node:assert/strict';
16
+ import test from 'node:test';
17
+ import {
18
+ parseJournalForImport,
19
+ validateMemoryImport,
20
+ createMemorySnapshot,
21
+ validateMemorySnapshot,
22
+ validateOntology,
23
+ getOntologyNodeKinds,
24
+ getOntologyEdgeKinds,
25
+ } from '../src/agent-memory-import.js';
26
+
27
+ // ---------------------------------------------------------------------------
28
+ // Fixtures
29
+ // ---------------------------------------------------------------------------
30
+
31
+ function makeJournal() {
32
+ return [
33
+ {
34
+ type: 'run_start',
35
+ runId: 'run-abc123',
36
+ processId: 'proc-deploy-v2',
37
+ timestamp: '2026-05-13T10:00:00Z',
38
+ },
39
+ {
40
+ type: 'task_completed',
41
+ taskId: 'task-1',
42
+ title: 'Build Docker image',
43
+ status: 'success',
44
+ timestamp: '2026-05-13T10:05:00Z',
45
+ effect: { kind: 'docker-build', result: 'success', image: 'myapp:v2' },
46
+ },
47
+ {
48
+ type: 'task_completed',
49
+ taskId: 'task-2',
50
+ title: 'Run tests',
51
+ status: 'success',
52
+ timestamp: '2026-05-13T10:08:00Z',
53
+ effect: { kind: 'test-run', result: 'success', passed: 42, failed: 0 },
54
+ },
55
+ {
56
+ type: 'task_completed',
57
+ taskId: 'task-3',
58
+ title: 'Deploy to staging',
59
+ status: 'failure',
60
+ timestamp: '2026-05-13T10:12:00Z',
61
+ effect: { kind: 'deployment', result: 'failure', reason: 'timeout' },
62
+ },
63
+ {
64
+ type: 'breakpoint',
65
+ reason: 'Awaiting approval for production deploy',
66
+ timestamp: '2026-05-13T10:13:00Z',
67
+ },
68
+ {
69
+ type: 'run_end',
70
+ status: 'partial',
71
+ timestamp: '2026-05-13T10:20:00Z',
72
+ },
73
+ ];
74
+ }
75
+
76
+ function makeOntologySpec() {
77
+ return {
78
+ name: 'acme-ontology',
79
+ organizationRef: 'acme',
80
+ nodeKinds: [
81
+ { name: 'Service', description: 'A microservice' },
82
+ { name: 'Team', description: 'An engineering team' },
83
+ { name: 'Runbook', description: 'A runbook document' },
84
+ ],
85
+ edgeKinds: [
86
+ { name: 'depends-on', description: 'Service dependency' },
87
+ { name: 'owned-by', description: 'Ownership relation' },
88
+ { name: 'references', description: 'Documentation reference' },
89
+ ],
90
+ };
91
+ }
92
+
93
+ // ---------------------------------------------------------------------------
94
+ // parseJournalForImport tests
95
+ // ---------------------------------------------------------------------------
96
+
97
+ test('parseJournalForImport extracts key events from a journal array', () => {
98
+ const journal = makeJournal();
99
+ const result = parseJournalForImport(journal);
100
+
101
+ assert.ok(result, 'result must be truthy');
102
+ assert.ok(Array.isArray(result.keyEvents), 'result must have keyEvents array');
103
+ // Should include run_start, task_completed events, breakpoints, run_end
104
+ const eventTypes = result.keyEvents.map(e => e.type);
105
+ assert.ok(eventTypes.includes('run_start'), 'must include run_start event');
106
+ assert.ok(eventTypes.includes('task_completed'), 'must include task_completed events');
107
+ assert.ok(eventTypes.includes('run_end'), 'must include run_end event');
108
+ });
109
+
110
+ test('parseJournalForImport returns summary with runId, processId, eventCount, duration', () => {
111
+ const journal = makeJournal();
112
+ const result = parseJournalForImport(journal);
113
+
114
+ assert.ok(result.summary, 'result must have summary object');
115
+ assert.equal(result.summary.runId, 'run-abc123', 'summary.runId must match journal run_start');
116
+ assert.equal(result.summary.processId, 'proc-deploy-v2', 'summary.processId must match journal run_start');
117
+ assert.ok(typeof result.summary.eventCount === 'number', 'summary.eventCount must be a number');
118
+ assert.ok(result.summary.eventCount > 0, 'summary.eventCount must be positive');
119
+ assert.ok(typeof result.summary.durationMs === 'number', 'summary.durationMs must be a number');
120
+ assert.ok(result.summary.durationMs > 0, 'summary.durationMs must be positive (start to end)');
121
+ });
122
+
123
+ test('parseJournalForImport extracts effect results (success/failure counts)', () => {
124
+ const journal = makeJournal();
125
+ const result = parseJournalForImport(journal);
126
+
127
+ assert.ok(result.effectSummary, 'result must have effectSummary');
128
+ assert.equal(typeof result.effectSummary.successCount, 'number', 'effectSummary.successCount must be a number');
129
+ assert.equal(typeof result.effectSummary.failureCount, 'number', 'effectSummary.failureCount must be a number');
130
+ // 2 successes (docker-build, test-run), 1 failure (deployment)
131
+ assert.equal(result.effectSummary.successCount, 2, 'should count 2 successful effects');
132
+ assert.equal(result.effectSummary.failureCount, 1, 'should count 1 failed effect');
133
+ });
134
+
135
+ test('parseJournalForImport handles empty journal', () => {
136
+ const result = parseJournalForImport([]);
137
+
138
+ assert.ok(result, 'result must be truthy for empty journal');
139
+ assert.ok(Array.isArray(result.keyEvents), 'keyEvents must be an array');
140
+ assert.equal(result.keyEvents.length, 0, 'keyEvents must be empty for empty journal');
141
+ assert.ok(result.summary, 'summary must be present');
142
+ assert.equal(result.summary.eventCount, 0, 'eventCount must be 0 for empty journal');
143
+ assert.equal(result.summary.runId, null, 'runId must be null for empty journal');
144
+ assert.equal(result.summary.processId, null, 'processId must be null for empty journal');
145
+ assert.equal(result.summary.durationMs, 0, 'durationMs must be 0 for empty journal');
146
+ assert.ok(result.effectSummary, 'effectSummary must be present');
147
+ assert.equal(result.effectSummary.successCount, 0, 'effectSummary.successCount must be 0 for empty journal');
148
+ assert.equal(result.effectSummary.failureCount, 0, 'effectSummary.failureCount must be 0 for empty journal');
149
+ });
150
+
151
+ test('parseJournalForImport filters to summary-only mode (no raw task content)', () => {
152
+ const journal = makeJournal();
153
+ const result = parseJournalForImport(journal);
154
+
155
+ // Summary-only mode: keyEvents must not include raw task content/body
156
+ // Each keyEvent should only contain structural fields (type, timestamp, taskId, status, effectKind)
157
+ // — NOT inline effect details or user-visible content strings from task execution
158
+ for (const event of result.keyEvents) {
159
+ if (event.type === 'task_completed') {
160
+ // Should have structured fields but NOT the raw effect object with arbitrary content
161
+ assert.ok(!('rawContent' in event), 'keyEvent must not include rawContent');
162
+ // effect data (if present) should be summarized, not the full effect object
163
+ if ('effect' in event) {
164
+ // effect should be stripped of arbitrary nested data — only kind and result allowed
165
+ const effectKeys = Object.keys(event.effect);
166
+ assert.ok(effectKeys.every(k => ['kind', 'result'].includes(k)),
167
+ 'effect in keyEvent must only contain kind and result (no arbitrary payload)');
168
+ }
169
+ }
170
+ }
171
+ });
172
+
173
+ // ---------------------------------------------------------------------------
174
+ // validateMemoryImport tests
175
+ // ---------------------------------------------------------------------------
176
+
177
+ test('validateMemoryImport accepts valid import (name, orgRef, runId, summary)', () => {
178
+ const importSpec = {
179
+ name: 'import-run-abc123',
180
+ organizationRef: 'acme',
181
+ runId: 'run-abc123',
182
+ summary: { eventCount: 6, successCount: 2, failureCount: 1 },
183
+ };
184
+
185
+ const result = validateMemoryImport(importSpec);
186
+
187
+ assert.ok(result, 'result must be truthy');
188
+ assert.equal(result.valid, true, 'valid import spec must pass validation');
189
+ assert.ok(Array.isArray(result.errors), 'result must have errors array');
190
+ assert.equal(result.errors.length, 0, 'errors must be empty for valid import');
191
+ });
192
+
193
+ test('validateMemoryImport rejects missing runId', () => {
194
+ const importSpec = {
195
+ name: 'import-no-run',
196
+ organizationRef: 'acme',
197
+ // runId is intentionally missing
198
+ summary: { eventCount: 3 },
199
+ };
200
+
201
+ const result = validateMemoryImport(importSpec);
202
+
203
+ assert.equal(result.valid, false, 'import without runId must fail validation');
204
+ assert.ok(result.errors.length > 0, 'errors must not be empty');
205
+ assert.ok(
206
+ result.errors.some(e => /runId/i.test(e)),
207
+ 'at least one error must mention "runId"'
208
+ );
209
+ });
210
+
211
+ test('validateMemoryImport rejects missing name', () => {
212
+ const importSpec = {
213
+ // name is intentionally missing
214
+ organizationRef: 'acme',
215
+ runId: 'run-abc123',
216
+ summary: { eventCount: 3 },
217
+ };
218
+
219
+ const result = validateMemoryImport(importSpec);
220
+
221
+ assert.equal(result.valid, false, 'import without name must fail validation');
222
+ assert.ok(result.errors.some(e => /name/i.test(e)), 'error must mention "name"');
223
+ });
224
+
225
+ test('validateMemoryImport rejects missing organizationRef', () => {
226
+ const importSpec = {
227
+ name: 'import-no-org',
228
+ // organizationRef missing
229
+ runId: 'run-abc123',
230
+ summary: { eventCount: 3 },
231
+ };
232
+
233
+ const result = validateMemoryImport(importSpec);
234
+
235
+ assert.equal(result.valid, false, 'import without organizationRef must fail');
236
+ assert.ok(result.errors.some(e => /organizationRef/i.test(e)), 'error must mention "organizationRef"');
237
+ });
238
+
239
+ // ---------------------------------------------------------------------------
240
+ // createMemorySnapshot tests
241
+ // ---------------------------------------------------------------------------
242
+
243
+ test('createMemorySnapshot creates a snapshot with timestamp and record refs', () => {
244
+ const snapshot = createMemorySnapshot({
245
+ sessionRef: 'session-xyz',
246
+ organizationRef: 'acme',
247
+ recordRefs: ['record/auth-api', 'record/user-db'],
248
+ });
249
+
250
+ assert.ok(snapshot, 'snapshot must be truthy');
251
+ assert.ok(snapshot.snapshotId, 'snapshot must have a snapshotId');
252
+ assert.ok(snapshot.createdAt, 'snapshot must have a createdAt timestamp');
253
+ assert.ok(typeof snapshot.createdAt === 'string', 'createdAt must be a string');
254
+ // Should be a valid ISO date
255
+ assert.ok(!isNaN(Date.parse(snapshot.createdAt)), 'createdAt must be a valid ISO date string');
256
+ assert.equal(snapshot.sessionRef, 'session-xyz', 'snapshot must include sessionRef');
257
+ assert.equal(snapshot.organizationRef, 'acme', 'snapshot must include organizationRef');
258
+ assert.deepEqual(snapshot.recordRefs, ['record/auth-api', 'record/user-db'],
259
+ 'snapshot must include recordRefs');
260
+ });
261
+
262
+ test('createMemorySnapshot accepts query criteria for filtering records', () => {
263
+ const snapshot = createMemorySnapshot({
264
+ sessionRef: 'session-xyz',
265
+ organizationRef: 'acme',
266
+ recordRefs: ['record/auth-api'],
267
+ queryCriteria: {
268
+ kinds: ['Service'],
269
+ textQuery: 'auth',
270
+ maxRecords: 50,
271
+ },
272
+ });
273
+
274
+ assert.ok(snapshot.queryCriteria, 'snapshot must include queryCriteria when provided');
275
+ assert.deepEqual(snapshot.queryCriteria.kinds, ['Service'], 'queryCriteria.kinds must be preserved');
276
+ assert.equal(snapshot.queryCriteria.textQuery, 'auth', 'queryCriteria.textQuery must be preserved');
277
+ assert.equal(snapshot.queryCriteria.maxRecords, 50, 'queryCriteria.maxRecords must be preserved');
278
+ });
279
+
280
+ test('createMemorySnapshot generates unique snapshotIds for concurrent calls', () => {
281
+ const s1 = createMemorySnapshot({ sessionRef: 'session-1', organizationRef: 'acme', recordRefs: [] });
282
+ const s2 = createMemorySnapshot({ sessionRef: 'session-2', organizationRef: 'acme', recordRefs: [] });
283
+
284
+ assert.notEqual(s1.snapshotId, s2.snapshotId, 'concurrent snapshots must have unique IDs');
285
+ });
286
+
287
+ // ---------------------------------------------------------------------------
288
+ // validateMemorySnapshot tests
289
+ // ---------------------------------------------------------------------------
290
+
291
+ test('validateMemorySnapshot rejects missing sessionRef', () => {
292
+ const snapshot = {
293
+ snapshotId: 'snap-001',
294
+ // sessionRef is missing
295
+ organizationRef: 'acme',
296
+ recordRefs: [],
297
+ createdAt: new Date().toISOString(),
298
+ };
299
+
300
+ const result = validateMemorySnapshot(snapshot);
301
+
302
+ assert.equal(result.valid, false, 'snapshot without sessionRef must fail validation');
303
+ assert.ok(result.errors.length > 0, 'errors must not be empty');
304
+ assert.ok(
305
+ result.errors.some(e => /sessionRef/i.test(e)),
306
+ 'at least one error must mention "sessionRef"'
307
+ );
308
+ });
309
+
310
+ test('validateMemorySnapshot accepts valid snapshot', () => {
311
+ const snapshot = {
312
+ snapshotId: 'snap-001',
313
+ sessionRef: 'session-xyz',
314
+ organizationRef: 'acme',
315
+ recordRefs: ['record/auth-api'],
316
+ createdAt: new Date().toISOString(),
317
+ };
318
+
319
+ const result = validateMemorySnapshot(snapshot);
320
+
321
+ assert.equal(result.valid, true, 'valid snapshot must pass validation');
322
+ assert.equal(result.errors.length, 0, 'errors must be empty for valid snapshot');
323
+ });
324
+
325
+ test('validateMemorySnapshot rejects missing snapshotId', () => {
326
+ const snapshot = {
327
+ // snapshotId missing
328
+ sessionRef: 'session-xyz',
329
+ organizationRef: 'acme',
330
+ recordRefs: [],
331
+ createdAt: new Date().toISOString(),
332
+ };
333
+
334
+ const result = validateMemorySnapshot(snapshot);
335
+
336
+ assert.equal(result.valid, false, 'snapshot without snapshotId must fail validation');
337
+ assert.ok(result.errors.some(e => /snapshotId/i.test(e)), 'error must mention "snapshotId"');
338
+ });
339
+
340
+ // ---------------------------------------------------------------------------
341
+ // validateOntology (standalone) tests
342
+ // ---------------------------------------------------------------------------
343
+
344
+ test('validateOntology accepts valid ontology (name, orgRef, nodeKinds, edgeKinds)', () => {
345
+ const ontologySpec = makeOntologySpec();
346
+
347
+ const result = validateOntology(ontologySpec);
348
+
349
+ assert.ok(result, 'result must be truthy');
350
+ assert.equal(result.valid, true, 'valid ontology spec must pass validation');
351
+ assert.ok(Array.isArray(result.errors), 'result must have errors array');
352
+ assert.equal(result.errors.length, 0, 'errors must be empty for valid ontology');
353
+ });
354
+
355
+ test('validateOntology rejects empty nodeKinds array', () => {
356
+ const ontologySpec = {
357
+ ...makeOntologySpec(),
358
+ nodeKinds: [],
359
+ };
360
+
361
+ const result = validateOntology(ontologySpec);
362
+
363
+ assert.equal(result.valid, false, 'ontology with empty nodeKinds must fail validation');
364
+ assert.ok(result.errors.length > 0, 'errors must not be empty');
365
+ assert.ok(
366
+ result.errors.some(e => /nodeKinds/i.test(e)),
367
+ 'at least one error must mention "nodeKinds"'
368
+ );
369
+ });
370
+
371
+ test('validateOntology rejects duplicate nodeKind names', () => {
372
+ const ontologySpec = {
373
+ ...makeOntologySpec(),
374
+ nodeKinds: [
375
+ { name: 'Service', description: 'A service' },
376
+ { name: 'Team', description: 'A team' },
377
+ { name: 'Service', description: 'Duplicate service' }, // duplicate
378
+ ],
379
+ };
380
+
381
+ const result = validateOntology(ontologySpec);
382
+
383
+ assert.equal(result.valid, false, 'ontology with duplicate nodeKind names must fail validation');
384
+ assert.ok(result.errors.length > 0, 'errors must not be empty');
385
+ assert.ok(
386
+ result.errors.some(e => /duplicate/i.test(e) || /nodeKind/i.test(e)),
387
+ 'error must mention duplicate or nodeKind'
388
+ );
389
+ });
390
+
391
+ test('validateOntology rejects missing name', () => {
392
+ const ontologySpec = {
393
+ // name missing
394
+ organizationRef: 'acme',
395
+ nodeKinds: [{ name: 'Service', description: 'A service' }],
396
+ edgeKinds: [],
397
+ };
398
+
399
+ const result = validateOntology(ontologySpec);
400
+
401
+ assert.equal(result.valid, false, 'ontology without name must fail validation');
402
+ assert.ok(result.errors.some(e => /name/i.test(e)), 'error must mention "name"');
403
+ });
404
+
405
+ test('validateOntology rejects missing organizationRef', () => {
406
+ const ontologySpec = {
407
+ name: 'my-ontology',
408
+ // organizationRef missing
409
+ nodeKinds: [{ name: 'Service', description: 'A service' }],
410
+ edgeKinds: [],
411
+ };
412
+
413
+ const result = validateOntology(ontologySpec);
414
+
415
+ assert.equal(result.valid, false, 'ontology without organizationRef must fail validation');
416
+ assert.ok(result.errors.some(e => /organizationRef/i.test(e)), 'error must mention "organizationRef"');
417
+ });
418
+
419
+ // ---------------------------------------------------------------------------
420
+ // getOntologyNodeKinds tests
421
+ // ---------------------------------------------------------------------------
422
+
423
+ test('getOntologyNodeKinds returns node kinds from spec', () => {
424
+ const ontologySpec = makeOntologySpec();
425
+
426
+ const nodeKinds = getOntologyNodeKinds(ontologySpec);
427
+
428
+ assert.ok(Array.isArray(nodeKinds), 'getOntologyNodeKinds must return an array');
429
+ assert.equal(nodeKinds.length, 3, 'must return all 3 nodeKinds');
430
+ const names = nodeKinds.map(k => k.name);
431
+ assert.ok(names.includes('Service'), 'must include Service');
432
+ assert.ok(names.includes('Team'), 'must include Team');
433
+ assert.ok(names.includes('Runbook'), 'must include Runbook');
434
+ });
435
+
436
+ test('getOntologyNodeKinds returns empty array when nodeKinds is absent', () => {
437
+ const ontologySpec = {
438
+ name: 'empty-ontology',
439
+ organizationRef: 'acme',
440
+ edgeKinds: [],
441
+ };
442
+
443
+ const nodeKinds = getOntologyNodeKinds(ontologySpec);
444
+
445
+ assert.ok(Array.isArray(nodeKinds), 'must return an array');
446
+ assert.equal(nodeKinds.length, 0, 'must return empty array when no nodeKinds');
447
+ });
448
+
449
+ // ---------------------------------------------------------------------------
450
+ // getOntologyEdgeKinds tests
451
+ // ---------------------------------------------------------------------------
452
+
453
+ test('getOntologyEdgeKinds returns edge kinds from spec', () => {
454
+ const ontologySpec = makeOntologySpec();
455
+
456
+ const edgeKinds = getOntologyEdgeKinds(ontologySpec);
457
+
458
+ assert.ok(Array.isArray(edgeKinds), 'getOntologyEdgeKinds must return an array');
459
+ assert.equal(edgeKinds.length, 3, 'must return all 3 edgeKinds');
460
+ const names = edgeKinds.map(k => k.name);
461
+ assert.ok(names.includes('depends-on'), 'must include depends-on');
462
+ assert.ok(names.includes('owned-by'), 'must include owned-by');
463
+ assert.ok(names.includes('references'), 'must include references');
464
+ });
465
+
466
+ test('getOntologyEdgeKinds returns empty array when edgeKinds is absent', () => {
467
+ const ontologySpec = {
468
+ name: 'edge-less-ontology',
469
+ organizationRef: 'acme',
470
+ nodeKinds: [{ name: 'Service', description: 'A service' }],
471
+ };
472
+
473
+ const edgeKinds = getOntologyEdgeKinds(ontologySpec);
474
+
475
+ assert.ok(Array.isArray(edgeKinds), 'must return an array');
476
+ assert.equal(edgeKinds.length, 0, 'must return empty array when no edgeKinds');
477
+ });