@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,302 @@
|
|
|
1
|
+
import { createResource, clone } from './resource-model.js';
|
|
2
|
+
|
|
3
|
+
export const AGENT_WRITEBACK_CONTROLLER_BOUNDARY = {
|
|
4
|
+
role: 'agent-writeback-controller',
|
|
5
|
+
scope: 'KrateArtifact write pipeline, branch push approval flow, and PR merge with status check enforcement',
|
|
6
|
+
owns: ['artifact creation', 'artifact listing', 'branch push requests', 'push approval/denial', 'PR merge requests', 'write intent validation', 'write intent status'],
|
|
7
|
+
delegatesTo: ['resource-model'],
|
|
8
|
+
mustNotOwn: ['git operations', 'secret values', 'agent execution', 'CI orchestration']
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const VALID_WRITE_TYPES = new Set(['artifact', 'branch-push', 'pr-merge']);
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* In-memory store of AgentWriteIntent records (plain objects, not K8s CRDs,
|
|
15
|
+
* because AgentWriteIntent is not in the canonical resource taxonomy).
|
|
16
|
+
* Controllers operate statelessly against caller-supplied resources maps;
|
|
17
|
+
* the write intent shape mirrors what would live in an aggregated store.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export function createAgentWritebackController() {
|
|
21
|
+
return {
|
|
22
|
+
role: 'agent-writeback-controller',
|
|
23
|
+
|
|
24
|
+
// -----------------------------------------------------------------------
|
|
25
|
+
// Artifacts
|
|
26
|
+
// -----------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
createArtifact({ name, runRef, contentRef, kind = 'output', namespace = 'default', organizationRef = 'default' }) {
|
|
29
|
+
if (!runRef) {
|
|
30
|
+
return { error: true, reason: 'missing-run-ref', message: 'runRef is required to link an artifact to a dispatch run' };
|
|
31
|
+
}
|
|
32
|
+
if (!contentRef) {
|
|
33
|
+
return { error: true, reason: 'missing-content-ref', message: 'contentRef is required to identify the artifact content' };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const now = new Date().toISOString();
|
|
37
|
+
const artifactName = name || `artifact-${runRef}-${Date.now()}`;
|
|
38
|
+
|
|
39
|
+
const artifact = createResource('KrateArtifact', { name: artifactName, namespace }, {
|
|
40
|
+
organizationRef,
|
|
41
|
+
dispatchRun: runRef,
|
|
42
|
+
kind,
|
|
43
|
+
digest: contentRef
|
|
44
|
+
});
|
|
45
|
+
artifact.status = { phase: 'Available', createdAt: now };
|
|
46
|
+
|
|
47
|
+
return { error: false, artifact };
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
validateArtifact({ artifact }) {
|
|
51
|
+
if (!artifact || typeof artifact !== 'object') {
|
|
52
|
+
return { valid: false, error: true, reason: 'invalid-artifact', message: 'artifact must be an object' };
|
|
53
|
+
}
|
|
54
|
+
if (artifact.kind !== 'KrateArtifact') {
|
|
55
|
+
return { valid: false, error: true, reason: 'wrong-kind', message: `Expected KrateArtifact, got: ${artifact.kind}` };
|
|
56
|
+
}
|
|
57
|
+
if (!artifact.spec?.dispatchRun) {
|
|
58
|
+
return { valid: false, error: true, reason: 'missing-run-ref', message: 'artifact spec.dispatchRun is required' };
|
|
59
|
+
}
|
|
60
|
+
if (!artifact.spec?.digest) {
|
|
61
|
+
return { valid: false, error: true, reason: 'missing-digest', message: 'artifact spec.digest is required' };
|
|
62
|
+
}
|
|
63
|
+
return { valid: true, error: false };
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
listArtifactsForRun({ runRef, resources = {} }) {
|
|
67
|
+
const artifacts = resources.KrateArtifact || [];
|
|
68
|
+
return artifacts.filter((a) => a.spec?.dispatchRun === runRef).map(clone);
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
// -----------------------------------------------------------------------
|
|
72
|
+
// Branch push approval flow
|
|
73
|
+
// -----------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
requestBranchPush({ runRef, branch, targetRepo, requestedBy, namespace = 'default', organizationRef = 'default' }) {
|
|
76
|
+
if (!runRef) {
|
|
77
|
+
return { error: true, reason: 'missing-run-ref', message: 'runRef is required' };
|
|
78
|
+
}
|
|
79
|
+
if (!branch) {
|
|
80
|
+
return { error: true, reason: 'missing-branch', message: 'branch is required' };
|
|
81
|
+
}
|
|
82
|
+
if (!targetRepo) {
|
|
83
|
+
return { error: true, reason: 'missing-target-repo', message: 'targetRepo is required' };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const now = new Date().toISOString();
|
|
87
|
+
const intentName = `push-${runRef}-${Date.now()}`;
|
|
88
|
+
|
|
89
|
+
const pushRequest = {
|
|
90
|
+
kind: 'AgentWriteIntent',
|
|
91
|
+
metadata: { name: intentName, namespace, labels: {}, annotations: {} },
|
|
92
|
+
spec: { organizationRef, runRef, branch, targetRepo, writeType: 'branch-push', requestedBy: requestedBy || 'unknown', requestedAt: now },
|
|
93
|
+
status: { approvalStatus: 'pending', phase: 'Pending', createdAt: now }
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
return { error: false, pushRequest };
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
approveBranchPush({ intentName, approvedBy, reason, resources = {} }) {
|
|
100
|
+
if (!intentName) {
|
|
101
|
+
return { error: true, reason: 'missing-intent-name', message: 'intentName is required' };
|
|
102
|
+
}
|
|
103
|
+
if (!approvedBy) {
|
|
104
|
+
return { error: true, reason: 'missing-approved-by', message: 'approvedBy is required' };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const intents = resources.AgentWriteIntent || [];
|
|
108
|
+
const intent = intents.find((i) => i.metadata?.name === intentName);
|
|
109
|
+
if (!intent) {
|
|
110
|
+
return { error: true, reason: 'not-found', message: `AgentWriteIntent not found: ${intentName}` };
|
|
111
|
+
}
|
|
112
|
+
if (intent.status?.approvalStatus !== 'pending') {
|
|
113
|
+
return { error: true, reason: 'already-decided', message: `AgentWriteIntent ${intentName} has already been decided: ${intent.status?.approvalStatus}` };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const now = new Date().toISOString();
|
|
117
|
+
const pushRequest = clone(intent);
|
|
118
|
+
pushRequest.status = {
|
|
119
|
+
...pushRequest.status,
|
|
120
|
+
approvalStatus: 'approved',
|
|
121
|
+
phase: 'Approved',
|
|
122
|
+
approvedBy,
|
|
123
|
+
approvedAt: now,
|
|
124
|
+
reason: reason || undefined
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
return { error: false, pushRequest };
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
denyBranchPush({ intentName, deniedBy, reason, resources = {} }) {
|
|
131
|
+
if (!intentName) {
|
|
132
|
+
return { error: true, reason: 'missing-intent-name', message: 'intentName is required' };
|
|
133
|
+
}
|
|
134
|
+
if (!deniedBy) {
|
|
135
|
+
return { error: true, reason: 'missing-denied-by', message: 'deniedBy is required' };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const intents = resources.AgentWriteIntent || [];
|
|
139
|
+
const intent = intents.find((i) => i.metadata?.name === intentName);
|
|
140
|
+
if (!intent) {
|
|
141
|
+
return { error: true, reason: 'not-found', message: `AgentWriteIntent not found: ${intentName}` };
|
|
142
|
+
}
|
|
143
|
+
if (intent.status?.approvalStatus !== 'pending') {
|
|
144
|
+
return { error: true, reason: 'already-decided', message: `AgentWriteIntent ${intentName} has already been decided: ${intent.status?.approvalStatus}` };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const now = new Date().toISOString();
|
|
148
|
+
const pushRequest = clone(intent);
|
|
149
|
+
pushRequest.status = {
|
|
150
|
+
...pushRequest.status,
|
|
151
|
+
approvalStatus: 'denied',
|
|
152
|
+
phase: 'Denied',
|
|
153
|
+
deniedBy,
|
|
154
|
+
deniedAt: now,
|
|
155
|
+
reason: reason || undefined
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
return { error: false, pushRequest };
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
// -----------------------------------------------------------------------
|
|
162
|
+
// PR merge with status check enforcement
|
|
163
|
+
// -----------------------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
requestPrMerge({ runRef, prRef, statusChecks = [], requestedBy, namespace = 'default', organizationRef = 'default' }) {
|
|
166
|
+
if (!runRef) {
|
|
167
|
+
return { error: true, reason: 'missing-run-ref', message: 'runRef is required' };
|
|
168
|
+
}
|
|
169
|
+
if (!prRef) {
|
|
170
|
+
return { error: true, reason: 'missing-pr-ref', message: 'prRef is required' };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Enforce status check gate — reject if any check is not 'success'
|
|
174
|
+
const failing = statusChecks.filter((c) => c.state !== 'success');
|
|
175
|
+
if (failing.length > 0) {
|
|
176
|
+
const names = failing.map((c) => c.name).join(', ');
|
|
177
|
+
return { error: true, reason: 'status-checks-failing', message: `The following status checks are not passing: ${names}` };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const now = new Date().toISOString();
|
|
181
|
+
const intentName = `merge-${runRef}-${Date.now()}`;
|
|
182
|
+
|
|
183
|
+
const mergeRequest = {
|
|
184
|
+
kind: 'AgentWriteIntent',
|
|
185
|
+
metadata: { name: intentName, namespace, labels: {}, annotations: {} },
|
|
186
|
+
spec: { organizationRef, runRef, prRef, writeType: 'pr-merge', statusChecks: clone(statusChecks), requestedBy: requestedBy || 'unknown', requestedAt: now },
|
|
187
|
+
status: { approvalStatus: 'pending', phase: 'Pending', createdAt: now }
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
return { error: false, mergeRequest };
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
// -----------------------------------------------------------------------
|
|
194
|
+
// Write intent validation & status
|
|
195
|
+
// -----------------------------------------------------------------------
|
|
196
|
+
|
|
197
|
+
validateWriteIntent({ intent }) {
|
|
198
|
+
if (!intent || typeof intent !== 'object') {
|
|
199
|
+
return { valid: false, error: true, reason: 'invalid-intent', message: 'intent must be an object' };
|
|
200
|
+
}
|
|
201
|
+
const writeType = intent.spec?.writeType;
|
|
202
|
+
if (!writeType || !VALID_WRITE_TYPES.has(writeType)) {
|
|
203
|
+
return { valid: false, error: true, reason: 'invalid-write-type', message: `Unknown write type: ${writeType}. Must be one of: ${[...VALID_WRITE_TYPES].join(', ')}` };
|
|
204
|
+
}
|
|
205
|
+
if (!intent.spec?.runRef) {
|
|
206
|
+
return { valid: false, error: true, reason: 'missing-run-ref', message: 'intent spec.runRef is required' };
|
|
207
|
+
}
|
|
208
|
+
return { valid: true, error: false };
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
getWriteIntentStatus({ intentName, resources = {} }) {
|
|
212
|
+
if (!intentName) {
|
|
213
|
+
return { error: true, reason: 'missing-intent-name', message: 'intentName is required' };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const intents = resources.AgentWriteIntent || [];
|
|
217
|
+
const intent = intents.find((i) => i.metadata?.name === intentName);
|
|
218
|
+
if (!intent) {
|
|
219
|
+
return { error: true, reason: 'not-found', message: `AgentWriteIntent not found: ${intentName}` };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
error: false,
|
|
224
|
+
intent: clone(intent),
|
|
225
|
+
approvalStatus: intent.status?.approvalStatus || 'pending',
|
|
226
|
+
phase: intent.status?.phase || 'Pending',
|
|
227
|
+
approvedBy: intent.status?.approvedBy || null,
|
|
228
|
+
deniedBy: intent.status?.deniedBy || null,
|
|
229
|
+
approvedAt: intent.status?.approvedAt || null,
|
|
230
|
+
deniedAt: intent.status?.deniedAt || null,
|
|
231
|
+
reason: intent.status?.reason || null
|
|
232
|
+
};
|
|
233
|
+
},
|
|
234
|
+
|
|
235
|
+
// -----------------------------------------------------------------------
|
|
236
|
+
// B2: Persistence
|
|
237
|
+
// -----------------------------------------------------------------------
|
|
238
|
+
|
|
239
|
+
async persistWriteIntent({ intent, applyResource }) {
|
|
240
|
+
if (!intent) {
|
|
241
|
+
return { error: true, reason: 'missing-intent', message: 'intent is required' };
|
|
242
|
+
}
|
|
243
|
+
if (typeof applyResource !== 'function') {
|
|
244
|
+
return { error: true, reason: 'missing-apply-resource', message: 'applyResource function is required' };
|
|
245
|
+
}
|
|
246
|
+
try {
|
|
247
|
+
const applyResult = await applyResource(intent);
|
|
248
|
+
return { error: false, intent, applyResult };
|
|
249
|
+
} catch (err) {
|
|
250
|
+
return { error: true, reason: 'persist-failed', message: err?.message || 'applyResource failed' };
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
|
|
254
|
+
// -----------------------------------------------------------------------
|
|
255
|
+
// B2: Execution pipeline
|
|
256
|
+
// -----------------------------------------------------------------------
|
|
257
|
+
|
|
258
|
+
async executeWriteIntent({ intent, gateway }) {
|
|
259
|
+
if (!intent) {
|
|
260
|
+
return { error: true, reason: 'missing-intent', message: 'intent is required' };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const approvalStatus = intent.status?.approvalStatus;
|
|
264
|
+
if (approvalStatus !== 'approved') {
|
|
265
|
+
return { error: true, reason: 'not-approved', message: `WriteIntent is not approved (current status: ${approvalStatus})` };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const writeType = intent.spec?.writeType;
|
|
269
|
+
|
|
270
|
+
try {
|
|
271
|
+
if (writeType === 'branch-push') {
|
|
272
|
+
const executionResult = await gateway.pushBranch({
|
|
273
|
+
branch: intent.spec.branch,
|
|
274
|
+
targetRepo: intent.spec.targetRepo,
|
|
275
|
+
runRef: intent.spec.runRef
|
|
276
|
+
});
|
|
277
|
+
return { error: false, executionResult };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (writeType === 'pr-merge') {
|
|
281
|
+
// Validate status checks before merging
|
|
282
|
+
const statusChecks = intent.spec?.statusChecks || [];
|
|
283
|
+
const failing = statusChecks.filter((c) => c.state !== 'success');
|
|
284
|
+
if (failing.length > 0) {
|
|
285
|
+
const names = failing.map((c) => c.name).join(', ');
|
|
286
|
+
return { error: true, reason: 'status-checks-failing', message: `The following status checks are not passing: ${names}` };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const executionResult = await gateway.mergePr({
|
|
290
|
+
prRef: intent.spec.prRef,
|
|
291
|
+
runRef: intent.spec.runRef
|
|
292
|
+
});
|
|
293
|
+
return { error: false, executionResult };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return { error: true, reason: 'unsupported-write-type', message: `Execution not supported for writeType: ${writeType}` };
|
|
297
|
+
} catch (err) {
|
|
298
|
+
return { error: true, reason: 'execution-failed', message: err?.message || 'Gateway execution failed' };
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
}
|