@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,198 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import { createAgentTriggerController, createResource } from '../src/index.js';
|
|
4
|
+
|
|
5
|
+
// ─── helpers ─────────────────────────────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
function makeWebhookRule(name, webhookTrigger = {}, extra = {}) {
|
|
8
|
+
return createResource('AgentTriggerRule', { name, namespace: 'krate-org-default' }, {
|
|
9
|
+
organizationRef: 'default',
|
|
10
|
+
agentStack: 'ci-stack',
|
|
11
|
+
taskKind: 'diagnostic',
|
|
12
|
+
webhookTrigger,
|
|
13
|
+
...extra,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function makeWebhookEvent(overrides = {}) {
|
|
18
|
+
return {
|
|
19
|
+
eventType: 'push',
|
|
20
|
+
repository: 'owner/repo',
|
|
21
|
+
ref: 'refs/heads/main',
|
|
22
|
+
action: null,
|
|
23
|
+
provider: 'github',
|
|
24
|
+
...overrides,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ─── tests ───────────────────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
// 1. evaluateWebhookEvent matches push event to a push trigger rule
|
|
31
|
+
test('evaluateWebhookEvent: matches push event to push trigger rule', () => {
|
|
32
|
+
const ctrl = createAgentTriggerController();
|
|
33
|
+
const rule = makeWebhookRule('push-rule', { events: ['push'] });
|
|
34
|
+
const event = makeWebhookEvent({ eventType: 'push' });
|
|
35
|
+
|
|
36
|
+
const { matchingRules, dispatchIntents } = ctrl.evaluateWebhookEvent(event, [rule]);
|
|
37
|
+
|
|
38
|
+
assert.equal(matchingRules.length, 1);
|
|
39
|
+
assert.equal(matchingRules[0].metadata.name, 'push-rule');
|
|
40
|
+
assert.equal(dispatchIntents.length, 1);
|
|
41
|
+
assert.equal(dispatchIntents[0].agentStack, 'ci-stack');
|
|
42
|
+
assert.equal(dispatchIntents[0].taskKind, 'diagnostic');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// 2. evaluateWebhookEvent matches pull_request event to pull_request trigger rule
|
|
46
|
+
test('evaluateWebhookEvent: matches pull_request event to pull_request trigger rule', () => {
|
|
47
|
+
const ctrl = createAgentTriggerController();
|
|
48
|
+
const rule = makeWebhookRule('pr-rule', { events: ['pull_request'] });
|
|
49
|
+
const event = makeWebhookEvent({ eventType: 'pull_request', action: 'opened' });
|
|
50
|
+
|
|
51
|
+
const { matchingRules, dispatchIntents } = ctrl.evaluateWebhookEvent(event, [rule]);
|
|
52
|
+
|
|
53
|
+
assert.equal(matchingRules.length, 1);
|
|
54
|
+
assert.equal(matchingRules[0].metadata.name, 'pr-rule');
|
|
55
|
+
assert.equal(dispatchIntents[0].taskKind, 'diagnostic');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// 3. evaluateWebhookEvent returns empty when no rules match event type
|
|
59
|
+
test('evaluateWebhookEvent: returns empty when no rules match event type', () => {
|
|
60
|
+
const ctrl = createAgentTriggerController();
|
|
61
|
+
const rule = makeWebhookRule('push-only', { events: ['push'] });
|
|
62
|
+
const event = makeWebhookEvent({ eventType: 'issues' });
|
|
63
|
+
|
|
64
|
+
const { matchingRules, dispatchIntents } = ctrl.evaluateWebhookEvent(event, [rule]);
|
|
65
|
+
|
|
66
|
+
assert.equal(matchingRules.length, 0);
|
|
67
|
+
assert.equal(dispatchIntents.length, 0);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// 4. evaluateWebhookEvent filters by repository — matches when equal
|
|
71
|
+
test('evaluateWebhookEvent: filters by repository — matches when equal', () => {
|
|
72
|
+
const ctrl = createAgentTriggerController();
|
|
73
|
+
const rule = makeWebhookRule('repo-rule', { events: ['push'], repository: 'owner/repo' });
|
|
74
|
+
const event = makeWebhookEvent({ eventType: 'push', repository: 'owner/repo' });
|
|
75
|
+
|
|
76
|
+
const { matchingRules } = ctrl.evaluateWebhookEvent(event, [rule]);
|
|
77
|
+
|
|
78
|
+
assert.equal(matchingRules.length, 1);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// 5. evaluateWebhookEvent filters by repository — no match when different
|
|
82
|
+
test('evaluateWebhookEvent: filters by repository — no match when different', () => {
|
|
83
|
+
const ctrl = createAgentTriggerController();
|
|
84
|
+
const rule = makeWebhookRule('repo-rule', { events: ['push'], repository: 'owner/repo' });
|
|
85
|
+
const event = makeWebhookEvent({ eventType: 'push', repository: 'other/repo' });
|
|
86
|
+
|
|
87
|
+
const { matchingRules } = ctrl.evaluateWebhookEvent(event, [rule]);
|
|
88
|
+
|
|
89
|
+
assert.equal(matchingRules.length, 0);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// 6. evaluateWebhookEvent respects enabled: false — disabled rules are excluded
|
|
93
|
+
test('evaluateWebhookEvent: respects enabled: false — disabled rules are excluded', () => {
|
|
94
|
+
const ctrl = createAgentTriggerController();
|
|
95
|
+
const rule = makeWebhookRule('disabled-rule', { events: ['push'] }, { enabled: false });
|
|
96
|
+
const event = makeWebhookEvent({ eventType: 'push' });
|
|
97
|
+
|
|
98
|
+
const { matchingRules } = ctrl.evaluateWebhookEvent(event, [rule]);
|
|
99
|
+
|
|
100
|
+
assert.equal(matchingRules.length, 0);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// 7. evaluateWebhookEvent deduplicates rules with the same name
|
|
104
|
+
test('evaluateWebhookEvent: deduplicates rules with the same name', () => {
|
|
105
|
+
const ctrl = createAgentTriggerController();
|
|
106
|
+
const rule1 = makeWebhookRule('dup-rule', { events: ['push'] });
|
|
107
|
+
const rule2 = makeWebhookRule('dup-rule', { events: ['push'] }); // same name
|
|
108
|
+
const event = makeWebhookEvent({ eventType: 'push' });
|
|
109
|
+
|
|
110
|
+
const { matchingRules } = ctrl.evaluateWebhookEvent(event, [rule1, rule2]);
|
|
111
|
+
|
|
112
|
+
assert.equal(matchingRules.length, 1);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// 8. evaluateWebhookEvent handles empty rules array
|
|
116
|
+
test('evaluateWebhookEvent: handles empty rules array', () => {
|
|
117
|
+
const ctrl = createAgentTriggerController();
|
|
118
|
+
const event = makeWebhookEvent();
|
|
119
|
+
|
|
120
|
+
const { matchingRules, dispatchIntents } = ctrl.evaluateWebhookEvent(event, []);
|
|
121
|
+
|
|
122
|
+
assert.equal(matchingRules.length, 0);
|
|
123
|
+
assert.equal(dispatchIntents.length, 0);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// 9. evaluateWebhookEvent handles undefined/null rules gracefully
|
|
127
|
+
test('evaluateWebhookEvent: handles undefined rules gracefully', () => {
|
|
128
|
+
const ctrl = createAgentTriggerController();
|
|
129
|
+
const event = makeWebhookEvent();
|
|
130
|
+
|
|
131
|
+
const resultUndefined = ctrl.evaluateWebhookEvent(event, undefined);
|
|
132
|
+
assert.equal(resultUndefined.matchingRules.length, 0);
|
|
133
|
+
assert.equal(resultUndefined.dispatchIntents.length, 0);
|
|
134
|
+
|
|
135
|
+
const resultNull = ctrl.evaluateWebhookEvent(event, null);
|
|
136
|
+
assert.equal(resultNull.matchingRules.length, 0);
|
|
137
|
+
assert.equal(resultNull.dispatchIntents.length, 0);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// 10. evaluateWebhookEvent matches wildcard events array ['*']
|
|
141
|
+
test('evaluateWebhookEvent: matches wildcard events array [\'*\']', () => {
|
|
142
|
+
const ctrl = createAgentTriggerController();
|
|
143
|
+
const rule = makeWebhookRule('wildcard-rule', { events: ['*'] });
|
|
144
|
+
const events = ['push', 'pull_request', 'issues', 'workflow_run', 'ping'];
|
|
145
|
+
|
|
146
|
+
for (const eventType of events) {
|
|
147
|
+
const { matchingRules } = ctrl.evaluateWebhookEvent(makeWebhookEvent({ eventType }), [rule]);
|
|
148
|
+
assert.equal(matchingRules.length, 1, `Should match event type '${eventType}' with wildcard`);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// 11. evaluateWebhookEvent filters by action — matches when equal
|
|
153
|
+
test('evaluateWebhookEvent: filters by action — matches when equal', () => {
|
|
154
|
+
const ctrl = createAgentTriggerController();
|
|
155
|
+
const rule = makeWebhookRule('pr-opened-rule', { events: ['pull_request'], action: 'opened' });
|
|
156
|
+
const event = makeWebhookEvent({ eventType: 'pull_request', action: 'opened' });
|
|
157
|
+
|
|
158
|
+
const { matchingRules } = ctrl.evaluateWebhookEvent(event, [rule]);
|
|
159
|
+
|
|
160
|
+
assert.equal(matchingRules.length, 1);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// 12. evaluateWebhookEvent filters by action — no match when action differs
|
|
164
|
+
test('evaluateWebhookEvent: filters by action — no match when action differs', () => {
|
|
165
|
+
const ctrl = createAgentTriggerController();
|
|
166
|
+
const rule = makeWebhookRule('pr-opened-rule', { events: ['pull_request'], action: 'opened' });
|
|
167
|
+
const event = makeWebhookEvent({ eventType: 'pull_request', action: 'closed' });
|
|
168
|
+
|
|
169
|
+
const { matchingRules } = ctrl.evaluateWebhookEvent(event, [rule]);
|
|
170
|
+
|
|
171
|
+
assert.equal(matchingRules.length, 0);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// 13. evaluateWebhookEvent matches when events array is absent (match all)
|
|
175
|
+
test('evaluateWebhookEvent: matches when events array is absent (match all)', () => {
|
|
176
|
+
const ctrl = createAgentTriggerController();
|
|
177
|
+
// webhookTrigger present but no events filter
|
|
178
|
+
const rule = makeWebhookRule('no-events-filter', {});
|
|
179
|
+
const event = makeWebhookEvent({ eventType: 'workflow_run' });
|
|
180
|
+
|
|
181
|
+
const { matchingRules } = ctrl.evaluateWebhookEvent(event, [rule]);
|
|
182
|
+
|
|
183
|
+
assert.equal(matchingRules.length, 1);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// 14. dispatchIntent includes correct agentStack and uses custom taskKind
|
|
187
|
+
test('evaluateWebhookEvent: dispatchIntent includes agentStack and custom taskKind', () => {
|
|
188
|
+
const ctrl = createAgentTriggerController();
|
|
189
|
+
const rule = makeWebhookRule('custom-kind-rule', { events: ['push'] }, { taskKind: 'repair', agentStack: 'my-stack' });
|
|
190
|
+
const event = makeWebhookEvent({ eventType: 'push' });
|
|
191
|
+
|
|
192
|
+
const { dispatchIntents } = ctrl.evaluateWebhookEvent(event, [rule]);
|
|
193
|
+
|
|
194
|
+
assert.equal(dispatchIntents.length, 1);
|
|
195
|
+
assert.equal(dispatchIntents[0].agentStack, 'my-stack');
|
|
196
|
+
assert.equal(dispatchIntents[0].taskKind, 'repair');
|
|
197
|
+
assert.deepEqual(dispatchIntents[0].event, event);
|
|
198
|
+
});
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import { createAgentWorkspaceController, AGENT_WORKSPACE_CONTROLLER_BOUNDARY, createResource } from '../src/index.js';
|
|
4
|
+
|
|
5
|
+
function makeWorkspace(name, opts = {}) {
|
|
6
|
+
const ws = createResource('KrateWorkspace', { name, namespace: opts.namespace || 'default' }, {
|
|
7
|
+
organizationRef: opts.organizationRef || 'default',
|
|
8
|
+
repository: opts.repository || 'https://github.com/acme/app.git',
|
|
9
|
+
volumeSpec: opts.volumeSpec || { storageClassName: 'standard', capacity: '10Gi', accessModes: ['ReadWriteOnce'] },
|
|
10
|
+
branch: opts.branch || 'main',
|
|
11
|
+
pvcName: opts.pvcName || `krate-ws-${name}`,
|
|
12
|
+
});
|
|
13
|
+
ws.status = {
|
|
14
|
+
phase: opts.phase || 'Ready',
|
|
15
|
+
volumeStatus: opts.volumeStatus || 'Bound',
|
|
16
|
+
createdAt: new Date().toISOString(),
|
|
17
|
+
...(opts.status || {}),
|
|
18
|
+
};
|
|
19
|
+
return ws;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// --- createWorkspace ---
|
|
23
|
+
|
|
24
|
+
test('createWorkspace generates valid PVC manifest with correct labels and capacity', () => {
|
|
25
|
+
const ctrl = createAgentWorkspaceController();
|
|
26
|
+
const result = ctrl.createWorkspace({
|
|
27
|
+
name: 'my-ws',
|
|
28
|
+
organizationRef: 'acme',
|
|
29
|
+
repository: 'https://github.com/acme/app.git',
|
|
30
|
+
volumeSpec: { capacity: '20Gi' },
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
assert.equal(result.error, false);
|
|
34
|
+
assert.ok(result.workspace);
|
|
35
|
+
assert.ok(result.pvcManifest);
|
|
36
|
+
assert.equal(result.pvcManifest.kind, 'PersistentVolumeClaim');
|
|
37
|
+
assert.equal(result.pvcManifest.metadata.name, 'krate-ws-my-ws');
|
|
38
|
+
assert.equal(result.pvcManifest.metadata.labels['krate.a5c.ai/workspace'], 'my-ws');
|
|
39
|
+
assert.equal(result.pvcManifest.metadata.labels['krate.a5c.ai/org'], 'acme');
|
|
40
|
+
assert.equal(result.pvcManifest.spec.resources.requests.storage, '20Gi');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('createWorkspace uses custom storageClassName when provided', () => {
|
|
44
|
+
const ctrl = createAgentWorkspaceController();
|
|
45
|
+
const result = ctrl.createWorkspace({
|
|
46
|
+
name: 'custom-sc',
|
|
47
|
+
organizationRef: 'acme',
|
|
48
|
+
repository: 'https://github.com/acme/app.git',
|
|
49
|
+
volumeSpec: { storageClassName: 'fast-ssd' },
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
assert.equal(result.error, false);
|
|
53
|
+
assert.equal(result.pvcManifest.spec.storageClassName, 'fast-ssd');
|
|
54
|
+
assert.equal(result.workspace.spec.volumeSpec.storageClassName, 'fast-ssd');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('createWorkspace defaults to 10Gi capacity', () => {
|
|
58
|
+
const ctrl = createAgentWorkspaceController();
|
|
59
|
+
const result = ctrl.createWorkspace({
|
|
60
|
+
name: 'default-cap',
|
|
61
|
+
organizationRef: 'acme',
|
|
62
|
+
repository: 'https://github.com/acme/app.git',
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
assert.equal(result.error, false);
|
|
66
|
+
assert.equal(result.pvcManifest.spec.resources.requests.storage, '10Gi');
|
|
67
|
+
assert.equal(result.workspace.spec.volumeSpec.capacity, '10Gi');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('createWorkspace validates required fields (org, repository)', () => {
|
|
71
|
+
const ctrl = createAgentWorkspaceController();
|
|
72
|
+
|
|
73
|
+
const noOrg = ctrl.createWorkspace({ name: 'x', repository: 'repo' });
|
|
74
|
+
assert.equal(noOrg.error, true);
|
|
75
|
+
assert.equal(noOrg.reason, 'missing-org');
|
|
76
|
+
|
|
77
|
+
const noRepo = ctrl.createWorkspace({ name: 'x', organizationRef: 'acme' });
|
|
78
|
+
assert.equal(noRepo.error, true);
|
|
79
|
+
assert.equal(noRepo.reason, 'missing-repository');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('createWorkspace rejects null resource (missing both org and repo)', () => {
|
|
83
|
+
const ctrl = createAgentWorkspaceController();
|
|
84
|
+
const result = ctrl.createWorkspace({});
|
|
85
|
+
assert.equal(result.error, true);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// --- deleteWorkspace ---
|
|
89
|
+
|
|
90
|
+
test('deleteWorkspace generates PVC delete manifest', () => {
|
|
91
|
+
const ctrl = createAgentWorkspaceController();
|
|
92
|
+
const ws = makeWorkspace('del-ws', { phase: 'Ready' });
|
|
93
|
+
|
|
94
|
+
const result = ctrl.deleteWorkspace({ name: 'del-ws', resources: { KrateWorkspace: [ws] } });
|
|
95
|
+
|
|
96
|
+
assert.equal(result.error, false);
|
|
97
|
+
assert.equal(result.workspace.status.phase, 'Terminating');
|
|
98
|
+
assert.ok(result.pvcDeleteManifest);
|
|
99
|
+
assert.equal(result.pvcDeleteManifest.metadata.name, 'krate-ws-del-ws');
|
|
100
|
+
assert.equal(result.pvcDeleteManifest.action, 'delete');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// --- getWorkspaceStatus ---
|
|
104
|
+
|
|
105
|
+
test('getWorkspaceStatus returns Pending for new workspace', () => {
|
|
106
|
+
const ctrl = createAgentWorkspaceController();
|
|
107
|
+
const ws = makeWorkspace('new-ws', { phase: 'Pending', volumeStatus: 'Pending' });
|
|
108
|
+
|
|
109
|
+
const result = ctrl.getWorkspaceStatus({ name: 'new-ws', resources: { KrateWorkspace: [ws] } });
|
|
110
|
+
|
|
111
|
+
assert.equal(result.error, false);
|
|
112
|
+
assert.equal(result.volumeStatus, 'Pending');
|
|
113
|
+
assert.equal(result.phase, 'Pending');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// --- initializeWorkspace ---
|
|
117
|
+
|
|
118
|
+
test('initializeWorkspace returns git clone command for https repo', () => {
|
|
119
|
+
const ctrl = createAgentWorkspaceController();
|
|
120
|
+
const ws = makeWorkspace('https-ws', { repository: 'https://github.com/acme/app.git' });
|
|
121
|
+
|
|
122
|
+
const result = ctrl.initializeWorkspace({ workspace: ws });
|
|
123
|
+
|
|
124
|
+
assert.equal(result.error, false);
|
|
125
|
+
assert.equal(result.commandSpec.command, 'git');
|
|
126
|
+
assert.deepEqual(result.commandSpec.args, ['clone', 'https://github.com/acme/app.git', '/workspace']);
|
|
127
|
+
assert.equal(Object.keys(result.commandSpec.env).length, 0, 'No special env for https');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('initializeWorkspace returns git clone command for ssh repo', () => {
|
|
131
|
+
const ctrl = createAgentWorkspaceController();
|
|
132
|
+
const ws = makeWorkspace('ssh-ws', { repository: 'git@github.com:acme/app.git' });
|
|
133
|
+
|
|
134
|
+
const result = ctrl.initializeWorkspace({ workspace: ws });
|
|
135
|
+
|
|
136
|
+
assert.equal(result.error, false);
|
|
137
|
+
assert.equal(result.commandSpec.command, 'git');
|
|
138
|
+
assert.deepEqual(result.commandSpec.args, ['clone', 'git@github.com:acme/app.git', '/workspace']);
|
|
139
|
+
assert.ok(result.commandSpec.env.GIT_SSH_COMMAND, 'Should set GIT_SSH_COMMAND for SSH repos');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// --- checkoutBranch ---
|
|
143
|
+
|
|
144
|
+
test('checkoutBranch returns git checkout command', () => {
|
|
145
|
+
const ctrl = createAgentWorkspaceController();
|
|
146
|
+
const ws = makeWorkspace('co-ws');
|
|
147
|
+
|
|
148
|
+
const result = ctrl.checkoutBranch({ workspace: ws, branch: 'feature-42' });
|
|
149
|
+
|
|
150
|
+
assert.equal(result.error, false);
|
|
151
|
+
assert.equal(result.commandSpec.command, 'git');
|
|
152
|
+
assert.deepEqual(result.commandSpec.args, ['checkout', 'feature-42']);
|
|
153
|
+
assert.equal(result.commandSpec.cwd, '/workspace');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// --- syncWorkspace ---
|
|
157
|
+
|
|
158
|
+
test('syncWorkspace returns git fetch + reset commands', () => {
|
|
159
|
+
const ctrl = createAgentWorkspaceController();
|
|
160
|
+
const ws = makeWorkspace('sync-ws', { branch: 'develop' });
|
|
161
|
+
|
|
162
|
+
const result = ctrl.syncWorkspace({ workspace: ws });
|
|
163
|
+
|
|
164
|
+
assert.equal(result.error, false);
|
|
165
|
+
assert.equal(result.commandSpecs.length, 2);
|
|
166
|
+
assert.deepEqual(result.commandSpecs[0].args, ['fetch', 'origin']);
|
|
167
|
+
assert.deepEqual(result.commandSpecs[1].args, ['reset', '--hard', 'origin/develop']);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// --- getMountSpec ---
|
|
171
|
+
|
|
172
|
+
test('getMountSpec returns valid K8s volume and volumeMount', () => {
|
|
173
|
+
const ctrl = createAgentWorkspaceController();
|
|
174
|
+
const ws = makeWorkspace('mount-ws', { pvcName: 'krate-ws-mount-ws' });
|
|
175
|
+
|
|
176
|
+
const result = ctrl.getMountSpec({ workspace: ws });
|
|
177
|
+
|
|
178
|
+
assert.equal(result.error, false);
|
|
179
|
+
assert.ok(result.volume);
|
|
180
|
+
assert.ok(result.volumeMount);
|
|
181
|
+
assert.equal(result.volume.name, 'workspace');
|
|
182
|
+
assert.equal(result.volumeMount.name, 'workspace');
|
|
183
|
+
assert.equal(result.volumeMount.mountPath, '/workspace');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test('getMountSpec uses correct PVC claim name', () => {
|
|
187
|
+
const ctrl = createAgentWorkspaceController();
|
|
188
|
+
const ws = makeWorkspace('pvc-test', { pvcName: 'krate-ws-pvc-test' });
|
|
189
|
+
|
|
190
|
+
const result = ctrl.getMountSpec({ workspace: ws });
|
|
191
|
+
|
|
192
|
+
assert.equal(result.error, false);
|
|
193
|
+
assert.equal(result.volume.persistentVolumeClaim.claimName, 'krate-ws-pvc-test');
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// --- findReusableWorkspace ---
|
|
197
|
+
|
|
198
|
+
test('findReusableWorkspace returns matching Ready workspace', () => {
|
|
199
|
+
const ctrl = createAgentWorkspaceController();
|
|
200
|
+
const ws1 = makeWorkspace('reuse-ws', {
|
|
201
|
+
organizationRef: 'acme',
|
|
202
|
+
repository: 'https://github.com/acme/app.git',
|
|
203
|
+
branch: 'main',
|
|
204
|
+
phase: 'Ready',
|
|
205
|
+
});
|
|
206
|
+
const ws2 = makeWorkspace('other-ws', {
|
|
207
|
+
organizationRef: 'acme',
|
|
208
|
+
repository: 'https://github.com/acme/other.git',
|
|
209
|
+
branch: 'main',
|
|
210
|
+
phase: 'Ready',
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const result = ctrl.findReusableWorkspace({
|
|
214
|
+
organizationRef: 'acme',
|
|
215
|
+
repository: 'https://github.com/acme/app.git',
|
|
216
|
+
branch: 'main',
|
|
217
|
+
resources: { KrateWorkspace: [ws1, ws2] },
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
assert.ok(result);
|
|
221
|
+
assert.equal(result.metadata.name, 'reuse-ws');
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test('findReusableWorkspace returns null when no match', () => {
|
|
225
|
+
const ctrl = createAgentWorkspaceController();
|
|
226
|
+
const ws = makeWorkspace('busy-ws', {
|
|
227
|
+
organizationRef: 'acme',
|
|
228
|
+
repository: 'https://github.com/acme/app.git',
|
|
229
|
+
branch: 'main',
|
|
230
|
+
phase: 'InUse',
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
const result = ctrl.findReusableWorkspace({
|
|
234
|
+
organizationRef: 'acme',
|
|
235
|
+
repository: 'https://github.com/acme/app.git',
|
|
236
|
+
branch: 'main',
|
|
237
|
+
resources: { KrateWorkspace: [ws] },
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
assert.equal(result, null);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// --- claimWorkspace ---
|
|
244
|
+
|
|
245
|
+
test('claimWorkspace transitions to InUse with runRef', () => {
|
|
246
|
+
const ctrl = createAgentWorkspaceController();
|
|
247
|
+
const ws = makeWorkspace('claim-ws', { phase: 'Ready' });
|
|
248
|
+
|
|
249
|
+
const result = ctrl.claimWorkspace({
|
|
250
|
+
name: 'claim-ws',
|
|
251
|
+
runRef: 'run-123',
|
|
252
|
+
resources: { KrateWorkspace: [ws] },
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
assert.equal(result.error, false);
|
|
256
|
+
assert.equal(result.workspace.status.phase, 'InUse');
|
|
257
|
+
assert.equal(result.workspace.status.runRef, 'run-123');
|
|
258
|
+
assert.ok(result.workspace.status.claimedAt);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test('claimWorkspace rejects already-InUse workspace', () => {
|
|
262
|
+
const ctrl = createAgentWorkspaceController();
|
|
263
|
+
const ws = makeWorkspace('busy-ws', { phase: 'InUse', status: { runRef: 'run-existing' } });
|
|
264
|
+
|
|
265
|
+
const result = ctrl.claimWorkspace({
|
|
266
|
+
name: 'busy-ws',
|
|
267
|
+
runRef: 'run-new',
|
|
268
|
+
resources: { KrateWorkspace: [ws] },
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
assert.equal(result.error, true);
|
|
272
|
+
assert.equal(result.reason, 'already-in-use');
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// --- releaseWorkspace ---
|
|
276
|
+
|
|
277
|
+
test('releaseWorkspace transitions to Ready', () => {
|
|
278
|
+
const ctrl = createAgentWorkspaceController();
|
|
279
|
+
const ws = makeWorkspace('release-ws', { phase: 'InUse', status: { runRef: 'run-done' } });
|
|
280
|
+
|
|
281
|
+
const result = ctrl.releaseWorkspace({
|
|
282
|
+
name: 'release-ws',
|
|
283
|
+
resources: { KrateWorkspace: [ws] },
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
assert.equal(result.error, false);
|
|
287
|
+
assert.equal(result.workspace.status.phase, 'Ready');
|
|
288
|
+
assert.equal(result.workspace.status.runRef, undefined);
|
|
289
|
+
assert.ok(result.workspace.status.releasedAt);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
test('releaseWorkspace rejects non-InUse workspace', () => {
|
|
293
|
+
const ctrl = createAgentWorkspaceController();
|
|
294
|
+
const ws = makeWorkspace('ready-ws', { phase: 'Ready' });
|
|
295
|
+
|
|
296
|
+
const result = ctrl.releaseWorkspace({
|
|
297
|
+
name: 'ready-ws',
|
|
298
|
+
resources: { KrateWorkspace: [ws] },
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
assert.equal(result.error, true);
|
|
302
|
+
assert.equal(result.reason, 'not-in-use');
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// --- BOUNDARY export ---
|
|
306
|
+
|
|
307
|
+
test('BOUNDARY exported', () => {
|
|
308
|
+
assert.ok(AGENT_WORKSPACE_CONTROLLER_BOUNDARY);
|
|
309
|
+
assert.equal(AGENT_WORKSPACE_CONTROLLER_BOUNDARY.role, 'agent-workspace-controller');
|
|
310
|
+
assert.ok(AGENT_WORKSPACE_CONTROLLER_BOUNDARY.owns.includes('PVC manifest generation'));
|
|
311
|
+
assert.ok(AGENT_WORKSPACE_CONTROLLER_BOUNDARY.owns.includes('workspace reuse'));
|
|
312
|
+
});
|