@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,282 @@
|
|
|
1
|
+
// Agent Secret/Config Grant Controller — Secret & ConfigMap grant lifecycle
|
|
2
|
+
//
|
|
3
|
+
// Provides:
|
|
4
|
+
// - createAgentSecretGrantController() — validates AgentSecretGrant resources
|
|
5
|
+
// - createAgentConfigGrantController() — validates AgentConfigGrant resources
|
|
6
|
+
// - listGrantsForAgent(agentRef) — filter grants by target agent
|
|
7
|
+
// - revokeGrant(grantName) — mark a grant as revoked
|
|
8
|
+
|
|
9
|
+
import { createResource, clone } from './resource-model.js';
|
|
10
|
+
|
|
11
|
+
export const AGENT_SECRET_GRANT_CONTROLLER_BOUNDARY = {
|
|
12
|
+
role: 'agent-secret-grant-controller',
|
|
13
|
+
scope: 'AgentSecretGrant lifecycle — creation, listing, revocation for agent-to-secret access grants',
|
|
14
|
+
owns: ['grant creation', 'grant listing', 'grant revocation', 'input validation'],
|
|
15
|
+
delegatesTo: ['resource-model'],
|
|
16
|
+
mustNotOwn: ['Kubernetes Secret storage', 'agent execution', 'secret value decryption', 'UI rendering']
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const AGENT_CONFIG_GRANT_CONTROLLER_BOUNDARY = {
|
|
20
|
+
role: 'agent-config-grant-controller',
|
|
21
|
+
scope: 'AgentConfigGrant lifecycle — creation, listing, revocation for agent-to-configmap access grants',
|
|
22
|
+
owns: ['grant creation', 'grant listing', 'grant revocation', 'input validation'],
|
|
23
|
+
delegatesTo: ['resource-model'],
|
|
24
|
+
mustNotOwn: ['Kubernetes ConfigMap storage', 'agent execution', 'UI rendering']
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const VALID_PERMISSIONS = new Set(['read', 'use', 'mount']);
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// Validation helpers
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Validate an AgentSecretGrant input.
|
|
35
|
+
*
|
|
36
|
+
* @param {object} input
|
|
37
|
+
* @returns {{ valid: boolean, errors: string[] }}
|
|
38
|
+
*/
|
|
39
|
+
export function validateAgentSecretGrant(input) {
|
|
40
|
+
const errors = [];
|
|
41
|
+
if (!input) return { valid: false, errors: ['input must not be null or undefined'] };
|
|
42
|
+
if (!input.name || typeof input.name !== 'string') errors.push('name is required and must be a non-empty string');
|
|
43
|
+
if (!input.orgRef || typeof input.orgRef !== 'string') errors.push('orgRef is required and must be a non-empty string');
|
|
44
|
+
if (!input.secretName || typeof input.secretName !== 'string') errors.push('secretName is required and must be a non-empty string');
|
|
45
|
+
if (!input.grantedTo || typeof input.grantedTo !== 'string') errors.push('grantedTo is required and must be a non-empty string');
|
|
46
|
+
if (!Array.isArray(input.permissions) || input.permissions.length === 0) {
|
|
47
|
+
errors.push('permissions is required and must be a non-empty array');
|
|
48
|
+
} else {
|
|
49
|
+
const invalid = input.permissions.filter((p) => !VALID_PERMISSIONS.has(p));
|
|
50
|
+
if (invalid.length > 0) errors.push(`invalid permissions: ${invalid.join(', ')}. Valid values: ${[...VALID_PERMISSIONS].join(', ')}`);
|
|
51
|
+
}
|
|
52
|
+
return { valid: errors.length === 0, errors };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Validate an AgentConfigGrant input.
|
|
57
|
+
*
|
|
58
|
+
* @param {object} input
|
|
59
|
+
* @returns {{ valid: boolean, errors: string[] }}
|
|
60
|
+
*/
|
|
61
|
+
export function validateAgentConfigGrant(input) {
|
|
62
|
+
const errors = [];
|
|
63
|
+
if (!input) return { valid: false, errors: ['input must not be null or undefined'] };
|
|
64
|
+
if (!input.name || typeof input.name !== 'string') errors.push('name is required and must be a non-empty string');
|
|
65
|
+
if (!input.orgRef || typeof input.orgRef !== 'string') errors.push('orgRef is required and must be a non-empty string');
|
|
66
|
+
if (!input.configMapName || typeof input.configMapName !== 'string') errors.push('configMapName is required and must be a non-empty string');
|
|
67
|
+
if (!input.grantedTo || typeof input.grantedTo !== 'string') errors.push('grantedTo is required and must be a non-empty string');
|
|
68
|
+
return { valid: errors.length === 0, errors };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// listGrantsForAgent — shared utility
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Filter grants by the grantedTo (agent ref) field from a collection of grant resources.
|
|
77
|
+
*
|
|
78
|
+
* @param {object[]} grants Array of AgentSecretGrant or AgentConfigGrant resources
|
|
79
|
+
* @param {string} agentRef The agent identifier to filter by
|
|
80
|
+
* @returns {object[]}
|
|
81
|
+
*/
|
|
82
|
+
export function listGrantsForAgent(grants, agentRef) {
|
|
83
|
+
if (!agentRef) return [];
|
|
84
|
+
return grants.filter((g) => g.spec?.grantedTo === agentRef || g.spec?.subject === agentRef);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
// revokeGrant — shared utility
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Mark a grant as revoked by returning an updated copy of the resource.
|
|
93
|
+
*
|
|
94
|
+
* @param {object[]} grants Array of grant resources
|
|
95
|
+
* @param {string} grantName Name of the grant to revoke
|
|
96
|
+
* @returns {{ grant: object } | { error: true, reason: string, message: string }}
|
|
97
|
+
*/
|
|
98
|
+
export function revokeGrant(grants, grantName) {
|
|
99
|
+
if (!grantName) return { error: true, reason: 'missing-name', message: 'grantName is required' };
|
|
100
|
+
|
|
101
|
+
const found = grants.find((g) => g.metadata?.name === grantName);
|
|
102
|
+
if (!found) return { error: true, reason: 'not-found', message: `Grant not found: ${grantName}` };
|
|
103
|
+
|
|
104
|
+
if (found.status?.phase === 'Revoked') {
|
|
105
|
+
return { error: true, reason: 'already-revoked', message: `Grant "${grantName}" is already revoked` };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const updated = clone(found);
|
|
109
|
+
updated.status = {
|
|
110
|
+
...updated.status,
|
|
111
|
+
phase: 'Revoked',
|
|
112
|
+
revokedAt: new Date().toISOString()
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
return { grant: updated };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
// createAgentSecretGrantController
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Create a controller for AgentSecretGrant resources.
|
|
124
|
+
*
|
|
125
|
+
* @param {{ persistFn?: (resource: object) => Promise<any> }} [opts]
|
|
126
|
+
* @returns {object}
|
|
127
|
+
*/
|
|
128
|
+
export function createAgentSecretGrantController({ persistFn } = {}) {
|
|
129
|
+
function persist(resource) {
|
|
130
|
+
if (typeof persistFn === 'function') {
|
|
131
|
+
Promise.resolve(persistFn(resource)).catch(() => {});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
role: 'agent-secret-grant-controller',
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Create an AgentSecretGrant resource.
|
|
140
|
+
*
|
|
141
|
+
* @param {{ name, orgRef, secretName, grantedTo, permissions, namespace?, keys? }} input
|
|
142
|
+
* @returns {{ grant: object } | { error: true, message: string }}
|
|
143
|
+
*/
|
|
144
|
+
createSecretGrant({
|
|
145
|
+
name,
|
|
146
|
+
orgRef,
|
|
147
|
+
secretName,
|
|
148
|
+
grantedTo,
|
|
149
|
+
permissions = ['read'],
|
|
150
|
+
namespace = 'default',
|
|
151
|
+
keys = []
|
|
152
|
+
}) {
|
|
153
|
+
const validation = validateAgentSecretGrant({ name, orgRef, secretName, grantedTo, permissions });
|
|
154
|
+
if (!validation.valid) {
|
|
155
|
+
return { error: true, message: validation.errors.join('; ') };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const now = new Date().toISOString();
|
|
159
|
+
const grant = createResource('AgentSecretGrant', { name, namespace }, {
|
|
160
|
+
organizationRef: orgRef,
|
|
161
|
+
orgRef,
|
|
162
|
+
secretName,
|
|
163
|
+
secretRef: secretName,
|
|
164
|
+
grantedTo,
|
|
165
|
+
subject: grantedTo,
|
|
166
|
+
permissions,
|
|
167
|
+
keys,
|
|
168
|
+
purpose: permissions.join(',')
|
|
169
|
+
});
|
|
170
|
+
grant.status = { phase: 'Active', createdAt: now };
|
|
171
|
+
|
|
172
|
+
persist(grant);
|
|
173
|
+
return { grant };
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* List all grants for a specific agent.
|
|
178
|
+
*
|
|
179
|
+
* @param {string} agentRef
|
|
180
|
+
* @param {object[]} grants
|
|
181
|
+
* @returns {object[]}
|
|
182
|
+
*/
|
|
183
|
+
listGrantsForAgent(agentRef, grants = []) {
|
|
184
|
+
return listGrantsForAgent(grants, agentRef);
|
|
185
|
+
},
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Revoke a grant by name.
|
|
189
|
+
*
|
|
190
|
+
* @param {string} grantName
|
|
191
|
+
* @param {object[]} grants
|
|
192
|
+
* @returns {{ grant: object } | { error: true, reason: string, message: string }}
|
|
193
|
+
*/
|
|
194
|
+
revokeGrant(grantName, grants = []) {
|
|
195
|
+
const result = revokeGrant(grants, grantName);
|
|
196
|
+
if (!result.error && result.grant) persist(result.grant);
|
|
197
|
+
return result;
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
// createAgentConfigGrantController
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Create a controller for AgentConfigGrant resources.
|
|
208
|
+
*
|
|
209
|
+
* @param {{ persistFn?: (resource: object) => Promise<any> }} [opts]
|
|
210
|
+
* @returns {object}
|
|
211
|
+
*/
|
|
212
|
+
export function createAgentConfigGrantController({ persistFn } = {}) {
|
|
213
|
+
function persist(resource) {
|
|
214
|
+
if (typeof persistFn === 'function') {
|
|
215
|
+
Promise.resolve(persistFn(resource)).catch(() => {});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
role: 'agent-config-grant-controller',
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Create an AgentConfigGrant resource.
|
|
224
|
+
*
|
|
225
|
+
* @param {{ name, orgRef, configMapName, grantedTo, namespace?, keys? }} input
|
|
226
|
+
* @returns {{ grant: object } | { error: true, message: string }}
|
|
227
|
+
*/
|
|
228
|
+
createConfigGrant({
|
|
229
|
+
name,
|
|
230
|
+
orgRef,
|
|
231
|
+
configMapName,
|
|
232
|
+
grantedTo,
|
|
233
|
+
namespace = 'default',
|
|
234
|
+
keys = []
|
|
235
|
+
}) {
|
|
236
|
+
const validation = validateAgentConfigGrant({ name, orgRef, configMapName, grantedTo });
|
|
237
|
+
if (!validation.valid) {
|
|
238
|
+
return { error: true, message: validation.errors.join('; ') };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const now = new Date().toISOString();
|
|
242
|
+
const grant = createResource('AgentConfigGrant', { name, namespace }, {
|
|
243
|
+
organizationRef: orgRef,
|
|
244
|
+
orgRef,
|
|
245
|
+
configMapName,
|
|
246
|
+
configMapRef: configMapName,
|
|
247
|
+
grantedTo,
|
|
248
|
+
subject: grantedTo,
|
|
249
|
+
keys,
|
|
250
|
+
purpose: 'read'
|
|
251
|
+
});
|
|
252
|
+
grant.status = { phase: 'Active', createdAt: now };
|
|
253
|
+
|
|
254
|
+
persist(grant);
|
|
255
|
+
return { grant };
|
|
256
|
+
},
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* List all grants for a specific agent.
|
|
260
|
+
*
|
|
261
|
+
* @param {string} agentRef
|
|
262
|
+
* @param {object[]} grants
|
|
263
|
+
* @returns {object[]}
|
|
264
|
+
*/
|
|
265
|
+
listGrantsForAgent(agentRef, grants = []) {
|
|
266
|
+
return listGrantsForAgent(grants, agentRef);
|
|
267
|
+
},
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Revoke a grant by name.
|
|
271
|
+
*
|
|
272
|
+
* @param {string} grantName
|
|
273
|
+
* @param {object[]} grants
|
|
274
|
+
* @returns {{ grant: object } | { error: true, reason: string, message: string }}
|
|
275
|
+
*/
|
|
276
|
+
revokeGrant(grantName, grants = []) {
|
|
277
|
+
const result = revokeGrant(grants, grantName);
|
|
278
|
+
if (!result.error && result.grant) persist(result.grant);
|
|
279
|
+
return result;
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// Agent Session Transcript Controller — Slice 1.2e
|
|
2
|
+
// Manages AgentSessionTranscript resources: durable transcript storage,
|
|
3
|
+
// message indexing, and pagination support.
|
|
4
|
+
|
|
5
|
+
export const AGENT_SESSION_TRANSCRIPT_CONTROLLER_BOUNDARY = {
|
|
6
|
+
role: 'agent-session-transcript-controller',
|
|
7
|
+
scope: 'AgentSessionTranscript lifecycle: validation, message indexing, pagination, role/tool filtering',
|
|
8
|
+
owns: ['transcript validation', 'message indexing', 'pagination', 'role filter', 'tool name filter'],
|
|
9
|
+
delegatesTo: ['resource-model'],
|
|
10
|
+
mustNotOwn: ['session lifecycle', 'dispatch execution', 'Agent Mux sessions', 'secret values']
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const VALID_ROLES = ['user', 'assistant', 'tool', 'system'];
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Validate an AgentSessionTranscript resource. Returns { valid, errors }.
|
|
17
|
+
* @param {object} resource
|
|
18
|
+
* @returns {{ valid: boolean, errors: string[] }}
|
|
19
|
+
*/
|
|
20
|
+
export function validateAgentSessionTranscript(resource) {
|
|
21
|
+
const errors = [];
|
|
22
|
+
|
|
23
|
+
// Guard against null/undefined resource
|
|
24
|
+
if (resource == null) {
|
|
25
|
+
errors.push('resource must not be null or undefined');
|
|
26
|
+
return { valid: false, errors };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Validate metadata.name
|
|
30
|
+
if (!resource?.metadata?.name) {
|
|
31
|
+
errors.push('metadata.name is required');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const spec = resource?.spec || {};
|
|
35
|
+
|
|
36
|
+
// Validate organizationRef
|
|
37
|
+
if (!spec.organizationRef) {
|
|
38
|
+
errors.push('spec.organizationRef is required');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Validate sessionRef
|
|
42
|
+
if (!spec.sessionRef) {
|
|
43
|
+
errors.push('spec.sessionRef is required; provide the AgentSession ID this transcript is linked to');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Validate messages — must be an array (can be empty for a new transcript)
|
|
47
|
+
const messages = spec.messages;
|
|
48
|
+
if (!Array.isArray(messages)) {
|
|
49
|
+
errors.push('spec.messages must be an array');
|
|
50
|
+
} else {
|
|
51
|
+
// Validate each message shape
|
|
52
|
+
for (let i = 0; i < messages.length; i++) {
|
|
53
|
+
const msg = messages[i];
|
|
54
|
+
if (msg == null || typeof msg !== 'object') {
|
|
55
|
+
errors.push(`spec.messages[${i}] must be an object`);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (!msg.role) {
|
|
59
|
+
errors.push(`spec.messages[${i}].role is required`);
|
|
60
|
+
} else if (!VALID_ROLES.includes(msg.role)) {
|
|
61
|
+
errors.push(`spec.messages[${i}].role "${msg.role}" is not valid; valid roles are: ${VALID_ROLES.join(', ')}`);
|
|
62
|
+
}
|
|
63
|
+
if (msg.content == null) {
|
|
64
|
+
errors.push(`spec.messages[${i}].content is required`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Validate pageSize if explicitly set
|
|
70
|
+
const pageSize = spec.pageSize;
|
|
71
|
+
if (pageSize != null && (!Number.isInteger(pageSize) || pageSize < 1)) {
|
|
72
|
+
errors.push('spec.pageSize must be a positive integer when specified');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return { valid: errors.length === 0, errors };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Factory that returns an AgentSessionTranscript controller instance.
|
|
80
|
+
*/
|
|
81
|
+
export function createAgentSessionTranscriptController() {
|
|
82
|
+
return {
|
|
83
|
+
role: 'agent-session-transcript-controller',
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Validate an AgentSessionTranscript resource.
|
|
87
|
+
* @param {object} resource
|
|
88
|
+
* @returns {{ valid: boolean, errors: string[] }}
|
|
89
|
+
*/
|
|
90
|
+
validate(resource) {
|
|
91
|
+
return validateAgentSessionTranscript(resource);
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Return all messages in the transcript, in order.
|
|
96
|
+
* @param {object} resource
|
|
97
|
+
* @returns {Array<{ role: string, content: string, timestamp?: string, toolCalls?: object[] }>}
|
|
98
|
+
*/
|
|
99
|
+
getMessages(resource) {
|
|
100
|
+
if (resource == null) {
|
|
101
|
+
throw new Error('resource must not be null or undefined');
|
|
102
|
+
}
|
|
103
|
+
const messages = resource?.spec?.messages;
|
|
104
|
+
if (!Array.isArray(messages)) {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
return [...messages];
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Return the total number of messages in the transcript.
|
|
112
|
+
* @param {object} resource
|
|
113
|
+
* @returns {number}
|
|
114
|
+
*/
|
|
115
|
+
getTotalMessages(resource) {
|
|
116
|
+
if (resource == null) {
|
|
117
|
+
throw new Error('resource must not be null or undefined');
|
|
118
|
+
}
|
|
119
|
+
const messages = resource?.spec?.messages;
|
|
120
|
+
return Array.isArray(messages) ? messages.length : 0;
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Return a page of messages from the transcript.
|
|
125
|
+
* @param {object} resource
|
|
126
|
+
* @param {number} pageIndex - zero-based page index
|
|
127
|
+
* @param {number} [pageSizeOverride] - override spec.pageSize (defaults to spec.pageSize or 20)
|
|
128
|
+
* @returns {{ messages: object[], pageIndex: number, pageSize: number, totalMessages: number, totalPages: number }}
|
|
129
|
+
*/
|
|
130
|
+
getPage(resource, pageIndex, pageSizeOverride) {
|
|
131
|
+
if (resource == null) {
|
|
132
|
+
throw new Error('resource must not be null or undefined');
|
|
133
|
+
}
|
|
134
|
+
const messages = Array.isArray(resource?.spec?.messages) ? resource.spec.messages : [];
|
|
135
|
+
const pageSize = pageSizeOverride ?? resource?.spec?.pageSize ?? 20;
|
|
136
|
+
const totalMessages = messages.length;
|
|
137
|
+
const totalPages = Math.max(1, Math.ceil(totalMessages / pageSize));
|
|
138
|
+
const safePageIndex = Math.max(0, Math.min(pageIndex, totalPages - 1));
|
|
139
|
+
const start = safePageIndex * pageSize;
|
|
140
|
+
const end = start + pageSize;
|
|
141
|
+
return {
|
|
142
|
+
messages: messages.slice(start, end),
|
|
143
|
+
pageIndex: safePageIndex,
|
|
144
|
+
pageSize,
|
|
145
|
+
totalMessages,
|
|
146
|
+
totalPages
|
|
147
|
+
};
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Return all messages filtered by role.
|
|
152
|
+
* @param {object} resource
|
|
153
|
+
* @param {string} role - one of: user, assistant, tool, system
|
|
154
|
+
* @returns {Array<object>}
|
|
155
|
+
*/
|
|
156
|
+
getMessagesByRole(resource, role) {
|
|
157
|
+
if (resource == null) {
|
|
158
|
+
throw new Error('resource must not be null or undefined');
|
|
159
|
+
}
|
|
160
|
+
const messages = Array.isArray(resource?.spec?.messages) ? resource.spec.messages : [];
|
|
161
|
+
return messages.filter((m) => m.role === role);
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Return all messages that contain a tool call with the given tool name.
|
|
166
|
+
* @param {object} resource
|
|
167
|
+
* @param {string} toolName
|
|
168
|
+
* @returns {Array<object>}
|
|
169
|
+
*/
|
|
170
|
+
getMessagesByToolName(resource, toolName) {
|
|
171
|
+
if (resource == null) {
|
|
172
|
+
throw new Error('resource must not be null or undefined');
|
|
173
|
+
}
|
|
174
|
+
const messages = Array.isArray(resource?.spec?.messages) ? resource.spec.messages : [];
|
|
175
|
+
return messages.filter((m) => {
|
|
176
|
+
if (!Array.isArray(m.toolCalls)) return false;
|
|
177
|
+
return m.toolCalls.some((tc) => tc.name === toolName);
|
|
178
|
+
});
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Return the list of valid message roles.
|
|
183
|
+
* @returns {string[]}
|
|
184
|
+
*/
|
|
185
|
+
getValidRoles() {
|
|
186
|
+
return [...VALID_ROLES];
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
}
|