@a5c-ai/krate 5.0.1-staging.f672fe79b

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 (174) hide show
  1. package/Dockerfile +29 -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 +2407 -0
  6. package/dist/krate-lifecycle.json +201 -0
  7. package/dist/krate-runtime-snapshot.json +2955 -0
  8. package/dist/krate-summary.json +687 -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/glossary.md +66 -0
  27. package/docs/agents/implementation-blueprint.md +324 -0
  28. package/docs/agents/implementation-rollout-slices.md +251 -0
  29. package/docs/agents/memory-context-integration-spec.md +194 -0
  30. package/docs/agents/memory-ontology-schema-spec.md +253 -0
  31. package/docs/agents/memory-operations-runbook.md +121 -0
  32. package/docs/agents/mvp-vertical-slice-spec.md +146 -0
  33. package/docs/agents/observability-audit-spec.md +265 -0
  34. package/docs/agents/operator-runbook.md +174 -0
  35. package/docs/agents/org-memory-api-payload-examples.md +333 -0
  36. package/docs/agents/org-memory-controller-sequence-spec.md +181 -0
  37. package/docs/agents/org-memory-e2e-fixture-plan.md +161 -0
  38. package/docs/agents/org-memory-ui-implementation-map.md +114 -0
  39. package/docs/agents/org-memory-vertical-slice-spec.md +168 -0
  40. package/docs/agents/org-resource-model-delta-spec.md +111 -0
  41. package/docs/agents/org-route-resource-model-spec.md +183 -0
  42. package/docs/agents/org-scoping-namespace-spec.md +114 -0
  43. package/docs/agents/rbac-secrets-management-spec.md +406 -0
  44. package/docs/agents/repository-page-integration-spec.md +255 -0
  45. package/docs/agents/resource-contract-examples.md +808 -0
  46. package/docs/agents/resource-relationship-map.md +190 -0
  47. package/docs/agents/security-threat-model.md +188 -0
  48. package/docs/agents/shared-memory-company-brain-spec.md +358 -0
  49. package/docs/agents/storage-migration-spec.md +168 -0
  50. package/docs/agents/subagent-orchestration-spec.md +152 -0
  51. package/docs/agents/system-overview.md +88 -0
  52. package/docs/agents/tools-mcp-skills-spec.md +189 -0
  53. package/docs/agents/traceability-matrix.md +79 -0
  54. package/docs/agents/ui-flow-spec.md +211 -0
  55. package/docs/agents/ui-ux-system-spec.md +426 -0
  56. package/docs/agents/workspace-lifecycle-spec.md +166 -0
  57. package/docs/architecture-spec.md +78 -0
  58. package/docs/components/control-plane.md +78 -0
  59. package/docs/components/data-plane.md +69 -0
  60. package/docs/components/hooks-events.md +67 -0
  61. package/docs/components/identity-rbac-policy.md +73 -0
  62. package/docs/components/kubevela-oam.md +70 -0
  63. package/docs/components/operations-publishing.md +81 -0
  64. package/docs/components/runners-ci.md +66 -0
  65. package/docs/components/web-ui.md +94 -0
  66. package/docs/external/README.md +47 -0
  67. package/docs/external/bidirectional-sync-design.md +134 -0
  68. package/docs/external/cicd-interface.md +64 -0
  69. package/docs/external/external-backend-controllers.md +170 -0
  70. package/docs/external/external-backend-crds.md +234 -0
  71. package/docs/external/external-backend-ui-spec.md +151 -0
  72. package/docs/external/external-backend-ux-flows.md +115 -0
  73. package/docs/external/external-object-mapping.md +125 -0
  74. package/docs/external/git-forge-interface.md +68 -0
  75. package/docs/external/github-integration-design.md +151 -0
  76. package/docs/external/issue-tracking-interface.md +66 -0
  77. package/docs/external/provider-capability-manifests.md +204 -0
  78. package/docs/external/provider-catalog.md +139 -0
  79. package/docs/external/provider-rollout-testing.md +78 -0
  80. package/docs/external/research-results.md +48 -0
  81. package/docs/external/security-auth-permissions.md +81 -0
  82. package/docs/external/sync-state-machines.md +108 -0
  83. package/docs/external/unified-external-backend-model.md +107 -0
  84. package/docs/external/user-facing-changes.md +67 -0
  85. package/docs/gaps.md +161 -0
  86. package/docs/install.md +94 -0
  87. package/docs/krate-design.md +334 -0
  88. package/docs/local-minikube.md +55 -0
  89. package/docs/ontology/README.md +32 -0
  90. package/docs/ontology/bounded-contexts.md +29 -0
  91. package/docs/ontology/events-and-hooks.md +32 -0
  92. package/docs/ontology/oam-kubevela.md +32 -0
  93. package/docs/ontology/operations-and-release.md +25 -0
  94. package/docs/ontology/personas-and-actors.md +32 -0
  95. package/docs/ontology/policies-and-invariants.md +33 -0
  96. package/docs/ontology/problem-space.md +30 -0
  97. package/docs/ontology/resource-contracts.md +40 -0
  98. package/docs/ontology/resource-taxonomy.md +42 -0
  99. package/docs/ontology/runners-and-ci.md +29 -0
  100. package/docs/ontology/solution-space.md +24 -0
  101. package/docs/ontology/storage-and-data-boundaries.md +29 -0
  102. package/docs/ontology/validation-matrix.md +24 -0
  103. package/docs/ontology/web-ui-excellent-flows.md +32 -0
  104. package/docs/ontology/workflows.md +39 -0
  105. package/docs/ontology/world.md +35 -0
  106. package/docs/product-requirements.md +62 -0
  107. package/docs/roadmap-mvp.md +87 -0
  108. package/docs/system-requirements.md +90 -0
  109. package/docs/tests/README.md +53 -0
  110. package/docs/tests/agent-qa-plan.md +63 -0
  111. package/docs/tests/browser-ui-tests.md +62 -0
  112. package/docs/tests/ci-quality-gates.md +48 -0
  113. package/docs/tests/coverage-model.md +64 -0
  114. package/docs/tests/e2e-scenario-tests.md +53 -0
  115. package/docs/tests/fixtures-test-data.md +63 -0
  116. package/docs/tests/observability-reliability-tests.md +54 -0
  117. package/docs/tests/product-test-matrix.md +145 -0
  118. package/docs/tests/qa-adoption-roadmap.md +130 -0
  119. package/docs/tests/qa-automation-plan.md +101 -0
  120. package/docs/tests/security-compliance-tests.md +57 -0
  121. package/docs/tests/test-framework-tools.md +88 -0
  122. package/docs/tests/test-suite-layout.md +121 -0
  123. package/docs/tests/unit-integration-tests.md +48 -0
  124. package/docs/todo-kyverno +714 -0
  125. package/docs/user-stories.md +78 -0
  126. package/examples/minikube-demo.yaml +190 -0
  127. package/examples/oam-application.yaml +23 -0
  128. package/examples/policy-kyverno-pr-title.yaml +18 -0
  129. package/package.json +63 -0
  130. package/scripts/build.mjs +29 -0
  131. package/scripts/setup-minikube.mjs +65 -0
  132. package/scripts/smoke.mjs +37 -0
  133. package/scripts/validate-doc-coverage.mjs +152 -0
  134. package/scripts/validate-package.mjs +93 -0
  135. package/scripts/validate-ui.mjs +207 -0
  136. package/src/agent-approval-controller.js +123 -0
  137. package/src/agent-context-bundles.js +242 -0
  138. package/src/agent-dispatch-controller.js +86 -0
  139. package/src/agent-mux-client.js +280 -0
  140. package/src/agent-permission-review.js +162 -0
  141. package/src/agent-stack-controller.js +296 -0
  142. package/src/agent-trigger-controller.js +108 -0
  143. package/src/api-controller.js +206 -0
  144. package/src/argocd-gitops.js +43 -0
  145. package/src/auth.js +265 -0
  146. package/src/component-catalog.js +41 -0
  147. package/src/control-plane.js +136 -0
  148. package/src/controller-client.js +38 -0
  149. package/src/controller-ui.js +538 -0
  150. package/src/data-plane.js +178 -0
  151. package/src/gitea-backend.js +95 -0
  152. package/src/handoff.js +98 -0
  153. package/src/hooks-events.js +63 -0
  154. package/src/http-server.js +151 -0
  155. package/src/identity-policy.js +86 -0
  156. package/src/index.js +30 -0
  157. package/src/kubernetes-controller.js +812 -0
  158. package/src/kubernetes-resource-gateway.js +48 -0
  159. package/src/operations.js +112 -0
  160. package/src/resource-model.js +203 -0
  161. package/src/runners-ci.js +48 -0
  162. package/src/runtime.js +196 -0
  163. package/src/web-ui.js +40 -0
  164. package/tests/agent-approval-controller.test.js +173 -0
  165. package/tests/agent-context-bundles.test.js +278 -0
  166. package/tests/agent-dispatch-controller.test.js +176 -0
  167. package/tests/agent-mux-client.test.js +204 -0
  168. package/tests/agent-permission-review.test.js +209 -0
  169. package/tests/agent-resources.test.js +212 -0
  170. package/tests/agent-stack-controller.test.js +221 -0
  171. package/tests/agent-trigger-controller.test.js +211 -0
  172. package/tests/deployment.test.js +395 -0
  173. package/tests/e2e/lifecycle.test.js +117 -0
  174. package/tests/krate.test.js +727 -0
@@ -0,0 +1,221 @@
1
+ import assert from 'node:assert/strict';
2
+ import test from 'node:test';
3
+ import { createAgentStackController, createResource } from '../src/index.js';
4
+
5
+ function makeStack(name, spec = {}) {
6
+ return createResource('AgentStack', { name, namespace: 'krate-org-default' }, {
7
+ organizationRef: 'default',
8
+ baseAgent: 'claude-code',
9
+ adapter: 'anthropic',
10
+ runtimeIdentity: { serviceAccountRef: 'sa-default' },
11
+ ...spec
12
+ });
13
+ }
14
+
15
+ function makeToolProfile(name) {
16
+ return createResource('AgentToolProfile', { name, namespace: 'krate-org-default' }, {
17
+ organizationRef: 'default',
18
+ filesystemPolicy: 'read-write',
19
+ approvalPolicyByTool: {}
20
+ });
21
+ }
22
+
23
+ function makeMcpServer(name) {
24
+ return createResource('AgentMcpServer', { name, namespace: 'krate-org-default' }, {
25
+ organizationRef: 'default',
26
+ transport: 'stdio',
27
+ scope: 'workspace'
28
+ });
29
+ }
30
+
31
+ function makeSkill(name, overrides = {}) {
32
+ return createResource('AgentSkill', { name, namespace: 'krate-org-default' }, {
33
+ organizationRef: 'default',
34
+ format: 'markdown',
35
+ sourceRef: 'git://repo/skills/' + name,
36
+ ...overrides
37
+ });
38
+ }
39
+
40
+ function makeSubagent(name, overrides = {}) {
41
+ return createResource('AgentSubagent', { name, namespace: 'krate-org-default' }, {
42
+ organizationRef: 'default',
43
+ rolePrompt: 'Test subagent',
44
+ taskKinds: ['code-review'],
45
+ ...overrides
46
+ });
47
+ }
48
+
49
+ function makeContextLabel(name) {
50
+ return createResource('AgentContextLabel', { name, namespace: 'krate-org-default' }, {
51
+ organizationRef: 'default',
52
+ promptFragment: 'context for ' + name,
53
+ allowedSources: ['manual']
54
+ });
55
+ }
56
+
57
+ function makeServiceAccount(name) {
58
+ return createResource('AgentServiceAccount', { name, namespace: 'krate-org-default' }, {
59
+ organizationRef: 'default',
60
+ namespace: 'krate-org-default',
61
+ serviceAccountName: name
62
+ });
63
+ }
64
+
65
+ function makeRoleBinding(name, subject) {
66
+ return createResource('AgentRoleBinding', { name, namespace: 'krate-org-default' }, {
67
+ organizationRef: 'default',
68
+ subject,
69
+ roleRef: 'agent-developer',
70
+ scope: 'namespace'
71
+ });
72
+ }
73
+
74
+ function makeSecretGrant(name, subject, purpose) {
75
+ return createResource('AgentSecretGrant', { name, namespace: 'krate-org-default' }, {
76
+ organizationRef: 'default',
77
+ subject,
78
+ secretRef: 'secret-' + purpose,
79
+ purpose
80
+ });
81
+ }
82
+
83
+ test('Stack with all refs present results in Ready=True', () => {
84
+ const controller = createAgentStackController();
85
+ const stack = makeStack('full-stack', {
86
+ toolPolicy: 'tool-profile-1',
87
+ mcpServerRefs: ['mcp-github'],
88
+ skillRefs: ['skill-review'],
89
+ subagentRefs: ['sub-linter'],
90
+ contextLabelRefs: ['ctx-security']
91
+ });
92
+
93
+ const resources = {
94
+ AgentStack: [stack],
95
+ AgentToolProfile: [makeToolProfile('tool-profile-1')],
96
+ AgentMcpServer: [makeMcpServer('mcp-github')],
97
+ AgentSkill: [makeSkill('skill-review')],
98
+ AgentSubagent: [makeSubagent('sub-linter')],
99
+ AgentContextLabel: [makeContextLabel('ctx-security')],
100
+ AgentServiceAccount: [makeServiceAccount('sa-default')],
101
+ AgentRoleBinding: [makeRoleBinding('rb-1', 'sa-default')],
102
+ AgentSecretGrant: [makeSecretGrant('sg-model', 'sa-default', 'model-provider')]
103
+ };
104
+
105
+ const result = controller.reconcileStack(stack, resources);
106
+ const readyCondition = result.conditions.find((c) => c.type === 'Ready');
107
+ assert.equal(readyCondition.status, 'True', 'Ready condition should be True when all refs are present');
108
+ assert.equal(result.validation, 'valid');
109
+ assert.equal(result.capabilities.tools.length, 1);
110
+ assert.equal(result.capabilities.mcpServers.length, 1);
111
+ assert.equal(result.capabilities.skills.length, 1);
112
+ assert.equal(result.capabilities.subagents.length, 1);
113
+ assert.equal(result.capabilities.contextLabels.length, 1);
114
+ });
115
+
116
+ test('Stack with missing MCP server results in McpHealthy=False and Ready=False', () => {
117
+ const controller = createAgentStackController();
118
+ const stack = makeStack('missing-mcp-stack', {
119
+ mcpServerRefs: ['mcp-github', 'mcp-missing']
120
+ });
121
+
122
+ const resources = {
123
+ AgentStack: [stack],
124
+ AgentMcpServer: [makeMcpServer('mcp-github')],
125
+ AgentServiceAccount: [makeServiceAccount('sa-default')],
126
+ AgentRoleBinding: [makeRoleBinding('rb-1', 'sa-default')],
127
+ AgentSecretGrant: [makeSecretGrant('sg-model', 'sa-default', 'model-provider')]
128
+ };
129
+
130
+ const result = controller.reconcileStack(stack, resources);
131
+ const mcpCondition = result.conditions.find((c) => c.type === 'McpHealthy');
132
+ const readyCondition = result.conditions.find((c) => c.type === 'Ready');
133
+ assert.equal(mcpCondition.status, 'False', 'McpHealthy should be False when MCP server is missing');
134
+ assert.equal(readyCondition.status, 'False', 'Ready should be False when McpHealthy is False');
135
+ assert.ok(mcpCondition.message.includes('mcp-missing'));
136
+ });
137
+
138
+ test('Stack with missing skill results in SkillsValidated=False and Ready=False', () => {
139
+ const controller = createAgentStackController();
140
+ const stack = makeStack('missing-skill-stack', {
141
+ skillRefs: ['skill-review', 'skill-ghost']
142
+ });
143
+
144
+ const resources = {
145
+ AgentStack: [stack],
146
+ AgentSkill: [makeSkill('skill-review')],
147
+ AgentServiceAccount: [makeServiceAccount('sa-default')],
148
+ AgentRoleBinding: [makeRoleBinding('rb-1', 'sa-default')],
149
+ AgentSecretGrant: [makeSecretGrant('sg-model', 'sa-default', 'model-provider')]
150
+ };
151
+
152
+ const result = controller.reconcileStack(stack, resources);
153
+ const skillsCondition = result.conditions.find((c) => c.type === 'SkillsValidated');
154
+ const readyCondition = result.conditions.find((c) => c.type === 'Ready');
155
+ assert.equal(skillsCondition.status, 'False', 'SkillsValidated should be False when skill is missing');
156
+ assert.equal(readyCondition.status, 'False', 'Ready should be False when SkillsValidated is False');
157
+ assert.ok(skillsCondition.message.includes('skill-ghost'));
158
+ });
159
+
160
+ test('Minimal stack with no capability refs results in Ready=True', () => {
161
+ const controller = createAgentStackController();
162
+ const stack = makeStack('minimal-stack');
163
+
164
+ const resources = {
165
+ AgentStack: [stack],
166
+ AgentServiceAccount: [makeServiceAccount('sa-default')],
167
+ AgentRoleBinding: [makeRoleBinding('rb-1', 'sa-default')],
168
+ AgentSecretGrant: [makeSecretGrant('sg-model', 'sa-default', 'model-provider')]
169
+ };
170
+
171
+ const result = controller.reconcileStack(stack, resources);
172
+ const readyCondition = result.conditions.find((c) => c.type === 'Ready');
173
+ assert.equal(readyCondition.status, 'True', 'Ready should be True for minimal stack with identity present');
174
+ assert.equal(result.validation, 'valid');
175
+ assert.equal(result.capabilities.tools.length, 0);
176
+ assert.equal(result.capabilities.mcpServers.length, 0);
177
+ assert.equal(result.capabilities.skills.length, 0);
178
+ assert.equal(result.capabilities.subagents.length, 0);
179
+ assert.equal(result.capabilities.contextLabels.length, 0);
180
+ });
181
+
182
+ test('listStackCapabilities returns correct normalized capability list', () => {
183
+ const controller = createAgentStackController();
184
+ const stack = makeStack('caps-stack', {
185
+ toolPolicy: 'tp-1',
186
+ mcpServerRefs: ['mcp-a', 'mcp-b'],
187
+ skillRefs: ['skill-x'],
188
+ subagentRefs: ['sub-y'],
189
+ contextLabelRefs: ['ctx-z']
190
+ });
191
+
192
+ const resources = {
193
+ AgentToolProfile: [makeToolProfile('tp-1')],
194
+ AgentMcpServer: [makeMcpServer('mcp-a')],
195
+ AgentSkill: [makeSkill('skill-x')],
196
+ AgentSubagent: [makeSubagent('sub-y')],
197
+ AgentContextLabel: [makeContextLabel('ctx-z')]
198
+ };
199
+
200
+ const capabilities = controller.listStackCapabilities(stack, resources);
201
+ assert.equal(capabilities.length, 6);
202
+
203
+ const tool = capabilities.find((c) => c.kind === 'tool');
204
+ assert.equal(tool.name, 'tp-1');
205
+ assert.equal(tool.status, 'resolved');
206
+
207
+ const mcpA = capabilities.find((c) => c.kind === 'mcp' && c.name === 'mcp-a');
208
+ assert.equal(mcpA.status, 'resolved');
209
+
210
+ const mcpB = capabilities.find((c) => c.kind === 'mcp' && c.name === 'mcp-b');
211
+ assert.equal(mcpB.status, 'missing');
212
+
213
+ const skill = capabilities.find((c) => c.kind === 'skill');
214
+ assert.equal(skill.status, 'resolved');
215
+
216
+ const subagent = capabilities.find((c) => c.kind === 'subagent');
217
+ assert.equal(subagent.status, 'resolved');
218
+
219
+ const ctxLabel = capabilities.find((c) => c.kind === 'contextLabel');
220
+ assert.equal(ctxLabel.status, 'resolved');
221
+ });
@@ -0,0 +1,211 @@
1
+ import assert from 'node:assert/strict';
2
+ import test from 'node:test';
3
+ import { createAgentTriggerController, createResource } from '../src/index.js';
4
+
5
+ function makeTriggerRule(name, spec = {}) {
6
+ return createResource('AgentTriggerRule', { name, namespace: 'krate-org-default' }, {
7
+ organizationRef: 'default',
8
+ sources: ['ci-failure'],
9
+ agentStack: 'debug-stack',
10
+ taskKind: 'diagnostic',
11
+ ...spec
12
+ });
13
+ }
14
+
15
+ function makeEvent(overrides = {}) {
16
+ return {
17
+ type: 'ci-failure',
18
+ repository: 'myapp',
19
+ ref: 'main',
20
+ actor: 'alice',
21
+ source: { kind: 'Pipeline', name: 'pipeline-123' },
22
+ ...overrides
23
+ };
24
+ }
25
+
26
+ function makeExecution(ruleName, eventUid, phase = 'Dispatched') {
27
+ const exec = createResource('AgentTriggerExecution', { name: `exec-${ruleName}-1`, namespace: 'krate-org-default' }, {
28
+ organizationRef: 'default',
29
+ triggerRule: ruleName,
30
+ sourceEvent: eventUid,
31
+ decision: phase
32
+ });
33
+ exec.status = { phase };
34
+ return exec;
35
+ }
36
+
37
+ // 1. matchRule: event type matches
38
+ test('matchRule: event type matches rule sources', () => {
39
+ const controller = createAgentTriggerController();
40
+ const rule = makeTriggerRule('rule-1', { sources: ['ci-failure'] });
41
+ const event = makeEvent({ type: 'ci-failure' });
42
+
43
+ const result = controller.matchRule(rule, event);
44
+
45
+ assert.equal(result.matches, true);
46
+ assert.equal(result.reason, 'All conditions met');
47
+ });
48
+
49
+ // 2. matchRule: event type doesn't match
50
+ test('matchRule: event type does not match rule sources', () => {
51
+ const controller = createAgentTriggerController();
52
+ const rule = makeTriggerRule('rule-2', { sources: ['ci-failure'] });
53
+ const event = makeEvent({ type: 'webhook' });
54
+
55
+ const result = controller.matchRule(rule, event);
56
+
57
+ assert.equal(result.matches, false);
58
+ assert.ok(result.reason.includes("'webhook'"), 'Reason should mention the event type');
59
+ assert.ok(result.reason.includes('ci-failure'), 'Reason should mention the expected source');
60
+ });
61
+
62
+ // 3. matchRule: repository scope match
63
+ test('matchRule: repository scope matches', () => {
64
+ const controller = createAgentTriggerController();
65
+ const rule = makeTriggerRule('rule-3', { sources: ['ci-failure'], repository: 'myapp' });
66
+ const event = makeEvent({ type: 'ci-failure', repository: 'myapp' });
67
+
68
+ const result = controller.matchRule(rule, event);
69
+
70
+ assert.equal(result.matches, true);
71
+ assert.equal(result.reason, 'All conditions met');
72
+ });
73
+
74
+ // 4. matchRule: repository scope mismatch
75
+ test('matchRule: repository scope mismatch', () => {
76
+ const controller = createAgentTriggerController();
77
+ const rule = makeTriggerRule('rule-4', { sources: ['ci-failure'], repository: 'myapp' });
78
+ const event = makeEvent({ type: 'ci-failure', repository: 'other' });
79
+
80
+ const result = controller.matchRule(rule, event);
81
+
82
+ assert.equal(result.matches, false);
83
+ assert.ok(result.reason.includes("'other'"), 'Reason should mention the event repository');
84
+ assert.ok(result.reason.includes("'myapp'"), 'Reason should mention the rule scope');
85
+ });
86
+
87
+ // 5. matchRule: actor filter
88
+ test('matchRule: actor not in allowedActors', () => {
89
+ const controller = createAgentTriggerController();
90
+ const rule = makeTriggerRule('rule-5', { sources: ['ci-failure'], allowedActors: ['alice'] });
91
+ const event = makeEvent({ type: 'ci-failure', actor: 'bob' });
92
+
93
+ const result = controller.matchRule(rule, event);
94
+
95
+ assert.equal(result.matches, false);
96
+ assert.ok(result.reason.includes("'bob'"), 'Reason should mention the rejected actor');
97
+ });
98
+
99
+ // 6. evaluateEvent: deduplication
100
+ test('evaluateEvent: marks duplicate when existing execution matches', () => {
101
+ const controller = createAgentTriggerController();
102
+ const rule = makeTriggerRule('rule-dedup');
103
+ const event = makeEvent();
104
+ const eventUid = `${event.type}:${event.source.kind}:${event.source.name}`;
105
+ const existingExecution = makeExecution('rule-dedup', eventUid, 'Dispatched');
106
+
107
+ const resources = {
108
+ AgentTriggerRule: [rule],
109
+ AgentTriggerExecution: [existingExecution]
110
+ };
111
+
112
+ const evaluations = controller.evaluateEvent({ event, resources });
113
+
114
+ assert.equal(evaluations.length, 1);
115
+ assert.equal(evaluations[0].matches, true);
116
+ assert.equal(evaluations[0].isDuplicate, true);
117
+ });
118
+
119
+ // 7. processEvent: dispatches matching rules
120
+ test('processEvent: dispatches matching rules via dispatch controller', async () => {
121
+ let dispatchCalled = false;
122
+ const mockDispatchController = {
123
+ async createManualDispatch(input) {
124
+ dispatchCalled = true;
125
+ const run = createResource('AgentDispatchRun', { name: `dispatch-${Date.now()}`, namespace: input.namespace }, {
126
+ organizationRef: input.organizationRef,
127
+ repository: input.repository,
128
+ sourceRefs: input.sourceRefs,
129
+ agentStack: input.agentStack,
130
+ taskKind: input.taskKind
131
+ });
132
+ run.status = { phase: 'Queued' };
133
+ return { error: false, run };
134
+ }
135
+ };
136
+ const controller = createAgentTriggerController({ dispatchController: mockDispatchController });
137
+ const rule = makeTriggerRule('rule-dispatch');
138
+ const event = makeEvent();
139
+ const resources = { AgentTriggerRule: [rule], AgentTriggerExecution: [] };
140
+
141
+ const result = await controller.processEvent({ event, resources, namespace: 'krate-org-default', organizationRef: 'default' });
142
+
143
+ assert.equal(dispatchCalled, true, 'Dispatch controller should be called');
144
+ assert.equal(result.dispatched, 1);
145
+ assert.equal(result.skipped, 0);
146
+ assert.equal(result.processed, 1);
147
+ assert.equal(result.executions.length, 1);
148
+ assert.equal(result.executions[0].status.phase, 'Dispatched');
149
+ assert.ok(result.executions[0].status.dispatchRunRef, 'Execution should reference the dispatch run');
150
+ });
151
+
152
+ // 8. processEvent: skips non-matching
153
+ test('processEvent: skips non-matching rules', async () => {
154
+ const controller = createAgentTriggerController();
155
+ const rule = makeTriggerRule('rule-skip', { sources: ['ci-failure'] });
156
+ const event = makeEvent({ type: 'webhook' });
157
+ const resources = { AgentTriggerRule: [rule], AgentTriggerExecution: [] };
158
+
159
+ const result = await controller.processEvent({ event, resources });
160
+
161
+ assert.equal(result.dispatched, 0);
162
+ assert.equal(result.skipped, 1);
163
+ assert.equal(result.processed, 1);
164
+ assert.equal(result.executions[0].status.phase, 'Skipped');
165
+ });
166
+
167
+ // 9. processEvent: multiple rules match same event
168
+ test('processEvent: multiple rules match same event', async () => {
169
+ let dispatchCount = 0;
170
+ const mockDispatchController = {
171
+ async createManualDispatch(input) {
172
+ dispatchCount++;
173
+ const run = createResource('AgentDispatchRun', { name: `dispatch-${Date.now()}-${dispatchCount}`, namespace: input.namespace }, {
174
+ organizationRef: input.organizationRef,
175
+ repository: input.repository,
176
+ sourceRefs: input.sourceRefs,
177
+ agentStack: input.agentStack,
178
+ taskKind: input.taskKind
179
+ });
180
+ run.status = { phase: 'Queued' };
181
+ return { error: false, run };
182
+ }
183
+ };
184
+ const controller = createAgentTriggerController({ dispatchController: mockDispatchController });
185
+ const rule1 = makeTriggerRule('rule-multi-1', { sources: ['ci-failure'], agentStack: 'stack-a' });
186
+ const rule2 = makeTriggerRule('rule-multi-2', { sources: ['ci-failure'], agentStack: 'stack-b' });
187
+ const event = makeEvent();
188
+ const resources = { AgentTriggerRule: [rule1, rule2], AgentTriggerExecution: [] };
189
+
190
+ const result = await controller.processEvent({ event, resources, namespace: 'krate-org-default', organizationRef: 'default' });
191
+
192
+ assert.equal(dispatchCount, 2, 'Dispatch should be called twice');
193
+ assert.equal(result.dispatched, 2);
194
+ assert.equal(result.skipped, 0);
195
+ assert.equal(result.processed, 2);
196
+ assert.equal(result.executions.length, 2);
197
+ });
198
+
199
+ // 10. processEvent: no rules
200
+ test('processEvent: empty rules array', async () => {
201
+ const controller = createAgentTriggerController();
202
+ const event = makeEvent();
203
+ const resources = { AgentTriggerRule: [], AgentTriggerExecution: [] };
204
+
205
+ const result = await controller.processEvent({ event, resources });
206
+
207
+ assert.equal(result.processed, 0);
208
+ assert.equal(result.dispatched, 0);
209
+ assert.equal(result.skipped, 0);
210
+ assert.equal(result.executions.length, 0);
211
+ });