@a5c-ai/krate 5.0.1-staging.f672fe79b

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/Dockerfile +29 -0
  2. package/README.md +183 -0
  3. package/bin/krate-demo.mjs +23 -0
  4. package/bin/krate-server.mjs +14 -0
  5. package/dist/krate-controller-ui.json +2407 -0
  6. package/dist/krate-lifecycle.json +201 -0
  7. package/dist/krate-runtime-snapshot.json +2955 -0
  8. package/dist/krate-summary.json +687 -0
  9. package/docs/README.md +61 -0
  10. package/docs/agents/README.md +83 -0
  11. package/docs/agents/acceptance-test-matrix.md +193 -0
  12. package/docs/agents/agent-mux-adapter-contract.md +167 -0
  13. package/docs/agents/agent-mux-source-map.md +310 -0
  14. package/docs/agents/agent-run-memory-import-spec.md +256 -0
  15. package/docs/agents/agent-stack-management-spec.md +421 -0
  16. package/docs/agents/api-contract-spec.md +309 -0
  17. package/docs/agents/artifacts-writeback-spec.md +145 -0
  18. package/docs/agents/chart-packaging-spec.md +128 -0
  19. package/docs/agents/ci-orchestration-spec.md +140 -0
  20. package/docs/agents/context-assembly-spec.md +219 -0
  21. package/docs/agents/controller-reconciliation-spec.md +255 -0
  22. package/docs/agents/crd-schema-spec.md +315 -0
  23. package/docs/agents/decision-log-open-questions.md +169 -0
  24. package/docs/agents/developer-implementation-checklist.md +329 -0
  25. package/docs/agents/dispatching-design.md +262 -0
  26. package/docs/agents/glossary.md +66 -0
  27. package/docs/agents/implementation-blueprint.md +324 -0
  28. package/docs/agents/implementation-rollout-slices.md +251 -0
  29. package/docs/agents/memory-context-integration-spec.md +194 -0
  30. package/docs/agents/memory-ontology-schema-spec.md +253 -0
  31. package/docs/agents/memory-operations-runbook.md +121 -0
  32. package/docs/agents/mvp-vertical-slice-spec.md +146 -0
  33. package/docs/agents/observability-audit-spec.md +265 -0
  34. package/docs/agents/operator-runbook.md +174 -0
  35. package/docs/agents/org-memory-api-payload-examples.md +333 -0
  36. package/docs/agents/org-memory-controller-sequence-spec.md +181 -0
  37. package/docs/agents/org-memory-e2e-fixture-plan.md +161 -0
  38. package/docs/agents/org-memory-ui-implementation-map.md +114 -0
  39. package/docs/agents/org-memory-vertical-slice-spec.md +168 -0
  40. package/docs/agents/org-resource-model-delta-spec.md +111 -0
  41. package/docs/agents/org-route-resource-model-spec.md +183 -0
  42. package/docs/agents/org-scoping-namespace-spec.md +114 -0
  43. package/docs/agents/rbac-secrets-management-spec.md +406 -0
  44. package/docs/agents/repository-page-integration-spec.md +255 -0
  45. package/docs/agents/resource-contract-examples.md +808 -0
  46. package/docs/agents/resource-relationship-map.md +190 -0
  47. package/docs/agents/security-threat-model.md +188 -0
  48. package/docs/agents/shared-memory-company-brain-spec.md +358 -0
  49. package/docs/agents/storage-migration-spec.md +168 -0
  50. package/docs/agents/subagent-orchestration-spec.md +152 -0
  51. package/docs/agents/system-overview.md +88 -0
  52. package/docs/agents/tools-mcp-skills-spec.md +189 -0
  53. package/docs/agents/traceability-matrix.md +79 -0
  54. package/docs/agents/ui-flow-spec.md +211 -0
  55. package/docs/agents/ui-ux-system-spec.md +426 -0
  56. package/docs/agents/workspace-lifecycle-spec.md +166 -0
  57. package/docs/architecture-spec.md +78 -0
  58. package/docs/components/control-plane.md +78 -0
  59. package/docs/components/data-plane.md +69 -0
  60. package/docs/components/hooks-events.md +67 -0
  61. package/docs/components/identity-rbac-policy.md +73 -0
  62. package/docs/components/kubevela-oam.md +70 -0
  63. package/docs/components/operations-publishing.md +81 -0
  64. package/docs/components/runners-ci.md +66 -0
  65. package/docs/components/web-ui.md +94 -0
  66. package/docs/external/README.md +47 -0
  67. package/docs/external/bidirectional-sync-design.md +134 -0
  68. package/docs/external/cicd-interface.md +64 -0
  69. package/docs/external/external-backend-controllers.md +170 -0
  70. package/docs/external/external-backend-crds.md +234 -0
  71. package/docs/external/external-backend-ui-spec.md +151 -0
  72. package/docs/external/external-backend-ux-flows.md +115 -0
  73. package/docs/external/external-object-mapping.md +125 -0
  74. package/docs/external/git-forge-interface.md +68 -0
  75. package/docs/external/github-integration-design.md +151 -0
  76. package/docs/external/issue-tracking-interface.md +66 -0
  77. package/docs/external/provider-capability-manifests.md +204 -0
  78. package/docs/external/provider-catalog.md +139 -0
  79. package/docs/external/provider-rollout-testing.md +78 -0
  80. package/docs/external/research-results.md +48 -0
  81. package/docs/external/security-auth-permissions.md +81 -0
  82. package/docs/external/sync-state-machines.md +108 -0
  83. package/docs/external/unified-external-backend-model.md +107 -0
  84. package/docs/external/user-facing-changes.md +67 -0
  85. package/docs/gaps.md +161 -0
  86. package/docs/install.md +94 -0
  87. package/docs/krate-design.md +334 -0
  88. package/docs/local-minikube.md +55 -0
  89. package/docs/ontology/README.md +32 -0
  90. package/docs/ontology/bounded-contexts.md +29 -0
  91. package/docs/ontology/events-and-hooks.md +32 -0
  92. package/docs/ontology/oam-kubevela.md +32 -0
  93. package/docs/ontology/operations-and-release.md +25 -0
  94. package/docs/ontology/personas-and-actors.md +32 -0
  95. package/docs/ontology/policies-and-invariants.md +33 -0
  96. package/docs/ontology/problem-space.md +30 -0
  97. package/docs/ontology/resource-contracts.md +40 -0
  98. package/docs/ontology/resource-taxonomy.md +42 -0
  99. package/docs/ontology/runners-and-ci.md +29 -0
  100. package/docs/ontology/solution-space.md +24 -0
  101. package/docs/ontology/storage-and-data-boundaries.md +29 -0
  102. package/docs/ontology/validation-matrix.md +24 -0
  103. package/docs/ontology/web-ui-excellent-flows.md +32 -0
  104. package/docs/ontology/workflows.md +39 -0
  105. package/docs/ontology/world.md +35 -0
  106. package/docs/product-requirements.md +62 -0
  107. package/docs/roadmap-mvp.md +87 -0
  108. package/docs/system-requirements.md +90 -0
  109. package/docs/tests/README.md +53 -0
  110. package/docs/tests/agent-qa-plan.md +63 -0
  111. package/docs/tests/browser-ui-tests.md +62 -0
  112. package/docs/tests/ci-quality-gates.md +48 -0
  113. package/docs/tests/coverage-model.md +64 -0
  114. package/docs/tests/e2e-scenario-tests.md +53 -0
  115. package/docs/tests/fixtures-test-data.md +63 -0
  116. package/docs/tests/observability-reliability-tests.md +54 -0
  117. package/docs/tests/product-test-matrix.md +145 -0
  118. package/docs/tests/qa-adoption-roadmap.md +130 -0
  119. package/docs/tests/qa-automation-plan.md +101 -0
  120. package/docs/tests/security-compliance-tests.md +57 -0
  121. package/docs/tests/test-framework-tools.md +88 -0
  122. package/docs/tests/test-suite-layout.md +121 -0
  123. package/docs/tests/unit-integration-tests.md +48 -0
  124. package/docs/todo-kyverno +714 -0
  125. package/docs/user-stories.md +78 -0
  126. package/examples/minikube-demo.yaml +190 -0
  127. package/examples/oam-application.yaml +23 -0
  128. package/examples/policy-kyverno-pr-title.yaml +18 -0
  129. package/package.json +63 -0
  130. package/scripts/build.mjs +29 -0
  131. package/scripts/setup-minikube.mjs +65 -0
  132. package/scripts/smoke.mjs +37 -0
  133. package/scripts/validate-doc-coverage.mjs +152 -0
  134. package/scripts/validate-package.mjs +93 -0
  135. package/scripts/validate-ui.mjs +207 -0
  136. package/src/agent-approval-controller.js +123 -0
  137. package/src/agent-context-bundles.js +242 -0
  138. package/src/agent-dispatch-controller.js +86 -0
  139. package/src/agent-mux-client.js +280 -0
  140. package/src/agent-permission-review.js +162 -0
  141. package/src/agent-stack-controller.js +296 -0
  142. package/src/agent-trigger-controller.js +108 -0
  143. package/src/api-controller.js +206 -0
  144. package/src/argocd-gitops.js +43 -0
  145. package/src/auth.js +265 -0
  146. package/src/component-catalog.js +41 -0
  147. package/src/control-plane.js +136 -0
  148. package/src/controller-client.js +38 -0
  149. package/src/controller-ui.js +538 -0
  150. package/src/data-plane.js +178 -0
  151. package/src/gitea-backend.js +95 -0
  152. package/src/handoff.js +98 -0
  153. package/src/hooks-events.js +63 -0
  154. package/src/http-server.js +151 -0
  155. package/src/identity-policy.js +86 -0
  156. package/src/index.js +30 -0
  157. package/src/kubernetes-controller.js +812 -0
  158. package/src/kubernetes-resource-gateway.js +48 -0
  159. package/src/operations.js +112 -0
  160. package/src/resource-model.js +203 -0
  161. package/src/runners-ci.js +48 -0
  162. package/src/runtime.js +196 -0
  163. package/src/web-ui.js +40 -0
  164. package/tests/agent-approval-controller.test.js +173 -0
  165. package/tests/agent-context-bundles.test.js +278 -0
  166. package/tests/agent-dispatch-controller.test.js +176 -0
  167. package/tests/agent-mux-client.test.js +204 -0
  168. package/tests/agent-permission-review.test.js +209 -0
  169. package/tests/agent-resources.test.js +212 -0
  170. package/tests/agent-stack-controller.test.js +221 -0
  171. package/tests/agent-trigger-controller.test.js +211 -0
  172. package/tests/deployment.test.js +395 -0
  173. package/tests/e2e/lifecycle.test.js +117 -0
  174. package/tests/krate.test.js +727 -0
@@ -0,0 +1,162 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { clone } from './resource-model.js';
3
+
4
+ export const AGENT_PERMISSION_REVIEW_BOUNDARY = {
5
+ role: 'agent-permission-review',
6
+ scope: 'Deterministic permission review for agent dispatch decisions',
7
+ owns: ['capability expansion', 'grant resolution', 'permission snapshot creation'],
8
+ delegatesTo: ['resource-model'],
9
+ mustNotOwn: ['secret values', 'native K8s API calls', 'runtime execution']
10
+ };
11
+
12
+ export function createPermissionReviewer(options = {}) {
13
+ return {
14
+ role: 'agent-permission-review',
15
+
16
+ reviewPermissions({ repository, ref, actor, agentStack, triggerSource, taskKind, runnerPool, toolRefs = [], skillRefs = [], mcpServerRefs = [], contextLabelRefs = [], resources = {} }) {
17
+ const reasons = [];
18
+ const grants = [];
19
+
20
+ // Step 1 — Resolve AgentStack
21
+ const stacks = resources.AgentStack || [];
22
+ const stack = stacks.find((s) => s.metadata?.name === agentStack);
23
+ if (!stack) {
24
+ return buildDecision({ decision: 'denied', reasons: [{ severity: 'error', message: `AgentStack not found: ${agentStack}` }], grants, capabilities: {}, actor, repository, ref, agentStack, taskKind });
25
+ }
26
+
27
+ // Step 2 — Expand capabilities from stack spec
28
+ const capabilities = {
29
+ toolRefs: clone(stack.spec?.toolPolicy ? [stack.spec.toolPolicy] : toolRefs),
30
+ mcpServerRefs: clone(stack.spec?.mcpServerRefs || mcpServerRefs),
31
+ skillRefs: clone(stack.spec?.skillRefs || skillRefs),
32
+ subagentRefs: clone(stack.spec?.subagentRefs || [])
33
+ };
34
+
35
+ // Step 3 — Check runtime identity (AgentServiceAccount)
36
+ const serviceAccountRef = stack.spec?.runtimeIdentity?.serviceAccountRef || stack.spec?.runtimeIdentity;
37
+ const serviceAccounts = resources.AgentServiceAccount || [];
38
+ const serviceAccount = serviceAccounts.find((sa) => sa.metadata?.name === serviceAccountRef);
39
+ if (!serviceAccount) {
40
+ reasons.push({ severity: 'error', message: `Missing AgentServiceAccount: ${serviceAccountRef}` });
41
+ } else {
42
+ grants.push({ kind: 'AgentServiceAccount', name: serviceAccount.metadata.name, status: 'bound' });
43
+ }
44
+
45
+ // Step 4 — Check role bindings
46
+ const roleBindings = resources.AgentRoleBinding || [];
47
+ const matchedBindings = roleBindings.filter((rb) => rb.spec?.subject === serviceAccountRef || rb.spec?.subject === agentStack);
48
+ for (const binding of matchedBindings) {
49
+ grants.push({ kind: 'AgentRoleBinding', name: binding.metadata.name, roleRef: binding.spec?.roleRef, scope: binding.spec?.scope, status: 'bound' });
50
+ }
51
+ if (matchedBindings.length === 0 && serviceAccount) {
52
+ reasons.push({ severity: 'warning', message: `No AgentRoleBinding found for subject: ${serviceAccountRef}` });
53
+ }
54
+
55
+ // Step 5 — Check secret grants
56
+ const secretGrants = resources.AgentSecretGrant || [];
57
+ const neededSecrets = collectSecretNeeds(stack, capabilities, resources);
58
+ for (const need of neededSecrets) {
59
+ const match = secretGrants.find((sg) => {
60
+ if (sg.spec?.subject !== serviceAccountRef && sg.spec?.subject !== agentStack) return false;
61
+ if (need.purpose && sg.spec?.purpose !== need.purpose) return false;
62
+ if (sg.spec?.allowedRepositories && sg.spec.allowedRepositories.length > 0 && !sg.spec.allowedRepositories.includes(repository)) return false;
63
+ if (sg.spec?.allowedRefs && sg.spec.allowedRefs.length > 0 && !sg.spec.allowedRefs.includes(ref)) return false;
64
+ return true;
65
+ });
66
+ if (!match) {
67
+ reasons.push({ severity: 'error', message: `Missing AgentSecretGrant for ${need.description} (purpose: ${need.purpose})` });
68
+ } else {
69
+ const grantEntry = { kind: 'AgentSecretGrant', name: match.metadata.name, purpose: match.spec?.purpose, status: 'granted' };
70
+ if (match.spec?.requiredApproval) {
71
+ grantEntry.status = 'requires-approval';
72
+ grantEntry.requiredApproval = match.spec.requiredApproval;
73
+ reasons.push({ severity: 'info', message: `AgentSecretGrant ${match.metadata.name} requires approval: ${match.spec.requiredApproval}` });
74
+ }
75
+ grants.push(grantEntry);
76
+ }
77
+ }
78
+
79
+ // Step 6 — Check config grants
80
+ const configGrants = resources.AgentConfigGrant || [];
81
+ const neededConfigs = collectConfigNeeds(stack, capabilities, resources);
82
+ for (const need of neededConfigs) {
83
+ const match = configGrants.find((cg) => {
84
+ if (cg.spec?.subject !== serviceAccountRef && cg.spec?.subject !== agentStack) return false;
85
+ if (need.purpose && cg.spec?.purpose !== need.purpose) return false;
86
+ return true;
87
+ });
88
+ if (!match) {
89
+ reasons.push({ severity: 'error', message: `Missing AgentConfigGrant for ${need.description} (purpose: ${need.purpose})` });
90
+ } else {
91
+ grants.push({ kind: 'AgentConfigGrant', name: match.metadata.name, purpose: match.spec?.purpose, status: 'granted' });
92
+ }
93
+ }
94
+
95
+ // Step 7 — Decision
96
+ const hasErrors = reasons.some((r) => r.severity === 'error');
97
+ const hasApprovals = grants.some((g) => g.status === 'requires-approval');
98
+ const decision = hasErrors ? 'denied' : hasApprovals ? 'requires-approval' : 'allowed';
99
+
100
+ return buildDecision({ decision, reasons, grants, capabilities, actor, repository, ref, agentStack, taskKind });
101
+ },
102
+
103
+ createPermissionSnapshot(reviewResult) {
104
+ const snapshot = clone(reviewResult);
105
+ snapshot.snapshotAt = new Date().toISOString();
106
+ snapshot.frozen = true;
107
+ snapshot.digest = reviewResult.digest;
108
+ return Object.freeze(snapshot);
109
+ }
110
+ };
111
+ }
112
+
113
+ function collectSecretNeeds(stack, capabilities, resources) {
114
+ const needs = [];
115
+ if (stack.spec?.adapter) {
116
+ needs.push({ description: `model provider for adapter ${stack.spec.adapter}`, purpose: 'model-provider' });
117
+ }
118
+ const mcpServers = resources.AgentMcpServer || [];
119
+ for (const ref of capabilities.mcpServerRefs) {
120
+ const server = mcpServers.find((s) => s.metadata?.name === ref);
121
+ if (server?.spec?.secretRef) {
122
+ needs.push({ description: `MCP server ${ref} secret`, purpose: `mcp-server:${ref}` });
123
+ }
124
+ }
125
+ return needs;
126
+ }
127
+
128
+ function collectConfigNeeds(stack, capabilities, resources) {
129
+ const needs = [];
130
+ const mcpServers = resources.AgentMcpServer || [];
131
+ for (const ref of capabilities.mcpServerRefs) {
132
+ const server = mcpServers.find((s) => s.metadata?.name === ref);
133
+ if (server?.spec?.configMapRef) {
134
+ needs.push({ description: `MCP server ${ref} config`, purpose: `mcp-server:${ref}` });
135
+ }
136
+ }
137
+ return needs;
138
+ }
139
+
140
+ function buildDecision({ decision, reasons, grants, capabilities, actor, repository, ref, agentStack, taskKind }) {
141
+ const result = {
142
+ decision,
143
+ actor,
144
+ repository,
145
+ ref,
146
+ agentStack,
147
+ taskKind,
148
+ capabilities: clone(capabilities),
149
+ grants: clone(grants),
150
+ reasons: clone(reasons),
151
+ reviewedAt: new Date().toISOString()
152
+ };
153
+ result.digest = computeDigest(result);
154
+ return result;
155
+ }
156
+
157
+ function computeDigest(result) {
158
+ const keys = Object.keys(result).filter((k) => k !== 'digest').sort();
159
+ const canonical = {};
160
+ for (const key of keys) canonical[key] = result[key];
161
+ return createHash('sha256').update(JSON.stringify(canonical)).digest('hex');
162
+ }
@@ -0,0 +1,296 @@
1
+ import { createPermissionReviewer } from './agent-permission-review.js';
2
+ import { clone } from './resource-model.js';
3
+
4
+ export const AGENT_STACK_CONTROLLER_BOUNDARY = {
5
+ role: 'agent-stack-controller',
6
+ scope: 'Stack readiness reconciliation with capability resolution and condition management',
7
+ owns: ['capability resolution', 'stack conditions', 'readiness computation'],
8
+ delegatesTo: ['agent-permission-review', 'resource-model'],
9
+ mustNotOwn: ['secret values', 'dispatch execution', 'Agent Mux sessions']
10
+ };
11
+
12
+ export function createAgentStackController(options = {}) {
13
+ const permissionReviewer = options.permissionReviewer || createPermissionReviewer();
14
+
15
+ return {
16
+ role: 'agent-stack-controller',
17
+
18
+ reconcileStack(stack, resources = {}) {
19
+ const spec = stack?.spec || {};
20
+ const conditions = [];
21
+ const missing = [];
22
+
23
+ // --- Resolve capability refs from stack spec ---
24
+ const resolvedTools = [];
25
+ const resolvedMcpServers = [];
26
+ const resolvedSkills = [];
27
+ const resolvedSubagents = [];
28
+ const resolvedContextLabels = [];
29
+
30
+ // toolPolicyRef
31
+ const toolPolicyRef = spec.toolPolicy || spec.toolPolicyRef || null;
32
+ let toolPolicyFound = true;
33
+ if (toolPolicyRef) {
34
+ const profiles = resources.AgentToolProfile || [];
35
+ const profile = profiles.find((p) => p.metadata?.name === toolPolicyRef);
36
+ if (profile) {
37
+ resolvedTools.push(profile.metadata.name);
38
+ } else {
39
+ toolPolicyFound = false;
40
+ missing.push(`AgentToolProfile/${toolPolicyRef}`);
41
+ }
42
+ }
43
+
44
+ // mcpServerRefs
45
+ const mcpServerRefs = spec.mcpServerRefs || [];
46
+ let allMcpFound = true;
47
+ for (const ref of mcpServerRefs) {
48
+ const servers = resources.AgentMcpServer || [];
49
+ const server = servers.find((s) => s.metadata?.name === ref);
50
+ if (server) {
51
+ resolvedMcpServers.push(server.metadata.name);
52
+ } else {
53
+ allMcpFound = false;
54
+ missing.push(`AgentMcpServer/${ref}`);
55
+ }
56
+ }
57
+
58
+ // skillRefs
59
+ const skillRefs = spec.skillRefs || [];
60
+ let allSkillsFound = true;
61
+ let allSkillsValid = true;
62
+ for (const ref of skillRefs) {
63
+ const skills = resources.AgentSkill || [];
64
+ const skill = skills.find((s) => s.metadata?.name === ref);
65
+ if (skill) {
66
+ resolvedSkills.push(skill.metadata.name);
67
+ if (!skill.spec?.format || !skill.spec?.sourceRef) {
68
+ allSkillsValid = false;
69
+ }
70
+ } else {
71
+ allSkillsFound = false;
72
+ allSkillsValid = false;
73
+ missing.push(`AgentSkill/${ref}`);
74
+ }
75
+ }
76
+
77
+ // subagentRefs
78
+ const subagentRefs = spec.subagentRefs || [];
79
+ let allSubagentsFound = true;
80
+ let allSubagentsValid = true;
81
+ for (const ref of subagentRefs) {
82
+ const subagents = resources.AgentSubagent || [];
83
+ const subagent = subagents.find((s) => s.metadata?.name === ref);
84
+ if (subagent) {
85
+ resolvedSubagents.push(subagent.metadata.name);
86
+ if (!subagent.spec?.taskKinds || subagent.spec.taskKinds.length === 0) {
87
+ allSubagentsValid = false;
88
+ }
89
+ } else {
90
+ allSubagentsFound = false;
91
+ allSubagentsValid = false;
92
+ missing.push(`AgentSubagent/${ref}`);
93
+ }
94
+ }
95
+
96
+ // contextLabelRefs
97
+ const contextLabelRefs = spec.contextLabelRefs || [];
98
+ let allContextLabelsFound = true;
99
+ for (const ref of contextLabelRefs) {
100
+ const labels = resources.AgentContextLabel || [];
101
+ const label = labels.find((l) => l.metadata?.name === ref);
102
+ if (label) {
103
+ resolvedContextLabels.push(label.metadata.name);
104
+ } else {
105
+ allContextLabelsFound = false;
106
+ missing.push(`AgentContextLabel/${ref}`);
107
+ }
108
+ }
109
+
110
+ // --- Build conditions ---
111
+ const allRefsFound = missing.length === 0;
112
+ conditions.push({
113
+ type: 'CapabilitiesResolved',
114
+ status: allRefsFound ? 'True' : 'False',
115
+ reason: allRefsFound ? 'AllRefsResolved' : 'MissingRefs',
116
+ message: allRefsFound ? 'All capability references resolved' : `Missing: ${missing.join(', ')}`
117
+ });
118
+
119
+ conditions.push({
120
+ type: 'ToolsAdmitted',
121
+ status: (toolPolicyFound || !toolPolicyRef) ? 'True' : 'False',
122
+ reason: !toolPolicyRef ? 'NoToolPolicyRef' : toolPolicyFound ? 'ToolPolicyResolved' : 'ToolPolicyMissing',
123
+ message: !toolPolicyRef ? 'No tool policy reference set' : toolPolicyFound ? 'Tool policy resolved' : `AgentToolProfile/${toolPolicyRef} not found`
124
+ });
125
+
126
+ conditions.push({
127
+ type: 'McpHealthy',
128
+ status: allMcpFound ? 'True' : 'False',
129
+ reason: allMcpFound ? 'AllMcpServersExist' : 'MissingMcpServers',
130
+ message: allMcpFound ? 'All MCP servers exist (health check deferred)' : `Missing MCP servers: ${mcpServerRefs.filter((ref) => !resolvedMcpServers.includes(ref)).join(', ')}`
131
+ });
132
+
133
+ conditions.push({
134
+ type: 'SkillsValidated',
135
+ status: (allSkillsFound && allSkillsValid) ? 'True' : 'False',
136
+ reason: !allSkillsFound ? 'MissingSkills' : !allSkillsValid ? 'InvalidSkillFormat' : 'AllSkillsValid',
137
+ message: !allSkillsFound ? `Missing skills: ${skillRefs.filter((ref) => !resolvedSkills.includes(ref)).join(', ')}` : !allSkillsValid ? 'Some skills have invalid format or missing sourceRef' : 'All skills validated'
138
+ });
139
+
140
+ conditions.push({
141
+ type: 'SubagentsValid',
142
+ status: (allSubagentsFound && allSubagentsValid) ? 'True' : 'False',
143
+ reason: !allSubagentsFound ? 'MissingSubagents' : !allSubagentsValid ? 'InvalidSubagentTaskKinds' : 'AllSubagentsValid',
144
+ message: !allSubagentsFound ? `Missing subagents: ${subagentRefs.filter((ref) => !resolvedSubagents.includes(ref)).join(', ')}` : !allSubagentsValid ? 'Some subagents have invalid or empty taskKinds' : 'All subagents validated'
145
+ });
146
+
147
+ conditions.push({
148
+ type: 'ContextLabelsValid',
149
+ status: allContextLabelsFound ? 'True' : 'False',
150
+ reason: allContextLabelsFound ? 'AllContextLabelsExist' : 'MissingContextLabels',
151
+ message: allContextLabelsFound ? 'All context labels exist' : `Missing context labels: ${contextLabelRefs.filter((ref) => !resolvedContextLabels.includes(ref)).join(', ')}`
152
+ });
153
+
154
+ // --- Permission review conditions via permissionReviewer ---
155
+ const serviceAccountRef = spec.runtimeIdentity?.serviceAccountRef || spec.runtimeIdentity;
156
+ const serviceAccounts = resources.AgentServiceAccount || [];
157
+ const serviceAccount = serviceAccounts.find((sa) => sa.metadata?.name === serviceAccountRef);
158
+ const runtimeIdentityReady = Boolean(serviceAccount);
159
+
160
+ conditions.push({
161
+ type: 'RuntimeIdentityReady',
162
+ status: runtimeIdentityReady ? 'True' : 'False',
163
+ reason: runtimeIdentityReady ? 'ServiceAccountBound' : 'MissingServiceAccount',
164
+ message: runtimeIdentityReady ? `AgentServiceAccount ${serviceAccountRef} bound` : `AgentServiceAccount ${serviceAccountRef || 'undefined'} not found`
165
+ });
166
+
167
+ // Run permission review for roles, secrets, config
168
+ const permissionReview = permissionReviewer.reviewPermissions({
169
+ repository: stack?.metadata?.labels?.repository || 'unknown',
170
+ ref: stack?.metadata?.labels?.ref || 'main',
171
+ actor: stack?.metadata?.labels?.actor || 'system',
172
+ agentStack: stack?.metadata?.name,
173
+ triggerSource: 'reconciliation',
174
+ taskKind: spec.taskKind || 'general',
175
+ resources
176
+ });
177
+
178
+ const rolesAdmitted = !permissionReview.reasons.some((r) => r.severity === 'error' && r.message.includes('AgentRoleBinding'));
179
+ const secretsAdmitted = !permissionReview.reasons.some((r) => r.severity === 'error' && r.message.includes('AgentSecretGrant'));
180
+ const configAdmitted = !permissionReview.reasons.some((r) => r.severity === 'error' && r.message.includes('AgentConfigGrant'));
181
+
182
+ conditions.push({
183
+ type: 'RolesAdmitted',
184
+ status: rolesAdmitted ? 'True' : 'False',
185
+ reason: rolesAdmitted ? 'RoleBindingsResolved' : 'MissingRoleBindings',
186
+ message: rolesAdmitted ? 'Role bindings satisfied' : 'Missing required AgentRoleBinding resources'
187
+ });
188
+
189
+ conditions.push({
190
+ type: 'SecretsAdmitted',
191
+ status: secretsAdmitted ? 'True' : 'False',
192
+ reason: secretsAdmitted ? 'SecretGrantsResolved' : 'MissingSecretGrants',
193
+ message: secretsAdmitted ? 'Secret grants satisfied' : 'Missing required AgentSecretGrant resources'
194
+ });
195
+
196
+ conditions.push({
197
+ type: 'ConfigAdmitted',
198
+ status: configAdmitted ? 'True' : 'False',
199
+ reason: configAdmitted ? 'ConfigGrantsResolved' : 'MissingConfigGrants',
200
+ message: configAdmitted ? 'Config grants satisfied' : 'Missing required AgentConfigGrant resources'
201
+ });
202
+
203
+ // --- Ready condition: true only if ALL other conditions are true ---
204
+ const allTrue = conditions.every((c) => c.status === 'True');
205
+ const hasErrors = conditions.some((c) => c.status === 'False');
206
+
207
+ conditions.push({
208
+ type: 'Ready',
209
+ status: allTrue ? 'True' : 'False',
210
+ reason: allTrue ? 'StackReady' : 'StackNotReady',
211
+ message: allTrue ? 'All conditions met' : `Failing conditions: ${conditions.filter((c) => c.status === 'False').map((c) => c.type).join(', ')}`
212
+ });
213
+
214
+ return {
215
+ conditions: clone(conditions),
216
+ capabilities: {
217
+ tools: clone(resolvedTools),
218
+ mcpServers: clone(resolvedMcpServers),
219
+ skills: clone(resolvedSkills),
220
+ subagents: clone(resolvedSubagents),
221
+ contextLabels: clone(resolvedContextLabels)
222
+ },
223
+ validation: allTrue ? 'valid' : hasErrors ? 'invalid' : 'warning',
224
+ permissionDecision: permissionReview.decision
225
+ };
226
+ },
227
+
228
+ listStackCapabilities(stack, resources = {}) {
229
+ const spec = stack?.spec || {};
230
+ const capabilities = [];
231
+
232
+ // Tools
233
+ const toolPolicyRef = spec.toolPolicy || spec.toolPolicyRef || null;
234
+ if (toolPolicyRef) {
235
+ const profiles = resources.AgentToolProfile || [];
236
+ const profile = profiles.find((p) => p.metadata?.name === toolPolicyRef);
237
+ capabilities.push({
238
+ kind: 'tool',
239
+ name: toolPolicyRef,
240
+ status: profile ? 'resolved' : 'missing',
241
+ ref: toolPolicyRef
242
+ });
243
+ }
244
+
245
+ // MCP Servers
246
+ for (const ref of spec.mcpServerRefs || []) {
247
+ const servers = resources.AgentMcpServer || [];
248
+ const server = servers.find((s) => s.metadata?.name === ref);
249
+ capabilities.push({
250
+ kind: 'mcp',
251
+ name: ref,
252
+ status: server ? 'resolved' : 'missing',
253
+ ref
254
+ });
255
+ }
256
+
257
+ // Skills
258
+ for (const ref of spec.skillRefs || []) {
259
+ const skills = resources.AgentSkill || [];
260
+ const skill = skills.find((s) => s.metadata?.name === ref);
261
+ capabilities.push({
262
+ kind: 'skill',
263
+ name: ref,
264
+ status: skill ? 'resolved' : 'missing',
265
+ ref
266
+ });
267
+ }
268
+
269
+ // Subagents
270
+ for (const ref of spec.subagentRefs || []) {
271
+ const subagents = resources.AgentSubagent || [];
272
+ const subagent = subagents.find((s) => s.metadata?.name === ref);
273
+ capabilities.push({
274
+ kind: 'subagent',
275
+ name: ref,
276
+ status: subagent ? 'resolved' : 'missing',
277
+ ref
278
+ });
279
+ }
280
+
281
+ // Context Labels
282
+ for (const ref of spec.contextLabelRefs || []) {
283
+ const labels = resources.AgentContextLabel || [];
284
+ const label = labels.find((l) => l.metadata?.name === ref);
285
+ capabilities.push({
286
+ kind: 'contextLabel',
287
+ name: ref,
288
+ status: label ? 'resolved' : 'missing',
289
+ ref
290
+ });
291
+ }
292
+
293
+ return capabilities;
294
+ }
295
+ };
296
+ }
@@ -0,0 +1,108 @@
1
+ import { createResource, clone } from './resource-model.js';
2
+
3
+ export const AGENT_TRIGGER_CONTROLLER_BOUNDARY = {
4
+ role: 'agent-trigger-controller',
5
+ scope: 'Event normalization, rule matching, deduplication, and dispatch creation',
6
+ owns: ['event normalization', 'rule matching', 'trigger execution records', 'dispatch initiation'],
7
+ delegatesTo: ['agent-dispatch-controller', 'resource-model'],
8
+ mustNotOwn: ['event sourcing', 'webhook delivery', 'secret values']
9
+ };
10
+
11
+ export function createAgentTriggerController(options = {}) {
12
+ const dispatchController = options.dispatchController;
13
+
14
+ return {
15
+ role: 'agent-trigger-controller',
16
+
17
+ matchRule(rule, event) {
18
+ // 1. Check event type is in rule.spec.sources
19
+ const sources = rule.spec?.sources || [];
20
+ if (!sources.includes(event.type)) return { matches: false, reason: `Event type '${event.type}' not in rule sources [${sources.join(', ')}]` };
21
+ // 2. Check repository scope (if rule has spec.repository, must match)
22
+ if (rule.spec?.repository && rule.spec.repository !== event.repository) return { matches: false, reason: `Repository '${event.repository}' does not match rule scope '${rule.spec.repository}'` };
23
+ // 3. Check actor filter (if rule has spec.allowedActors)
24
+ if (rule.spec?.allowedActors?.length > 0 && !rule.spec.allowedActors.includes(event.actor)) return { matches: false, reason: `Actor '${event.actor}' not in allowed actors` };
25
+ return { matches: true, reason: 'All conditions met' };
26
+ },
27
+
28
+ evaluateEvent({ event, resources }) {
29
+ const rules = resources.AgentTriggerRule || [];
30
+ const executions = resources.AgentTriggerExecution || [];
31
+ const eventUid = `${event.type}:${event.source?.kind}:${event.source?.name}`;
32
+
33
+ return rules.map(rule => {
34
+ const match = this.matchRule(rule, event);
35
+ const isDuplicate = executions.some(ex =>
36
+ ex.spec?.triggerRule === rule.metadata?.name &&
37
+ ex.spec?.sourceEvent === eventUid &&
38
+ ex.status?.phase !== 'Failed'
39
+ );
40
+ return { rule, matches: match.matches, reason: match.reason, isDuplicate };
41
+ });
42
+ },
43
+
44
+ createTriggerExecution({ rule, event, decision, reason, namespace = 'default', organizationRef = 'default' }) {
45
+ const eventUid = `${event.type}:${event.source?.kind}:${event.source?.name}`;
46
+ const name = `trigger-exec-${rule.metadata?.name}-${Date.now()}`;
47
+ const execution = createResource('AgentTriggerExecution', { name, namespace }, {
48
+ organizationRef,
49
+ triggerRule: rule.metadata?.name,
50
+ sourceEvent: eventUid,
51
+ decision,
52
+ });
53
+ execution.status = { phase: decision, reason, evaluatedAt: new Date().toISOString() };
54
+ return execution;
55
+ },
56
+
57
+ async processEvent({ event, resources, namespace = 'default', organizationRef = 'default' }) {
58
+ const evaluations = this.evaluateEvent({ event, resources });
59
+ const executions = [];
60
+ let dispatched = 0;
61
+ let skipped = 0;
62
+
63
+ for (const { rule, matches, reason, isDuplicate } of evaluations) {
64
+ if (!matches) {
65
+ executions.push(this.createTriggerExecution({ rule, event, decision: 'Skipped', reason, namespace, organizationRef }));
66
+ skipped++;
67
+ continue;
68
+ }
69
+ if (isDuplicate) {
70
+ executions.push(this.createTriggerExecution({ rule, event, decision: 'Deduplicated', reason: 'Already dispatched for this event', namespace, organizationRef }));
71
+ skipped++;
72
+ continue;
73
+ }
74
+
75
+ const execution = this.createTriggerExecution({ rule, event, decision: 'Dispatching', reason, namespace, organizationRef });
76
+
77
+ if (dispatchController) {
78
+ const result = await dispatchController.createManualDispatch({
79
+ repository: event.repository,
80
+ ref: event.ref,
81
+ sourceRefs: [event.source],
82
+ agentStack: rule.spec?.agentStack,
83
+ taskKind: rule.spec?.taskKind || 'diagnostic',
84
+ actor: event.actor,
85
+ namespace,
86
+ organizationRef,
87
+ resources,
88
+ });
89
+ if (result.error) {
90
+ execution.status.phase = 'Failed';
91
+ execution.status.reason = result.message;
92
+ } else {
93
+ execution.status.phase = 'Dispatched';
94
+ execution.status.dispatchRunRef = result.run?.metadata?.name;
95
+ }
96
+ } else {
97
+ execution.status.phase = 'Dispatched';
98
+ execution.status.reason = 'No dispatch controller configured (dry-run)';
99
+ }
100
+
101
+ executions.push(execution);
102
+ dispatched++;
103
+ }
104
+
105
+ return { processed: evaluations.length, dispatched, skipped, executions };
106
+ },
107
+ };
108
+ }