@intentius/chant-lexicon-cpln 0.53.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 (266) hide show
  1. package/README.md +85 -0
  2. package/dist/api.d.ts +77 -0
  3. package/dist/api.d.ts.map +1 -0
  4. package/dist/codegen/docs-cli.d.ts +3 -0
  5. package/dist/codegen/docs-cli.d.ts.map +1 -0
  6. package/dist/codegen/docs.d.ts +17 -0
  7. package/dist/codegen/docs.d.ts.map +1 -0
  8. package/dist/codegen/generate-cli.d.ts +6 -0
  9. package/dist/codegen/generate-cli.d.ts.map +1 -0
  10. package/dist/codegen/generate-lexicon.d.ts +36 -0
  11. package/dist/codegen/generate-lexicon.d.ts.map +1 -0
  12. package/dist/codegen/generate-typescript.d.ts +11 -0
  13. package/dist/codegen/generate-typescript.d.ts.map +1 -0
  14. package/dist/codegen/generate.d.ts +15 -0
  15. package/dist/codegen/generate.d.ts.map +1 -0
  16. package/dist/codegen/naming.d.ts +30 -0
  17. package/dist/codegen/naming.d.ts.map +1 -0
  18. package/dist/codegen/package.d.ts +17 -0
  19. package/dist/codegen/package.d.ts.map +1 -0
  20. package/dist/composites/cron-job.d.ts +60 -0
  21. package/dist/composites/cron-job.d.ts.map +1 -0
  22. package/dist/composites/gvc-environment.d.ts +47 -0
  23. package/dist/composites/gvc-environment.d.ts.map +1 -0
  24. package/dist/composites/index.d.ts +13 -0
  25. package/dist/composites/index.d.ts.map +1 -0
  26. package/dist/composites/public-domain.d.ts +68 -0
  27. package/dist/composites/public-domain.d.ts.map +1 -0
  28. package/dist/composites/secret-access.d.ts +78 -0
  29. package/dist/composites/secret-access.d.ts.map +1 -0
  30. package/dist/composites/serverless-service.d.ts +78 -0
  31. package/dist/composites/serverless-service.d.ts.map +1 -0
  32. package/dist/composites/stateful-service.d.ts +79 -0
  33. package/dist/composites/stateful-service.d.ts.map +1 -0
  34. package/dist/coverage.d.ts +59 -0
  35. package/dist/coverage.d.ts.map +1 -0
  36. package/dist/describe-resources.d.ts +71 -0
  37. package/dist/describe-resources.d.ts.map +1 -0
  38. package/dist/detect.d.ts +14 -0
  39. package/dist/detect.d.ts.map +1 -0
  40. package/dist/entity-props.d.ts +34 -0
  41. package/dist/entity-props.d.ts.map +1 -0
  42. package/dist/generated/index.d.ts +151 -0
  43. package/dist/generated/index.d.ts.map +1 -0
  44. package/dist/generated/runtime.d.ts +5 -0
  45. package/dist/generated/runtime.d.ts.map +1 -0
  46. package/dist/index.d.ts +8 -0
  47. package/dist/index.d.ts.map +1 -0
  48. package/dist/init-templates.d.ts +16 -0
  49. package/dist/init-templates.d.ts.map +1 -0
  50. package/dist/integrity.json +35 -0
  51. package/dist/kinds.d.ts +70 -0
  52. package/dist/kinds.d.ts.map +1 -0
  53. package/dist/lint/audit-catalog.d.ts +19 -0
  54. package/dist/lint/audit-catalog.d.ts.map +1 -0
  55. package/dist/lint/post-synth/cpl010-unrestricted-outbound.d.ts +12 -0
  56. package/dist/lint/post-synth/cpl010-unrestricted-outbound.d.ts.map +1 -0
  57. package/dist/lint/post-synth/cpl011-internal-firewall-scope.d.ts +11 -0
  58. package/dist/lint/post-synth/cpl011-internal-firewall-scope.d.ts.map +1 -0
  59. package/dist/lint/post-synth/cpl012-no-literal-credential-env.d.ts +11 -0
  60. package/dist/lint/post-synth/cpl012-no-literal-credential-env.d.ts.map +1 -0
  61. package/dist/lint/post-synth/cpl013-qualified-identity-principal.d.ts +11 -0
  62. package/dist/lint/post-synth/cpl013-qualified-identity-principal.d.ts.map +1 -0
  63. package/dist/lint/post-synth/cpl014-qualified-secret-ref.d.ts +14 -0
  64. package/dist/lint/post-synth/cpl014-qualified-secret-ref.d.ts.map +1 -0
  65. package/dist/lint/post-synth/cpl020-serverless-single-port.d.ts +10 -0
  66. package/dist/lint/post-synth/cpl020-serverless-single-port.d.ts.map +1 -0
  67. package/dist/lint/post-synth/cpl021-cron-job-shape.d.ts +11 -0
  68. package/dist/lint/post-synth/cpl021-cron-job-shape.d.ts.map +1 -0
  69. package/dist/lint/post-synth/cpl022-container-ports.d.ts +10 -0
  70. package/dist/lint/post-synth/cpl022-container-ports.d.ts.map +1 -0
  71. package/dist/lint/post-synth/cpl023-container-resources.d.ts +12 -0
  72. package/dist/lint/post-synth/cpl023-container-resources.d.ts.map +1 -0
  73. package/dist/lint/post-synth/cpl024-probe-handler.d.ts +10 -0
  74. package/dist/lint/post-synth/cpl024-probe-handler.d.ts.map +1 -0
  75. package/dist/lint/post-synth/cpl025-autoscaling-shape.d.ts +11 -0
  76. package/dist/lint/post-synth/cpl025-autoscaling-shape.d.ts.map +1 -0
  77. package/dist/lint/post-synth/cpl026-scale-to-zero.d.ts +11 -0
  78. package/dist/lint/post-synth/cpl026-scale-to-zero.d.ts.map +1 -0
  79. package/dist/lint/post-synth/cpl027-capacity-ai-conflict.d.ts +13 -0
  80. package/dist/lint/post-synth/cpl027-capacity-ai-conflict.d.ts.map +1 -0
  81. package/dist/lint/post-synth/cpl028-volume-set.d.ts +3 -0
  82. package/dist/lint/post-synth/cpl028-volume-set.d.ts.map +1 -0
  83. package/dist/lint/post-synth/cpl029-link-targets.d.ts +3 -0
  84. package/dist/lint/post-synth/cpl029-link-targets.d.ts.map +1 -0
  85. package/dist/lint/post-synth/cpl030-domain-routing.d.ts +3 -0
  86. package/dist/lint/post-synth/cpl030-domain-routing.d.ts.map +1 -0
  87. package/dist/lint/post-synth/cpl040-pinned-image.d.ts +11 -0
  88. package/dist/lint/post-synth/cpl040-pinned-image.d.ts.map +1 -0
  89. package/dist/lint/post-synth/cpl041-image-reference-form.d.ts +12 -0
  90. package/dist/lint/post-synth/cpl041-image-reference-form.d.ts.map +1 -0
  91. package/dist/lint/post-synth/cpl042-gvc-placement.d.ts +10 -0
  92. package/dist/lint/post-synth/cpl042-gvc-placement.d.ts.map +1 -0
  93. package/dist/lint/post-synth/cpl043-policy-scope.d.ts +16 -0
  94. package/dist/lint/post-synth/cpl043-policy-scope.d.ts.map +1 -0
  95. package/dist/lint/post-synth/helpers.d.ts +45 -0
  96. package/dist/lint/post-synth/helpers.d.ts.map +1 -0
  97. package/dist/lint/post-synth/index.d.ts +3 -0
  98. package/dist/lint/post-synth/index.d.ts.map +1 -0
  99. package/dist/lint/rules/cpl001-no-secret-literals.d.ts +3 -0
  100. package/dist/lint/rules/cpl001-no-secret-literals.d.ts.map +1 -0
  101. package/dist/lint/rules/cpl002-prefer-resource-reference.d.ts +3 -0
  102. package/dist/lint/rules/cpl002-prefer-resource-reference.d.ts.map +1 -0
  103. package/dist/lint/rules/index.d.ts +6 -0
  104. package/dist/lint/rules/index.d.ts.map +1 -0
  105. package/dist/lsp/completions.d.ts +7 -0
  106. package/dist/lsp/completions.d.ts.map +1 -0
  107. package/dist/lsp/hover.d.ts +4 -0
  108. package/dist/lsp/hover.d.ts.map +1 -0
  109. package/dist/manifest.json +6 -0
  110. package/dist/meta.json +2110 -0
  111. package/dist/okf/index.md +39 -0
  112. package/dist/okf/rules/CPL001.md +22 -0
  113. package/dist/okf/rules/CPL002.md +22 -0
  114. package/dist/okf/rules/CPL010.md +15 -0
  115. package/dist/okf/rules/CPL011.md +15 -0
  116. package/dist/okf/rules/CPL012.md +15 -0
  117. package/dist/okf/rules/CPL013.md +15 -0
  118. package/dist/okf/rules/CPL014.md +16 -0
  119. package/dist/okf/rules/CPL020.md +11 -0
  120. package/dist/okf/rules/CPL021.md +15 -0
  121. package/dist/okf/rules/CPL022.md +15 -0
  122. package/dist/okf/rules/CPL023.md +15 -0
  123. package/dist/okf/rules/CPL024.md +15 -0
  124. package/dist/okf/rules/CPL025.md +15 -0
  125. package/dist/okf/rules/CPL026.md +15 -0
  126. package/dist/okf/rules/CPL027.md +15 -0
  127. package/dist/okf/rules/CPL028.md +15 -0
  128. package/dist/okf/rules/CPL029.md +15 -0
  129. package/dist/okf/rules/CPL030.md +16 -0
  130. package/dist/okf/rules/CPL040.md +15 -0
  131. package/dist/okf/rules/CPL041.md +15 -0
  132. package/dist/okf/rules/CPL042.md +11 -0
  133. package/dist/okf/rules/CPL043.md +15 -0
  134. package/dist/okf/types/Domain.md +22 -0
  135. package/dist/okf/types/Gvc.md +21 -0
  136. package/dist/okf/types/Identity.md +29 -0
  137. package/dist/okf/types/IpSet.md +21 -0
  138. package/dist/okf/types/Policy.md +28 -0
  139. package/dist/okf/types/Secret.md +23 -0
  140. package/dist/okf/types/VolumeSet.md +22 -0
  141. package/dist/okf/types/Workload.md +38 -0
  142. package/dist/ownership.d.ts +23 -0
  143. package/dist/ownership.d.ts.map +1 -0
  144. package/dist/package-cli.d.ts +3 -0
  145. package/dist/package-cli.d.ts.map +1 -0
  146. package/dist/plugin.d.ts +3 -0
  147. package/dist/plugin.d.ts.map +1 -0
  148. package/dist/reference-catalog.d.ts +24 -0
  149. package/dist/reference-catalog.d.ts.map +1 -0
  150. package/dist/rules/cpl001-no-secret-literals.ts +83 -0
  151. package/dist/rules/cpl002-prefer-resource-reference.ts +71 -0
  152. package/dist/rules/cpl010-unrestricted-outbound.ts +41 -0
  153. package/dist/rules/cpl011-internal-firewall-scope.ts +38 -0
  154. package/dist/rules/cpl012-no-literal-credential-env.ts +58 -0
  155. package/dist/rules/cpl013-qualified-identity-principal.ts +45 -0
  156. package/dist/rules/cpl014-qualified-secret-ref.ts +54 -0
  157. package/dist/rules/cpl020-serverless-single-port.ts +58 -0
  158. package/dist/rules/cpl021-cron-job-shape.ts +64 -0
  159. package/dist/rules/cpl022-container-ports.ts +64 -0
  160. package/dist/rules/cpl023-container-resources.ts +87 -0
  161. package/dist/rules/cpl024-probe-handler.ts +51 -0
  162. package/dist/rules/cpl025-autoscaling-shape.ts +100 -0
  163. package/dist/rules/cpl026-scale-to-zero.ts +67 -0
  164. package/dist/rules/cpl027-capacity-ai-conflict.ts +78 -0
  165. package/dist/rules/cpl028-volume-set.ts +183 -0
  166. package/dist/rules/cpl029-link-targets.ts +155 -0
  167. package/dist/rules/cpl030-domain-routing.ts +137 -0
  168. package/dist/rules/cpl040-pinned-image.ts +48 -0
  169. package/dist/rules/cpl041-image-reference-form.ts +60 -0
  170. package/dist/rules/cpl042-gvc-placement.ts +57 -0
  171. package/dist/rules/cpl043-policy-scope.ts +0 -0
  172. package/dist/rules/helpers.ts +114 -0
  173. package/dist/serializer.d.ts +69 -0
  174. package/dist/serializer.d.ts.map +1 -0
  175. package/dist/skills/chant-cpln-secrets.md +110 -0
  176. package/dist/skills/chant-cpln-workloads.md +78 -0
  177. package/dist/skills/chant-cpln.md +103 -0
  178. package/dist/spec/fetch.d.ts +43 -0
  179. package/dist/spec/fetch.d.ts.map +1 -0
  180. package/dist/spec/parse.d.ts +79 -0
  181. package/dist/spec/parse.d.ts.map +1 -0
  182. package/dist/spec/snapshot-cli.d.ts +18 -0
  183. package/dist/spec/snapshot-cli.d.ts.map +1 -0
  184. package/dist/types/index.d.ts +1649 -0
  185. package/dist/validate-cli.d.ts +3 -0
  186. package/dist/validate-cli.d.ts.map +1 -0
  187. package/dist/validate.d.ts +26 -0
  188. package/dist/validate.d.ts.map +1 -0
  189. package/package.json +74 -0
  190. package/src/api.ts +134 -0
  191. package/src/codegen/docs-cli.ts +7 -0
  192. package/src/codegen/docs.ts +94 -0
  193. package/src/codegen/generate-cli.ts +34 -0
  194. package/src/codegen/generate-lexicon.ts +88 -0
  195. package/src/codegen/generate-typescript.ts +71 -0
  196. package/src/codegen/generate.ts +110 -0
  197. package/src/codegen/naming.ts +61 -0
  198. package/src/codegen/package.ts +35 -0
  199. package/src/composites/composites.test.ts +252 -0
  200. package/src/composites/cron-job.ts +124 -0
  201. package/src/composites/gvc-environment.ts +104 -0
  202. package/src/composites/index.ts +17 -0
  203. package/src/composites/public-domain.ts +144 -0
  204. package/src/composites/secret-access.ts +136 -0
  205. package/src/composites/serverless-service.ts +143 -0
  206. package/src/composites/stateful-service.ts +179 -0
  207. package/src/coverage.ts +149 -0
  208. package/src/describe-resources.test.ts +252 -0
  209. package/src/describe-resources.ts +310 -0
  210. package/src/detect.ts +58 -0
  211. package/src/entity-props.ts +98 -0
  212. package/src/generated/index.d.ts +1649 -0
  213. package/src/generated/index.ts +156 -0
  214. package/src/generated/lexicon-cpln.json +2110 -0
  215. package/src/generated/runtime.ts +4 -0
  216. package/src/index.ts +31 -0
  217. package/src/init-templates.ts +131 -0
  218. package/src/kinds.ts +173 -0
  219. package/src/lint/audit-catalog.ts +230 -0
  220. package/src/lint/post-synth/cpl010-unrestricted-outbound.ts +41 -0
  221. package/src/lint/post-synth/cpl011-internal-firewall-scope.ts +38 -0
  222. package/src/lint/post-synth/cpl012-no-literal-credential-env.ts +58 -0
  223. package/src/lint/post-synth/cpl013-qualified-identity-principal.ts +45 -0
  224. package/src/lint/post-synth/cpl014-qualified-secret-ref.ts +54 -0
  225. package/src/lint/post-synth/cpl020-serverless-single-port.ts +58 -0
  226. package/src/lint/post-synth/cpl021-cron-job-shape.ts +64 -0
  227. package/src/lint/post-synth/cpl022-container-ports.ts +64 -0
  228. package/src/lint/post-synth/cpl023-container-resources.ts +87 -0
  229. package/src/lint/post-synth/cpl024-probe-handler.ts +51 -0
  230. package/src/lint/post-synth/cpl025-autoscaling-shape.ts +100 -0
  231. package/src/lint/post-synth/cpl026-scale-to-zero.ts +67 -0
  232. package/src/lint/post-synth/cpl027-capacity-ai-conflict.ts +78 -0
  233. package/src/lint/post-synth/cpl028-volume-set.ts +183 -0
  234. package/src/lint/post-synth/cpl029-link-targets.ts +155 -0
  235. package/src/lint/post-synth/cpl030-domain-routing.ts +137 -0
  236. package/src/lint/post-synth/cpl040-pinned-image.ts +48 -0
  237. package/src/lint/post-synth/cpl041-image-reference-form.ts +60 -0
  238. package/src/lint/post-synth/cpl042-gvc-placement.ts +57 -0
  239. package/src/lint/post-synth/cpl043-policy-scope.ts +0 -0
  240. package/src/lint/post-synth/helpers.ts +114 -0
  241. package/src/lint/post-synth/index.ts +45 -0
  242. package/src/lint/post-synth/post-synth.test.ts +737 -0
  243. package/src/lint/rules/cpl001-no-secret-literals.ts +83 -0
  244. package/src/lint/rules/cpl002-prefer-resource-reference.ts +71 -0
  245. package/src/lint/rules/index.ts +9 -0
  246. package/src/lint/rules/rules.test.ts +108 -0
  247. package/src/lsp/completions.test.ts +44 -0
  248. package/src/lsp/completions.ts +27 -0
  249. package/src/lsp/hover.test.ts +53 -0
  250. package/src/lsp/hover.ts +130 -0
  251. package/src/ownership.ts +28 -0
  252. package/src/package-cli.ts +25 -0
  253. package/src/plugin.test.ts +131 -0
  254. package/src/plugin.ts +187 -0
  255. package/src/reference-catalog.ts +110 -0
  256. package/src/serializer.test.ts +233 -0
  257. package/src/serializer.ts +253 -0
  258. package/src/skills/chant-cpln-secrets.md +110 -0
  259. package/src/skills/chant-cpln-workloads.md +78 -0
  260. package/src/skills/chant-cpln.md +103 -0
  261. package/src/spec/cpln-openapi.snapshot.json +10651 -0
  262. package/src/spec/fetch.ts +77 -0
  263. package/src/spec/parse.ts +451 -0
  264. package/src/spec/snapshot-cli.ts +71 -0
  265. package/src/validate-cli.ts +8 -0
  266. package/src/validate.ts +102 -0
@@ -0,0 +1,144 @@
1
+ /**
2
+ * PublicDomain — a custom domain routed to workloads in one GVC.
3
+ *
4
+ * Domains carry several rules that are easy to violate by picking the
5
+ * combination that reads best rather than the one that works:
6
+ *
7
+ * - An **apex domain must use `cname` mode**. NS mode does not support apex,
8
+ * and there is no partial-credit failure — the domain simply will not
9
+ * validate.
10
+ * - **NS mode requires the `dns01` certificate challenge**; `http01` is
11
+ * rejected outright.
12
+ * - `gvcLink`, `workloadLink` and `ports[].routes` are **mutually exclusive**
13
+ * ways of saying where traffic goes; exactly one may be set.
14
+ * - Every route in a domain must target workloads in the **same GVC**.
15
+ *
16
+ * This composite takes a GVC and a route table, derives the mode when it can,
17
+ * and refuses the combinations above at build time.
18
+ */
19
+
20
+ import { Composite, mergeDefaults } from "@intentius/chant";
21
+ import { Domain } from "../generated";
22
+
23
+ export interface DomainRoute {
24
+ /** Path prefix to match (e.g. `/api`). One of `prefix` or `regex`. */
25
+ prefix?: string;
26
+ /** RE2 path regex to match. One of `prefix` or `regex`. */
27
+ regex?: string;
28
+ /** Name of the workload in `gvc` to route to. */
29
+ workload: string;
30
+ /** Port on the workload (default 8080). */
31
+ port?: number;
32
+ /** Rewrite the matched prefix to this before forwarding. */
33
+ replacePrefix?: string;
34
+ }
35
+
36
+ export interface PublicDomainProps {
37
+ /** The domain name, e.g. `api.example.com` or the apex `example.com`. */
38
+ name: string;
39
+ /** The GVC every route targets. */
40
+ gvc: string;
41
+ /**
42
+ * Routes for the domain. Omit to send all traffic to the GVC's default
43
+ * routing (a `gvcLink` domain) instead.
44
+ */
45
+ routes?: DomainRoute[];
46
+ /**
47
+ * DNS mode. Defaults to `cname`, which is also the only mode valid for an
48
+ * apex domain.
49
+ */
50
+ dnsMode?: "cname" | "ns";
51
+ /**
52
+ * Certificate challenge type. Defaults to `dns01` under `ns` (the only
53
+ * option there) and `http01` under `cname`.
54
+ */
55
+ certChallengeType?: "dns01" | "http01";
56
+ /** Public port (default 443). */
57
+ port?: number;
58
+ /** Protocol (default `http2`). `tcp` requires a dedicated load balancer. */
59
+ protocol?: "http" | "http2" | "tcp";
60
+ /** Tags applied to the domain. */
61
+ tags?: Record<string, string>;
62
+ /** Per-member defaults for customizing the underlying resource. */
63
+ defaults?: {
64
+ domain?: Partial<ConstructorParameters<typeof Domain>[0]>;
65
+ };
66
+ }
67
+
68
+ /** An apex domain has exactly one dot: `example.com`, not `api.example.com`. */
69
+ export function isApexDomain(name: string): boolean {
70
+ return name.split(".").filter(Boolean).length === 2;
71
+ }
72
+
73
+ export const PublicDomain = Composite((props: PublicDomainProps) => {
74
+ const {
75
+ name,
76
+ gvc,
77
+ routes,
78
+ dnsMode = "cname",
79
+ certChallengeType = dnsMode === "ns" ? "dns01" : "http01",
80
+ port = 443,
81
+ protocol = "http2",
82
+ tags,
83
+ defaults: defs,
84
+ } = props;
85
+
86
+ if (dnsMode === "ns" && isApexDomain(name)) {
87
+ throw new Error(
88
+ `PublicDomain "${name}": an apex domain must use dnsMode "cname". NS mode does not support apex domains.`,
89
+ );
90
+ }
91
+
92
+ if (dnsMode === "ns" && certChallengeType !== "dns01") {
93
+ throw new Error(
94
+ `PublicDomain "${name}": dnsMode "ns" requires certChallengeType "dns01"; "${certChallengeType}" is rejected.`,
95
+ );
96
+ }
97
+
98
+ for (const route of routes ?? []) {
99
+ if (!route.prefix && !route.regex) {
100
+ throw new Error(`PublicDomain "${name}": every route needs a \`prefix\` or a \`regex\`.`);
101
+ }
102
+ if (route.prefix && route.regex) {
103
+ throw new Error(
104
+ `PublicDomain "${name}": route for workload "${route.workload}" sets both \`prefix\` and \`regex\`; ` +
105
+ `only one may be provided.`,
106
+ );
107
+ }
108
+ }
109
+
110
+ const domain = new Domain(
111
+ mergeDefaults(
112
+ {
113
+ name,
114
+ ...(tags && { tags }),
115
+ spec: {
116
+ dnsMode,
117
+ certChallengeType,
118
+ // `gvcLink` and `ports[].routes` are mutually exclusive: a domain
119
+ // with explicit routes must not also carry a GVC link.
120
+ ...(routes && routes.length > 0
121
+ ? {
122
+ ports: [
123
+ {
124
+ number: port,
125
+ protocol,
126
+ routes: routes.map((route) => ({
127
+ ...(route.prefix && { prefix: route.prefix }),
128
+ ...(route.regex && { regex: route.regex }),
129
+ ...(route.replacePrefix && { replacePrefix: route.replacePrefix }),
130
+ port: route.port ?? 8080,
131
+ workloadLink: `//gvc/${gvc}/workload/${route.workload}`,
132
+ })),
133
+ },
134
+ ],
135
+ }
136
+ : { gvcLink: `//gvc/${gvc}` }),
137
+ },
138
+ } as Record<string, unknown>,
139
+ defs?.domain,
140
+ ),
141
+ );
142
+
143
+ return { domain };
144
+ }, "PublicDomain");
@@ -0,0 +1,136 @@
1
+ /**
2
+ * SecretAccess — the identity and policy a workload needs to read a secret.
3
+ *
4
+ * Control Plane's own documentation calls a partial version of this its number
5
+ * one support issue: reading a secret at runtime takes three separate steps,
6
+ * and **missing any one of them fails silently at runtime** rather than at
7
+ * apply time.
8
+ *
9
+ * 1. An identity exists, and the workload's `spec.identityLink` points at it.
10
+ * 2. A policy grants that identity `reveal` on the secret, with the identity
11
+ * named as a principal in a binding.
12
+ * 3. The workload references the secret in the field-qualified form —
13
+ * `cpln://secret/NAME.payload`, not `cpln://secret/NAME`.
14
+ *
15
+ * This composite owns steps 1 and 2, and {@link secretRef} exists for step 3 so
16
+ * the field qualifier is hard to forget.
17
+ *
18
+ * The principal link is the part most worth automating. A binding written
19
+ * against `//identity/NAME` — which reads perfectly naturally, and is what
20
+ * you would guess — is accepted by the API and then **silently ignored**. Only
21
+ * the GVC-qualified `//gvc/GVC/identity/NAME` form works. This composite only
22
+ * emits the qualified form.
23
+ */
24
+
25
+ import { Composite, mergeDefaults } from "@intentius/chant";
26
+ import { Identity, Policy } from "../generated";
27
+
28
+ export interface SecretAccessProps {
29
+ /** Identity name. The policy is named `<name>-secret-access` unless overridden. */
30
+ name: string;
31
+ /** GVC the identity lives in. Identities cannot be shared across GVCs. */
32
+ gvc: string;
33
+ /**
34
+ * Names of the org-scoped secrets this identity may read. Each becomes a
35
+ * `//secret/NAME` target link on the policy.
36
+ */
37
+ secrets: string[];
38
+ /** Policy name (default `<name>-secret-access`). */
39
+ policyName?: string;
40
+ /**
41
+ * Permissions granted on the target secrets (default `["reveal"]`).
42
+ * Control Plane requires the list to be sorted and unique, which this does.
43
+ */
44
+ permissions?: string[];
45
+ /** Tags applied to both resources. */
46
+ tags?: Record<string, string>;
47
+ /** Per-member defaults for customizing individual resources. */
48
+ defaults?: {
49
+ identity?: Partial<ConstructorParameters<typeof Identity>[0]>;
50
+ policy?: Partial<ConstructorParameters<typeof Policy>[0]>;
51
+ };
52
+ }
53
+
54
+ export const SecretAccess = Composite((props: SecretAccessProps) => {
55
+ const {
56
+ name,
57
+ gvc,
58
+ secrets,
59
+ policyName = `${name}-secret-access`,
60
+ permissions = ["reveal"],
61
+ tags,
62
+ defaults: defs,
63
+ } = props;
64
+
65
+ if (secrets.length === 0) {
66
+ throw new Error(
67
+ `SecretAccess "${name}": no secrets listed. A policy with no target links grants nothing, ` +
68
+ `and the workload would fail to read its secrets at runtime with no apply-time error.`,
69
+ );
70
+ }
71
+
72
+ const identity = new Identity(
73
+ mergeDefaults(
74
+ {
75
+ name,
76
+ gvc,
77
+ ...(tags && { tags }),
78
+ } as Record<string, unknown>,
79
+ defs?.identity,
80
+ ),
81
+ );
82
+
83
+ const policy = new Policy(
84
+ mergeDefaults(
85
+ {
86
+ name: policyName,
87
+ ...(tags && { tags }),
88
+ targetKind: "secret",
89
+ targetLinks: [...secrets].sort().map((secret) => `//secret/${secret}`),
90
+ bindings: [
91
+ {
92
+ // Sorted and deduplicated: Control Plane requires permissions in a
93
+ // binding to be both.
94
+ permissions: [...new Set(permissions)].sort(),
95
+ // The GVC-qualified form. The bare `//identity/NAME` is silently
96
+ // ignored — see the module comment.
97
+ principalLinks: [identityLink(gvc, name)],
98
+ },
99
+ ],
100
+ } as Record<string, unknown>,
101
+ defs?.policy,
102
+ ),
103
+ );
104
+
105
+ return { identity, policy };
106
+ }, "SecretAccess");
107
+
108
+ /**
109
+ * The GVC-qualified principal link for an identity.
110
+ *
111
+ * Exported because this is the form policies must use, and the unqualified one
112
+ * fails silently.
113
+ */
114
+ export function identityLink(gvc: string, name: string): string {
115
+ return `//gvc/${gvc}/identity/${name}`;
116
+ }
117
+
118
+ /**
119
+ * A field-qualified secret reference for use in a container's env or volumes.
120
+ *
121
+ * The field name is required. `cpln://secret/db.password` resolves; the
122
+ * unqualified `cpln://secret/db` does not, except for `gcp` secrets mounted as
123
+ * a file. The field differs by secret type:
124
+ *
125
+ * | Type | Field |
126
+ * |---|---|
127
+ * | `opaque` | `payload` |
128
+ * | `dictionary` | any key |
129
+ * | `userpass` | `username`, `password` |
130
+ * | `tls` | `cert`, `key` |
131
+ * | `keypair` | `publicKey`, `privateKey` |
132
+ * | `aws` | `accessKey`, `secretKey`, `roleArn` |
133
+ */
134
+ export function secretRef(name: string, field: string): string {
135
+ return `cpln://secret/${name}.${field}`;
136
+ }
@@ -0,0 +1,143 @@
1
+ /**
2
+ * ServerlessService — a serverless workload with the port, probe and firewall
3
+ * settings a public HTTP service actually needs.
4
+ *
5
+ * Serverless is the Control Plane workload type with the most rules attached to
6
+ * it, and most of them are easy to get wrong by omission rather than by writing
7
+ * something incorrect:
8
+ *
9
+ * - It must expose *exactly one* HTTP port. Zero or two is rejected.
10
+ * - The external firewall's inbound is closed by default, so a workload that
11
+ * looks completely correct serves nothing until a CIDR is added.
12
+ * - `concurrency` and `rps` are the only strategies that scale to zero, and
13
+ * only on this workload type.
14
+ *
15
+ * The defaults here are Control Plane's own, restated so they are visible at
16
+ * the call site rather than implied.
17
+ */
18
+
19
+ import { Composite, mergeDefaults } from "@intentius/chant";
20
+ import { Workload } from "../generated";
21
+
22
+ export interface ServerlessServiceProps {
23
+ /** Workload name. Max 49 characters, and cannot end with `-headless`. */
24
+ name: string;
25
+ /** GVC this workload is deployed into. */
26
+ gvc: string;
27
+ /** Container image. Use `//image/NAME:TAG` for the org's own registry. */
28
+ image: string;
29
+ /**
30
+ * The port the container listens on. Serverless exposes exactly one HTTP
31
+ * port, and it must match what the process actually binds or health checks
32
+ * fail.
33
+ */
34
+ port?: number;
35
+ /** Protocol for the exposed port (default `http`). */
36
+ protocol?: "http" | "http2";
37
+ /** Container name (default `main`). */
38
+ containerName?: string;
39
+ /** CPU in millicores (default `50m`). Minimum 25m. */
40
+ cpu?: string;
41
+ /** Memory (default `128Mi`). Minimum 32Mi, and memory(MiB)/cpu(m) must be ≤ 8. */
42
+ memory?: string;
43
+ /** Environment variables, as a plain map. */
44
+ env?: Record<string, string>;
45
+ /**
46
+ * CIDRs allowed to reach the workload from the internet. Defaults to closed;
47
+ * pass `["0.0.0.0/0"]` for a public service.
48
+ */
49
+ inboundAllowCidr?: string[];
50
+ /** Hostnames or CIDRs the workload may call out to. Defaults to closed. */
51
+ outboundAllowCidr?: string[];
52
+ /** Minimum replicas (default 1). `0` scales to zero. */
53
+ minScale?: number;
54
+ /** Maximum replicas (default 5). */
55
+ maxScale?: number;
56
+ /**
57
+ * Autoscaling strategy (default `concurrency`). Only `concurrency` and `rps`
58
+ * scale a serverless workload to zero; `cpu`/`memory` cannot, and `latency`
59
+ * and multi-metric are not available on serverless at all.
60
+ */
61
+ autoscalingMetric?: "concurrency" | "rps" | "cpu" | "memory" | "disabled";
62
+ /** Scaling target (default 95). Capped at 100 for cpu/memory. */
63
+ autoscalingTarget?: number;
64
+ /**
65
+ * Identity to run as, as a link (`//gvc/GVC/identity/NAME`). Leave unset
66
+ * unless the workload needs secret access, credential-free cloud access or
67
+ * private networking — an unnecessary identity assignment complicates audit
68
+ * traces.
69
+ */
70
+ identityLink?: string;
71
+ /** Tags applied to the workload. */
72
+ tags?: Record<string, string>;
73
+ /** Per-member defaults for customizing the underlying resource. */
74
+ defaults?: {
75
+ workload?: Partial<ConstructorParameters<typeof Workload>[0]>;
76
+ };
77
+ }
78
+
79
+ export const ServerlessService = Composite((props: ServerlessServiceProps) => {
80
+ const {
81
+ name,
82
+ gvc,
83
+ image,
84
+ port = 8080,
85
+ protocol = "http",
86
+ containerName = "main",
87
+ cpu = "50m",
88
+ memory = "128Mi",
89
+ env,
90
+ inboundAllowCidr = [],
91
+ outboundAllowCidr = [],
92
+ minScale = 1,
93
+ maxScale = 5,
94
+ autoscalingMetric = "concurrency",
95
+ autoscalingTarget = 95,
96
+ identityLink,
97
+ tags,
98
+ defaults: defs,
99
+ } = props;
100
+
101
+ const workload = new Workload(
102
+ mergeDefaults(
103
+ {
104
+ name,
105
+ gvc,
106
+ ...(tags && { tags }),
107
+ spec: {
108
+ type: "serverless",
109
+ ...(identityLink && { identityLink }),
110
+ containers: [
111
+ {
112
+ name: containerName,
113
+ image,
114
+ cpu,
115
+ memory,
116
+ ports: [{ number: port, protocol }],
117
+ ...(env && {
118
+ env: Object.entries(env).map(([envName, value]) => ({ name: envName, value })),
119
+ }),
120
+ },
121
+ ],
122
+ firewallConfig: {
123
+ external: {
124
+ inboundAllowCIDR: inboundAllowCidr,
125
+ outboundAllowCIDR: outboundAllowCidr,
126
+ },
127
+ },
128
+ defaultOptions: {
129
+ autoscaling: {
130
+ metric: autoscalingMetric,
131
+ target: autoscalingTarget,
132
+ minScale,
133
+ maxScale,
134
+ },
135
+ },
136
+ },
137
+ } as Record<string, unknown>,
138
+ defs?.workload,
139
+ ),
140
+ );
141
+
142
+ return { workload };
143
+ }, "ServerlessService");
@@ -0,0 +1,179 @@
1
+ /**
2
+ * StatefulService — a stateful workload with its volume set, mounted.
3
+ *
4
+ * Persistent storage on Control Plane is two resources that have to agree, and
5
+ * the ways they can disagree are all expensive:
6
+ *
7
+ * - `ext4` and `xfs` volume sets bind to exactly one *stateful* workload.
8
+ * Mounting one from a serverless or standard workload is not a degraded
9
+ * mode, it is rejected.
10
+ * - `fileSystemType` and `performanceClass` are both immutable. Changing
11
+ * either later means delete and recreate, which means data loss.
12
+ * - `high-throughput-ssd` has a 200 GB floor, not the 10 GB floor the other
13
+ * classes have, so an initial capacity that is fine for one class is invalid
14
+ * for another.
15
+ * - A volume set is GVC-scoped and only mountable from its own GVC.
16
+ *
17
+ * This composite pairs them so the mount URI, the GVC and the workload type
18
+ * cannot drift apart, and validates the capacity floor at build time rather
19
+ * than at apply time.
20
+ */
21
+
22
+ import { Composite, mergeDefaults } from "@intentius/chant";
23
+ import { Workload, VolumeSet } from "../generated";
24
+
25
+ /** Minimum initial capacity in GB, by performance class. */
26
+ const MIN_CAPACITY_GB: Record<string, number> = {
27
+ "general-purpose-ssd": 10,
28
+ "high-throughput-ssd": 200,
29
+ shared: 10,
30
+ };
31
+
32
+ export interface StatefulServiceProps {
33
+ /** Workload name. The volume set is named `<name>-data` unless overridden. */
34
+ name: string;
35
+ /** GVC for both the workload and the volume set. */
36
+ gvc: string;
37
+ /** Container image. */
38
+ image: string;
39
+ /** Mount path inside the container. */
40
+ mountPath: string;
41
+ /** Initial volume capacity in GB. Must clear the performance class floor. */
42
+ capacityGb: number;
43
+ /** Volume set name (default `<name>-data`). */
44
+ volumeSetName?: string;
45
+ /** Filesystem (default `ext4`). Immutable after creation. */
46
+ fileSystemType?: "ext4" | "xfs" | "shared";
47
+ /** Performance class (default `general-purpose-ssd`). Immutable after creation. */
48
+ performanceClass?: "general-purpose-ssd" | "high-throughput-ssd" | "shared";
49
+ /**
50
+ * What happens to the volume when the workload releases it (default
51
+ * `retain`). `recycle` discards the data.
52
+ */
53
+ recoveryPolicy?: "retain" | "recycle";
54
+ /** Ports the container exposes. */
55
+ ports?: Array<{ number: number; protocol?: "http" | "http2" | "grpc" | "tcp" }>;
56
+ /** Container name (default `main`). */
57
+ containerName?: string;
58
+ /** CPU in millicores (default `50m`). */
59
+ cpu?: string;
60
+ /** Memory (default `128Mi`). */
61
+ memory?: string;
62
+ /** Environment variables, as a plain map. */
63
+ env?: Record<string, string>;
64
+ /** CIDRs allowed to reach the workload from the internet. Defaults to closed. */
65
+ inboundAllowCidr?: string[];
66
+ /** Hostnames or CIDRs the workload may call out to. Defaults to closed. */
67
+ outboundAllowCidr?: string[];
68
+ /** Minimum replicas (default 1). Stateful cannot scale to zero without KEDA. */
69
+ minScale?: number;
70
+ /** Maximum replicas (default 1). */
71
+ maxScale?: number;
72
+ /** Identity to run as, as a link (`//gvc/GVC/identity/NAME`). */
73
+ identityLink?: string;
74
+ /** Tags applied to both resources. */
75
+ tags?: Record<string, string>;
76
+ /** Per-member defaults for customizing individual resources. */
77
+ defaults?: {
78
+ workload?: Partial<ConstructorParameters<typeof Workload>[0]>;
79
+ volumeSet?: Partial<ConstructorParameters<typeof VolumeSet>[0]>;
80
+ };
81
+ }
82
+
83
+ export const StatefulService = Composite((props: StatefulServiceProps) => {
84
+ const {
85
+ name,
86
+ gvc,
87
+ image,
88
+ mountPath,
89
+ capacityGb,
90
+ volumeSetName = `${name}-data`,
91
+ fileSystemType = "ext4",
92
+ performanceClass = "general-purpose-ssd",
93
+ recoveryPolicy = "retain",
94
+ ports = [],
95
+ containerName = "main",
96
+ cpu = "50m",
97
+ memory = "128Mi",
98
+ env,
99
+ inboundAllowCidr = [],
100
+ outboundAllowCidr = [],
101
+ minScale = 1,
102
+ maxScale = 1,
103
+ identityLink,
104
+ tags,
105
+ defaults: defs,
106
+ } = props;
107
+
108
+ const floor = MIN_CAPACITY_GB[performanceClass];
109
+ if (floor !== undefined && capacityGb < floor) {
110
+ throw new Error(
111
+ `StatefulService "${name}": capacityGb ${capacityGb} is below the ${floor} GB minimum for ` +
112
+ `performanceClass "${performanceClass}". Both fileSystemType and performanceClass are immutable ` +
113
+ `after creation, so this is worth getting right before the first apply.`,
114
+ );
115
+ }
116
+
117
+ const volumeSet = new VolumeSet(
118
+ mergeDefaults(
119
+ {
120
+ name: volumeSetName,
121
+ gvc,
122
+ ...(tags && { tags }),
123
+ spec: {
124
+ initialCapacity: capacityGb,
125
+ fileSystemType,
126
+ // Control Plane forces `shared` for a shared filesystem; setting it
127
+ // explicitly keeps the emitted manifest equal to what comes back.
128
+ performanceClass: fileSystemType === "shared" ? "shared" : performanceClass,
129
+ snapshots: { createFinalSnapshot: true },
130
+ },
131
+ } as Record<string, unknown>,
132
+ defs?.volumeSet,
133
+ ),
134
+ );
135
+
136
+ const workload = new Workload(
137
+ mergeDefaults(
138
+ {
139
+ name,
140
+ gvc,
141
+ ...(tags && { tags }),
142
+ spec: {
143
+ type: "stateful",
144
+ ...(identityLink && { identityLink }),
145
+ containers: [
146
+ {
147
+ name: containerName,
148
+ image,
149
+ cpu,
150
+ memory,
151
+ ...(ports.length > 0 && {
152
+ ports: ports.map((p) => ({ number: p.number, protocol: p.protocol ?? "http" })),
153
+ }),
154
+ ...(env && {
155
+ env: Object.entries(env).map(([envName, value]) => ({ name: envName, value })),
156
+ }),
157
+ volumes: [
158
+ {
159
+ uri: `cpln://volumeset/${volumeSetName}`,
160
+ path: mountPath,
161
+ recoveryPolicy,
162
+ },
163
+ ],
164
+ },
165
+ ],
166
+ firewallConfig: {
167
+ external: { inboundAllowCIDR: inboundAllowCidr, outboundAllowCIDR: outboundAllowCidr },
168
+ },
169
+ defaultOptions: {
170
+ autoscaling: { minScale, maxScale },
171
+ },
172
+ },
173
+ } as Record<string, unknown>,
174
+ defs?.workload,
175
+ ),
176
+ );
177
+
178
+ return { workload, volumeSet };
179
+ }, "StatefulService");