@a5c-ai/kradle 5.0.1-staging.3abdf9534c25

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 (295) hide show
  1. package/Dockerfile +31 -0
  2. package/README.md +187 -0
  3. package/bin/kradle-demo.mjs +23 -0
  4. package/bin/kradle-server.mjs +14 -0
  5. package/dist/kradle-controller-ui.json +3482 -0
  6. package/dist/kradle-lifecycle.json +201 -0
  7. package/dist/kradle-runtime-snapshot.json +3125 -0
  8. package/dist/kradle-summary.json +724 -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/gaps-agent-mux-to-kradle-crds.md +298 -0
  27. package/docs/agents/glossary.md +66 -0
  28. package/docs/agents/implementation-blueprint.md +324 -0
  29. package/docs/agents/implementation-rollout-slices.md +251 -0
  30. package/docs/agents/memory-context-integration-spec.md +194 -0
  31. package/docs/agents/memory-ontology-schema-spec.md +253 -0
  32. package/docs/agents/memory-operations-runbook.md +121 -0
  33. package/docs/agents/mvp-vertical-slice-spec.md +146 -0
  34. package/docs/agents/observability-audit-spec.md +265 -0
  35. package/docs/agents/operator-runbook.md +174 -0
  36. package/docs/agents/org-memory-api-payload-examples.md +333 -0
  37. package/docs/agents/org-memory-controller-sequence-spec.md +181 -0
  38. package/docs/agents/org-memory-e2e-fixture-plan.md +161 -0
  39. package/docs/agents/org-memory-ui-implementation-map.md +114 -0
  40. package/docs/agents/org-memory-vertical-slice-spec.md +168 -0
  41. package/docs/agents/org-resource-model-delta-spec.md +111 -0
  42. package/docs/agents/org-route-resource-model-spec.md +183 -0
  43. package/docs/agents/org-scoping-namespace-spec.md +114 -0
  44. package/docs/agents/rbac-secrets-management-spec.md +406 -0
  45. package/docs/agents/repository-page-integration-spec.md +255 -0
  46. package/docs/agents/resource-contract-examples.md +808 -0
  47. package/docs/agents/resource-relationship-map.md +190 -0
  48. package/docs/agents/security-threat-model.md +188 -0
  49. package/docs/agents/shared-memory-company-brain-spec.md +358 -0
  50. package/docs/agents/storage-migration-spec.md +168 -0
  51. package/docs/agents/subagent-orchestration-spec.md +152 -0
  52. package/docs/agents/system-overview.md +88 -0
  53. package/docs/agents/tools-mcp-skills-spec.md +189 -0
  54. package/docs/agents/traceability-matrix.md +79 -0
  55. package/docs/agents/ui-flow-spec.md +211 -0
  56. package/docs/agents/ui-ux-system-spec.md +426 -0
  57. package/docs/agents/workspace-lifecycle-spec.md +166 -0
  58. package/docs/architecture-spec.md +78 -0
  59. package/docs/architecture-v2.md +2759 -0
  60. package/docs/components/control-plane.md +78 -0
  61. package/docs/components/data-plane.md +69 -0
  62. package/docs/components/hooks-events.md +67 -0
  63. package/docs/components/identity-rbac-policy.md +73 -0
  64. package/docs/components/kubevela-oam.md +70 -0
  65. package/docs/components/operations-publishing.md +81 -0
  66. package/docs/components/runners-ci.md +66 -0
  67. package/docs/components/web-ui.md +94 -0
  68. package/docs/crd-behaviors-and-relationships.md +3926 -0
  69. package/docs/external/README.md +47 -0
  70. package/docs/external/bidirectional-sync-design.md +134 -0
  71. package/docs/external/cicd-interface.md +64 -0
  72. package/docs/external/external-backend-controllers.md +170 -0
  73. package/docs/external/external-backend-crds.md +234 -0
  74. package/docs/external/external-backend-ui-spec.md +151 -0
  75. package/docs/external/external-backend-ux-flows.md +115 -0
  76. package/docs/external/external-object-mapping.md +125 -0
  77. package/docs/external/git-forge-interface.md +68 -0
  78. package/docs/external/github-integration-design.md +151 -0
  79. package/docs/external/issue-tracking-interface.md +66 -0
  80. package/docs/external/provider-capability-manifests.md +204 -0
  81. package/docs/external/provider-catalog.md +139 -0
  82. package/docs/external/provider-rollout-testing.md +78 -0
  83. package/docs/external/research-results.md +48 -0
  84. package/docs/external/security-auth-permissions.md +81 -0
  85. package/docs/external/sync-state-machines.md +108 -0
  86. package/docs/external/unified-external-backend-model.md +107 -0
  87. package/docs/external/user-facing-changes.md +67 -0
  88. package/docs/gaps.md +161 -0
  89. package/docs/install.md +94 -0
  90. package/docs/integration-and-design-decisions.md +1530 -0
  91. package/docs/kradle-design.md +334 -0
  92. package/docs/local-minikube.md +55 -0
  93. package/docs/ontology/README.md +32 -0
  94. package/docs/ontology/bounded-contexts.md +29 -0
  95. package/docs/ontology/events-and-hooks.md +32 -0
  96. package/docs/ontology/oam-kubevela.md +32 -0
  97. package/docs/ontology/operations-and-release.md +25 -0
  98. package/docs/ontology/personas-and-actors.md +32 -0
  99. package/docs/ontology/policies-and-invariants.md +33 -0
  100. package/docs/ontology/problem-space.md +30 -0
  101. package/docs/ontology/resource-contracts.md +40 -0
  102. package/docs/ontology/resource-taxonomy.md +42 -0
  103. package/docs/ontology/runners-and-ci.md +29 -0
  104. package/docs/ontology/solution-space.md +24 -0
  105. package/docs/ontology/storage-and-data-boundaries.md +29 -0
  106. package/docs/ontology/validation-matrix.md +24 -0
  107. package/docs/ontology/web-ui-excellent-flows.md +32 -0
  108. package/docs/ontology/workflows.md +39 -0
  109. package/docs/ontology/world.md +35 -0
  110. package/docs/openapi.yaml +1291 -0
  111. package/docs/product-requirements.md +62 -0
  112. package/docs/requirements-v2.md +235 -0
  113. package/docs/roadmap-mvp.md +87 -0
  114. package/docs/sdk-api-reference.md +1108 -0
  115. package/docs/system-requirements.md +90 -0
  116. package/docs/system-spec-v2.md +1230 -0
  117. package/docs/tests/README.md +53 -0
  118. package/docs/tests/agent-qa-plan.md +63 -0
  119. package/docs/tests/browser-ui-tests.md +62 -0
  120. package/docs/tests/ci-quality-gates.md +48 -0
  121. package/docs/tests/coverage-model.md +64 -0
  122. package/docs/tests/e2e-scenario-tests.md +53 -0
  123. package/docs/tests/fixtures-test-data.md +63 -0
  124. package/docs/tests/observability-reliability-tests.md +54 -0
  125. package/docs/tests/product-test-matrix.md +145 -0
  126. package/docs/tests/qa-adoption-roadmap.md +130 -0
  127. package/docs/tests/qa-automation-plan.md +101 -0
  128. package/docs/tests/security-compliance-tests.md +57 -0
  129. package/docs/tests/test-framework-tools.md +88 -0
  130. package/docs/tests/test-suite-layout.md +121 -0
  131. package/docs/tests/unit-integration-tests.md +48 -0
  132. package/docs/todo-kyverno +714 -0
  133. package/docs/todos.md +4 -0
  134. package/docs/user-stories.md +78 -0
  135. package/docs/web-console-spec.md +533 -0
  136. package/examples/minikube-demo.yaml +190 -0
  137. package/examples/oam-application.yaml +23 -0
  138. package/examples/policy-kyverno-pr-title.yaml +18 -0
  139. package/package.json +66 -0
  140. package/scripts/build.mjs +29 -0
  141. package/scripts/setup-minikube.mjs +65 -0
  142. package/scripts/smoke.mjs +37 -0
  143. package/scripts/validate-doc-coverage.mjs +152 -0
  144. package/scripts/validate-package.mjs +95 -0
  145. package/scripts/validate-ui.mjs +305 -0
  146. package/src/agent-adapter-controller.js +169 -0
  147. package/src/agent-approval-controller.js +170 -0
  148. package/src/agent-context-bundles.js +242 -0
  149. package/src/agent-dispatch-controller.js +549 -0
  150. package/src/agent-gateway-config-controller.js +147 -0
  151. package/src/agent-identity-migration.js +115 -0
  152. package/src/agent-memory-controller.js +357 -0
  153. package/src/agent-memory-import.js +327 -0
  154. package/src/agent-memory-query.js +292 -0
  155. package/src/agent-memory-repository-source-controller.js +255 -0
  156. package/src/agent-mux-client.js +589 -0
  157. package/src/agent-permission-review.js +250 -0
  158. package/src/agent-persona-controller.js +135 -0
  159. package/src/agent-project-controller.js +117 -0
  160. package/src/agent-prompt-composition.js +55 -0
  161. package/src/agent-provider-config-controller.js +151 -0
  162. package/src/agent-secret-config-grant-controller.js +282 -0
  163. package/src/agent-session-transcript-controller.js +189 -0
  164. package/src/agent-stack-controller.js +421 -0
  165. package/src/agent-subagent-controller.js +160 -0
  166. package/src/agent-transport-binding-controller.js +121 -0
  167. package/src/agent-trigger-controller.js +387 -0
  168. package/src/agent-workspace-controller.js +702 -0
  169. package/src/agent-writeback-controller.js +302 -0
  170. package/src/api-controller.js +621 -0
  171. package/src/argocd-gitops.js +43 -0
  172. package/src/artifact-registry-controller.js +542 -0
  173. package/src/assistant-runtime.js +284 -0
  174. package/src/async-controller.js +207 -0
  175. package/src/audit-controller.js +191 -0
  176. package/src/auth.js +310 -0
  177. package/src/component-catalog.js +41 -0
  178. package/src/control-plane.js +136 -0
  179. package/src/controller-client.js +112 -0
  180. package/src/controller-ui.js +620 -0
  181. package/src/data-plane.js +179 -0
  182. package/src/event-bus.js +397 -0
  183. package/src/external/conflict-controller.js +225 -0
  184. package/src/external/github/auth.js +96 -0
  185. package/src/external/github/cicd.js +180 -0
  186. package/src/external/github/git-forge.js +240 -0
  187. package/src/external/github/index.js +144 -0
  188. package/src/external/github/issue-tracking.js +163 -0
  189. package/src/external/provider-adapter.js +161 -0
  190. package/src/external/provider-resource-factory.js +221 -0
  191. package/src/external/sync-controller.js +235 -0
  192. package/src/external/webhook-controller.js +144 -0
  193. package/src/external/write-controller.js +283 -0
  194. package/src/gitea-backend.js +131 -0
  195. package/src/gitea-service.js +173 -0
  196. package/src/handoff.js +98 -0
  197. package/src/health-probes.js +134 -0
  198. package/src/hooks-events.js +63 -0
  199. package/src/hooks-lifecycle.js +117 -0
  200. package/src/http-server.js +409 -0
  201. package/src/identity-policy.js +86 -0
  202. package/src/index.js +71 -0
  203. package/src/jitsi-agent-bridge.js +141 -0
  204. package/src/jitsi-meeting-controller.js +291 -0
  205. package/src/jitsi-sync-controller.js +198 -0
  206. package/src/kradle-inference-service-controller.js +246 -0
  207. package/src/kubernetes-controller-async.js +531 -0
  208. package/src/kubernetes-controller.js +904 -0
  209. package/src/kubernetes-resource-gateway.js +48 -0
  210. package/src/model-route-controller.js +364 -0
  211. package/src/notification-controller.js +178 -0
  212. package/src/operations.js +112 -0
  213. package/src/org-scoping.js +5 -0
  214. package/src/resource-model.js +282 -0
  215. package/src/runner-controller.js +272 -0
  216. package/src/runners-ci.js +48 -0
  217. package/src/runtime.js +196 -0
  218. package/src/snapshot-cache.js +157 -0
  219. package/src/virtual-model-controller.js +538 -0
  220. package/src/virtual-model-hook-bridge.js +200 -0
  221. package/src/web-ui.js +40 -0
  222. package/tests/agent-adapter-controller.test.js +361 -0
  223. package/tests/agent-approval-controller.test.js +173 -0
  224. package/tests/agent-context-bundles.test.js +278 -0
  225. package/tests/agent-dispatch-controller.test.js +679 -0
  226. package/tests/agent-gateway-config-controller.test.js +386 -0
  227. package/tests/agent-identity-migration.test.js +87 -0
  228. package/tests/agent-memory-controller.test.js +461 -0
  229. package/tests/agent-memory-import-snapshot.test.js +477 -0
  230. package/tests/agent-memory-query.test.js +404 -0
  231. package/tests/agent-memory-repository-source.test.js +514 -0
  232. package/tests/agent-mux-client.test.js +389 -0
  233. package/tests/agent-mux-integration.test.js +971 -0
  234. package/tests/agent-permission-review-v2.test.js +317 -0
  235. package/tests/agent-permission-review.test.js +209 -0
  236. package/tests/agent-persona-controller.test.js +127 -0
  237. package/tests/agent-project-controller.test.js +302 -0
  238. package/tests/agent-prompt-composition.test.js +76 -0
  239. package/tests/agent-provider-config-controller.test.js +376 -0
  240. package/tests/agent-resources.test.js +303 -0
  241. package/tests/agent-secret-config-grant.test.js +231 -0
  242. package/tests/agent-session-transcript-controller.test.js +499 -0
  243. package/tests/agent-stack-controller.test.js +283 -0
  244. package/tests/agent-subagent-controller.test.js +201 -0
  245. package/tests/agent-transport-binding-controller.test.js +294 -0
  246. package/tests/agent-trigger-controller.test.js +271 -0
  247. package/tests/agent-trigger-routes.test.js +190 -0
  248. package/tests/agent-trigger-sources.test.js +245 -0
  249. package/tests/agent-workspace-controller.test.js +181 -0
  250. package/tests/agent-writeback.test.js +292 -0
  251. package/tests/approval-persistence.test.js +171 -0
  252. package/tests/artifact-registry.test.js +511 -0
  253. package/tests/assistant-runtime.test.js +506 -0
  254. package/tests/async-controller.test.js +252 -0
  255. package/tests/audit-controller.test.js +227 -0
  256. package/tests/codespace-controller.test.js +318 -0
  257. package/tests/controller-client.test.js +133 -0
  258. package/tests/deployment.test.js +527 -0
  259. package/tests/e2e/lifecycle.test.js +120 -0
  260. package/tests/event-bus-integration.test.js +355 -0
  261. package/tests/external-github-forge.test.js +560 -0
  262. package/tests/external-github-issues-cicd.test.js +520 -0
  263. package/tests/external-integration.test.js +470 -0
  264. package/tests/external-persistence.test.js +415 -0
  265. package/tests/external-provider-adapter.test.js +365 -0
  266. package/tests/external-resource-model.test.js +223 -0
  267. package/tests/external-webhook-sync.test.js +287 -0
  268. package/tests/external-write-conflict.test.js +353 -0
  269. package/tests/gitea-service.test.js +253 -0
  270. package/tests/health-check-real.test.js +165 -0
  271. package/tests/health-probes.test.js +90 -0
  272. package/tests/hooks-lifecycle.test.js +364 -0
  273. package/tests/integration/full-flow.test.js +266 -0
  274. package/tests/jitsi-agent-bridge.test.js +119 -0
  275. package/tests/jitsi-helm-integration.test.js +77 -0
  276. package/tests/jitsi-meeting-controller.test.js +170 -0
  277. package/tests/jitsi-resource-model.test.js +73 -0
  278. package/tests/jitsi-sync-controller.test.js +112 -0
  279. package/tests/kradle-inference-service.test.js +689 -0
  280. package/tests/kradle.test.js +779 -0
  281. package/tests/memory-search-wiring.test.js +270 -0
  282. package/tests/model-route-controller.test.js +733 -0
  283. package/tests/notification-controller.test.js +196 -0
  284. package/tests/notification-integration.test.js +179 -0
  285. package/tests/org-scoping.test.js +687 -0
  286. package/tests/runner-controller.test.js +327 -0
  287. package/tests/runner-integration.test.js +231 -0
  288. package/tests/session-cookie-hmac.test.js +151 -0
  289. package/tests/snapshot-performance.test.js +315 -0
  290. package/tests/sse-events.test.js +107 -0
  291. package/tests/virtual-model-controller.test.js +877 -0
  292. package/tests/virtual-model-hook-bridge.test.js +384 -0
  293. package/tests/webhook-trigger.test.js +198 -0
  294. package/tests/workspace-volumes.test.js +312 -0
  295. package/tests/writeback-persistence.test.js +207 -0
@@ -0,0 +1,511 @@
1
+ import assert from 'node:assert/strict';
2
+ import test from 'node:test';
3
+ import { createArtifactRegistryController } from '../src/artifact-registry-controller.js';
4
+ import { validateResource, createResource, RESOURCE_DEFINITIONS, CONFIG_KINDS, AGGREGATED_KINDS, ALL_KINDS } from '../src/resource-model.js';
5
+
6
+ // ---------------------------------------------------------------------------
7
+ // Helpers
8
+ // ---------------------------------------------------------------------------
9
+
10
+ function makeRegistry(overrides = {}) {
11
+ return {
12
+ metadata: { name: 'my-npm-registry' },
13
+ spec: {
14
+ organizationRef: 'acme',
15
+ registryType: 'npm',
16
+ storageBackend: 'internal',
17
+ ...overrides,
18
+ },
19
+ };
20
+ }
21
+
22
+ function makeFeed(overrides = {}) {
23
+ return {
24
+ metadata: { name: 'my-feed' },
25
+ spec: {
26
+ organizationRef: 'acme',
27
+ registryRef: 'my-npm-registry',
28
+ feedName: '@acme',
29
+ ...overrides,
30
+ },
31
+ };
32
+ }
33
+
34
+ function makeAccessPolicy(overrides = {}) {
35
+ return {
36
+ metadata: { name: 'my-policy' },
37
+ spec: {
38
+ organizationRef: 'acme',
39
+ feedRef: 'my-feed',
40
+ subjects: ['user:alice', 'team:platform'],
41
+ permissions: ['read', 'write'],
42
+ ...overrides,
43
+ },
44
+ };
45
+ }
46
+
47
+ // ---------------------------------------------------------------------------
48
+ // Resource model integration
49
+ // ---------------------------------------------------------------------------
50
+
51
+ test('resource model includes ArtifactRegistry in CONFIG_KINDS', () => {
52
+ assert.ok(CONFIG_KINDS.has('ArtifactRegistry'));
53
+ assert.ok(CONFIG_KINDS.has('ArtifactFeed'));
54
+ assert.ok(CONFIG_KINDS.has('ArtifactAccessPolicy'));
55
+ });
56
+
57
+ test('resource model includes ArtifactVersion and ArtifactDownload in AGGREGATED_KINDS', () => {
58
+ assert.ok(AGGREGATED_KINDS.has('ArtifactVersion'));
59
+ assert.ok(AGGREGATED_KINDS.has('ArtifactDownload'));
60
+ });
61
+
62
+ test('ALL_KINDS includes all 5 artifact kinds', () => {
63
+ for (const kind of ['ArtifactRegistry', 'ArtifactFeed', 'ArtifactAccessPolicy', 'ArtifactVersion', 'ArtifactDownload']) {
64
+ assert.ok(ALL_KINDS.has(kind), `ALL_KINDS should include ${kind}`);
65
+ }
66
+ });
67
+
68
+ test('RESOURCE_DEFINITIONS has correct definitions for artifact kinds', () => {
69
+ assert.equal(RESOURCE_DEFINITIONS.ArtifactRegistry.storage, 'etcd');
70
+ assert.equal(RESOURCE_DEFINITIONS.ArtifactRegistry.context, 'artifacts');
71
+ assert.equal(RESOURCE_DEFINITIONS.ArtifactRegistry.plural, 'artifactregistries');
72
+ assert.equal(RESOURCE_DEFINITIONS.ArtifactVersion.storage, 'postgres');
73
+ assert.equal(RESOURCE_DEFINITIONS.ArtifactDownload.storage, 'postgres');
74
+ });
75
+
76
+ test('createResource works for ArtifactRegistry', () => {
77
+ const resource = createResource('ArtifactRegistry', { name: 'test-reg' }, { organizationRef: 'acme', registryType: 'npm', storageBackend: 'internal' });
78
+ assert.equal(resource.kind, 'ArtifactRegistry');
79
+ assert.equal(resource.spec.registryType, 'npm');
80
+ });
81
+
82
+ test('validateResource enforces required spec for ArtifactRegistry', () => {
83
+ const resource = createResource('ArtifactRegistry', { name: 'test-reg' }, { organizationRef: 'acme', registryType: 'npm', storageBackend: 's3' });
84
+ assert.doesNotThrow(() => validateResource(resource));
85
+ });
86
+
87
+ test('validateResource rejects ArtifactRegistry with missing required fields', () => {
88
+ const resource = createResource('ArtifactRegistry', { name: 'test-reg' }, { organizationRef: 'acme' });
89
+ assert.throws(() => validateResource(resource), /registryType/);
90
+ });
91
+
92
+ // ---------------------------------------------------------------------------
93
+ // Registry validation
94
+ // ---------------------------------------------------------------------------
95
+
96
+ test('validateRegistry accepts valid npm registry config', () => {
97
+ const controller = createArtifactRegistryController();
98
+ const result = controller.validateRegistry(makeRegistry({ registryType: 'npm' }));
99
+ assert.ok(result.valid);
100
+ assert.equal(result.errors.length, 0);
101
+ });
102
+
103
+ test('validateRegistry accepts valid docker registry config', () => {
104
+ const controller = createArtifactRegistryController();
105
+ const result = controller.validateRegistry(makeRegistry({ registryType: 'docker', storageBackend: 's3' }));
106
+ assert.ok(result.valid);
107
+ assert.equal(result.errors.length, 0);
108
+ });
109
+
110
+ test('validateRegistry accepts valid pip registry config', () => {
111
+ const controller = createArtifactRegistryController();
112
+ const result = controller.validateRegistry(makeRegistry({ registryType: 'pip', storageBackend: 'gcs' }));
113
+ assert.ok(result.valid);
114
+ });
115
+
116
+ test('validateRegistry accepts generic type', () => {
117
+ const controller = createArtifactRegistryController();
118
+ const result = controller.validateRegistry(makeRegistry({ registryType: 'generic', storageBackend: 'azure-blob' }));
119
+ assert.ok(result.valid);
120
+ });
121
+
122
+ test('validateRegistry rejects unknown type', () => {
123
+ const controller = createArtifactRegistryController();
124
+ const result = controller.validateRegistry(makeRegistry({ registryType: 'maven' }));
125
+ assert.ok(!result.valid);
126
+ assert.ok(result.errors.some((e) => e.includes('maven')));
127
+ assert.ok(result.errors.some((e) => e.includes('not supported')));
128
+ });
129
+
130
+ test('validateRegistry rejects missing storageBackend', () => {
131
+ const controller = createArtifactRegistryController();
132
+ const result = controller.validateRegistry({ metadata: { name: 'r' }, spec: { organizationRef: 'acme', registryType: 'npm' } });
133
+ assert.ok(!result.valid);
134
+ assert.ok(result.errors.some((e) => e.includes('storageBackend')));
135
+ });
136
+
137
+ test('validateRegistry rejects null resource', () => {
138
+ const controller = createArtifactRegistryController();
139
+ const result = controller.validateRegistry(null);
140
+ assert.ok(!result.valid);
141
+ assert.ok(result.errors.some((e) => e.includes('null')));
142
+ });
143
+
144
+ // ---------------------------------------------------------------------------
145
+ // Feed validation
146
+ // ---------------------------------------------------------------------------
147
+
148
+ test('validateFeed accepts valid feed with registryRef', () => {
149
+ const controller = createArtifactRegistryController();
150
+ const result = controller.validateFeed(makeFeed());
151
+ assert.ok(result.valid);
152
+ assert.equal(result.errors.length, 0);
153
+ });
154
+
155
+ test('validateFeed rejects missing feedName', () => {
156
+ const controller = createArtifactRegistryController();
157
+ const result = controller.validateFeed(makeFeed({ feedName: '' }));
158
+ assert.ok(!result.valid);
159
+ assert.ok(result.errors.some((e) => e.includes('feedName')));
160
+ });
161
+
162
+ test('validateFeed rejects missing registryRef', () => {
163
+ const controller = createArtifactRegistryController();
164
+ const result = controller.validateFeed({ metadata: { name: 'f' }, spec: { organizationRef: 'acme', feedName: 'test' } });
165
+ assert.ok(!result.valid);
166
+ assert.ok(result.errors.some((e) => e.includes('registryRef')));
167
+ });
168
+
169
+ // ---------------------------------------------------------------------------
170
+ // Access policy validation
171
+ // ---------------------------------------------------------------------------
172
+
173
+ test('validateAccessPolicy accepts valid policy with subjects and permissions', () => {
174
+ const controller = createArtifactRegistryController();
175
+ const result = controller.validateAccessPolicy(makeAccessPolicy());
176
+ assert.ok(result.valid);
177
+ });
178
+
179
+ test('validateAccessPolicy rejects empty subjects', () => {
180
+ const controller = createArtifactRegistryController();
181
+ const result = controller.validateAccessPolicy(makeAccessPolicy({ subjects: [] }));
182
+ assert.ok(!result.valid);
183
+ assert.ok(result.errors.some((e) => e.includes('subjects')));
184
+ });
185
+
186
+ test('validateAccessPolicy rejects unknown permissions', () => {
187
+ const controller = createArtifactRegistryController();
188
+ const result = controller.validateAccessPolicy(makeAccessPolicy({ permissions: ['read', 'delete'] }));
189
+ assert.ok(!result.valid);
190
+ assert.ok(result.errors.some((e) => e.includes('delete')));
191
+ });
192
+
193
+ // ---------------------------------------------------------------------------
194
+ // Registry endpoint
195
+ // ---------------------------------------------------------------------------
196
+
197
+ test('getRegistryEndpoint returns internal URL for internal backend', () => {
198
+ const controller = createArtifactRegistryController();
199
+ const registry = makeRegistry();
200
+ const url = controller.getRegistryEndpoint(registry);
201
+ assert.ok(url.includes('/api/v1/registry/npm/acme'));
202
+ assert.ok(url.startsWith('https://kradle.example.com'));
203
+ });
204
+
205
+ test('getRegistryEndpoint returns external URL for npm.pkg.github.com', () => {
206
+ const controller = createArtifactRegistryController();
207
+ const registry = makeRegistry({
208
+ registryType: 'npm',
209
+ storageBackend: 's3',
210
+ externalBackendRef: 'github-packages',
211
+ externalUrl: 'https://npm.pkg.github.com/@acme',
212
+ });
213
+ const url = controller.getRegistryEndpoint(registry);
214
+ assert.equal(url, 'https://npm.pkg.github.com/@acme');
215
+ });
216
+
217
+ test('getRegistryEndpoint returns external URL for ghcr.io', () => {
218
+ const controller = createArtifactRegistryController();
219
+ const registry = makeRegistry({
220
+ registryType: 'docker',
221
+ storageBackend: 's3',
222
+ externalBackendRef: 'github-packages',
223
+ externalUrl: 'https://ghcr.io/acme',
224
+ });
225
+ const url = controller.getRegistryEndpoint(registry);
226
+ assert.equal(url, 'https://ghcr.io/acme');
227
+ });
228
+
229
+ test('getRegistryEndpoint generates default external npm URL when no externalUrl', () => {
230
+ const controller = createArtifactRegistryController();
231
+ const registry = makeRegistry({
232
+ registryType: 'npm',
233
+ storageBackend: 's3',
234
+ externalBackendRef: 'github-packages',
235
+ });
236
+ const url = controller.getRegistryEndpoint(registry);
237
+ assert.equal(url, 'https://npm.pkg.github.com/@acme');
238
+ });
239
+
240
+ // ---------------------------------------------------------------------------
241
+ // Artifact publish / list / get / delete
242
+ // ---------------------------------------------------------------------------
243
+
244
+ test('publishArtifact creates ArtifactVersion with digest', async () => {
245
+ const controller = createArtifactRegistryController();
246
+ const feed = makeFeed();
247
+ const result = await controller.publishArtifact(feed, {
248
+ name: '@acme/utils',
249
+ version: '1.0.0',
250
+ size: 12345,
251
+ metadata: { license: 'MIT' },
252
+ });
253
+ assert.ok(result.version);
254
+ assert.equal(result.version.kind, 'ArtifactVersion');
255
+ assert.equal(result.version.spec.name, '@acme/utils');
256
+ assert.equal(result.version.spec.version, '1.0.0');
257
+ assert.ok(result.digest);
258
+ assert.ok(result.publishedAt);
259
+ assert.equal(result.version.status.phase, 'Published');
260
+ });
261
+
262
+ test('listVersions returns paginated versions', async () => {
263
+ const controller = createArtifactRegistryController();
264
+ const feed = makeFeed();
265
+ await controller.publishArtifact(feed, { name: 'pkg-a', version: '1.0.0', digest: 'aaa' });
266
+ await controller.publishArtifact(feed, { name: 'pkg-a', version: '2.0.0', digest: 'bbb' });
267
+ await controller.publishArtifact(feed, { name: 'pkg-b', version: '1.0.0', digest: 'ccc' });
268
+
269
+ const all = await controller.listVersions(feed);
270
+ assert.equal(all.total, 3);
271
+ assert.equal(all.items.length, 3);
272
+
273
+ const filtered = await controller.listVersions(feed, { name: 'pkg-a' });
274
+ assert.equal(filtered.total, 2);
275
+
276
+ const paginated = await controller.listVersions(feed, { limit: 1, offset: 1 });
277
+ assert.equal(paginated.items.length, 1);
278
+ assert.equal(paginated.total, 3);
279
+ });
280
+
281
+ test('getVersion returns specific version', async () => {
282
+ const controller = createArtifactRegistryController();
283
+ const feed = makeFeed();
284
+ await controller.publishArtifact(feed, { name: 'my-lib', version: '3.2.1', digest: 'xyz' });
285
+ const version = await controller.getVersion(feed, 'my-lib', '3.2.1');
286
+ assert.ok(version);
287
+ assert.equal(version.spec.name, 'my-lib');
288
+ assert.equal(version.spec.version, '3.2.1');
289
+ });
290
+
291
+ test('getVersion returns null for non-existent version', async () => {
292
+ const controller = createArtifactRegistryController();
293
+ const feed = makeFeed();
294
+ const result = await controller.getVersion(feed, 'nope', '0.0.0');
295
+ assert.equal(result, null);
296
+ });
297
+
298
+ test('deleteVersion marks as deleted', async () => {
299
+ const controller = createArtifactRegistryController();
300
+ const feed = makeFeed();
301
+ await controller.publishArtifact(feed, { name: 'doomed', version: '1.0.0', digest: 'ddd' });
302
+ const result = await controller.deleteVersion(feed, 'doomed', '1.0.0');
303
+ assert.ok(result.deleted);
304
+ assert.ok(result.deletedAt);
305
+
306
+ // getVersion should not return soft-deleted versions
307
+ const version = await controller.getVersion(feed, 'doomed', '1.0.0');
308
+ assert.equal(version, null);
309
+ });
310
+
311
+ test('deleteVersion returns deleted: false for non-existent version', async () => {
312
+ const controller = createArtifactRegistryController();
313
+ const feed = makeFeed();
314
+ const result = await controller.deleteVersion(feed, 'nope', '0.0.0');
315
+ assert.ok(!result.deleted);
316
+ });
317
+
318
+ // ---------------------------------------------------------------------------
319
+ // Docker-specific
320
+ // ---------------------------------------------------------------------------
321
+
322
+ test('Docker: listDockerTags returns tags', async () => {
323
+ const controller = createArtifactRegistryController();
324
+ const feed = makeFeed({ feedName: 'docker-feed' });
325
+ await controller.publishArtifact(feed, { name: 'myapp', version: 'v1.0', digest: 'd1' });
326
+ await controller.publishArtifact(feed, { name: 'myapp', version: 'v1.1', digest: 'd2' });
327
+ await controller.publishArtifact(feed, { name: 'other', version: 'latest', digest: 'd3' });
328
+
329
+ const result = await controller.listDockerTags(feed, 'myapp');
330
+ assert.equal(result.tags.length, 2);
331
+ assert.ok(result.tags.includes('v1.0'));
332
+ assert.ok(result.tags.includes('v1.1'));
333
+ });
334
+
335
+ test('Docker: getDockerManifest returns manifest by tag', async () => {
336
+ const controller = createArtifactRegistryController();
337
+ const feed = makeFeed();
338
+ await controller.publishArtifact(feed, { name: 'myapp', version: 'latest', digest: 'sha256:abc123', size: 50000 });
339
+ const manifest = await controller.getDockerManifest(feed, 'myapp', 'latest');
340
+ assert.ok(manifest);
341
+ assert.equal(manifest.repository, 'myapp');
342
+ assert.equal(manifest.tag, 'latest');
343
+ assert.equal(manifest.digest, 'sha256:abc123');
344
+ assert.equal(manifest.size, 50000);
345
+ });
346
+
347
+ // ---------------------------------------------------------------------------
348
+ // npm-specific
349
+ // ---------------------------------------------------------------------------
350
+
351
+ test('npm: getNpmPackageInfo returns package metadata', async () => {
352
+ const controller = createArtifactRegistryController();
353
+ const feed = makeFeed();
354
+ await controller.publishArtifact(feed, { name: '@acme/sdk', version: '1.0.0', digest: 'n1', size: 100 });
355
+ await controller.publishArtifact(feed, { name: '@acme/sdk', version: '2.0.0', digest: 'n2', size: 200 });
356
+
357
+ const info = await controller.getNpmPackageInfo(feed, '@acme/sdk');
358
+ assert.ok(info);
359
+ assert.equal(info.name, '@acme/sdk');
360
+ assert.equal(info.latestVersion, '2.0.0');
361
+ assert.ok(info.versions['1.0.0']);
362
+ assert.ok(info.versions['2.0.0']);
363
+ });
364
+
365
+ test('npm: getNpmPackageInfo returns null for unknown package', async () => {
366
+ const controller = createArtifactRegistryController();
367
+ const feed = makeFeed();
368
+ const info = await controller.getNpmPackageInfo(feed, '@acme/unknown');
369
+ assert.equal(info, null);
370
+ });
371
+
372
+ test('npm: listNpmVersions returns versions', async () => {
373
+ const controller = createArtifactRegistryController();
374
+ const feed = makeFeed();
375
+ await controller.publishArtifact(feed, { name: '@acme/lib', version: '0.1.0', digest: 'v1' });
376
+ await controller.publishArtifact(feed, { name: '@acme/lib', version: '0.2.0', digest: 'v2' });
377
+
378
+ const result = await controller.listNpmVersions(feed, '@acme/lib');
379
+ assert.equal(result.versions.length, 2);
380
+ assert.ok(result.versions.includes('0.1.0'));
381
+ assert.ok(result.versions.includes('0.2.0'));
382
+ });
383
+
384
+ // ---------------------------------------------------------------------------
385
+ // pip-specific
386
+ // ---------------------------------------------------------------------------
387
+
388
+ test('pip: listPipPackages returns packages', async () => {
389
+ const controller = createArtifactRegistryController();
390
+ const feed = makeFeed();
391
+ await controller.publishArtifact(feed, { name: 'my-package', version: '1.0', digest: 'p1' });
392
+ await controller.publishArtifact(feed, { name: 'another-package', version: '2.0', digest: 'p2' });
393
+
394
+ const result = await controller.listPipPackages(feed);
395
+ assert.equal(result.packages.length, 2);
396
+ assert.ok(result.packages.includes('my-package'));
397
+ assert.ok(result.packages.includes('another-package'));
398
+ });
399
+
400
+ test('pip: getPipPackageInfo returns package info', async () => {
401
+ const controller = createArtifactRegistryController();
402
+ const feed = makeFeed();
403
+ await controller.publishArtifact(feed, { name: 'flask-ext', version: '0.1.0', digest: 'pp1' });
404
+ const info = await controller.getPipPackageInfo(feed, 'flask-ext');
405
+ assert.ok(info);
406
+ assert.equal(info.name, 'flask-ext');
407
+ assert.equal(info.versions.length, 1);
408
+ assert.equal(info.versions[0].version, '0.1.0');
409
+ });
410
+
411
+ // ---------------------------------------------------------------------------
412
+ // Generic/ad-hoc
413
+ // ---------------------------------------------------------------------------
414
+
415
+ test('Generic: uploadArtifact creates version from file', async () => {
416
+ const controller = createArtifactRegistryController();
417
+ const feed = makeFeed();
418
+ const result = await controller.uploadArtifact(feed, { name: 'build.tar.gz', size: 100000 }, { version: '20240101' });
419
+ assert.ok(result.version);
420
+ assert.equal(result.version.spec.name, 'build.tar.gz');
421
+ assert.ok(result.digest);
422
+ });
423
+
424
+ test('Generic: downloadArtifact returns artifact and records audit', async () => {
425
+ const controller = createArtifactRegistryController();
426
+ const feed = makeFeed();
427
+ await controller.publishArtifact(feed, { name: 'release.zip', version: '1.0', digest: 'gen1', size: 50000 });
428
+ const result = await controller.downloadArtifact(feed, 'release.zip', '1.0', 'user:bob');
429
+ assert.ok(result.artifact);
430
+ assert.ok(result.download);
431
+ assert.equal(result.download.kind, 'ArtifactDownload');
432
+ assert.equal(result.download.spec.requestedBy, 'user:bob');
433
+ });
434
+
435
+ test('Generic: downloadArtifact returns null for non-existent artifact', async () => {
436
+ const controller = createArtifactRegistryController();
437
+ const feed = makeFeed();
438
+ const result = await controller.downloadArtifact(feed, 'nope', '0.0', 'user:bob');
439
+ assert.equal(result.artifact, null);
440
+ assert.equal(result.download, null);
441
+ });
442
+
443
+ // ---------------------------------------------------------------------------
444
+ // Access policy enforcement
445
+ // ---------------------------------------------------------------------------
446
+
447
+ test('Access policy enforces read permissions', () => {
448
+ const controller = createArtifactRegistryController();
449
+ const policy = makeAccessPolicy({ subjects: ['user:alice', 'user:bob'], permissions: ['read'] });
450
+ assert.ok(controller.checkAccess(policy, 'user:alice', 'read'));
451
+ assert.ok(!controller.checkAccess(policy, 'user:alice', 'write'));
452
+ assert.ok(!controller.checkAccess(policy, 'user:alice', 'admin'));
453
+ assert.ok(!controller.checkAccess(policy, 'user:charlie', 'read'));
454
+ });
455
+
456
+ test('Access policy enforces write permissions (implies read)', () => {
457
+ const controller = createArtifactRegistryController();
458
+ const policy = makeAccessPolicy({ subjects: ['user:alice'], permissions: ['write'] });
459
+ assert.ok(controller.checkAccess(policy, 'user:alice', 'read'));
460
+ assert.ok(controller.checkAccess(policy, 'user:alice', 'write'));
461
+ assert.ok(!controller.checkAccess(policy, 'user:alice', 'admin'));
462
+ });
463
+
464
+ test('Access policy enforces admin permissions (implies read + write)', () => {
465
+ const controller = createArtifactRegistryController();
466
+ const policy = makeAccessPolicy({ subjects: ['team:platform'], permissions: ['admin'] });
467
+ assert.ok(controller.checkAccess(policy, 'team:platform', 'read'));
468
+ assert.ok(controller.checkAccess(policy, 'team:platform', 'write'));
469
+ assert.ok(controller.checkAccess(policy, 'team:platform', 'admin'));
470
+ });
471
+
472
+ // ---------------------------------------------------------------------------
473
+ // External registry integration
474
+ // ---------------------------------------------------------------------------
475
+
476
+ test('resolveExternalRegistryCapability returns capability for enabled binding', () => {
477
+ const controller = createArtifactRegistryController();
478
+ const binding = {
479
+ spec: { interfaces: { artifactRegistry: { enabled: true, mode: 'read-write' } } },
480
+ };
481
+ const result = controller.resolveExternalRegistryCapability(binding);
482
+ assert.ok(result);
483
+ assert.ok(result.enabled);
484
+ assert.equal(result.mode, 'read-write');
485
+ });
486
+
487
+ test('resolveExternalRegistryCapability returns null for disabled binding', () => {
488
+ const controller = createArtifactRegistryController();
489
+ const binding = {
490
+ spec: { interfaces: { artifactRegistry: { enabled: false } } },
491
+ };
492
+ const result = controller.resolveExternalRegistryCapability(binding);
493
+ assert.equal(result, null);
494
+ });
495
+
496
+ test('resolveExternalRegistryCapability returns null for missing interfaces', () => {
497
+ const controller = createArtifactRegistryController();
498
+ const binding = { spec: {} };
499
+ const result = controller.resolveExternalRegistryCapability(binding);
500
+ assert.equal(result, null);
501
+ });
502
+
503
+ test('resolveExternalRegistryCapability defaults mode to read-only', () => {
504
+ const controller = createArtifactRegistryController();
505
+ const binding = {
506
+ spec: { interfaces: { artifactRegistry: { enabled: true } } },
507
+ };
508
+ const result = controller.resolveExternalRegistryCapability(binding);
509
+ assert.ok(result);
510
+ assert.equal(result.mode, 'read-only');
511
+ });