@celilo/cli 0.1.0

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 (267) hide show
  1. package/README.md +1566 -0
  2. package/bin/celilo +16 -0
  3. package/drizzle/0000_complex_puma.sql +179 -0
  4. package/drizzle/0001_dizzy_wolfpack.sql +2 -0
  5. package/drizzle/0002_web_routes.sql +16 -0
  6. package/drizzle/0003_backup_storage.sql +32 -0
  7. package/drizzle/meta/0000_snapshot.json +1151 -0
  8. package/drizzle/meta/0001_snapshot.json +1167 -0
  9. package/drizzle/meta/0002_snapshot.json +1257 -0
  10. package/drizzle/meta/_journal.json +27 -0
  11. package/package.json +64 -0
  12. package/schemas/system_config.json +106 -0
  13. package/src/__integration__/container-services-cli.integration.test.ts +246 -0
  14. package/src/ansible/dependencies.test.ts +309 -0
  15. package/src/ansible/dependencies.ts +896 -0
  16. package/src/ansible/inventory.test.ts +463 -0
  17. package/src/ansible/inventory.ts +445 -0
  18. package/src/ansible/secrets.ts +222 -0
  19. package/src/ansible/validation.test.ts +92 -0
  20. package/src/ansible/validation.ts +272 -0
  21. package/src/api-clients/digitalocean.ts +94 -0
  22. package/src/api-clients/proxmox.ts +655 -0
  23. package/src/capabilities/logging-wrapper.test.ts +217 -0
  24. package/src/capabilities/lookup.test.ts +149 -0
  25. package/src/capabilities/lookup.ts +89 -0
  26. package/src/capabilities/public-web-helpers.test.ts +198 -0
  27. package/src/capabilities/public-web-publish.test.ts +458 -0
  28. package/src/capabilities/registration.test.ts +395 -0
  29. package/src/capabilities/registration.ts +200 -0
  30. package/src/capabilities/route-validation.test.ts +121 -0
  31. package/src/capabilities/route-validation.ts +96 -0
  32. package/src/capabilities/secret-ref.test.ts +313 -0
  33. package/src/capabilities/secret-validation.ts +157 -0
  34. package/src/capabilities/secrets.test.ts +750 -0
  35. package/src/capabilities/secrets.ts +244 -0
  36. package/src/capabilities/validation.test.ts +613 -0
  37. package/src/capabilities/validation.ts +160 -0
  38. package/src/capabilities/well-known.test.ts +238 -0
  39. package/src/capabilities/well-known.ts +222 -0
  40. package/src/cli/cli.test.ts +654 -0
  41. package/src/cli/command-registry.ts +742 -0
  42. package/src/cli/command-tree-parser.test.ts +180 -0
  43. package/src/cli/command-tree-parser.ts +193 -0
  44. package/src/cli/commands/backup-create.ts +137 -0
  45. package/src/cli/commands/backup-delete.ts +74 -0
  46. package/src/cli/commands/backup-import.ts +97 -0
  47. package/src/cli/commands/backup-list.ts +132 -0
  48. package/src/cli/commands/backup-name.ts +73 -0
  49. package/src/cli/commands/backup-prune.ts +98 -0
  50. package/src/cli/commands/backup-restore.ts +122 -0
  51. package/src/cli/commands/capability-info.ts +121 -0
  52. package/src/cli/commands/capability-list.ts +47 -0
  53. package/src/cli/commands/completion.ts +87 -0
  54. package/src/cli/commands/hook-run.ts +176 -0
  55. package/src/cli/commands/ipam.ts +607 -0
  56. package/src/cli/commands/machine-add.ts +235 -0
  57. package/src/cli/commands/machine-earmark.ts +82 -0
  58. package/src/cli/commands/machine-list.ts +77 -0
  59. package/src/cli/commands/machine-remove.ts +90 -0
  60. package/src/cli/commands/machine-status.ts +131 -0
  61. package/src/cli/commands/module-audit.ts +51 -0
  62. package/src/cli/commands/module-build.ts +60 -0
  63. package/src/cli/commands/module-config.ts +170 -0
  64. package/src/cli/commands/module-deploy.ts +71 -0
  65. package/src/cli/commands/module-generate.ts +236 -0
  66. package/src/cli/commands/module-health.ts +108 -0
  67. package/src/cli/commands/module-import.ts +80 -0
  68. package/src/cli/commands/module-list.ts +43 -0
  69. package/src/cli/commands/module-logs.ts +73 -0
  70. package/src/cli/commands/module-remove.ts +162 -0
  71. package/src/cli/commands/module-show.ts +208 -0
  72. package/src/cli/commands/module-status.ts +131 -0
  73. package/src/cli/commands/module-types.ts +189 -0
  74. package/src/cli/commands/module-upgrade.ts +192 -0
  75. package/src/cli/commands/package.ts +68 -0
  76. package/src/cli/commands/secret-list.ts +99 -0
  77. package/src/cli/commands/secret-set.ts +134 -0
  78. package/src/cli/commands/service-add-digitalocean.ts +133 -0
  79. package/src/cli/commands/service-add-proxmox.ts +342 -0
  80. package/src/cli/commands/service-config-get.ts +83 -0
  81. package/src/cli/commands/service-config-set.ts +145 -0
  82. package/src/cli/commands/service-list.ts +74 -0
  83. package/src/cli/commands/service-reconfigure.ts +230 -0
  84. package/src/cli/commands/service-remove.ts +103 -0
  85. package/src/cli/commands/service-verify.ts +240 -0
  86. package/src/cli/commands/status.ts +216 -0
  87. package/src/cli/commands/storage-add-local.ts +106 -0
  88. package/src/cli/commands/storage-add-s3.ts +114 -0
  89. package/src/cli/commands/storage-list.ts +72 -0
  90. package/src/cli/commands/storage-remove.ts +54 -0
  91. package/src/cli/commands/storage-set-default.ts +44 -0
  92. package/src/cli/commands/storage-verify.ts +54 -0
  93. package/src/cli/commands/system-config.ts +168 -0
  94. package/src/cli/commands/system-init.ts +314 -0
  95. package/src/cli/commands/system-secret-get.ts +98 -0
  96. package/src/cli/commands/system-secret-set.ts +76 -0
  97. package/src/cli/commands/system-vault-password.ts +34 -0
  98. package/src/cli/completion.test.ts +37 -0
  99. package/src/cli/completion.ts +482 -0
  100. package/src/cli/fuel-gauge.test.ts +208 -0
  101. package/src/cli/fuel-gauge.ts +405 -0
  102. package/src/cli/generate-zsh-completion.test.ts +95 -0
  103. package/src/cli/generate-zsh-completion.ts +497 -0
  104. package/src/cli/index.ts +1583 -0
  105. package/src/cli/interactive-config.test.ts +201 -0
  106. package/src/cli/interactive-config.ts +62 -0
  107. package/src/cli/parser.test.ts +227 -0
  108. package/src/cli/parser.ts +244 -0
  109. package/src/cli/prompts.test.ts +33 -0
  110. package/src/cli/prompts.ts +121 -0
  111. package/src/cli/types.ts +38 -0
  112. package/src/cli/validators.test.ts +235 -0
  113. package/src/cli/validators.ts +188 -0
  114. package/src/config/env.ts +41 -0
  115. package/src/config/paths.test.ts +172 -0
  116. package/src/config/paths.ts +108 -0
  117. package/src/db/client.ts +190 -0
  118. package/src/db/migrate.ts +30 -0
  119. package/src/db/schema.test.ts +221 -0
  120. package/src/db/schema.ts +434 -0
  121. package/src/hooks/capability-loader-firewall.test.ts +246 -0
  122. package/src/hooks/capability-loader.test.ts +100 -0
  123. package/src/hooks/capability-loader.ts +520 -0
  124. package/src/hooks/define-hook.test.ts +488 -0
  125. package/src/hooks/executor.test.ts +462 -0
  126. package/src/hooks/executor.ts +469 -0
  127. package/src/hooks/logger.test.ts +54 -0
  128. package/src/hooks/logger.ts +95 -0
  129. package/src/hooks/test-fixtures/failing-hook.ts +13 -0
  130. package/src/hooks/test-fixtures/no-default-hook.ts +6 -0
  131. package/src/hooks/test-fixtures/success-hook.ts +20 -0
  132. package/src/hooks/test-fixtures/unbranded-hook.ts +11 -0
  133. package/src/hooks/test-fixtures/void-hook.ts +13 -0
  134. package/src/hooks/types.ts +89 -0
  135. package/src/infrastructure/property-extractor.test.ts +194 -0
  136. package/src/infrastructure/property-extractor.ts +151 -0
  137. package/src/ipam/allocator.test.ts +442 -0
  138. package/src/ipam/allocator.ts +369 -0
  139. package/src/ipam/auto-allocator.test.ts +247 -0
  140. package/src/ipam/auto-allocator.ts +270 -0
  141. package/src/ipam/subnet-parser.test.ts +107 -0
  142. package/src/ipam/subnet-parser.ts +136 -0
  143. package/src/manifest/contracts/index.ts +61 -0
  144. package/src/manifest/contracts/v1.ts +118 -0
  145. package/src/manifest/json-schema-roundtrip.test.ts +99 -0
  146. package/src/manifest/schema.ts +367 -0
  147. package/src/manifest/template-validator.test.ts +231 -0
  148. package/src/manifest/template-validator.ts +322 -0
  149. package/src/manifest/validate.test.ts +1180 -0
  150. package/src/manifest/validate.ts +415 -0
  151. package/src/module/import.test.ts +355 -0
  152. package/src/module/import.ts +676 -0
  153. package/src/module/packaging/audit.ts +169 -0
  154. package/src/module/packaging/build.ts +228 -0
  155. package/src/module/packaging/checksum.ts +41 -0
  156. package/src/module/packaging/extract.ts +234 -0
  157. package/src/module/packaging/signature.ts +47 -0
  158. package/src/secrets/encryption.test.ts +284 -0
  159. package/src/secrets/encryption.ts +162 -0
  160. package/src/secrets/generators.test.ts +112 -0
  161. package/src/secrets/generators.ts +127 -0
  162. package/src/secrets/master-key.test.ts +159 -0
  163. package/src/secrets/master-key.ts +114 -0
  164. package/src/secrets/storage.test.ts +115 -0
  165. package/src/secrets/storage.ts +106 -0
  166. package/src/secrets/vault.test.ts +35 -0
  167. package/src/secrets/vault.ts +42 -0
  168. package/src/services/backup-create.ts +532 -0
  169. package/src/services/backup-metadata.ts +198 -0
  170. package/src/services/backup-restore.ts +229 -0
  171. package/src/services/backup-retention.ts +84 -0
  172. package/src/services/backup-storage.ts +281 -0
  173. package/src/services/build-stream.test.ts +122 -0
  174. package/src/services/build-stream.ts +201 -0
  175. package/src/services/config-interview.ts +694 -0
  176. package/src/services/container-service.test.ts +298 -0
  177. package/src/services/container-service.ts +401 -0
  178. package/src/services/cross-module-data-manager.test.ts +405 -0
  179. package/src/services/cross-module-data-manager.ts +412 -0
  180. package/src/services/deploy-ansible.ts +88 -0
  181. package/src/services/deploy-planner.ts +153 -0
  182. package/src/services/deploy-preflight.ts +274 -0
  183. package/src/services/deploy-ssh.ts +131 -0
  184. package/src/services/deploy-terraform.test.ts +55 -0
  185. package/src/services/deploy-terraform.ts +445 -0
  186. package/src/services/deploy-validation.ts +311 -0
  187. package/src/services/dns-auto-register.ts +211 -0
  188. package/src/services/health-runner.ts +184 -0
  189. package/src/services/infrastructure-selector.test.ts +485 -0
  190. package/src/services/infrastructure-selector.ts +245 -0
  191. package/src/services/infrastructure-variable-resolver.test.ts +751 -0
  192. package/src/services/infrastructure-variable-resolver.ts +234 -0
  193. package/src/services/machine-detector.ts +328 -0
  194. package/src/services/machine-pool.test.ts +405 -0
  195. package/src/services/machine-pool.ts +316 -0
  196. package/src/services/manifest-validation.ts +120 -0
  197. package/src/services/module-build.test.ts +290 -0
  198. package/src/services/module-build.ts +431 -0
  199. package/src/services/module-config.test.ts +237 -0
  200. package/src/services/module-config.ts +298 -0
  201. package/src/services/module-deploy.ts +862 -0
  202. package/src/services/module-types-drift.test.ts +73 -0
  203. package/src/services/module-types-generator.test.ts +288 -0
  204. package/src/services/module-types-generator.ts +189 -0
  205. package/src/services/proxmox-state-recovery.ts +140 -0
  206. package/src/services/schema-validation.ts +155 -0
  207. package/src/services/secret-schema-loader.test.ts +311 -0
  208. package/src/services/secret-schema-loader.ts +239 -0
  209. package/src/services/ssh-key-manager.test.ts +283 -0
  210. package/src/services/ssh-key-manager.ts +193 -0
  211. package/src/services/storage-providers/local.ts +105 -0
  212. package/src/services/storage-providers/s3.ts +182 -0
  213. package/src/services/storage-providers/types.ts +24 -0
  214. package/src/services/system-config-schema-types.ts +25 -0
  215. package/src/services/system-config-validator.test.ts +160 -0
  216. package/src/services/system-config-validator.ts +74 -0
  217. package/src/services/system-init.test.ts +153 -0
  218. package/src/services/system-init.ts +253 -0
  219. package/src/services/terraform-safety.ts +174 -0
  220. package/src/services/zone-detector.test.ts +110 -0
  221. package/src/services/zone-detector.ts +102 -0
  222. package/src/services/zone-policy.test.ts +97 -0
  223. package/src/services/zone-policy.ts +126 -0
  224. package/src/templates/generator.test.ts +645 -0
  225. package/src/templates/generator.ts +1119 -0
  226. package/src/templates/types.ts +62 -0
  227. package/src/test-utils/INTERACTIVE_PROMPTS.md +167 -0
  228. package/src/test-utils/cli-context-interactive.test.ts +152 -0
  229. package/src/test-utils/cli-context-server.test.ts +66 -0
  230. package/src/test-utils/cli-context.test.ts +273 -0
  231. package/src/test-utils/cli-context.ts +677 -0
  232. package/src/test-utils/cli-result.test.ts +282 -0
  233. package/src/test-utils/cli-result.ts +241 -0
  234. package/src/test-utils/cli.ts +55 -0
  235. package/src/test-utils/completion-harness.test.ts +126 -0
  236. package/src/test-utils/completion-harness.ts +82 -0
  237. package/src/test-utils/database.test.ts +182 -0
  238. package/src/test-utils/database.ts +126 -0
  239. package/src/test-utils/filesystem.test.ts +208 -0
  240. package/src/test-utils/filesystem.ts +142 -0
  241. package/src/test-utils/fixtures.test.ts +123 -0
  242. package/src/test-utils/fixtures.ts +160 -0
  243. package/src/test-utils/golden-diff.ts +197 -0
  244. package/src/test-utils/index.ts +77 -0
  245. package/src/test-utils/integration.ts +81 -0
  246. package/src/test-utils/module-fixtures.ts +468 -0
  247. package/src/test-utils/modules.test.ts +144 -0
  248. package/src/test-utils/modules.ts +183 -0
  249. package/src/test-utils/setup-test-db.ts +90 -0
  250. package/src/test-utils/value-extractor.test.ts +231 -0
  251. package/src/test-utils/value-extractor.ts +228 -0
  252. package/src/types/infrastructure.ts +157 -0
  253. package/src/utils/shell.test.ts +365 -0
  254. package/src/utils/shell.ts +159 -0
  255. package/src/validation/schemas.ts +166 -0
  256. package/src/variables/ansible-resolver.test.ts +142 -0
  257. package/src/variables/ansible-resolver.ts +69 -0
  258. package/src/variables/capability-self-ref.test.ts +220 -0
  259. package/src/variables/context.test.ts +1265 -0
  260. package/src/variables/context.ts +624 -0
  261. package/src/variables/declarative-derivation.test.ts +743 -0
  262. package/src/variables/declarative-derivation.ts +200 -0
  263. package/src/variables/parser.test.ts +231 -0
  264. package/src/variables/parser.ts +76 -0
  265. package/src/variables/resolver.test.ts +458 -0
  266. package/src/variables/resolver.ts +282 -0
  267. package/src/variables/types.ts +59 -0
@@ -0,0 +1,613 @@
1
+ import type { Database } from 'bun:sqlite';
2
+ import { describe, expect, test } from 'bun:test';
3
+ import type { ModuleManifest } from '../manifest/schema';
4
+ import { checkAllowlist, getProviderManifest, validateCapabilityAccess } from './validation';
5
+
6
+ describe('Capability Access Validation', () => {
7
+ describe('checkAllowlist', () => {
8
+ test('should return true when consumer provides capability in allowlist', () => {
9
+ const consumerCapabilities = ['public_web'];
10
+ const allowlist = ['public_web', 'api_gateway'];
11
+
12
+ const result = checkAllowlist(consumerCapabilities, allowlist);
13
+
14
+ expect(result).toBe(true);
15
+ });
16
+
17
+ test('should return true when consumer provides multiple capabilities and one matches', () => {
18
+ const consumerCapabilities = ['dns_internal', 'monitoring', 'public_web'];
19
+ const allowlist = ['public_web', 'api_gateway'];
20
+
21
+ const result = checkAllowlist(consumerCapabilities, allowlist);
22
+
23
+ expect(result).toBe(true);
24
+ });
25
+
26
+ test('should return false when consumer provides no matching capability', () => {
27
+ const consumerCapabilities = ['monitoring', 'logging'];
28
+ const allowlist = ['public_web', 'api_gateway'];
29
+
30
+ const result = checkAllowlist(consumerCapabilities, allowlist);
31
+
32
+ expect(result).toBe(false);
33
+ });
34
+
35
+ test('should return false when consumer provides no capabilities', () => {
36
+ const consumerCapabilities: string[] = [];
37
+ const allowlist = ['public_web', 'api_gateway'];
38
+
39
+ const result = checkAllowlist(consumerCapabilities, allowlist);
40
+
41
+ expect(result).toBe(false);
42
+ });
43
+
44
+ test('should return false when allowlist is empty', () => {
45
+ const consumerCapabilities = ['public_web'];
46
+ const allowlist: string[] = [];
47
+
48
+ const result = checkAllowlist(consumerCapabilities, allowlist);
49
+
50
+ expect(result).toBe(false);
51
+ });
52
+
53
+ test('should return false when both are empty', () => {
54
+ const consumerCapabilities: string[] = [];
55
+ const allowlist: string[] = [];
56
+
57
+ const result = checkAllowlist(consumerCapabilities, allowlist);
58
+
59
+ expect(result).toBe(false);
60
+ });
61
+
62
+ test('should match exact capability names', () => {
63
+ const consumerCapabilities = ['public_web_v2'];
64
+ const allowlist = ['public_web'];
65
+
66
+ const result = checkAllowlist(consumerCapabilities, allowlist);
67
+
68
+ expect(result).toBe(false);
69
+ });
70
+
71
+ test('should be case-sensitive', () => {
72
+ const consumerCapabilities = ['Public_Web'];
73
+ const allowlist = ['public_web'];
74
+
75
+ const result = checkAllowlist(consumerCapabilities, allowlist);
76
+
77
+ expect(result).toBe(false);
78
+ });
79
+ });
80
+
81
+ describe('getProviderManifest', () => {
82
+ test('should return manifest when capability exists', () => {
83
+ const mockManifest: ModuleManifest = {
84
+ celilo_contract: '1.0',
85
+ id: 'dns-external',
86
+ name: 'DNS External',
87
+ version: '1.0.0',
88
+ description: 'External DNS',
89
+ requires: { capabilities: [] },
90
+ provides: {
91
+ capabilities: [
92
+ {
93
+ name: 'dns_external',
94
+ version: '1.0.0',
95
+ data: {},
96
+ secrets: [
97
+ {
98
+ name: 'tsig',
99
+ type: 'string',
100
+ description: 'TSIG secret',
101
+ readable_by: ['public_web'],
102
+ },
103
+ ],
104
+ },
105
+ ],
106
+ },
107
+ variables: { owns: [], imports: [] },
108
+ };
109
+
110
+ const mockDb = {
111
+ prepare: (_query: string) => ({
112
+ get: (capabilityName: string) => {
113
+ if (capabilityName === 'dns_external') {
114
+ return {
115
+ manifest_data: JSON.stringify(mockManifest),
116
+ };
117
+ }
118
+ return undefined;
119
+ },
120
+ }),
121
+ } as unknown as Database;
122
+
123
+ const result = getProviderManifest('dns_external', mockDb);
124
+
125
+ expect(result).toEqual(mockManifest);
126
+ });
127
+
128
+ test('should return null when capability does not exist', () => {
129
+ const mockDb = {
130
+ prepare: () => ({
131
+ get: () => undefined,
132
+ }),
133
+ } as unknown as Database;
134
+
135
+ const result = getProviderManifest('unknown_capability', mockDb);
136
+
137
+ expect(result).toBeNull();
138
+ });
139
+
140
+ test('should parse JSON manifest correctly', () => {
141
+ const mockManifest: ModuleManifest = {
142
+ celilo_contract: '1.0',
143
+ id: 'test-module',
144
+ name: 'Test Module',
145
+ version: '1.0.0',
146
+ description: 'Test',
147
+ requires: { capabilities: [] },
148
+ provides: {
149
+ capabilities: [
150
+ {
151
+ name: 'test_capability',
152
+ version: '1.0.0',
153
+ data: { nested: { value: 'test' } },
154
+ },
155
+ ],
156
+ },
157
+ variables: { owns: [], imports: [] },
158
+ };
159
+
160
+ const mockDb = {
161
+ prepare: () => ({
162
+ get: () => ({
163
+ manifest_data: JSON.stringify(mockManifest),
164
+ }),
165
+ }),
166
+ } as unknown as Database;
167
+
168
+ const result = getProviderManifest('test_capability', mockDb);
169
+
170
+ expect(result?.provides?.capabilities?.[0]?.data).toEqual({
171
+ nested: { value: 'test' },
172
+ });
173
+ });
174
+ });
175
+
176
+ describe('validateCapabilityAccess', () => {
177
+ test('should return success when module does not require capabilities', async () => {
178
+ const manifest: ModuleManifest = {
179
+ celilo_contract: '1.0',
180
+ id: 'test-module',
181
+ name: 'Test Module',
182
+ version: '1.0.0',
183
+ description: 'Test',
184
+ requires: { capabilities: [] },
185
+ provides: { capabilities: [] },
186
+ variables: { owns: [], imports: [] },
187
+ };
188
+
189
+ const mockDb = {} as Database;
190
+
191
+ const result = await validateCapabilityAccess(manifest, mockDb);
192
+
193
+ expect(result.success).toBe(true);
194
+ });
195
+
196
+ test('should return success when module requires capabilities array is empty', async () => {
197
+ const manifest: ModuleManifest = {
198
+ celilo_contract: '1.0',
199
+ id: 'test-module',
200
+ name: 'Test Module',
201
+ version: '1.0.0',
202
+ description: 'Test',
203
+ requires: {
204
+ capabilities: [],
205
+ },
206
+ provides: { capabilities: [] },
207
+ variables: { owns: [], imports: [] },
208
+ };
209
+
210
+ const mockDb = {} as Database;
211
+
212
+ const result = await validateCapabilityAccess(manifest, mockDb);
213
+
214
+ expect(result.success).toBe(true);
215
+ });
216
+
217
+ test('should return error when required capability not found', async () => {
218
+ const manifest: ModuleManifest = {
219
+ celilo_contract: '1.0',
220
+ id: 'caddy',
221
+ name: 'Caddy',
222
+ version: '1.0.0',
223
+ description: 'Web server',
224
+ requires: {
225
+ capabilities: [{ name: 'dns_external', version: '1.0.0' }],
226
+ },
227
+ provides: { capabilities: [] },
228
+ variables: { owns: [], imports: [] },
229
+ };
230
+
231
+ const mockDb = {
232
+ prepare: () => ({
233
+ get: () => undefined,
234
+ }),
235
+ } as unknown as Database;
236
+
237
+ const result = await validateCapabilityAccess(manifest, mockDb);
238
+
239
+ expect(result.success).toBe(false);
240
+ expect(result.error).toContain("Required capability 'dns_external' not found");
241
+ expect(result.error).toContain('No module provides this capability');
242
+ });
243
+
244
+ test('should return success when capability has no secrets', async () => {
245
+ const manifest: ModuleManifest = {
246
+ celilo_contract: '1.0',
247
+ id: 'consumer',
248
+ name: 'Consumer',
249
+ version: '1.0.0',
250
+ description: 'Test',
251
+ requires: {
252
+ capabilities: [{ name: 'monitoring', version: '1.0.0' }],
253
+ },
254
+ provides: { capabilities: [] },
255
+ variables: { owns: [], imports: [] },
256
+ };
257
+
258
+ const providerManifest: ModuleManifest = {
259
+ celilo_contract: '1.0',
260
+ id: 'provider',
261
+ name: 'Provider',
262
+ version: '1.0.0',
263
+ description: 'Test',
264
+ requires: { capabilities: [] },
265
+ provides: {
266
+ capabilities: [
267
+ {
268
+ name: 'monitoring',
269
+ version: '1.0.0',
270
+ data: {},
271
+ },
272
+ ],
273
+ },
274
+ variables: { owns: [], imports: [] },
275
+ };
276
+
277
+ const mockDb = {
278
+ prepare: () => ({
279
+ get: () => ({
280
+ manifest_data: JSON.stringify(providerManifest),
281
+ }),
282
+ }),
283
+ } as unknown as Database;
284
+
285
+ const result = await validateCapabilityAccess(manifest, mockDb);
286
+
287
+ expect(result.success).toBe(true);
288
+ });
289
+
290
+ test('should return success when secret has no readable_by restriction', async () => {
291
+ const manifest: ModuleManifest = {
292
+ celilo_contract: '1.0',
293
+ id: 'consumer',
294
+ name: 'Consumer',
295
+ version: '1.0.0',
296
+ description: 'Test',
297
+ requires: {
298
+ capabilities: [{ name: 'dns_external', version: '1.0.0' }],
299
+ },
300
+ provides: { capabilities: [] },
301
+ variables: { owns: [], imports: [] },
302
+ };
303
+
304
+ const providerManifest: ModuleManifest = {
305
+ celilo_contract: '1.0',
306
+ id: 'dns-external',
307
+ name: 'DNS External',
308
+ version: '1.0.0',
309
+ description: 'Test',
310
+ requires: { capabilities: [] },
311
+ provides: {
312
+ capabilities: [
313
+ {
314
+ name: 'dns_external',
315
+ version: '1.0.0',
316
+ data: {},
317
+ secrets: [
318
+ {
319
+ name: 'api_key',
320
+ type: 'string',
321
+ description: 'API key',
322
+ // No readable_by - accessible to all
323
+ },
324
+ ],
325
+ },
326
+ ],
327
+ },
328
+ variables: { owns: [], imports: [] },
329
+ };
330
+
331
+ const mockDb = {
332
+ prepare: () => ({
333
+ get: () => ({
334
+ manifest_data: JSON.stringify(providerManifest),
335
+ }),
336
+ }),
337
+ } as unknown as Database;
338
+
339
+ const result = await validateCapabilityAccess(manifest, mockDb);
340
+
341
+ expect(result.success).toBe(true);
342
+ });
343
+
344
+ test('should return success when consumer matches allowlist', async () => {
345
+ const manifest: ModuleManifest = {
346
+ celilo_contract: '1.0',
347
+ id: 'caddy',
348
+ name: 'Caddy',
349
+ version: '1.0.0',
350
+ description: 'Web server',
351
+ requires: {
352
+ capabilities: [{ name: 'dns_external', version: '1.0.0' }],
353
+ },
354
+ provides: {
355
+ capabilities: [
356
+ {
357
+ name: 'public_web',
358
+ version: '1.0.0',
359
+ data: {},
360
+ },
361
+ ],
362
+ },
363
+ variables: { owns: [], imports: [] },
364
+ };
365
+
366
+ const providerManifest: ModuleManifest = {
367
+ celilo_contract: '1.0',
368
+ id: 'dns-external',
369
+ name: 'DNS External',
370
+ version: '1.0.0',
371
+ description: 'Test',
372
+ requires: { capabilities: [] },
373
+ provides: {
374
+ capabilities: [
375
+ {
376
+ name: 'dns_external',
377
+ version: '1.0.0',
378
+ data: {},
379
+ secrets: [
380
+ {
381
+ name: 'tsig',
382
+ type: 'string',
383
+ description: 'TSIG secret',
384
+ readable_by: ['public_web'],
385
+ },
386
+ ],
387
+ },
388
+ ],
389
+ },
390
+ variables: { owns: [], imports: [] },
391
+ };
392
+
393
+ const mockDb = {
394
+ prepare: () => ({
395
+ get: () => ({
396
+ manifest_data: JSON.stringify(providerManifest),
397
+ }),
398
+ }),
399
+ } as unknown as Database;
400
+
401
+ const result = await validateCapabilityAccess(manifest, mockDb);
402
+
403
+ expect(result.success).toBe(true);
404
+ });
405
+
406
+ test('should return error when consumer does not match allowlist', async () => {
407
+ const manifest: ModuleManifest = {
408
+ celilo_contract: '1.0',
409
+ id: 'some-app',
410
+ name: 'Some App',
411
+ version: '1.0.0',
412
+ description: 'Test',
413
+ requires: {
414
+ capabilities: [{ name: 'dns_external', version: '1.0.0' }],
415
+ },
416
+ provides: {
417
+ capabilities: [
418
+ {
419
+ name: 'monitoring',
420
+ version: '1.0.0',
421
+ data: {},
422
+ },
423
+ ],
424
+ },
425
+ variables: { owns: [], imports: [] },
426
+ };
427
+
428
+ const providerManifest: ModuleManifest = {
429
+ celilo_contract: '1.0',
430
+ id: 'dns-external',
431
+ name: 'DNS External',
432
+ version: '1.0.0',
433
+ description: 'Test',
434
+ requires: { capabilities: [] },
435
+ provides: {
436
+ capabilities: [
437
+ {
438
+ name: 'dns_external',
439
+ version: '1.0.0',
440
+ data: {},
441
+ secrets: [
442
+ {
443
+ name: 'tsig',
444
+ type: 'string',
445
+ description: 'TSIG secret',
446
+ readable_by: ['public_web'],
447
+ },
448
+ ],
449
+ },
450
+ ],
451
+ },
452
+ variables: { owns: [], imports: [] },
453
+ };
454
+
455
+ const mockDb = {
456
+ prepare: () => ({
457
+ get: () => ({
458
+ manifest_data: JSON.stringify(providerManifest),
459
+ }),
460
+ }),
461
+ } as unknown as Database;
462
+
463
+ const result = await validateCapabilityAccess(manifest, mockDb);
464
+
465
+ expect(result.success).toBe(false);
466
+ expect(result.error).toContain("Module 'some-app' cannot access secret 'tsig'");
467
+ expect(result.error).toContain("capability 'dns_external'");
468
+ expect(result.error).toContain('only allows access to modules that provide: public_web');
469
+ expect(result.error).toContain('This module provides: monitoring');
470
+ });
471
+
472
+ test('should return error when consumer provides no capabilities', async () => {
473
+ const manifest: ModuleManifest = {
474
+ celilo_contract: '1.0',
475
+ id: 'consumer',
476
+ name: 'Consumer',
477
+ version: '1.0.0',
478
+ description: 'Test',
479
+ requires: {
480
+ capabilities: [{ name: 'dns_external', version: '1.0.0' }],
481
+ },
482
+ provides: { capabilities: [] }, // No provides section - empty
483
+ variables: { owns: [], imports: [] },
484
+ };
485
+
486
+ const providerManifest: ModuleManifest = {
487
+ celilo_contract: '1.0',
488
+ id: 'dns-external',
489
+ name: 'DNS External',
490
+ version: '1.0.0',
491
+ description: 'Test',
492
+ requires: { capabilities: [] },
493
+ provides: {
494
+ capabilities: [
495
+ {
496
+ name: 'dns_external',
497
+ version: '1.0.0',
498
+ data: {},
499
+ secrets: [
500
+ {
501
+ name: 'tsig',
502
+ type: 'string',
503
+ description: 'TSIG secret',
504
+ readable_by: ['public_web'],
505
+ },
506
+ ],
507
+ },
508
+ ],
509
+ },
510
+ variables: { owns: [], imports: [] },
511
+ };
512
+
513
+ const mockDb = {
514
+ prepare: () => ({
515
+ get: () => ({
516
+ manifest_data: JSON.stringify(providerManifest),
517
+ }),
518
+ }),
519
+ } as unknown as Database;
520
+
521
+ const result = await validateCapabilityAccess(manifest, mockDb);
522
+
523
+ expect(result.success).toBe(false);
524
+ expect(result.error).toContain('This module provides: (none)');
525
+ });
526
+
527
+ test('should validate all required capabilities', async () => {
528
+ const manifest: ModuleManifest = {
529
+ celilo_contract: '1.0',
530
+ id: 'complex-app',
531
+ name: 'Complex App',
532
+ version: '1.0.0',
533
+ description: 'Test',
534
+ requires: {
535
+ capabilities: [
536
+ { name: 'dns_external', version: '1.0.0' },
537
+ { name: 'database', version: '1.0.0' },
538
+ ],
539
+ },
540
+ provides: {
541
+ capabilities: [
542
+ {
543
+ name: 'public_web',
544
+ version: '1.0.0',
545
+ data: {},
546
+ },
547
+ ],
548
+ },
549
+ variables: { owns: [], imports: [] },
550
+ };
551
+
552
+ let callCount = 0;
553
+ const mockDb = {
554
+ prepare: () => ({
555
+ get: (capabilityName: string) => {
556
+ callCount++;
557
+ if (capabilityName === 'dns_external') {
558
+ return {
559
+ manifest_data: JSON.stringify({
560
+ id: 'dns-external',
561
+ name: 'DNS',
562
+ version: '1.0.0',
563
+ description: 'Test',
564
+ provides: {
565
+ capabilities: [
566
+ {
567
+ name: 'dns_external',
568
+ version: '1.0.0',
569
+ data: {},
570
+ secrets: [
571
+ {
572
+ name: 'tsig',
573
+ type: 'string',
574
+ readable_by: ['public_web'],
575
+ },
576
+ ],
577
+ },
578
+ ],
579
+ },
580
+ }),
581
+ };
582
+ }
583
+ if (capabilityName === 'database') {
584
+ return {
585
+ manifest_data: JSON.stringify({
586
+ id: 'postgres',
587
+ name: 'PostgreSQL',
588
+ version: '1.0.0',
589
+ description: 'Test',
590
+ provides: {
591
+ capabilities: [
592
+ {
593
+ name: 'database',
594
+ version: '1.0.0',
595
+ data: {},
596
+ },
597
+ ],
598
+ },
599
+ }),
600
+ };
601
+ }
602
+ return undefined;
603
+ },
604
+ }),
605
+ } as unknown as Database;
606
+
607
+ const result = await validateCapabilityAccess(manifest, mockDb);
608
+
609
+ expect(result.success).toBe(true);
610
+ expect(callCount).toBe(2); // Should query both capabilities
611
+ });
612
+ });
613
+ });