@a5c-ai/krate 5.0.1-staging.04a3db697
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 +3067 -0
- package/dist/krate-lifecycle.json +201 -0
- package/dist/krate-runtime-snapshot.json +2955 -0
- package/dist/krate-summary.json +722 -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/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 +236 -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 +321 -0
- package/src/agent-workspace-controller.js +447 -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 +50 -0
- package/src/controller-ui.js +551 -0
- package/src/data-plane.js +178 -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 +95 -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 +55 -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/operations.js +112 -0
- package/src/org-scoping.js +5 -0
- package/src/resource-model.js +221 -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/deployment.test.js +396 -0
- package/tests/e2e/lifecycle.test.js +117 -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 +727 -0
- package/tests/memory-search-wiring.test.js +270 -0
- package/tests/org-scoping.test.js +687 -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/workspace-volumes.test.js +312 -0
- package/tests/writeback-persistence.test.js +207 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import { createAgentWritebackController, createResource } from '../src/index.js';
|
|
4
|
+
|
|
5
|
+
function makeArtifact(name, runRef, contentRef, extra = {}) {
|
|
6
|
+
const artifact = createResource('KrateArtifact', { name, namespace: 'krate-org-default' }, {
|
|
7
|
+
organizationRef: 'default',
|
|
8
|
+
dispatchRun: runRef,
|
|
9
|
+
kind: 'output',
|
|
10
|
+
digest: contentRef,
|
|
11
|
+
...extra.spec
|
|
12
|
+
});
|
|
13
|
+
artifact.status = { phase: 'Available', createdAt: new Date().toISOString(), ...extra.status };
|
|
14
|
+
return artifact;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// --- createArtifact ---
|
|
18
|
+
|
|
19
|
+
test('createArtifact creates an artifact resource with name, runRef, and content ref', () => {
|
|
20
|
+
const controller = createAgentWritebackController();
|
|
21
|
+
const result = controller.createArtifact({
|
|
22
|
+
name: 'output-1',
|
|
23
|
+
runRef: 'run-abc',
|
|
24
|
+
contentRef: 'sha256:deadbeef',
|
|
25
|
+
kind: 'output',
|
|
26
|
+
namespace: 'krate-org-default',
|
|
27
|
+
organizationRef: 'default'
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
assert.equal(result.error, false, 'Should succeed');
|
|
31
|
+
assert.ok(result.artifact, 'Should return an artifact resource');
|
|
32
|
+
assert.equal(result.artifact.kind, 'KrateArtifact');
|
|
33
|
+
assert.equal(result.artifact.spec.dispatchRun, 'run-abc');
|
|
34
|
+
assert.equal(result.artifact.spec.digest, 'sha256:deadbeef');
|
|
35
|
+
assert.ok(result.artifact.metadata.name, 'Should have a name');
|
|
36
|
+
assert.equal(result.artifact.status.phase, 'Available');
|
|
37
|
+
assert.ok(result.artifact.status.createdAt, 'Should have createdAt timestamp');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('createArtifact rejects missing runRef', () => {
|
|
41
|
+
const controller = createAgentWritebackController();
|
|
42
|
+
const result = controller.createArtifact({
|
|
43
|
+
name: 'output-2',
|
|
44
|
+
contentRef: 'sha256:aabbcc',
|
|
45
|
+
kind: 'output',
|
|
46
|
+
namespace: 'krate-org-default',
|
|
47
|
+
organizationRef: 'default'
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
assert.equal(result.error, true, 'Should fail');
|
|
51
|
+
assert.equal(result.reason, 'missing-run-ref');
|
|
52
|
+
assert.ok(result.message.includes('runRef'), 'Message should mention runRef');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('createArtifact rejects missing contentRef', () => {
|
|
56
|
+
const controller = createAgentWritebackController();
|
|
57
|
+
const result = controller.createArtifact({
|
|
58
|
+
name: 'output-3',
|
|
59
|
+
runRef: 'run-xyz',
|
|
60
|
+
kind: 'output',
|
|
61
|
+
namespace: 'krate-org-default',
|
|
62
|
+
organizationRef: 'default'
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
assert.equal(result.error, true, 'Should fail');
|
|
66
|
+
assert.equal(result.reason, 'missing-content-ref');
|
|
67
|
+
assert.ok(result.message.includes('contentRef'), 'Message should mention contentRef');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// --- validateArtifact ---
|
|
71
|
+
|
|
72
|
+
test('validateArtifact accepts valid artifact', () => {
|
|
73
|
+
const controller = createAgentWritebackController();
|
|
74
|
+
const artifact = makeArtifact('artifact-valid', 'run-1', 'sha256:cafebabe');
|
|
75
|
+
const result = controller.validateArtifact({ artifact });
|
|
76
|
+
|
|
77
|
+
assert.equal(result.valid, true, 'Should be valid');
|
|
78
|
+
assert.equal(result.error, false, 'Should not have error');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// --- listArtifactsForRun ---
|
|
82
|
+
|
|
83
|
+
test('listArtifactsForRun returns artifacts filtered by runRef', () => {
|
|
84
|
+
const controller = createAgentWritebackController();
|
|
85
|
+
const a1 = makeArtifact('art-1', 'run-10', 'sha256:111');
|
|
86
|
+
const a2 = makeArtifact('art-2', 'run-10', 'sha256:222');
|
|
87
|
+
const a3 = makeArtifact('art-3', 'run-99', 'sha256:333');
|
|
88
|
+
|
|
89
|
+
const result = controller.listArtifactsForRun({
|
|
90
|
+
runRef: 'run-10',
|
|
91
|
+
resources: { KrateArtifact: [a1, a2, a3] }
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
assert.equal(result.length, 2, 'Should return only artifacts for run-10');
|
|
95
|
+
assert.ok(result.every((a) => a.spec.dispatchRun === 'run-10'), 'All should belong to run-10');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// --- requestBranchPush ---
|
|
99
|
+
|
|
100
|
+
test('requestBranchPush creates a push request with branch, target repo, approval status', () => {
|
|
101
|
+
const controller = createAgentWritebackController();
|
|
102
|
+
const result = controller.requestBranchPush({
|
|
103
|
+
runRef: 'run-abc',
|
|
104
|
+
branch: 'feature/new-thing',
|
|
105
|
+
targetRepo: 'org/my-repo',
|
|
106
|
+
requestedBy: 'agent-stack-1',
|
|
107
|
+
namespace: 'krate-org-default',
|
|
108
|
+
organizationRef: 'default'
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
assert.equal(result.error, false, 'Should succeed');
|
|
112
|
+
assert.ok(result.pushRequest, 'Should return a push request');
|
|
113
|
+
assert.equal(result.pushRequest.spec.branch, 'feature/new-thing');
|
|
114
|
+
assert.equal(result.pushRequest.spec.targetRepo, 'org/my-repo');
|
|
115
|
+
assert.equal(result.pushRequest.spec.runRef, 'run-abc');
|
|
116
|
+
assert.ok(result.pushRequest.status.approvalStatus, 'Should have approvalStatus');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test('requestBranchPush defaults approval to pending', () => {
|
|
120
|
+
const controller = createAgentWritebackController();
|
|
121
|
+
const result = controller.requestBranchPush({
|
|
122
|
+
runRef: 'run-def',
|
|
123
|
+
branch: 'fix/buggy',
|
|
124
|
+
targetRepo: 'org/other-repo',
|
|
125
|
+
requestedBy: 'agent-stack-2',
|
|
126
|
+
namespace: 'krate-org-default',
|
|
127
|
+
organizationRef: 'default'
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
assert.equal(result.error, false, 'Should succeed');
|
|
131
|
+
assert.equal(result.pushRequest.status.approvalStatus, 'pending', 'Default approval should be pending');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('approveBranchPush transitions approval to approved', () => {
|
|
135
|
+
const controller = createAgentWritebackController();
|
|
136
|
+
const pushRequest = {
|
|
137
|
+
kind: 'AgentWriteIntent',
|
|
138
|
+
metadata: { name: 'push-req-1', namespace: 'krate-org-default' },
|
|
139
|
+
spec: { runRef: 'run-1', branch: 'main', targetRepo: 'org/repo', writeType: 'branch-push', requestedBy: 'agent-1' },
|
|
140
|
+
status: { approvalStatus: 'pending', phase: 'Pending' }
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const result = controller.approveBranchPush({
|
|
144
|
+
intentName: 'push-req-1',
|
|
145
|
+
approvedBy: 'owner',
|
|
146
|
+
reason: 'Looks safe',
|
|
147
|
+
resources: { AgentWriteIntent: [pushRequest] }
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
assert.equal(result.error, false, 'Should succeed');
|
|
151
|
+
assert.equal(result.pushRequest.status.approvalStatus, 'approved', 'Should be approved');
|
|
152
|
+
assert.equal(result.pushRequest.status.approvedBy, 'owner');
|
|
153
|
+
assert.ok(result.pushRequest.status.approvedAt, 'Should have approvedAt timestamp');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test('denyBranchPush transitions approval to denied', () => {
|
|
157
|
+
const controller = createAgentWritebackController();
|
|
158
|
+
const pushRequest = {
|
|
159
|
+
kind: 'AgentWriteIntent',
|
|
160
|
+
metadata: { name: 'push-req-2', namespace: 'krate-org-default' },
|
|
161
|
+
spec: { runRef: 'run-2', branch: 'feature/x', targetRepo: 'org/repo', writeType: 'branch-push', requestedBy: 'agent-2' },
|
|
162
|
+
status: { approvalStatus: 'pending', phase: 'Pending' }
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const result = controller.denyBranchPush({
|
|
166
|
+
intentName: 'push-req-2',
|
|
167
|
+
deniedBy: 'admin',
|
|
168
|
+
reason: 'Not allowed in this repo',
|
|
169
|
+
resources: { AgentWriteIntent: [pushRequest] }
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
assert.equal(result.error, false, 'Should succeed');
|
|
173
|
+
assert.equal(result.pushRequest.status.approvalStatus, 'denied', 'Should be denied');
|
|
174
|
+
assert.equal(result.pushRequest.status.deniedBy, 'admin');
|
|
175
|
+
assert.ok(result.pushRequest.status.deniedAt, 'Should have deniedAt timestamp');
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// --- requestPrMerge ---
|
|
179
|
+
|
|
180
|
+
test('requestPrMerge creates a merge request with PR ref and checks status', () => {
|
|
181
|
+
const controller = createAgentWritebackController();
|
|
182
|
+
const result = controller.requestPrMerge({
|
|
183
|
+
runRef: 'run-merge-1',
|
|
184
|
+
prRef: 'pr-42',
|
|
185
|
+
statusChecks: [
|
|
186
|
+
{ name: 'ci/build', state: 'success' },
|
|
187
|
+
{ name: 'ci/lint', state: 'success' }
|
|
188
|
+
],
|
|
189
|
+
requestedBy: 'agent-stack-3',
|
|
190
|
+
namespace: 'krate-org-default',
|
|
191
|
+
organizationRef: 'default'
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
assert.equal(result.error, false, 'Should succeed');
|
|
195
|
+
assert.ok(result.mergeRequest, 'Should return a merge request');
|
|
196
|
+
assert.equal(result.mergeRequest.spec.prRef, 'pr-42');
|
|
197
|
+
assert.equal(result.mergeRequest.spec.runRef, 'run-merge-1');
|
|
198
|
+
assert.ok(Array.isArray(result.mergeRequest.spec.statusChecks), 'Should have statusChecks');
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test('requestPrMerge rejects when status checks are failing', () => {
|
|
202
|
+
const controller = createAgentWritebackController();
|
|
203
|
+
const result = controller.requestPrMerge({
|
|
204
|
+
runRef: 'run-merge-2',
|
|
205
|
+
prRef: 'pr-99',
|
|
206
|
+
statusChecks: [
|
|
207
|
+
{ name: 'ci/build', state: 'success' },
|
|
208
|
+
{ name: 'ci/tests', state: 'failure' }
|
|
209
|
+
],
|
|
210
|
+
requestedBy: 'agent-stack-4',
|
|
211
|
+
namespace: 'krate-org-default',
|
|
212
|
+
organizationRef: 'default'
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
assert.equal(result.error, true, 'Should fail');
|
|
216
|
+
assert.equal(result.reason, 'status-checks-failing');
|
|
217
|
+
assert.ok(result.message.includes('ci/tests'), 'Message should name the failing check');
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test('requestPrMerge accepts when all status checks pass', () => {
|
|
221
|
+
const controller = createAgentWritebackController();
|
|
222
|
+
const result = controller.requestPrMerge({
|
|
223
|
+
runRef: 'run-merge-3',
|
|
224
|
+
prRef: 'pr-77',
|
|
225
|
+
statusChecks: [
|
|
226
|
+
{ name: 'ci/build', state: 'success' },
|
|
227
|
+
{ name: 'ci/lint', state: 'success' },
|
|
228
|
+
{ name: 'security/scan', state: 'success' }
|
|
229
|
+
],
|
|
230
|
+
requestedBy: 'agent-stack-5',
|
|
231
|
+
namespace: 'krate-org-default',
|
|
232
|
+
organizationRef: 'default'
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
assert.equal(result.error, false, 'Should succeed when all checks pass');
|
|
236
|
+
assert.ok(result.mergeRequest, 'Should return a merge request');
|
|
237
|
+
assert.equal(result.mergeRequest.spec.prRef, 'pr-77');
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// --- validateWriteIntent ---
|
|
241
|
+
|
|
242
|
+
test('validateWriteIntent accepts valid write intent (artifact, push, or merge)', () => {
|
|
243
|
+
const controller = createAgentWritebackController();
|
|
244
|
+
const intent = {
|
|
245
|
+
kind: 'AgentWriteIntent',
|
|
246
|
+
metadata: { name: 'intent-valid', namespace: 'krate-org-default' },
|
|
247
|
+
spec: { runRef: 'run-1', writeType: 'artifact', requestedBy: 'agent-1' },
|
|
248
|
+
status: { approvalStatus: 'pending', phase: 'Pending' }
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const result = controller.validateWriteIntent({ intent });
|
|
252
|
+
assert.equal(result.valid, true, 'Should be valid for artifact type');
|
|
253
|
+
assert.equal(result.error, false);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test('validateWriteIntent rejects unknown write type', () => {
|
|
257
|
+
const controller = createAgentWritebackController();
|
|
258
|
+
const intent = {
|
|
259
|
+
kind: 'AgentWriteIntent',
|
|
260
|
+
metadata: { name: 'intent-bad', namespace: 'krate-org-default' },
|
|
261
|
+
spec: { runRef: 'run-1', writeType: 'unknown-type', requestedBy: 'agent-1' },
|
|
262
|
+
status: { approvalStatus: 'pending', phase: 'Pending' }
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const result = controller.validateWriteIntent({ intent });
|
|
266
|
+
assert.equal(result.valid, false, 'Should be invalid');
|
|
267
|
+
assert.equal(result.error, true);
|
|
268
|
+
assert.ok(result.message.includes('unknown-type'), 'Message should mention the bad type');
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// --- getWriteIntentStatus ---
|
|
272
|
+
|
|
273
|
+
test('getWriteIntentStatus returns current approval/execution status', () => {
|
|
274
|
+
const controller = createAgentWritebackController();
|
|
275
|
+
const intent = {
|
|
276
|
+
kind: 'AgentWriteIntent',
|
|
277
|
+
metadata: { name: 'intent-status-1', namespace: 'krate-org-default' },
|
|
278
|
+
spec: { runRef: 'run-1', writeType: 'branch-push', branch: 'main', targetRepo: 'org/repo', requestedBy: 'agent-1' },
|
|
279
|
+
status: { approvalStatus: 'approved', phase: 'Executing', approvedBy: 'owner', approvedAt: new Date().toISOString() }
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
const result = controller.getWriteIntentStatus({
|
|
283
|
+
intentName: 'intent-status-1',
|
|
284
|
+
resources: { AgentWriteIntent: [intent] }
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
assert.equal(result.error, false, 'Should succeed');
|
|
288
|
+
assert.equal(result.approvalStatus, 'approved');
|
|
289
|
+
assert.equal(result.phase, 'Executing');
|
|
290
|
+
assert.equal(result.approvedBy, 'owner');
|
|
291
|
+
assert.ok(result.intent, 'Should return the intent resource');
|
|
292
|
+
});
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* B1: Approval persistence tests
|
|
3
|
+
* Tests for persistApproval (saves AgentApproval via applyResource) and
|
|
4
|
+
* enforceApproval (gates dispatch when approval is pending/missing).
|
|
5
|
+
*/
|
|
6
|
+
import assert from 'node:assert/strict';
|
|
7
|
+
import test from 'node:test';
|
|
8
|
+
import { createAgentApprovalController, createResource } from '../src/index.js';
|
|
9
|
+
|
|
10
|
+
function makeApproval(name, dispatchRun, action, phase = 'Pending') {
|
|
11
|
+
const approval = createResource('AgentApproval', { name, namespace: 'krate-org-default' }, {
|
|
12
|
+
organizationRef: 'default',
|
|
13
|
+
dispatchRun,
|
|
14
|
+
action,
|
|
15
|
+
requestedBy: 'agent-stack-1',
|
|
16
|
+
description: `Request to perform: ${action}`,
|
|
17
|
+
requestedAt: new Date().toISOString()
|
|
18
|
+
});
|
|
19
|
+
approval.status = { phase, createdAt: new Date().toISOString() };
|
|
20
|
+
return approval;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// --- persistApproval ---
|
|
24
|
+
|
|
25
|
+
test('persistApproval calls applyResource with the approval resource', async () => {
|
|
26
|
+
const controller = createAgentApprovalController();
|
|
27
|
+
const approval = makeApproval('approval-persist-1', 'run-persist-1', 'tool-use', 'Pending');
|
|
28
|
+
|
|
29
|
+
const applied = [];
|
|
30
|
+
const mockApplyResource = async (resource) => {
|
|
31
|
+
applied.push(resource);
|
|
32
|
+
return { ok: true, resource };
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const result = await controller.persistApproval({ approval, applyResource: mockApplyResource });
|
|
36
|
+
|
|
37
|
+
assert.equal(result.error, false, 'Should succeed');
|
|
38
|
+
assert.equal(applied.length, 1, 'applyResource should be called exactly once');
|
|
39
|
+
assert.equal(applied[0].kind, 'AgentApproval', 'Applied resource should be AgentApproval');
|
|
40
|
+
assert.equal(applied[0].metadata.name, 'approval-persist-1');
|
|
41
|
+
assert.equal(applied[0].spec.dispatchRun, 'run-persist-1');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('persistApproval returns error when applyResource throws', async () => {
|
|
45
|
+
const controller = createAgentApprovalController();
|
|
46
|
+
const approval = makeApproval('approval-persist-2', 'run-persist-2', 'secret-access', 'Pending');
|
|
47
|
+
|
|
48
|
+
const mockApplyResource = async () => {
|
|
49
|
+
throw new Error('Kubernetes API unavailable');
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const result = await controller.persistApproval({ approval, applyResource: mockApplyResource });
|
|
53
|
+
|
|
54
|
+
assert.equal(result.error, true, 'Should return error when applyResource throws');
|
|
55
|
+
assert.equal(result.reason, 'persist-failed');
|
|
56
|
+
assert.ok(result.message, 'Should include error message');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('persistApproval returns error when approval is missing', async () => {
|
|
60
|
+
const controller = createAgentApprovalController();
|
|
61
|
+
|
|
62
|
+
const mockApplyResource = async () => ({ ok: true });
|
|
63
|
+
const result = await controller.persistApproval({ approval: null, applyResource: mockApplyResource });
|
|
64
|
+
|
|
65
|
+
assert.equal(result.error, true, 'Should fail when approval is null');
|
|
66
|
+
assert.equal(result.reason, 'missing-approval');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('persistApproval returns error when applyResource is not provided', async () => {
|
|
70
|
+
const controller = createAgentApprovalController();
|
|
71
|
+
const approval = makeApproval('approval-persist-3', 'run-persist-3', 'release', 'Pending');
|
|
72
|
+
|
|
73
|
+
const result = await controller.persistApproval({ approval, applyResource: null });
|
|
74
|
+
|
|
75
|
+
assert.equal(result.error, true, 'Should fail without applyResource');
|
|
76
|
+
assert.equal(result.reason, 'missing-apply-resource');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// --- enforceApproval ---
|
|
80
|
+
|
|
81
|
+
test('enforceApproval blocks dispatch when no approval exists for the action', () => {
|
|
82
|
+
const controller = createAgentApprovalController();
|
|
83
|
+
|
|
84
|
+
const result = controller.enforceApproval({
|
|
85
|
+
dispatchRun: 'run-enforce-1',
|
|
86
|
+
action: 'write-back',
|
|
87
|
+
resources: {}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
assert.equal(result.allowed, false, 'Should block when no approval exists');
|
|
91
|
+
assert.equal(result.reason, 'no-approval-found');
|
|
92
|
+
assert.ok(result.message, 'Should include blocking reason message');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('enforceApproval blocks dispatch when approval is still Pending', () => {
|
|
96
|
+
const controller = createAgentApprovalController();
|
|
97
|
+
const pending = makeApproval('approval-enforce-1', 'run-enforce-2', 'secret-access', 'Pending');
|
|
98
|
+
|
|
99
|
+
const result = controller.enforceApproval({
|
|
100
|
+
dispatchRun: 'run-enforce-2',
|
|
101
|
+
action: 'secret-access',
|
|
102
|
+
resources: { AgentApproval: [pending] }
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
assert.equal(result.allowed, false, 'Should block when approval is Pending');
|
|
106
|
+
assert.equal(result.reason, 'approval-pending');
|
|
107
|
+
assert.ok(result.message, 'Should include blocking reason');
|
|
108
|
+
assert.ok(result.approval, 'Should return the approval resource');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('enforceApproval allows dispatch when approval is Approved', () => {
|
|
112
|
+
const controller = createAgentApprovalController();
|
|
113
|
+
const approved = makeApproval('approval-enforce-2', 'run-enforce-3', 'tool-use', 'Approved');
|
|
114
|
+
approved.status.decidedBy = 'owner';
|
|
115
|
+
approved.status.decidedAt = new Date().toISOString();
|
|
116
|
+
|
|
117
|
+
const result = controller.enforceApproval({
|
|
118
|
+
dispatchRun: 'run-enforce-3',
|
|
119
|
+
action: 'tool-use',
|
|
120
|
+
resources: { AgentApproval: [approved] }
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
assert.equal(result.allowed, true, 'Should allow when approval is Approved');
|
|
124
|
+
assert.ok(result.approval, 'Should return the approval resource');
|
|
125
|
+
assert.equal(result.approval.status.phase, 'Approved');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('enforceApproval blocks dispatch when approval was Denied', () => {
|
|
129
|
+
const controller = createAgentApprovalController();
|
|
130
|
+
const denied = makeApproval('approval-enforce-3', 'run-enforce-4', 'release', 'Denied');
|
|
131
|
+
denied.status.decidedBy = 'admin';
|
|
132
|
+
denied.status.decidedAt = new Date().toISOString();
|
|
133
|
+
|
|
134
|
+
const result = controller.enforceApproval({
|
|
135
|
+
dispatchRun: 'run-enforce-4',
|
|
136
|
+
action: 'release',
|
|
137
|
+
resources: { AgentApproval: [denied] }
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
assert.equal(result.allowed, false, 'Should block when approval was Denied');
|
|
141
|
+
assert.equal(result.reason, 'approval-denied');
|
|
142
|
+
assert.ok(result.approval, 'Should return the denial record');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('persistApproval passes applyResource result back in response', async () => {
|
|
146
|
+
const controller = createAgentApprovalController();
|
|
147
|
+
const approval = makeApproval('approval-persist-4', 'run-persist-4', 'escalation', 'Pending');
|
|
148
|
+
|
|
149
|
+
const mockApplyResult = { ok: true, uid: 'test-uid-1234', resource: approval };
|
|
150
|
+
const mockApplyResource = async () => mockApplyResult;
|
|
151
|
+
|
|
152
|
+
const result = await controller.persistApproval({ approval, applyResource: mockApplyResource });
|
|
153
|
+
|
|
154
|
+
assert.equal(result.error, false, 'Should succeed');
|
|
155
|
+
assert.deepEqual(result.applyResult, mockApplyResult, 'Should include applyResource result');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test('enforceApproval checks the correct action type (no cross-action interference)', () => {
|
|
159
|
+
const controller = createAgentApprovalController();
|
|
160
|
+
// Approval for 'tool-use' but checking 'secret-access' — should block
|
|
161
|
+
const wrongAction = makeApproval('approval-enforce-5', 'run-enforce-5', 'tool-use', 'Approved');
|
|
162
|
+
|
|
163
|
+
const result = controller.enforceApproval({
|
|
164
|
+
dispatchRun: 'run-enforce-5',
|
|
165
|
+
action: 'secret-access',
|
|
166
|
+
resources: { AgentApproval: [wrongAction] }
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
assert.equal(result.allowed, false, 'Should block when matching action is not found');
|
|
170
|
+
assert.equal(result.reason, 'no-approval-found');
|
|
171
|
+
});
|