@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.
- package/Dockerfile +31 -0
- package/README.md +183 -0
- package/bin/krate-demo.mjs +23 -0
- package/bin/krate-server.mjs +14 -0
- package/dist/krate-controller-ui.json +3205 -0
- package/dist/krate-lifecycle.json +201 -0
- package/dist/krate-runtime-snapshot.json +3125 -0
- package/dist/krate-summary.json +724 -0
- package/docs/README.md +61 -0
- package/docs/agents/README.md +83 -0
- package/docs/agents/acceptance-test-matrix.md +193 -0
- package/docs/agents/agent-mux-adapter-contract.md +167 -0
- package/docs/agents/agent-mux-source-map.md +310 -0
- package/docs/agents/agent-run-memory-import-spec.md +256 -0
- package/docs/agents/agent-stack-management-spec.md +421 -0
- package/docs/agents/api-contract-spec.md +309 -0
- package/docs/agents/artifacts-writeback-spec.md +145 -0
- package/docs/agents/chart-packaging-spec.md +128 -0
- package/docs/agents/ci-orchestration-spec.md +140 -0
- package/docs/agents/context-assembly-spec.md +219 -0
- package/docs/agents/controller-reconciliation-spec.md +255 -0
- package/docs/agents/crd-schema-spec.md +315 -0
- package/docs/agents/decision-log-open-questions.md +169 -0
- package/docs/agents/developer-implementation-checklist.md +329 -0
- package/docs/agents/dispatching-design.md +262 -0
- package/docs/agents/gaps-agent-mux-to-krate-crds.md +298 -0
- package/docs/agents/glossary.md +66 -0
- package/docs/agents/implementation-blueprint.md +324 -0
- package/docs/agents/implementation-rollout-slices.md +251 -0
- package/docs/agents/memory-context-integration-spec.md +194 -0
- package/docs/agents/memory-ontology-schema-spec.md +253 -0
- package/docs/agents/memory-operations-runbook.md +121 -0
- package/docs/agents/mvp-vertical-slice-spec.md +146 -0
- package/docs/agents/observability-audit-spec.md +265 -0
- package/docs/agents/operator-runbook.md +174 -0
- package/docs/agents/org-memory-api-payload-examples.md +333 -0
- package/docs/agents/org-memory-controller-sequence-spec.md +181 -0
- package/docs/agents/org-memory-e2e-fixture-plan.md +161 -0
- package/docs/agents/org-memory-ui-implementation-map.md +114 -0
- package/docs/agents/org-memory-vertical-slice-spec.md +168 -0
- package/docs/agents/org-resource-model-delta-spec.md +111 -0
- package/docs/agents/org-route-resource-model-spec.md +183 -0
- package/docs/agents/org-scoping-namespace-spec.md +114 -0
- package/docs/agents/rbac-secrets-management-spec.md +406 -0
- package/docs/agents/repository-page-integration-spec.md +255 -0
- package/docs/agents/resource-contract-examples.md +808 -0
- package/docs/agents/resource-relationship-map.md +190 -0
- package/docs/agents/security-threat-model.md +188 -0
- package/docs/agents/shared-memory-company-brain-spec.md +358 -0
- package/docs/agents/storage-migration-spec.md +168 -0
- package/docs/agents/subagent-orchestration-spec.md +152 -0
- package/docs/agents/system-overview.md +88 -0
- package/docs/agents/tools-mcp-skills-spec.md +189 -0
- package/docs/agents/traceability-matrix.md +79 -0
- package/docs/agents/ui-flow-spec.md +211 -0
- package/docs/agents/ui-ux-system-spec.md +426 -0
- package/docs/agents/workspace-lifecycle-spec.md +166 -0
- package/docs/architecture-spec.md +78 -0
- package/docs/components/control-plane.md +78 -0
- package/docs/components/data-plane.md +69 -0
- package/docs/components/hooks-events.md +67 -0
- package/docs/components/identity-rbac-policy.md +73 -0
- package/docs/components/kubevela-oam.md +70 -0
- package/docs/components/operations-publishing.md +81 -0
- package/docs/components/runners-ci.md +66 -0
- package/docs/components/web-ui.md +94 -0
- package/docs/external/README.md +47 -0
- package/docs/external/bidirectional-sync-design.md +134 -0
- package/docs/external/cicd-interface.md +64 -0
- package/docs/external/external-backend-controllers.md +170 -0
- package/docs/external/external-backend-crds.md +234 -0
- package/docs/external/external-backend-ui-spec.md +151 -0
- package/docs/external/external-backend-ux-flows.md +115 -0
- package/docs/external/external-object-mapping.md +125 -0
- package/docs/external/git-forge-interface.md +68 -0
- package/docs/external/github-integration-design.md +151 -0
- package/docs/external/issue-tracking-interface.md +66 -0
- package/docs/external/provider-capability-manifests.md +204 -0
- package/docs/external/provider-catalog.md +139 -0
- package/docs/external/provider-rollout-testing.md +78 -0
- package/docs/external/research-results.md +48 -0
- package/docs/external/security-auth-permissions.md +81 -0
- package/docs/external/sync-state-machines.md +108 -0
- package/docs/external/unified-external-backend-model.md +107 -0
- package/docs/external/user-facing-changes.md +67 -0
- package/docs/gaps.md +161 -0
- package/docs/install.md +94 -0
- package/docs/krate-design.md +334 -0
- package/docs/local-minikube.md +55 -0
- package/docs/ontology/README.md +32 -0
- package/docs/ontology/bounded-contexts.md +29 -0
- package/docs/ontology/events-and-hooks.md +32 -0
- package/docs/ontology/oam-kubevela.md +32 -0
- package/docs/ontology/operations-and-release.md +25 -0
- package/docs/ontology/personas-and-actors.md +32 -0
- package/docs/ontology/policies-and-invariants.md +33 -0
- package/docs/ontology/problem-space.md +30 -0
- package/docs/ontology/resource-contracts.md +40 -0
- package/docs/ontology/resource-taxonomy.md +42 -0
- package/docs/ontology/runners-and-ci.md +29 -0
- package/docs/ontology/solution-space.md +24 -0
- package/docs/ontology/storage-and-data-boundaries.md +29 -0
- package/docs/ontology/validation-matrix.md +24 -0
- package/docs/ontology/web-ui-excellent-flows.md +32 -0
- package/docs/ontology/workflows.md +39 -0
- package/docs/ontology/world.md +35 -0
- package/docs/openapi.yaml +1275 -0
- package/docs/product-requirements.md +62 -0
- package/docs/roadmap-mvp.md +87 -0
- package/docs/system-requirements.md +90 -0
- package/docs/tests/README.md +53 -0
- package/docs/tests/agent-qa-plan.md +63 -0
- package/docs/tests/browser-ui-tests.md +62 -0
- package/docs/tests/ci-quality-gates.md +48 -0
- package/docs/tests/coverage-model.md +64 -0
- package/docs/tests/e2e-scenario-tests.md +53 -0
- package/docs/tests/fixtures-test-data.md +63 -0
- package/docs/tests/observability-reliability-tests.md +54 -0
- package/docs/tests/product-test-matrix.md +145 -0
- package/docs/tests/qa-adoption-roadmap.md +130 -0
- package/docs/tests/qa-automation-plan.md +101 -0
- package/docs/tests/security-compliance-tests.md +57 -0
- package/docs/tests/test-framework-tools.md +88 -0
- package/docs/tests/test-suite-layout.md +121 -0
- package/docs/tests/unit-integration-tests.md +48 -0
- package/docs/todo-kyverno +714 -0
- package/docs/todos.md +4 -0
- package/docs/user-stories.md +78 -0
- package/examples/minikube-demo.yaml +190 -0
- package/examples/oam-application.yaml +23 -0
- package/examples/policy-kyverno-pr-title.yaml +18 -0
- package/package.json +63 -0
- package/scripts/build.mjs +29 -0
- package/scripts/setup-minikube.mjs +65 -0
- package/scripts/smoke.mjs +37 -0
- package/scripts/validate-doc-coverage.mjs +152 -0
- package/scripts/validate-package.mjs +93 -0
- package/scripts/validate-ui.mjs +278 -0
- package/src/agent-adapter-controller.js +169 -0
- package/src/agent-approval-controller.js +170 -0
- package/src/agent-context-bundles.js +242 -0
- package/src/agent-dispatch-controller.js +209 -0
- package/src/agent-gateway-config-controller.js +147 -0
- package/src/agent-memory-controller.js +357 -0
- package/src/agent-memory-import.js +327 -0
- package/src/agent-memory-query.js +292 -0
- package/src/agent-memory-repository-source-controller.js +255 -0
- package/src/agent-mux-client.js +280 -0
- package/src/agent-permission-review.js +250 -0
- package/src/agent-project-controller.js +117 -0
- package/src/agent-provider-config-controller.js +150 -0
- package/src/agent-secret-config-grant-controller.js +282 -0
- package/src/agent-session-transcript-controller.js +189 -0
- package/src/agent-stack-controller.js +347 -0
- package/src/agent-subagent-controller.js +160 -0
- package/src/agent-transport-binding-controller.js +121 -0
- package/src/agent-trigger-controller.js +381 -0
- package/src/agent-workspace-controller.js +702 -0
- package/src/agent-writeback-controller.js +302 -0
- package/src/api-controller.js +541 -0
- package/src/argocd-gitops.js +43 -0
- package/src/async-controller.js +207 -0
- package/src/audit-controller.js +191 -0
- package/src/auth.js +307 -0
- package/src/component-catalog.js +41 -0
- package/src/control-plane.js +136 -0
- package/src/controller-client.js +72 -0
- package/src/controller-ui.js +617 -0
- package/src/data-plane.js +179 -0
- package/src/event-bus.js +61 -0
- package/src/external/conflict-controller.js +225 -0
- package/src/external/github/auth.js +96 -0
- package/src/external/github/cicd.js +180 -0
- package/src/external/github/git-forge.js +240 -0
- package/src/external/github/index.js +144 -0
- package/src/external/github/issue-tracking.js +163 -0
- package/src/external/provider-adapter.js +161 -0
- package/src/external/provider-resource-factory.js +161 -0
- package/src/external/sync-controller.js +235 -0
- package/src/external/webhook-controller.js +144 -0
- package/src/external/write-controller.js +283 -0
- package/src/gitea-backend.js +131 -0
- package/src/gitea-service.js +173 -0
- package/src/handoff.js +98 -0
- package/src/hooks-events.js +63 -0
- package/src/http-server.js +377 -0
- package/src/identity-policy.js +86 -0
- package/src/index.js +57 -0
- package/src/kubernetes-controller-async.js +511 -0
- package/src/kubernetes-controller.js +878 -0
- package/src/kubernetes-resource-gateway.js +48 -0
- package/src/notification-controller.js +178 -0
- package/src/operations.js +112 -0
- package/src/org-scoping.js +5 -0
- package/src/resource-model.js +221 -0
- package/src/runner-controller.js +272 -0
- package/src/runners-ci.js +48 -0
- package/src/runtime.js +196 -0
- package/src/snapshot-cache.js +157 -0
- package/src/web-ui.js +40 -0
- package/tests/agent-adapter-controller.test.js +361 -0
- package/tests/agent-approval-controller.test.js +173 -0
- package/tests/agent-context-bundles.test.js +278 -0
- package/tests/agent-dispatch-controller.test.js +315 -0
- package/tests/agent-gateway-config-controller.test.js +386 -0
- package/tests/agent-memory-controller.test.js +308 -0
- package/tests/agent-memory-import-snapshot.test.js +477 -0
- package/tests/agent-memory-query.test.js +404 -0
- package/tests/agent-memory-repository-source.test.js +514 -0
- package/tests/agent-mux-client.test.js +204 -0
- package/tests/agent-permission-review-v2.test.js +317 -0
- package/tests/agent-permission-review.test.js +209 -0
- package/tests/agent-project-controller.test.js +302 -0
- package/tests/agent-provider-config-controller.test.js +376 -0
- package/tests/agent-resources.test.js +228 -0
- package/tests/agent-secret-config-grant.test.js +231 -0
- package/tests/agent-session-transcript-controller.test.js +499 -0
- package/tests/agent-stack-controller.test.js +221 -0
- package/tests/agent-subagent-controller.test.js +201 -0
- package/tests/agent-transport-binding-controller.test.js +294 -0
- package/tests/agent-trigger-controller.test.js +211 -0
- package/tests/agent-trigger-routes.test.js +190 -0
- package/tests/agent-trigger-sources.test.js +245 -0
- package/tests/agent-workspace-controller.test.js +181 -0
- package/tests/agent-writeback.test.js +292 -0
- package/tests/approval-persistence.test.js +171 -0
- package/tests/async-controller.test.js +252 -0
- package/tests/audit-controller.test.js +227 -0
- package/tests/codespace-controller.test.js +318 -0
- package/tests/deployment.test.js +407 -0
- package/tests/e2e/lifecycle.test.js +117 -0
- package/tests/event-bus-integration.test.js +190 -0
- package/tests/external-github-forge.test.js +560 -0
- package/tests/external-github-issues-cicd.test.js +520 -0
- package/tests/external-integration.test.js +470 -0
- package/tests/external-persistence.test.js +340 -0
- package/tests/external-provider-adapter.test.js +365 -0
- package/tests/external-resource-model.test.js +215 -0
- package/tests/external-webhook-sync.test.js +287 -0
- package/tests/external-write-conflict.test.js +353 -0
- package/tests/gitea-service.test.js +253 -0
- package/tests/health-check-real.test.js +165 -0
- package/tests/integration/full-flow.test.js +266 -0
- package/tests/krate.test.js +756 -0
- package/tests/memory-search-wiring.test.js +270 -0
- package/tests/notification-controller.test.js +196 -0
- package/tests/notification-integration.test.js +179 -0
- package/tests/org-scoping.test.js +687 -0
- package/tests/runner-controller.test.js +327 -0
- package/tests/runner-integration.test.js +231 -0
- package/tests/session-cookie-hmac.test.js +151 -0
- package/tests/snapshot-performance.test.js +247 -0
- package/tests/sse-events.test.js +107 -0
- package/tests/webhook-trigger.test.js +198 -0
- package/tests/workspace-volumes.test.js +312 -0
- 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
|
+
});
|