@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,395 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { execFileSync } from 'node:child_process';
4
+ import { existsSync, readFileSync } from 'node:fs';
5
+ import { createControllerUiModel, createKrateApiController, createKrateHttpServer } from '../src/index.js';
6
+
7
+ function read(path) {
8
+ return readFileSync(path, 'utf8');
9
+ }
10
+
11
+ function workflowJobBlock(workflow, jobName) {
12
+ const match = workflow.match(new RegExp(`\r?\n ${jobName}:\r?\n([\\s\\S]*?)(?=\r?\n [a-zA-Z0-9_-]+:\r?\n|$)`));
13
+ assert.ok(match, `workflow has ${jobName} job`);
14
+ return match[1];
15
+ }
16
+
17
+ function fixtureKubernetesController() {
18
+ return {
19
+ async snapshot() {
20
+ return {
21
+ source: 'kubernetes',
22
+ namespace: 'krate-org-default',
23
+ generatedAt: 'test-time',
24
+ correlationId: 'test-correlation',
25
+ kubectl: { available: true, context: 'kind-krate', clientVersion: 'v1.test', errors: [] },
26
+ apiService: { metadata: { name: 'v1alpha1.krate.a5c.ai' } },
27
+ crds: [{ metadata: { name: 'repositories.krate.a5c.ai' } }],
28
+ storage: { etcd: 'etcd', postgres: 'postgres', repositories: 'rwx', objects: 'object' },
29
+ commands: [],
30
+ permissions: [],
31
+ events: [],
32
+ resources: {
33
+ Organization: [{ apiVersion: 'krate.a5c.ai/v1alpha1', kind: 'Organization', metadata: { name: 'default', namespace: 'krate-system' }, spec: { slug: 'default', namespaceName: 'krate-org-default', displayName: 'Default org' } }],
34
+ User: [{ apiVersion: 'krate.a5c.ai/v1alpha1', kind: 'User', metadata: { name: 'alice', namespace: 'krate-org-default' }, spec: { organizationRef: 'default', displayName: 'Alice', email: 'alice@example.com', teams: ['maintainers'] }, status: { phase: 'Active' } }],
35
+ Team: [{ apiVersion: 'krate.a5c.ai/v1alpha1', kind: 'Team', metadata: { name: 'maintainers', namespace: 'krate-org-default' }, spec: { organizationRef: 'default', displayName: 'Maintainers', members: ['alice'] }, status: { phase: 'Active' } }],
36
+ Invite: [],
37
+ IdentityMapping: [{ apiVersion: 'krate.a5c.ai/v1alpha1', kind: 'IdentityMapping', metadata: { name: 'sso-alice', namespace: 'krate-org-default' }, spec: { organizationRef: 'default', user: 'alice', provider: 'sso', subject: 'alice', repositoryIdentity: { username: 'alice' } }, status: { phase: 'Synced' } }],
38
+ AuthProvider: [{ apiVersion: 'krate.a5c.ai/v1alpha1', kind: 'AuthProvider', metadata: { name: 'sso', namespace: 'krate-org-default' }, spec: { organizationRef: 'default', type: 'oidc', label: 'Company SSO', enabled: true }, status: { phase: 'Configured' } }],
39
+ Repository: [{ apiVersion: 'krate.a5c.ai/v1alpha1', kind: 'Repository', metadata: { name: 'app', namespace: 'krate-org-default' }, spec: { organizationRef: 'default', visibility: 'internal', defaultBranch: 'main' }, status: { phase: 'Ready' } }],
40
+ PolicyProfile: [{ apiVersion: 'krate.a5c.ai/v1alpha1', kind: 'PolicyProfile', metadata: { name: 'default-profile', namespace: 'krate-org-default' }, spec: { organizationRef: 'default', displayName: 'Default policy posture', mode: 'audit' }, status: { phase: 'Synced', lastViolationCount: 1 } }],
41
+ PolicyTemplate: [{ apiVersion: 'krate.a5c.ai/v1alpha1', kind: 'PolicyTemplate', metadata: { name: 'require-pr-description', namespace: 'krate-org-default' }, spec: { organizationRef: 'default', displayName: 'Require PR description', targetKinds: ['PullRequest'], kyverno: { kind: 'ValidatingPolicy' } }, status: { phase: 'Ready' } }],
42
+ PolicyBinding: [{ apiVersion: 'krate.a5c.ai/v1alpha1', kind: 'PolicyBinding', metadata: { name: 'require-pr-description-audit', namespace: 'krate-org-default' }, spec: { organizationRef: 'default', templateRef: 'require-pr-description', mode: 'audit' }, status: { phase: 'Applied', lastViolationCount: 1 } }],
43
+ PolicyExceptionRequest: [{ apiVersion: 'krate.a5c.ai/v1alpha1', kind: 'PolicyExceptionRequest', metadata: { name: 'pr-1-exception', namespace: 'krate-org-default' }, spec: { organizationRef: 'default', policyRef: { name: 'require-pr-description' }, justification: 'temporary rollout', expiresAt: '2026-06-01T00:00:00Z' }, status: { phase: 'Requested' } }],
44
+ PullRequest: [],
45
+ Pipeline: [],
46
+ RunnerPool: [],
47
+ WebhookSubscription: []
48
+ },
49
+ kyverno: {
50
+ mode: 'byo',
51
+ namespace: 'kyverno',
52
+ policyNamespace: 'krate-org-default',
53
+ detected: true,
54
+ controllers: [{ name: 'kyverno-admission-controller', ready: true, readyReplicas: 1, replicas: 1 }],
55
+ permissions: [{ kind: 'PolicyReport', verbs: { list: true, watch: true } }],
56
+ resources: { KyvernoValidatingPolicy: [{ metadata: { name: 'require-pr-description' } }], PolicyReport: [{ metadata: { name: 'app-policy', namespace: 'krate-org-default' }, results: [{ policy: 'require-pr-description', rule: 'description', result: 'fail', message: 'description required', resources: [{ kind: 'PullRequest', name: 'pr-1' }] }] }] },
57
+ reports: { policyReports: [{ metadata: { name: 'app-policy', namespace: 'krate-org-default' } }], clusterPolicyReports: [], results: [{ policy: 'require-pr-description', rule: 'description', result: 'fail', message: 'description required' }], violations: [{ policy: 'require-pr-description', rule: 'description', result: 'fail', message: 'description required' }] },
58
+ degraded: []
59
+ }
60
+ };
61
+ },
62
+ async applyResource(resource) {
63
+ return { operation: 'apply', resource };
64
+ },
65
+ async deleteResource(kind, name) {
66
+ return { operation: 'delete', resource: { kind, metadata: { name } } };
67
+ },
68
+ async createRepository(input) {
69
+ return { operation: 'apply', resource: { kind: 'Repository', metadata: { name: input.name } } };
70
+ }
71
+ };
72
+ }
73
+
74
+ test('controller deployment assets build and publish the runnable controller', () => {
75
+ for (const file of ['Dockerfile', '.dockerignore', '.github/workflows/publish.yml']) assert.equal(existsSync(file), true, `${file} exists`);
76
+
77
+ const dockerfile = read('Dockerfile');
78
+ assert.match(dockerfile, /FROM node:20-alpine AS deps/);
79
+ assert.match(dockerfile, /npm ci/);
80
+ assert.match(dockerfile, /npm run build/);
81
+ assert.match(dockerfile, /HEALTHCHECK/);
82
+ assert.match(dockerfile, /bin\/krate-server\.mjs/);
83
+ assert.ok(dockerfile.includes('apps/web/package.json'), 'Dockerfile makes Next standalone server.js CommonJS under the ESM root package');
84
+ for (const runtimePath of ['/app/bin', '/app/src', '/app/dist', '/app/charts', '/app/examples']) assert.ok(dockerfile.includes(runtimePath), `Dockerfile copies ${runtimePath}`);
85
+
86
+ const dockerignore = read('.dockerignore');
87
+ for (const ignored of ['.a5c', 'node_modules', '**/.next', 'dist']) assert.ok(dockerignore.includes(ignored), `.dockerignore excludes ${ignored}`);
88
+
89
+ const chartDeployment = read('../charts/templates/deployments.yaml');
90
+ const chartRbac = read('../charts/templates/rbac.yaml');
91
+ const chartIngress = read('../charts/templates/ingress.yaml');
92
+ const chartValues = read('../charts/values.yaml');
93
+ const authSecret = read('../charts/templates/auth-secret.yaml');
94
+ assert.ok(chartValues.includes('auth:'), 'chart values include auth configuration');
95
+ assert.ok(chartValues.includes('github:') && chartValues.includes('sso:'), 'chart values expose GitHub and SSO configuration');
96
+ assert.ok(chartValues.includes('mode: auto') && chartValues.includes('policyReporter:'), 'chart values expose Kyverno auto-discovery modes and policy reporter settings');
97
+ assert.ok(chartRbac.includes('policyprofiles') && chartRbac.includes('policies.kyverno.io') && chartRbac.includes('policyreports'), 'RBAC covers Krate policy CRDs and Kyverno read/write surfaces');
98
+ assert.ok(chartDeployment.includes('KRATE_KYVERNO_MODE') && chartDeployment.includes('KRATE_KYVERNO_POLICY_NAMESPACE') && chartDeployment.includes('KRATE_KYVERNO_DISCOVER_EXISTING'), 'deployments receive Kyverno discovery env');
99
+ assert.ok(read('../charts/templates/argocd-kyverno-application.yaml').includes('krate.a5c.ai/policy-engine: kyverno'), 'managed Kyverno Argo CD application template is present');
100
+ assert.ok(authSecret.includes('github-client-id') && authSecret.includes('sso-client-secret'), 'chart renders auth secret keys');
101
+ assert.ok(chartIngress.includes('kind: Ingress') && chartIngress.includes('ingressClassName') && chartIngress.includes('app.kubernetes.io/component: web'), 'chart renders web ingress');
102
+ assert.ok(chartDeployment.includes('imagePullSecrets') && chartDeployment.includes('global.imagePullSecrets'), 'workloads can use registry pull secrets');
103
+ assert.ok(chartDeployment.includes('KRATE_AUTH_GITHUB_ENABLED') && chartDeployment.includes('KRATE_AUTH_SSO_ENABLED') && chartDeployment.includes('KRATE_AUTH_DELEGATED_EMAIL_HEADER'), 'workloads receive auth provider configuration');
104
+ assert.ok(chartDeployment.includes('KRATE_AUTH_DELEGATED_LOCAL_DEVELOPMENT') && chartDeployment.includes('KRATE_AUTH_DELEGATED_LOCAL_GROUPS'), 'workloads can opt into local delegated development login');
105
+ assert.ok(chartRbac.includes('core.oam.dev') && chartRbac.includes('applications') && chartRbac.includes('create'), 'delivery resources can be composed through Krate');
106
+ assert.ok(chartValues.includes('localDevelopment:') && chartValues.includes('enabled: false'), 'local delegated development login is off by default');
107
+ for (const token of ['command: ["node", "bin/krate-server.mjs"]', '--port=3080', 'app.kubernetes.io/component: controllers']) {
108
+ assert.ok(chartDeployment.includes(token), `chart deployment includes ${token}`);
109
+ }
110
+ });
111
+
112
+ test('web proxy protects UI pages and authenticated APIs behind login', async () => {
113
+ const { NextRequest } = await import('next/server.js');
114
+ const { proxy, config } = await import(`../../web/proxy.js?test=${Date.now()}`);
115
+
116
+ assert.ok(config.matcher.some((entry) => entry.includes('_next/static')));
117
+ for (const path of ['/', '/orgs/default/repositories?tab=code', '/orgs/default/repositories/demo/code', '/orgs/default/people', '/logout', '/api/controller']) {
118
+ const response = proxy(new NextRequest(`http://localhost:3000${path}`));
119
+ assert.equal(response.status, 307, `${path} redirects to login without session`);
120
+ assert.equal(new URL(response.headers.get('location')).pathname, '/login');
121
+ }
122
+
123
+ const nextResponse = proxy(new NextRequest('http://localhost:3000/orgs/default/repositories?tab=code'));
124
+ assert.equal(new URL(nextResponse.headers.get('location')).searchParams.get('next'), '/orgs/default/repositories?tab=code');
125
+ assert.equal(proxy(new NextRequest('http://localhost:3000/login')).headers.get('x-middleware-next'), '1');
126
+ assert.equal(proxy(new NextRequest('http://localhost:3000/api/auth/delegated')).headers.get('x-middleware-next'), '1');
127
+ assert.equal(proxy(new NextRequest('http://localhost:3000/orgs/default/repositories', { headers: { cookie: 'krate_session=dev' } })).headers.get('x-middleware-next'), '1');
128
+ });
129
+
130
+ test('login page stays minimal and does not expose the authenticated console shell', () => {
131
+ const layout = read('../web/app/layout.jsx');
132
+ const shell = read('../web/app/ui-shell.jsx');
133
+ const loginStart = shell.indexOf('export function LoginPage() {');
134
+ const logoutStart = shell.indexOf('export async function LogoutPage');
135
+ const loginSource = shell.slice(loginStart, logoutStart);
136
+
137
+ assert.notEqual(loginStart, -1, 'LoginPage is defined in ui shell module');
138
+ assert.ok(logoutStart > loginStart, 'LoginPage source can be isolated');
139
+ assert.ok(!layout.includes('<AppShell>{children}</AppShell>'), 'root layout does not wrap public routes in AppShell');
140
+ assert.ok(loginSource.includes('loginMain'), 'login page uses standalone login layout');
141
+ assert.ok(loginSource.includes('listEnabledAuthProviders'), 'login page renders configured browser auth providers');
142
+ assert.ok(loginSource.includes('Use workspace identity'), 'login page can render workspace identity login when configured');
143
+ assert.ok(!loginSource.includes('loadKrateUi'), 'login page does not fetch controller UI data');
144
+ assert.ok(!loginSource.includes('IdentitySummary'), 'login page does not expose identity details');
145
+ assert.ok(!loginSource.includes('PageFrame'), 'login page does not render console navigation shell');
146
+ });
147
+
148
+ test('auth chart uses existing secrets without rendering empty provider keys', () => {
149
+ const mixed = execFileSync('helm', [
150
+ 'template', 'krate', '../charts', '-n', 'krate-system',
151
+ '--set', 'argocd.enabled=false',
152
+ '--set', 'auth.github.clientId=github-client',
153
+ '--set', 'auth.github.clientSecret=github-secret',
154
+ '--set', 'auth.sso.enabled=true',
155
+ '--set', 'auth.sso.existingSecret=shared-auth'
156
+ ], { encoding: 'utf8' });
157
+ assert.match(mixed, /name: krate-krate-auth/);
158
+ assert.match(mixed, /github-client-id: "github-client"/);
159
+ assert.doesNotMatch(mixed, /sso-client-id:/);
160
+ assert.match(mixed, /name: "shared-auth"[\s\S]*?key: sso-client-id/);
161
+
162
+ const externalOnly = execFileSync('helm', [
163
+ 'template', 'krate', '../charts', '-n', 'krate-system',
164
+ '--set', 'argocd.enabled=false',
165
+ '--set', 'auth.github.existingSecret=shared-auth',
166
+ '--set', 'auth.sso.enabled=true',
167
+ '--set', 'auth.sso.existingSecret=shared-auth'
168
+ ], { encoding: 'utf8' });
169
+ assert.doesNotMatch(externalOnly, /name: krate-krate-auth/);
170
+ assert.match(externalOnly, /name: "shared-auth"[\s\S]*?key: github-client-id/);
171
+ assert.match(externalOnly, /name: "shared-auth"[\s\S]*?key: sso-client-secret/);
172
+ });
173
+
174
+
175
+ test('web UI and controller API expose live Kubernetes deployment and publishing metadata', async () => {
176
+ const controller = fixtureKubernetesController();
177
+ const model = createControllerUiModel(await controller.snapshot());
178
+ assert.equal(model.controller.mode, 'krate-workspace');
179
+ assert.ok(model.controller.endpoints.some((endpoint) => endpoint.path === '/api/controller?org=:org'));
180
+ assert.ok(model.controller.endpoints.some((endpoint) => endpoint.path === '/api/orgs/:org/resources'));
181
+ assert.equal(model.controller.architecture.apiController.role, 'krate-api-controller');
182
+ assert.equal(model.controller.architecture.resourceGateway.role, 'krate-resource-gateway');
183
+ assert.equal(model.controller.architecture.resourceClient.role, 'krate-resource-client');
184
+ assert.equal(model.controller.architecture.repositoryService.role, 'repository-service');
185
+ assert.deepEqual(model.controller.architecture.apiController.delegatesTo, ['krate-resource-gateway', 'repository-service']);
186
+ assert.ok(model.resources.some((resource) => resource.kind === 'Repository' && resource.count > 0));
187
+ assert.equal(model.policyEngine.health, 'ready');
188
+ assert.equal(model.policyEngine.violations.length, 1);
189
+ assert.ok(model.resources.some((resource) => resource.kind === 'PolicyBinding' && resource.count > 0));
190
+ assert.ok(model.resources.find((resource) => resource.kind === 'Repository').action.list.includes('Open Repository records'));
191
+ assert.match(model.operations.image, /krate-controller/);
192
+ assert.equal(model.operations.chart, 'charts/krate');
193
+ for (const gate of ['npm run check', 'docker build', 'helm package charts/krate', 'npm pack --json']) assert.ok(model.operations.releaseGates.includes(gate), `release gate ${gate}`);
194
+ assert.ok(model.validation.every((item) => typeof item.evidence === 'string' && item.evidence.length > 0));
195
+
196
+ const server = createKrateHttpServer({ controller });
197
+ await new Promise((resolve) => server.listen(0, resolve));
198
+ try {
199
+ const response = await fetch(`http://127.0.0.1:${server.address().port}/api/controller`);
200
+ assert.equal(response.status, 200);
201
+ const body = await response.json();
202
+ assert.deepEqual(body.operations.releaseGates, model.operations.releaseGates);
203
+ assert.equal(body.metrics.resources, model.metrics.resources);
204
+ } finally {
205
+ await new Promise((resolve) => server.close(resolve));
206
+ }
207
+ });
208
+
209
+ test('web UI is wired to the Kubernetes controller API instead of a static local snapshot', () => {
210
+ const page = read('../web/app/page.jsx');
211
+ const orgPage = read('../web/app/orgs/[org]/page.jsx');
212
+ const shell = read('../web/app/ui-shell.jsx');
213
+ const actions = read('../web/app/components/resource-actions.jsx');
214
+ const client = read('src/controller-client.js');
215
+ const apiController = read('src/api-controller.js');
216
+ const gateway = read('src/kubernetes-resource-gateway.js');
217
+ const kubernetes = read('src/kubernetes-controller.js');
218
+ const server = read('src/http-server.js');
219
+ assert.ok(page.includes("redirect('/orgs/'"));
220
+ assert.ok(orgPage.includes('DashboardPage'));
221
+ assert.ok(read('../web/app/orgs/page.jsx').includes('Choose an organization'));
222
+ assert.ok(client.includes('KRATE_CONTROLLER_URL'));
223
+ assert.ok(client.includes('createKrateApiController'));
224
+ assert.ok(client.includes('createKubernetesResourceGateway'));
225
+ assert.ok(apiController.includes('resourceGateway'));
226
+ assert.ok(apiController.includes('withArchitecture'));
227
+ assert.ok(apiController.includes('kubernetes-resource-gateway'));
228
+ assert.ok(!apiController.includes('createKubernetesController'));
229
+ assert.ok(gateway.includes('createKubernetesResourceClient'));
230
+ assert.ok(kubernetes.includes('createKubernetesController(options = {})')); // resource-client alias
231
+ assert.ok(kubernetes.includes('/var/run/secrets/kubernetes.io/serviceaccount'), 'Kubernetes client can use in-cluster service-account credentials');
232
+ assert.ok(gateway.includes('repositoryManifest'));
233
+ assert.ok(shell.includes('/api/controller'));
234
+ assert.ok(shell.includes('ArchitectureMap'));
235
+ assert.ok(shell.includes('Repository home'));
236
+ assert.ok(shell.includes('DeploymentCenter'));
237
+ assert.ok(shell.includes('DeploymentManager'));
238
+ assert.ok(shell.includes('Krate deployment center'));
239
+ assert.ok(shell.includes('PlanCard'));
240
+ assert.ok(shell.includes('Advanced resource details'));
241
+ assert.ok(shell.includes('Releases'));
242
+ assert.ok(shell.includes('managed resources'));
243
+ assert.ok(shell.includes('Advanced resource records'));
244
+ assert.ok(shell.includes('Deployments'));
245
+ assert.ok(actions.includes('Create deployment'));
246
+ assert.ok(actions.includes('Prepare deployment'));
247
+ assert.ok(shell.includes('environments'));
248
+ assert.ok(shell.includes('Repository code browser'));
249
+ assert.ok(shell.includes('Manage access'));
250
+ assert.ok(shell.includes('PeopleAdmin'));
251
+ assert.ok(shell.includes('getSignedInUser'));
252
+ assert.ok(shell.includes('topbarAccount'));
253
+ assert.ok(shell.includes('Signed-in user'));
254
+ assert.ok(shell.includes('Sign out'));
255
+ assert.ok(shell.includes('Invite people'));
256
+ assert.ok(shell.includes('identity links'));
257
+ assert.ok(shell.includes('repository permissions'));
258
+ assert.ok(shell.includes('Access readiness'));
259
+ assert.match(actions, /Mark accepted/i);
260
+ assert.ok(actions.includes('Revoke invite'));
261
+ assert.ok(actions.includes('Disable user'));
262
+ assert.ok(actions.includes('Restore user'));
263
+ assert.ok(actions.includes('Revoke grant'));
264
+ assert.ok(actions.includes('SSH keys'));
265
+ assert.ok(actions.includes('Save SSH key'));
266
+ assert.ok(actions.includes('Revoke SSH key'));
267
+ assert.equal(existsSync('../web/app/orgs/[org]/people/page.jsx'), true);
268
+ assert.equal(existsSync('../web/app/login/page.jsx'), true);
269
+ assert.equal(existsSync('../web/app/logout/page.jsx'), true);
270
+ assert.equal(existsSync('../web/app/orgs/[org]/runs/page.jsx'), true);
271
+ assert.equal(existsSync('../web/app/runs/page.jsx'), false);
272
+ assert.equal(existsSync('../web/app/pipelines/page.jsx'), false);
273
+ assert.equal(existsSync('../web/app/orgs/[org]/repositories/[repo]/runs/page.jsx'), true);
274
+ assert.equal(existsSync('../web/app/repositories/[repo]/runs/page.jsx'), false);
275
+ assert.equal(existsSync('../web/app/repositories/[repo]/pipelines/page.jsx'), false);
276
+ assert.equal(existsSync('../web/proxy.js'), true);
277
+ assert.equal(existsSync('../web/app/api/auth/[provider]/route.js'), true);
278
+ assert.equal(existsSync('../web/app/api/auth/callback/[provider]/route.js'), true);
279
+ assert.equal(existsSync('../web/app/api/auth/delegated/route.js'), true);
280
+ assert.ok(shell.includes('RepositoryCodePage'));
281
+ assert.ok(shell.includes('RepositoryPullRequestsPage'));
282
+ assert.ok(shell.includes('RepositorySettingsPage'));
283
+ assert.ok(shell.includes('PullRequestReviewPanel'));
284
+ assert.ok(shell.includes('RunCenter'));
285
+ assert.ok(shell.includes('Workspace runs'));
286
+ assert.ok(shell.includes('Run event stream'));
287
+ assert.ok(shell.includes('/runs'));
288
+ assert.ok(!shell.includes("['/pipelines', 'Runs']"));
289
+ assert.ok(!shell.includes('PipelinesPage'), 'legacy pipelines UI component is not exported');
290
+ assert.ok(!shell.includes('RepositoryPipelinesPage'), 'legacy repository pipelines UI component is not exported');
291
+ assert.ok(!shell.includes('function PipelineDebugger'), 'runs page does not use the old debugger-first panel');
292
+ assert.ok(shell.includes('Automation inspector'));
293
+ assert.ok(shell.includes('Readiness checklist'));
294
+ assert.ok(kubernetes.includes('spawnSync'));
295
+ assert.ok(kubernetes.includes('kubectl'));
296
+ assert.ok(!client.includes('createKrateUiDemoRuntime'));
297
+ assert.ok(!page.includes('createKrateMvpDemo'));
298
+ assert.ok(!page.includes('createKrateLifecycleSnapshot'));
299
+ assert.ok(apiController.includes('const repoPath = `/orgs/${encodeURIComponent(org)}/repositories/${encodeURIComponent(name)}`'));
300
+ assert.ok(apiController.includes('runs: `${repoPath}/runs`'));
301
+ assert.ok(server.includes('/api/controller'));
302
+ assert.ok(server.includes('orgResourceCollectionMatch'));
303
+ assert.ok(!server.includes("url.pathname === '/api/repositories'"));
304
+ });
305
+
306
+ test('API controller delegates through resource gateway instead of owning Kubernetes scope', async () => {
307
+ const calls = [];
308
+ const resourceGateway = {
309
+ namespace: 'krate-org-default',
310
+ resourceDefinitions: [],
311
+ async snapshot() { calls.push('snapshot'); return { source: 'kubernetes', namespace: 'krate-org-default', resources: {}, kubectl: { available: true, errors: [] }, commands: [], events: [], permissions: [], storage: {} }; },
312
+ async list(kind) { calls.push(`list:${kind}`); return { kind, items: [] }; },
313
+ async get(kind, name) { calls.push(`get:${kind}/${name}`); return { kind, metadata: { name } }; },
314
+ async apply(resource) { calls.push(`apply:${resource.kind}`); return { operation: 'apply', resource }; },
315
+ async delete(kind, name) { calls.push(`delete:${kind}/${name}`); return { operation: 'delete', kind, name }; },
316
+ async createRepository(input) { calls.push(`createRepository:${input.name}`); return { operation: 'apply', resource: { kind: 'Repository', metadata: { name: input.name } } }; },
317
+ async createOrganization(input) { calls.push(`createOrganization:${input.name}`); return { operation: 'create-organization', organization: { metadata: { name: input.name } } }; },
318
+ watch(resourcePath) { calls.push(`watch:${resourcePath}`); return { child: { kill() {} } }; }
319
+ };
320
+ const controller = createKrateApiController({ resourceGateway });
321
+ await controller.snapshot();
322
+ await controller.listResource('Repository');
323
+ await controller.getResource('Repository', 'app');
324
+ await controller.applyResource({ kind: 'Repository', metadata: { name: 'app' } });
325
+ await controller.deleteResource('Repository', 'app');
326
+ await controller.createRepository({ name: 'next-app' });
327
+ await controller.createOrganization({ name: 'product' });
328
+ controller.watchResource('orgs/default/repositories');
329
+ assert.deepEqual(calls, ['snapshot', 'list:Repository', 'get:Repository/app', 'apply:Repository', 'delete:Repository/app', 'createRepository:next-app', 'createOrganization:product', 'watch:orgs/default/repositories']);
330
+ assert.equal(controller.resourceGateway, resourceGateway);
331
+ });
332
+
333
+ test('GitHub workflow publishes deployable image and chart artifacts with safe gates', () => {
334
+ const workflow = read('.github/workflows/publish.yml');
335
+ const validate = workflowJobBlock(workflow, 'validate');
336
+ assert.ok(workflow.includes('branches: [develop, staging, main]'));
337
+ assert.ok(workflow.includes('concurrency:') && workflow.includes('group: publish-${{ github.ref_name }}'));
338
+ assert.ok(validate.includes('npm run check'));
339
+ assert.ok(validate.includes('npm pack --json'));
340
+ assert.ok(validate.includes('name: npm-package'));
341
+ assert.ok(validate.includes('name: dist-artifacts'));
342
+ assert.ok(validate.includes('name: ui-standalone'));
343
+ assert.ok(validate.includes('Generate release checksums'));
344
+ assert.ok(validate.includes('sha256sum'));
345
+ assert.ok(validate.includes('name: release-checksums'));
346
+ assert.ok(read('../charts/templates/gitea.yaml').includes('gitea-backend'));
347
+ const argocdTemplate = read('../charts/templates/argocd-application.yaml');
348
+ const values = read('../charts/values.yaml');
349
+ assert.ok(argocdTemplate.includes('kind: Application'));
350
+ assert.ok(argocdTemplate.includes('.Values.argocd.syncPolicy.prune'));
351
+ assert.ok(argocdTemplate.includes('.Values.argocd.syncPolicy.selfHeal'));
352
+ assert.ok(!values.includes('`n'));
353
+ assert.match(values, /^ syncPolicy:\r?\n\s+automated: true\r?\n\s+prune: true\r?\n\s+selfHeal: true/m);
354
+
355
+ const image = workflowJobBlock(workflow, 'publish-image');
356
+ assert.ok(image.includes('needs: validate'));
357
+ assert.ok(image.includes("push: ${{ github.event_name != 'pull_request' }}"));
358
+ assert.ok(image.includes('docker/build-push-action'));
359
+ assert.ok(image.includes('ghcr.io/${{ github.repository }}/krate-controller'));
360
+
361
+ const chart = workflowJobBlock(workflow, 'publish-chart');
362
+ assert.ok(chart.includes('needs: validate'));
363
+ assert.ok(chart.includes('helm package charts/krate'));
364
+ assert.ok(chart.includes('SHA256SUMS'));
365
+ assert.ok(chart.includes('sha256sum dist/charts/*.tgz'));
366
+ assert.ok(chart.includes("if: startsWith(github.ref, 'refs/tags/v')"));
367
+ assert.ok(chart.includes('helm push dist/charts/*.tgz'));
368
+
369
+ const deploy = workflowJobBlock(workflow, 'deploy-krate');
370
+ assert.ok(deploy.includes('Deploy Krate To AKS'));
371
+ assert.ok(deploy.includes("github.ref == 'refs/heads/develop'"));
372
+ assert.ok(deploy.includes("github.ref == 'refs/heads/staging'"));
373
+ assert.ok(deploy.includes("github.ref == 'refs/heads/main'"));
374
+ assert.ok(deploy.includes('environment:') && deploy.includes('krate-production') && deploy.includes('https://krate.a5c.ai'));
375
+ assert.ok(deploy.includes('AZURE_ACR_NAME') && deploy.includes('KUBE_CONFIG'));
376
+ assert.ok(deploy.includes('KRATE_GITHUB_CLIENT_ID') && deploy.includes('KRATE_GITHUB_CLIENT_SECRET'));
377
+ assert.ok(deploy.includes('krate-develop.a5c.ai') && deploy.includes('krate-staging.a5c.ai') && deploy.includes('krate.a5c.ai'));
378
+ assert.ok(deploy.includes('docker build -f Dockerfile') && deploy.includes('docker push'));
379
+ assert.ok(deploy.includes('create secret docker-registry acr-pull'));
380
+ assert.ok(deploy.includes('helm upgrade --install'));
381
+ assert.ok(deploy.includes('--values /tmp/krate-deploy-values.yaml'));
382
+ assert.ok(deploy.includes('--wait'));
383
+ assert.ok(deploy.includes('rollout status deployment/"${HELM_RELEASE}-krate-web"'));
384
+ assert.doesNotMatch(workflow, /publish-npm:/);
385
+ assert.doesNotMatch(workflow, /npm publish/);
386
+ assert.doesNotMatch(workflow, /PUBLISH_NPM/);
387
+ });
388
+
389
+
390
+
391
+
392
+
393
+
394
+
395
+
@@ -0,0 +1,117 @@
1
+ import test from 'node:test';
2
+
3
+ import assert from 'node:assert/strict';
4
+
5
+ import { execFileSync } from 'node:child_process';
6
+
7
+ import { existsSync, readFileSync } from 'node:fs';
8
+
9
+ import { buildMinikubePlan } from '../../scripts/setup-minikube.mjs';
10
+
11
+
12
+
13
+ const requiredChartFiles = [
14
+
15
+ '../charts/Chart.yaml',
16
+
17
+ '../charts/values.yaml',
18
+
19
+ '../charts/crds/repositories.yaml',
20
+
21
+ '../charts/crds/aggregated-resources.yaml',
22
+
23
+ '../charts/templates/apiservice.yaml',
24
+
25
+ '../charts/templates/deployments.yaml',
26
+
27
+ '../charts/templates/rbac.yaml',
28
+
29
+ '../charts/templates/services.yaml',
30
+
31
+
32
+ '../charts/templates/networkpolicy.yaml',
33
+ '../charts/templates/gitea.yaml',
34
+ '../charts/templates/argocd-application.yaml',
35
+
36
+ 'examples/minikube-demo.yaml'
37
+
38
+ ];
39
+
40
+
41
+
42
+ test('chart package contains the MVP Kubernetes install surface', () => {
43
+
44
+ for (const file of requiredChartFiles) assert.equal(existsSync(file), true, `${file} exists`);
45
+
46
+ const chart = requiredChartFiles.map((file) => readFileSync(file, 'utf8')).join('\n');
47
+
48
+ for (const kind of ['CustomResourceDefinition', 'Deployment', 'Service', 'ServiceAccount', 'ClusterRole', 'NetworkPolicy', 'PersistentVolumeClaim']) assert.ok(chart.includes(`kind: ${kind}`), `chart includes ${kind}`);
49
+
50
+ for (const resource of ['Organization', 'Repository', 'SSHKey', 'RepositoryPermission', 'BranchProtection', 'RefPolicy', 'PullRequest', 'Issue', 'Review', 'Pipeline', 'Job', 'RunnerPool', 'WebhookSubscription', 'WebhookDelivery', 'View', 'Selector']) assert.ok(chart.includes(`kind: ${resource}`), `package covers ${resource}`);
51
+ assert.ok(chart.includes('kind: Pipeline'), 'package includes demo Pipeline workflow');
52
+ assert.ok(chart.includes('kind: Application'), 'package includes Argo CD Application');
53
+ assert.ok(chart.includes('krate-kubevela'), 'package installs KubeVela through Argo CD');
54
+ assert.ok(chart.includes('core.oam.dev'), 'Krate service account can manage delivery resources');
55
+ assert.ok(chart.includes('create') && chart.includes('delete'), 'Krate service account can compose and remove delivery resources');
56
+ assert.ok(chart.includes('vela-core'), 'package references upstream KubeVela vela-core chart');
57
+ assert.ok(chart.includes('apiService') && chart.includes('enabled: false'), 'APIService registration is optional for CRD-backed local installs');
58
+ assert.ok(chart.includes('gitea/gitea'), 'package includes Gitea backend image');
59
+ assert.ok(chart.includes('readinessProbe'), 'workloads expose readiness probes for live installs');
60
+ assert.ok(chart.includes('serviceAccountName'), 'workloads run with the Krate service account');
61
+ assert.ok(chart.includes('customresourcedefinitions'), 'service account can discover installed Krate CRDs');
62
+ assert.ok(chart.includes('apps/web/server.js'), 'web deployment serves the built Next.js app');
63
+ assert.ok(chart.includes('sshkeys.krate.a5c.ai'), 'package includes SSH key reconciliation resources');
64
+ assert.ok(chart.includes('repositorypermissions.krate.a5c.ai'), 'package includes Gitea permission reconciliation resources');
65
+ assert.ok(chart.includes('revoked'), 'package access CRDs allow revocation state');
66
+ assert.ok(chart.includes('KRATE_CONTROLLER_URL'), 'web deployment points at the in-cluster controller API');
67
+ assert.ok(chart.includes('containerPort: 2222'), 'rootless Gitea SSH service targets port 2222');
68
+ assert.ok(chart.includes('krate.fullname') && chart.includes('gitea-http'), 'Gitea and Argo CD URLs derive the release-scoped service name');
69
+ assert.ok(chart.includes('krate.a5c.ai/gitops-engine: argocd'), 'package labels Argo CD GitOps engine');
70
+ assert.ok(!chart.includes('`n'), 'package values use real YAML newlines');
71
+ assert.ok(chart.includes('.Values.argocd.syncPolicy.prune'), 'Argo CD template consumes syncPolicy.prune');
72
+ assert.ok(chart.includes('.Values.argocd.syncPolicy.selfHeal'), 'Argo CD template consumes syncPolicy.selfHeal');
73
+ assert.ok(chart.includes('- CreateNamespace=true'), 'values expose Argo CD CreateNamespace sync option');
74
+ for (const valueTerm of ['externalDependencies', 'objectStorage', 'oidc', 'gatekeeper', 'autoscaling', 'targetCPUUtilizationPercentage', 'gitea', 'argocd', 'repoURL', 'syncPolicy']) assert.ok(chart.includes(valueTerm), `values expose ${valueTerm}`);
75
+ });
76
+
77
+
78
+
79
+ test('minikube setup dry-run exposes deterministic local install commands', () => {
80
+
81
+ const plan = buildMinikubePlan({ apply: false, json: true, profile: 'krate-test', namespace: 'krate-system', release: 'krate', driver: 'docker', chart: '../charts' });
82
+
83
+ const commands = plan.commands.map(([cmd, args]) => [cmd, ...args].join(' '));
84
+
85
+ assert.equal(plan.mode, 'dry-run');
86
+
87
+ assert.ok(commands.some((command) => command.startsWith('minikube start -p krate-test')));
88
+
89
+ assert.ok(commands.includes('helm lint ../charts'));
90
+
91
+ assert.ok(commands.some((command) => command.includes('helm upgrade --install krate ../charts')));
92
+
93
+ assert.ok(commands.includes('kubectl apply -n krate-system -f examples/minikube-demo.yaml'));
94
+
95
+ assert.ok(commands.includes('node scripts/smoke.mjs'));
96
+
97
+ });
98
+
99
+
100
+
101
+ test('setup:minikube --dry-run --json is machine-readable', () => {
102
+
103
+ const output = execFileSync(process.execPath, ['scripts/setup-minikube.mjs', '--dry-run', '--json', '--profile=krate-test'], { encoding: 'utf8' });
104
+
105
+ const parsed = JSON.parse(output);
106
+
107
+ assert.equal(parsed.mode, 'dry-run');
108
+
109
+ assert.ok(parsed.requiredTools.includes('minikube'));
110
+
111
+ assert.ok(parsed.commands.some((command) => command.includes('helm lint ../charts')));
112
+
113
+ });
114
+
115
+
116
+
117
+