@intentius/chant-lexicon-k8s 0.124.31 → 0.124.33

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 (270) hide show
  1. package/dist/actions/apps.js +18 -0
  2. package/dist/actions/batch.js +6 -0
  3. package/dist/actions/core.js +48 -0
  4. package/dist/actions/index.js +13 -0
  5. package/dist/actions/networking.js +11 -0
  6. package/dist/actions/rbac.js +8 -0
  7. package/dist/api/classify.js +123 -0
  8. package/dist/api/connect.js +62 -0
  9. package/dist/api/fake-cluster.js +175 -0
  10. package/dist/api/operation-surface.d.ts.map +1 -1
  11. package/dist/api/operation-surface.js +109 -0
  12. package/dist/api/owner-chain.js +100 -0
  13. package/dist/api/sweep-types.js +35 -0
  14. package/dist/behaviour-kinds.js +82 -0
  15. package/dist/codegen/docs-cli.js +2 -0
  16. package/dist/codegen/docs.js +146 -0
  17. package/dist/codegen/generate-cli.js +30 -0
  18. package/dist/codegen/generate-lexicon.js +74 -0
  19. package/dist/codegen/generate-list-map-keys.js +88 -0
  20. package/dist/codegen/generate-operations.js +53 -0
  21. package/dist/codegen/generate-typescript.js +124 -0
  22. package/dist/codegen/generate.js +173 -0
  23. package/dist/codegen/naming.js +174 -0
  24. package/dist/codegen/package.js +36 -0
  25. package/dist/codegen/patches.js +88 -0
  26. package/dist/codegen/typecheck.js +4 -0
  27. package/dist/codegen/versions.js +41 -0
  28. package/dist/components/argo-app.js +51 -0
  29. package/dist/components/builders.js +22 -0
  30. package/dist/components/capability-plugin.js +31 -0
  31. package/dist/components/flux-reconcile.js +61 -0
  32. package/dist/components/index.d.ts +1 -1
  33. package/dist/components/index.d.ts.map +1 -1
  34. package/dist/components/index.js +18 -0
  35. package/dist/components/kubectl-apply.d.ts +83 -1
  36. package/dist/components/kubectl-apply.d.ts.map +1 -1
  37. package/dist/components/kubectl-apply.js +274 -0
  38. package/dist/components/kustomize-apply.js +25 -0
  39. package/dist/composites/adot-collector.js +184 -0
  40. package/dist/composites/agic-ingress.js +85 -0
  41. package/dist/composites/aks-external-dns-agent.js +146 -0
  42. package/dist/composites/alb-ingress.js +86 -0
  43. package/dist/composites/argo-app.js +199 -0
  44. package/dist/composites/autoscaled-service.js +174 -0
  45. package/dist/composites/azure-disk-storage-class.js +47 -0
  46. package/dist/composites/azure-file-storage-class.js +48 -0
  47. package/dist/composites/azure-monitor-collector.js +176 -0
  48. package/dist/composites/batch-job.js +139 -0
  49. package/dist/composites/catalog.js +4110 -0
  50. package/dist/composites/cockroachdb-cluster.js +371 -0
  51. package/dist/composites/cockroachdb-region-stack.js +369 -0
  52. package/dist/composites/config-connector-context.js +40 -0
  53. package/dist/composites/configured-app.js +135 -0
  54. package/dist/composites/cron-schedule.js +38 -0
  55. package/dist/composites/cron-workload.js +109 -0
  56. package/dist/composites/ebs-storage-class.js +54 -0
  57. package/dist/composites/efs-storage-class.js +46 -0
  58. package/dist/composites/external-dns-agent.js +122 -0
  59. package/dist/composites/filestore-storage-class.js +49 -0
  60. package/dist/composites/fluent-bit-agent.js +157 -0
  61. package/dist/composites/flux-app.js +156 -0
  62. package/dist/composites/gce-ingress.js +83 -0
  63. package/dist/composites/gce-pd-storage-class.js +49 -0
  64. package/dist/composites/gke-external-dns-agent.js +129 -0
  65. package/dist/composites/gke-fluent-bit-agent.js +165 -0
  66. package/dist/composites/gke-gateway.js +91 -0
  67. package/dist/composites/gke-otel-collector.js +94 -0
  68. package/dist/composites/index.js +51 -0
  69. package/dist/composites/inference-service.js +76 -0
  70. package/dist/composites/irsa-service-account.js +77 -0
  71. package/dist/composites/member-defaults.js +14 -0
  72. package/dist/composites/metrics-server.js +181 -0
  73. package/dist/composites/model.js +92 -0
  74. package/dist/composites/monitored-service.js +152 -0
  75. package/dist/composites/namespace-env.js +114 -0
  76. package/dist/composites/network-isolated-app.js +124 -0
  77. package/dist/composites/node-agent.js +149 -0
  78. package/dist/composites/operator-stack.js +268 -0
  79. package/dist/composites/otel-collector-agent.js +142 -0
  80. package/dist/composites/otel-collector-gateway.js +216 -0
  81. package/dist/composites/otel-collector-node.js +176 -0
  82. package/dist/composites/otel-collector-rbac.js +115 -0
  83. package/dist/composites/otel-collector-shape.js +128 -0
  84. package/dist/composites/otel-collector.js +115 -0
  85. package/dist/composites/otel-operator-collector.js +123 -0
  86. package/dist/composites/ray-cluster.js +378 -0
  87. package/dist/composites/ray-job.js +136 -0
  88. package/dist/composites/ray-service.js +178 -0
  89. package/dist/composites/secure-ingress.js +100 -0
  90. package/dist/composites/security-context.js +1 -0
  91. package/dist/composites/sidecar-app.js +105 -0
  92. package/dist/composites/stateful-app.js +111 -0
  93. package/dist/composites/vllm-serving-runtime.js +114 -0
  94. package/dist/composites/web-app.js +157 -0
  95. package/dist/composites/worker-pool.js +167 -0
  96. package/dist/composites/workload-identity-sa.js +81 -0
  97. package/dist/composites/workload-identity-service-account.js +77 -0
  98. package/dist/config-schema.js +72 -0
  99. package/dist/config.js +59 -0
  100. package/dist/coverage.js +27 -0
  101. package/dist/crd/crd-sources.js +615 -0
  102. package/dist/crd/loader.js +181 -0
  103. package/dist/crd/oci-chart.js +144 -0
  104. package/dist/crd/parser.js +382 -0
  105. package/dist/crd/project-codegen.js +273 -0
  106. package/dist/crd/types.js +7 -0
  107. package/dist/deep-observe-hooks.js +306 -0
  108. package/dist/deep-observe.js +234 -0
  109. package/dist/default-labels.js +87 -0
  110. package/dist/describe-resources.js +699 -0
  111. package/dist/describe-stack-status.js +99 -0
  112. package/dist/detect.js +11 -0
  113. package/dist/effect-receipt-row.js +205 -0
  114. package/dist/export-resources.js +39 -0
  115. package/dist/generated/index.js +1092 -0
  116. package/dist/generated/runtime.js +4 -0
  117. package/dist/group-namespace.js +96 -0
  118. package/dist/import/embedded.js +197 -0
  119. package/dist/import/generator.js +288 -0
  120. package/dist/import/live-export.js +85 -0
  121. package/dist/import/parser.js +204 -0
  122. package/dist/import/testdata/embedded/fixtures.js +60 -0
  123. package/dist/include/entity.js +78 -0
  124. package/dist/include/resolve.js +170 -0
  125. package/dist/index.js +59 -0
  126. package/dist/integrity.json +3 -3
  127. package/dist/kube/apply.js +90 -0
  128. package/dist/kube/connect.js +46 -0
  129. package/dist/kube/delete.js +138 -0
  130. package/dist/kube/describe.js +169 -0
  131. package/dist/kube/events.js +132 -0
  132. package/dist/kube/flags.js +91 -0
  133. package/dist/kube/get.js +159 -0
  134. package/dist/kube/group.js +123 -0
  135. package/dist/kube/logs.js +82 -0
  136. package/dist/kube/project.js +94 -0
  137. package/dist/kube/render.js +227 -0
  138. package/dist/kube/source.js +122 -0
  139. package/dist/kube/target.js +63 -0
  140. package/dist/kube/testing.js +28 -0
  141. package/dist/kube/top.js +99 -0
  142. package/dist/kube/verdict.js +72 -0
  143. package/dist/kube/wait.js +129 -0
  144. package/dist/kustomize/render.js +73 -0
  145. package/dist/kustomize/rendered-entity.js +67 -0
  146. package/dist/kustomize/root.js +68 -0
  147. package/dist/lint/audit-catalog.js +61 -0
  148. package/dist/lint/audit-lineage.js +118 -0
  149. package/dist/lint/post-synth/argo-helpers.js +36 -0
  150. package/dist/lint/post-synth/argo002.js +31 -0
  151. package/dist/lint/post-synth/argo003.js +53 -0
  152. package/dist/lint/post-synth/argo005.js +54 -0
  153. package/dist/lint/post-synth/crd-schema-helpers.d.ts.map +1 -1
  154. package/dist/lint/post-synth/crd-schema-helpers.js +214 -0
  155. package/dist/lint/post-synth/flux002.js +56 -0
  156. package/dist/lint/post-synth/flux003.js +50 -0
  157. package/dist/lint/post-synth/index.js +104 -0
  158. package/dist/lint/post-synth/k8s-helpers.js +186 -0
  159. package/dist/lint/post-synth/monitoring-selection-helpers.js +163 -0
  160. package/dist/lint/post-synth/otel-placement-helpers.js +293 -0
  161. package/dist/lint/post-synth/sops-helpers.js +43 -0
  162. package/dist/lint/post-synth/wk8005.js +44 -0
  163. package/dist/lint/post-synth/wk8006.js +56 -0
  164. package/dist/lint/post-synth/wk8041.js +60 -0
  165. package/dist/lint/post-synth/wk8042.js +36 -0
  166. package/dist/lint/post-synth/wk8101.js +52 -0
  167. package/dist/lint/post-synth/wk8102.js +30 -0
  168. package/dist/lint/post-synth/wk8103.js +33 -0
  169. package/dist/lint/post-synth/wk8104.js +58 -0
  170. package/dist/lint/post-synth/wk8105.js +33 -0
  171. package/dist/lint/post-synth/wk8201.js +44 -0
  172. package/dist/lint/post-synth/wk8202.js +34 -0
  173. package/dist/lint/post-synth/wk8203.js +34 -0
  174. package/dist/lint/post-synth/wk8204.js +71 -0
  175. package/dist/lint/post-synth/wk8205.js +38 -0
  176. package/dist/lint/post-synth/wk8207.js +33 -0
  177. package/dist/lint/post-synth/wk8208.js +33 -0
  178. package/dist/lint/post-synth/wk8209.js +33 -0
  179. package/dist/lint/post-synth/wk8301.js +49 -0
  180. package/dist/lint/post-synth/wk8302.js +34 -0
  181. package/dist/lint/post-synth/wk8303.js +68 -0
  182. package/dist/lint/post-synth/wk8304.js +58 -0
  183. package/dist/lint/post-synth/wk8305.js +96 -0
  184. package/dist/lint/post-synth/wk8306.js +38 -0
  185. package/dist/lint/post-synth/wk8401.js +86 -0
  186. package/dist/lint/post-synth/wk8402.js +31 -0
  187. package/dist/lint/post-synth/wk8403.js +49 -0
  188. package/dist/lint/post-synth/wk8404.js +36 -0
  189. package/dist/lint/post-synth/wk8405.js +65 -0
  190. package/dist/lint/post-synth/wk8406.js +50 -0
  191. package/dist/lint/post-synth/wk8407.js +58 -0
  192. package/dist/lint/post-synth/wk8501.js +36 -0
  193. package/dist/lint/post-synth/wk8502.js +35 -0
  194. package/dist/lint/post-synth/wk8503.js +164 -0
  195. package/dist/lint/post-synth/wk8504.js +20 -0
  196. package/dist/lint/post-synth/wk8505.js +31 -0
  197. package/dist/lint/post-synth/wk8601.js +40 -0
  198. package/dist/lint/post-synth/wk8602.js +120 -0
  199. package/dist/lint/post-synth/wk8603.js +63 -0
  200. package/dist/lint/post-synth/wk8604.js +31 -0
  201. package/dist/lint/post-synth/wk8605.js +124 -0
  202. package/dist/lint/post-synth/wk8606.js +22 -0
  203. package/dist/lint/post-synth/wk8701.js +31 -0
  204. package/dist/lint/post-synth/wk8702.js +37 -0
  205. package/dist/lint/post-synth/wk8703.js +31 -0
  206. package/dist/lint/post-synth/wk8704.js +135 -0
  207. package/dist/lint/post-synth/wk8705.js +38 -0
  208. package/dist/lint/rules/argo-appset-single-project.js +56 -0
  209. package/dist/lint/rules/argo-ast.js +94 -0
  210. package/dist/lint/rules/argo-automated-prune.js +59 -0
  211. package/dist/lint/rules/flux-source-ref-pin.js +44 -0
  212. package/dist/lint/rules/hardcoded-namespace.js +57 -0
  213. package/dist/lint/rules/latest-image-tag.js +103 -0
  214. package/dist/lint/rules/missing-resource-limits.js +85 -0
  215. package/dist/lsp/completions.d.ts.map +1 -1
  216. package/dist/lsp/completions.js +18 -0
  217. package/dist/lsp/hover.d.ts.map +1 -1
  218. package/dist/lsp/hover.js +57 -0
  219. package/dist/manifest-entity.js +71 -0
  220. package/dist/manifest.json +1 -1
  221. package/dist/micro-time.js +49 -0
  222. package/dist/op/activities/apply-diff.d.ts +31 -0
  223. package/dist/op/activities/apply-diff.d.ts.map +1 -0
  224. package/dist/op/activities/apply-diff.js +79 -0
  225. package/dist/op/activities/argo.js +98 -0
  226. package/dist/op/activities/ensure-secret.js +38 -0
  227. package/dist/op/activities/index.js +51 -0
  228. package/dist/op/activities/kubectl.d.ts +64 -0
  229. package/dist/op/activities/kubectl.d.ts.map +1 -1
  230. package/dist/op/activities/kubectl.js +517 -0
  231. package/dist/op/activities/wait-for-ready.js +229 -0
  232. package/dist/op/builders.js +68 -0
  233. package/dist/package-cli.js +21 -0
  234. package/dist/patch/json-patch.js +187 -0
  235. package/dist/patch/rendered-patch.js +94 -0
  236. package/dist/plugin.js +741 -0
  237. package/dist/project-kinds.js +56 -0
  238. package/dist/receipt-store.js +216 -0
  239. package/dist/rules/crd-schema-helpers.ts +2 -1
  240. package/dist/secret-labels.js +33 -0
  241. package/dist/secret-store.js +116 -0
  242. package/dist/serializer.d.ts.map +1 -1
  243. package/dist/serializer.js +578 -0
  244. package/dist/sops/encrypted-secret-file.js +194 -0
  245. package/dist/sops/entity.js +32 -0
  246. package/dist/spec/fetch.js +58 -0
  247. package/dist/spec/parse.js +622 -0
  248. package/dist/subscribe-changes.js +168 -0
  249. package/dist/teardown.js +197 -0
  250. package/dist/telemetry-release.js +86 -0
  251. package/dist/validate-cli.js +17 -0
  252. package/dist/validate.js +47 -0
  253. package/dist/variables.js +34 -0
  254. package/dist/whoami.js +109 -0
  255. package/package.json +12 -12
  256. package/src/api/operation-surface.ts +2 -1
  257. package/src/codegen/package.ts +2 -2
  258. package/src/components/index.ts +3 -0
  259. package/src/components/kubectl-apply-plan.k3d.e2e.test.ts +127 -0
  260. package/src/components/kubectl-apply.test.ts +262 -0
  261. package/src/components/kubectl-apply.ts +341 -12
  262. package/src/deep-observe-hooks.ts +2 -2
  263. package/src/import/roundtrip-build.test.ts +46 -0
  264. package/src/lint/post-synth/crd-schema-helpers.ts +2 -1
  265. package/src/lsp/completions.ts +2 -1
  266. package/src/lsp/hover.ts +2 -1
  267. package/src/op/activities/apply-diff.ts +87 -0
  268. package/src/op/activities/kubectl-plan.test.ts +224 -0
  269. package/src/op/activities/kubectl.ts +204 -16
  270. package/src/serializer.ts +2 -1
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Resolving a live Kubernetes object's `ownerReferences` chain up to a
3
+ * declared, chant-observed entity (chant #1077).
4
+ *
5
+ * There is no server-side "who ultimately owns this object" query — Kubernetes'
6
+ * own garbage collector does not have one either; it walks the same chain one
7
+ * hop at a time, reading each intermediate owner. A Pod's `ownerReferences`
8
+ * names its ReplicaSet, which chant never declared; the ReplicaSet's own
9
+ * `ownerReferences` names the Deployment, which is declared. Reaching that
10
+ * declared entity takes reading the ReplicaSet in between.
11
+ *
12
+ * This module's only job is assembling that chain — fetching each hop through
13
+ * the typed client, bounded and cycle-guarded so a corrupt or adversarial
14
+ * chain cannot hang an observation. The bounded/cycle-safe *interpretation* of
15
+ * the assembled chain (declared / unowned / foreign / unknown) is core's
16
+ * `classifyOwnerChain` (`@intentius/chant/owner-chain`) — the issue's own
17
+ * division of labor: core owns the category, the lexicon supplies the chain.
18
+ */
19
+ import { classifyOwnerChain, DEFAULT_MAX_OWNER_CHAIN_DEPTH, } from "@intentius/chant/owner-chain";
20
+ function ownerRefs(obj) {
21
+ const refs = obj.metadata?.ownerReferences;
22
+ return Array.isArray(refs) ? refs : [];
23
+ }
24
+ /**
25
+ * The reference Kubernetes' own garbage collector treats as the controlling
26
+ * parent: the entry with `controller: true`, else the first. An object with
27
+ * several owner references (rare — usually a shared, non-controller owner
28
+ * alongside the controlling one) is walked through its controller, matching
29
+ * what actually recreates the object were it deleted.
30
+ */
31
+ function controllingRef(obj) {
32
+ const refs = ownerRefs(obj);
33
+ return refs.find((r) => r.controller === true) ?? refs[0];
34
+ }
35
+ /**
36
+ * Walk `obj`'s owner-reference chain up to a declared entity, a foreign root,
37
+ * or a bound (#1077). Fetches at most `maxDepth + 1` objects (the starting
38
+ * object plus up to `maxDepth` ancestors) — the same bound `classifyOwnerChain`
39
+ * enforces when interpreting the result, so nothing is fetched that the
40
+ * interpretation would not have used anyway.
41
+ */
42
+ export async function resolveK8sOwnerChain(obj, options) {
43
+ const startUid = obj.metadata?.uid;
44
+ if (!startUid)
45
+ return { root: "unknown" };
46
+ const maxDepth = options.maxDepth ?? DEFAULT_MAX_OWNER_CHAIN_DEPTH;
47
+ const nodes = new Map();
48
+ const visited = new Set();
49
+ let uid = startUid;
50
+ let object = obj;
51
+ for (let depth = 0; uid !== undefined && depth <= maxDepth; depth++) {
52
+ if (visited.has(uid))
53
+ break; // cycle — classifyOwnerChain reads it off `nodes`
54
+ visited.add(uid);
55
+ const declaredEntity = options.declaredByUid.get(uid);
56
+ if (declaredEntity) {
57
+ nodes.set(uid, { declaredEntity });
58
+ break;
59
+ }
60
+ if (!object) {
61
+ nodes.set(uid, { ownerUnreadable: true });
62
+ break;
63
+ }
64
+ const ref = controllingRef(object);
65
+ if (!ref?.uid || !ref.kind || !ref.apiVersion || !ref.name) {
66
+ nodes.set(uid, {}); // no further owner — a real, live root
67
+ break;
68
+ }
69
+ nodes.set(uid, { ownerId: ref.uid });
70
+ if (visited.has(ref.uid)) {
71
+ // About to cycle back to an already-visited node — let the loop's own
72
+ // check classify it next iteration rather than issuing a wasted read.
73
+ uid = ref.uid;
74
+ continue;
75
+ }
76
+ if (options.declaredByUid.has(ref.uid)) {
77
+ // Already known to be a declared entity from this observation's own
78
+ // resolved set — no need to read it, the next iteration resolves it
79
+ // from `declaredByUid` directly.
80
+ uid = ref.uid;
81
+ object = undefined;
82
+ continue;
83
+ }
84
+ try {
85
+ object = await options.reader.readIfPresent({
86
+ apiVersion: ref.apiVersion,
87
+ kind: ref.kind,
88
+ name: ref.name,
89
+ namespace: options.namespace,
90
+ });
91
+ }
92
+ catch {
93
+ object = undefined;
94
+ }
95
+ if (!object)
96
+ nodes.set(ref.uid, { ownerUnreadable: true });
97
+ uid = ref.uid;
98
+ }
99
+ return classifyOwnerChain(startUid, nodes, maxDepth);
100
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * The kinds chant sweeps when nothing narrows the question.
3
+ *
4
+ * A *product* decision — what a bare `chant import` should pull back, and
5
+ * (chant #1075) which kinds an ownership-scoped prune has to look at to notice
6
+ * that a whole kind was removed from source. Not an addressing limit: since
7
+ * chant #1074 removed `KUBECTL_RESOURCE`, a `--selector type=<entity type>`
8
+ * import can name any of the ~180 types the generated operation surface
9
+ * carries, CRDs included.
10
+ *
11
+ * It lives in its own module, with no imports of its own, because both
12
+ * consumers reach it from different directions — `../export-resources.ts`
13
+ * pulls in the whole import parser, and the apply activity should not.
14
+ */
15
+ export const DEFAULT_IMPORT_TYPES = [
16
+ "K8s::Apps::Deployment",
17
+ "K8s::Apps::StatefulSet",
18
+ "K8s::Apps::DaemonSet",
19
+ "K8s::Apps::ReplicaSet",
20
+ "K8s::Core::Service",
21
+ "K8s::Core::ConfigMap",
22
+ "K8s::Core::Secret",
23
+ "K8s::Core::Namespace",
24
+ "K8s::Core::Pod",
25
+ "K8s::Core::PersistentVolumeClaim",
26
+ "K8s::Core::ServiceAccount",
27
+ "K8s::Batch::Job",
28
+ "K8s::Batch::CronJob",
29
+ "K8s::Networking::Ingress",
30
+ "K8s::Networking::NetworkPolicy",
31
+ "K8s::Rbac::Role",
32
+ "K8s::Rbac::RoleBinding",
33
+ "K8s::Rbac::ClusterRole",
34
+ "K8s::Rbac::ClusterRoleBinding",
35
+ ];
@@ -0,0 +1,82 @@
1
+ /**
2
+ * What this lexicon's entity types are worth to a behaviour engine (#2382).
3
+ *
4
+ * Contributed as rows through the plugin's `behaviourKinds` field; the
5
+ * vocabulary, the resolution and the prediction are
6
+ * `packages/core/src/behaviour-kinds.ts`. These rows were a section of one
7
+ * cross-lexicon table in one lexicon until #2382 moved each substrate's
8
+ * rows to the lexicon that owns the types — this file can say what
9
+ * `K8s::Apps::Deployment` means because it is the file that defines it.
10
+ *
11
+ * A table rather than a heuristic over type names: a heuristic would map
12
+ * `K8s::Rbac::RoleBinding` to storage and price a join table.
13
+ */
14
+ /** Types an engine prices, with the declared property its size is read from. */
15
+ const MAPPED = {
16
+ // No `sizeProp` on the five workload kinds. A workload's size is its pod
17
+ // template's container resource requests — an object, per container, over an
18
+ // array — and this contract's `size` is one string in the provider's own
19
+ // vocabulary. Kubernetes has no such string for a workload. Naming
20
+ // `spec.template.spec.containers` here, as the first version did, advertised
21
+ // a column that resolved to an array and therefore to nothing, on every
22
+ // Kubernetes workload in the repository.
23
+ "K8s::Apps::Deployment": { kind: "compute" },
24
+ "K8s::Apps::StatefulSet": { kind: "compute" },
25
+ "K8s::Apps::DaemonSet": { kind: "compute" },
26
+ "K8s::Core::Pod": { kind: "compute" },
27
+ "K8s::Batch::Job": { kind: "serverless" },
28
+ "K8s::Batch::CronJob": { kind: "serverless" },
29
+ // These two do state a size, as a single string in Kubernetes' own
30
+ // vocabulary: a quantity (`20Gi`) and a service type (`LoadBalancer`).
31
+ "K8s::Core::PersistentVolumeClaim": { kind: "block-store", sizeProp: "spec.resources.requests.storage", sizeType: "string" },
32
+ "K8s::Core::Service": { kind: "load-balancer", sizeProp: "spec.type", sizeType: "string" },
33
+ "K8s::Networking::Ingress": { kind: "load-balancer" },
34
+ };
35
+ /**
36
+ * Types considered and declared unsendable, each with the reason it carries no
37
+ * rate. The fifth family core's contributor guidance names is this lexicon's
38
+ * own: a controller's request is not the thing it creates. A `Certificate`
39
+ * costs nothing; the issuer's work and the Secret it writes are elsewhere in
40
+ * the graph or outside the cluster entirely.
41
+ */
42
+ const UNMAPPED = {
43
+ "K8s::Rbac::Role": "an RBAC role is a grant; the workloads it admits are what cost and saturate",
44
+ "K8s::Rbac::RoleBinding": "an RBAC binding is a grant; the workloads it admits are what cost and saturate",
45
+ "K8s::Rbac::ClusterRole": "the cluster-scoped twin of an RBAC role, and a grant for the same reason",
46
+ "K8s::Rbac::ClusterRoleBinding": "the cluster-scoped twin of an RBAC binding, and a grant for the same reason",
47
+ "K8s::Core::ServiceAccount": "an identity, not a workload",
48
+ "K8s::Core::Namespace": "a boundary the workloads sit inside, and the workloads carry the figures",
49
+ "K8s::Networking::IngressClass": "names which controller handles an Ingress; the controller and the load balancer it creates carry the figures",
50
+ "K8s::Networking::NetworkPolicy": "a filter on edges that already exist, not a thing at either end of one",
51
+ "K8s::Storage::StorageClass": "the kind of disk a claim may ask for; the PersistentVolumeClaim carries the figures",
52
+ "K8s::Autoscaling::HorizontalPodAutoscaler": "an instruction about how many replicas to run; the workload it scales carries the figures, and a prediction is at one stated traffic level rather than across a scaling range",
53
+ "K8s::HorizontalPodAutoscaler": "an instruction about how many replicas to run; the workload it scales carries the figures, and a prediction is at one stated traffic level rather than across a scaling range",
54
+ "K8s::Policy::PodDisruptionBudget": "a constraint on voluntary eviction. It shapes what a lost zone does to a workload and has no rate of its own; expressing that constraint to an engine is a resilience input this contract has no field for",
55
+ "K8s::Core::LimitRange": "a default and a ceiling for workloads in a namespace; the workloads carry the figures",
56
+ "K8s::Core::ResourceQuota": "a ceiling on a namespace's total requests; the workloads under it carry the figures",
57
+ // ── A controller's request is not the thing it creates ───────────
58
+ "K8s::Argo::Application": "a request to Argo CD to reconcile a source; whatever it creates is priced where that lands",
59
+ "K8s::Flux::GitRepository": "a source Flux polls; the workloads it reconciles carry the figures",
60
+ "K8s::Flux::Kustomization": "a request to Flux to apply a source; whatever it creates is priced where that lands",
61
+ "K8s::CertManager::Certificate": "a request to an issuer. The certificate costs nothing; the issuer's work and the Secret it writes are elsewhere",
62
+ "K8s::CertManager::ClusterIssuer": "the issuer a certificate request names; it holds no capacity and serves no traffic",
63
+ "K8s::ExternalSecrets::ExternalSecret": "a request to copy a value from an external store into a Secret; neither end is a rate",
64
+ "K8s::ExternalSecrets::ClusterSecretStore": "names where secrets are fetched from; it holds no capacity and serves no traffic",
65
+ "K8s::Monitoring::ServiceMonitor": "tells Prometheus what to scrape; Prometheus is a workload elsewhere in the graph and carries the figures",
66
+ "K8s::Monitoring::PrometheusRule": "alerting and recording rules evaluated by Prometheus, which carries the figures",
67
+ "K8s::Traefik::IngressRoute": "routing handled by the Traefik workload, which carries the figures",
68
+ "K8s::Calico::FelixConfiguration": "tuning for the CNI agent on every node; the nodes carry the figures",
69
+ "K8s::GKE::BackendConfig": "tuning for a Google load balancer an Ingress creates; the gcp substrate is not modelled here, so neither end is priced here",
70
+ "K8s::NetworkingGKE::ManagedCertificate": "a certificate Google issues for an Ingress; the gcp substrate is not modelled here",
71
+ "K8s::NetworkingGKEBeta::FrontendConfig": "tuning for a Google load balancer an Ingress creates; the gcp substrate is not modelled here",
72
+ // ── Configuration and secrets: real, declared, carrying no rate ──
73
+ "K8s::Core::ConfigMap": "configuration the workloads read; it has no rate and no saturation axis of its own",
74
+ "K8s::Core::Secret": "configuration the workloads read; it has no rate and no saturation axis of its own",
75
+ };
76
+ /** This lexicon's answer for its own types. */
77
+ export const k8sBehaviourKinds = {
78
+ provider: "kubernetes",
79
+ prefixes: ["K8s::"],
80
+ mapped: MAPPED,
81
+ unmapped: UNMAPPED,
82
+ };
@@ -0,0 +1,2 @@
1
+ import { generateDocs } from "./docs.js";
2
+ await generateDocs({ verbose: true });
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Documentation generation for the Kubernetes lexicon.
3
+ *
4
+ * Generates Starlight MDX pages for K8s entities using the core docs pipeline.
5
+ */
6
+ import { dirname, join } from "path";
7
+ import { fileURLToPath } from "url";
8
+ import { docsPipeline, writeDocsSite } from "@intentius/chant/codegen/docs";
9
+ /**
10
+ * Extract service name from K8s type: "K8s::Apps::Deployment" → "Apps"
11
+ */
12
+ function serviceFromType(resourceType) {
13
+ const parts = resourceType.split("::");
14
+ return parts.length >= 2 ? parts[1] : "Core";
15
+ }
16
+ const overview = `The **Kubernetes** lexicon provides typed constructors for Kubernetes resource
17
+ manifests. It covers Deployments, Services, ConfigMaps, StatefulSets, Jobs,
18
+ Ingress, RBAC, and 147 resources and 50 property types.
19
+
20
+ New? Start with the [Getting Started](/chant/lexicons/k8s/getting-started/) guide.
21
+
22
+ Install it with:
23
+
24
+ \`\`\`bash
25
+ npm install --save-dev @intentius/chant-lexicon-k8s
26
+ \`\`\`
27
+
28
+ ## Quick Start
29
+
30
+ \`\`\`typescript
31
+ import { Deployment, Service, Container, Probe } from "@intentius/chant-lexicon-k8s";
32
+
33
+ export const deployment = new Deployment({
34
+ metadata: { name: "my-app", labels: { "app.kubernetes.io/name": "my-app" } },
35
+ spec: {
36
+ replicas: 2,
37
+ selector: { matchLabels: { "app.kubernetes.io/name": "my-app" } },
38
+ template: {
39
+ metadata: { labels: { "app.kubernetes.io/name": "my-app" } },
40
+ spec: {
41
+ containers: [
42
+ new Container({
43
+ name: "app",
44
+ image: "my-app:1.0",
45
+ ports: [{ containerPort: 8080, name: "http" }],
46
+ livenessProbe: new Probe({ httpGet: { path: "/healthz", port: 8080 } }),
47
+ readinessProbe: new Probe({ httpGet: { path: "/readyz", port: 8080 } }),
48
+ }),
49
+ ],
50
+ },
51
+ },
52
+ },
53
+ });
54
+
55
+ export const service = new Service({
56
+ metadata: { name: "my-app" },
57
+ spec: {
58
+ selector: { "app.kubernetes.io/name": "my-app" },
59
+ ports: [{ port: 80, targetPort: 8080, name: "http" }],
60
+ },
61
+ });
62
+ \`\`\`
63
+
64
+ The lexicon provides **147 resource types** (Deployment, Service, ConfigMap, StatefulSet, and more), **50 property types** (Container, Probe, Volume, SecurityContext, etc.), and composites (WebApp, StatefulApp, CronWorkload, AutoscaledService, WorkerPool, NamespaceEnv, NodeAgent) for common patterns.
65
+ `;
66
+ const outputFormat = `The Kubernetes lexicon serializes resources into **multi-document YAML** with
67
+ \`---\` separators between resources. Each resource gets the standard K8s
68
+ structure: \`apiVersion\`, \`kind\`, \`metadata\`, and \`spec\`.
69
+
70
+ ## Building
71
+
72
+ Run \`chant build\` to produce Kubernetes manifests from your declarations:
73
+
74
+ \`\`\`bash
75
+ chant build src/ --output dist/manifests.yaml
76
+ # Writes dist/manifests.yaml
77
+ \`\`\`
78
+
79
+ The generated file includes:
80
+
81
+ - Multi-document YAML with \`---\` separators
82
+ - Correct \`apiVersion\` and \`kind\` for each resource
83
+ - \`metadata.name\` auto-generated from export names (camelCase → kebab-case)
84
+ - Default labels and annotations injected from \`defaultLabels()\`/\`defaultAnnotations()\`
85
+
86
+ The lexicon also materializes [effect receipts](/chant/concepts/effect-receipts/) as
87
+ ConfigMaps named \`chant-receipt.<stack>.<env>.<effect>\` in \`k8s.receipts.namespace\`
88
+ (\`default\` when unset), holding the expectation under \`data.expectation\`. A receipt is
89
+ never a document in the manifest stream: the rows ride a trailing
90
+ \`# chant:effect-receipts\` comment, which \`kubectl apply\` ignores, because the
91
+ \`effect()\` step is a receipt's sole writer.
92
+
93
+ ## Key conversions
94
+
95
+ | Chant (TypeScript) | YAML output | Rule |
96
+ |--------------------|-------------|------|
97
+ | \`export const myApp = new Deployment({...})\` | \`metadata.name: my-app\` | Export name → kebab-case |
98
+ | \`new Container({...})\` | Inline container spec | Property types expanded inline |
99
+ | \`defaultLabels({...})\` | Merged into all resources | Project-wide label injection |
100
+
101
+ ## Applying
102
+
103
+ The output is standard Kubernetes YAML. Apply with kubectl:
104
+
105
+ \`\`\`bash
106
+ # Dry run first
107
+ kubectl apply -f dist/manifests.yaml --dry-run=server
108
+
109
+ # Apply
110
+ kubectl apply -f dist/manifests.yaml
111
+
112
+ # Diff before applying
113
+ kubectl diff -f dist/manifests.yaml
114
+ \`\`\`
115
+
116
+ ## Compatibility
117
+
118
+ The output is compatible with:
119
+ - kubectl apply/diff
120
+ - Helm (as raw manifests)
121
+ - ArgoCD / Flux GitOps controllers
122
+ - Kustomize (as a base)
123
+ - Any tool that processes Kubernetes YAML`;
124
+ /**
125
+ * Generate documentation for the Kubernetes lexicon.
126
+ */
127
+ export async function generateDocs(opts) {
128
+ const pkgDir = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
129
+ const config = {
130
+ name: "k8s",
131
+ displayName: "Kubernetes",
132
+ description: "Typed constructors for Kubernetes resource manifests",
133
+ distDir: join(pkgDir, "dist"),
134
+ outDir: join(pkgDir, "docs"),
135
+ overview,
136
+ outputFormat,
137
+ serviceFromType,
138
+ suppressPages: ["pseudo-parameters"],
139
+ basePath: "/chant/lexicons/k8s/",
140
+ };
141
+ const result = await docsPipeline(config);
142
+ writeDocsSite(config, result);
143
+ if (opts?.verbose) {
144
+ console.error(`Generated docs: ${result.stats.resources} resources, ${result.stats.properties} properties, ${result.stats.services} services, ${result.stats.rules} rules`);
145
+ }
146
+ }
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * CLI entry point for Kubernetes lexicon generation.
4
+ */
5
+ import { dirname } from "path";
6
+ import { fileURLToPath } from "url";
7
+ import { generate, writeGeneratedFiles } from "./generate.js";
8
+ import { K8S_SCHEMA_VERSION } from "../spec/fetch.js";
9
+ const pkgDir = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
10
+ async function main() {
11
+ const verbose = process.argv.includes("--verbose") || process.argv.includes("-v");
12
+ const force = process.argv.includes("--force") || process.argv.includes("-f");
13
+ // Parse --schema-version <ref>
14
+ const versionIdx = process.argv.indexOf("--schema-version");
15
+ const schemaVersion = versionIdx !== -1 ? process.argv[versionIdx + 1] : undefined;
16
+ console.error(`Generating Kubernetes lexicon (schema: ${schemaVersion ?? K8S_SCHEMA_VERSION})...`);
17
+ const result = await generate({ verbose, force, schemaVersion });
18
+ writeGeneratedFiles(result, pkgDir);
19
+ console.error(`Generated ${result.resources} resources, ${result.properties} property types, ${result.enums} enums`);
20
+ if (result.warnings.length > 0) {
21
+ console.error(`${result.warnings.length} warnings:`);
22
+ for (const w of result.warnings) {
23
+ console.error(` ${w.file}: ${w.error}`);
24
+ }
25
+ }
26
+ }
27
+ main().catch((err) => {
28
+ console.error("Generation failed:", err);
29
+ process.exit(1);
30
+ });
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Lexicon JSON generator — produces lexicon-k8s.json with metadata
3
+ * for all Kubernetes resource and property types.
4
+ */
5
+ import { k8sShortName, gvkToApiVersion } from "../spec/parse.js";
6
+ import { buildRegistry, serializeRegistry, } from "@intentius/chant/codegen/generate-registry";
7
+ /**
8
+ * Generate the lexicon-k8s.json content.
9
+ */
10
+ export function generateLexiconJSON(results, naming) {
11
+ const registryResources = results.map((r) => ({
12
+ typeName: r.resource.typeName,
13
+ attributes: r.resource.attributes,
14
+ properties: r.resource.properties,
15
+ propertyTypes: r.propertyTypes.map((pt) => ({ name: pt.name, specType: pt.defType })),
16
+ }));
17
+ const entries = buildRegistry(registryResources, naming, {
18
+ shortName: k8sShortName,
19
+ buildEntry: (resource, _tsName, _attrs, propConstraints) => {
20
+ const r = results.find((res) => res.resource.typeName === resource.typeName);
21
+ const entry = {
22
+ resourceType: resource.typeName,
23
+ kind: (r?.isProperty ? "property" : "resource"),
24
+ lexicon: "k8s",
25
+ };
26
+ if (r && !r.isProperty) {
27
+ entry.apiVersion = gvkToApiVersion(r.gvk);
28
+ entry.gvkKind = r.gvk.kind;
29
+ }
30
+ if (r?.resource.deprecatedProperties?.length) {
31
+ entry.deprecatedProperties = r.resource.deprecatedProperties;
32
+ }
33
+ if (propConstraints && Object.keys(propConstraints).length > 0) {
34
+ entry.constraints = propConstraints;
35
+ }
36
+ if (r?.specSchema) {
37
+ entry.specSchema = r.specSchema;
38
+ }
39
+ return entry;
40
+ },
41
+ buildPropertyEntry: (resourceType, propertyType) => ({
42
+ resourceType: `${resourceType}.${propertyType}`,
43
+ kind: "property",
44
+ lexicon: "k8s",
45
+ }),
46
+ });
47
+ return serializeWithCompactSchemas(entries);
48
+ }
49
+ /**
50
+ * Pretty-print the registry the way every lexicon does, but keep each
51
+ * `specSchema` on one line (chant #1372). A CRD schema nests deeply — an
52
+ * ApplicationSet or RayCluster embeds a whole PodTemplateSpec — and two-space
53
+ * indentation on that tree quadruples the file for no reader's benefit.
54
+ * Compact, the schemas add ~0.8 MB to the registry; pretty, ~3.9 MB.
55
+ */
56
+ export function serializeWithCompactSchemas(entries) {
57
+ const compact = new Map();
58
+ const stripped = {};
59
+ for (const [name, entry] of Object.entries(entries)) {
60
+ if (!entry.specSchema) {
61
+ stripped[name] = entry;
62
+ continue;
63
+ }
64
+ const { specSchema, ...rest } = entry;
65
+ const token = `__chant_spec_schema_${compact.size}__`;
66
+ compact.set(token, JSON.stringify(specSchema));
67
+ stripped[name] = { ...rest, specSchema: token };
68
+ }
69
+ let out = serializeRegistry(stripped);
70
+ for (const [token, json] of compact) {
71
+ out = out.replace(`"${token}"`, json);
72
+ }
73
+ return out;
74
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Associative-list key table, derived from the spec rather than hardcoded
3
+ * (chant #1441).
4
+ *
5
+ * The Kubernetes OpenAPI spec states, per property, how the API server merges
6
+ * a list: `x-kubernetes-list-type: map` plus `x-kubernetes-list-map-keys`
7
+ * naming the fields that jointly identify one element. chant's drift engine
8
+ * needs exactly that to compare `containers` by name rather than by index.
9
+ * Before this, `k8sListMapOrderKey` (core's `managed-fields.ts`) hardcoded six
10
+ * properties; the spec annotates 32.
11
+ *
12
+ * ## Why the table is keyed by property name
13
+ *
14
+ * The consumer is `DeepNormalizationHooks.orderKey`, which receives an
15
+ * index-erased `pattern` (`spec.template.spec.containers`) and keys off its
16
+ * LAST segment. A by-name table matches that lookup exactly and needs no
17
+ * resolution of the definition graph at generation time.
18
+ *
19
+ * The cost is that two definitions declaring the same property name with
20
+ * different keys collapse into one entry. That is real but small, and it is
21
+ * represented rather than lost: each name maps to a LIST of candidate key
22
+ * sets, and the consumer picks the first whose fields are all present on the
23
+ * element. In v1.36.2 exactly one property needs it — `ports`, keyed
24
+ * `(containerPort, protocol)` on a container and `(port, protocol)` on a
25
+ * Service — which is the same dual shape the hardcoded implementation already
26
+ * special-cased by hand.
27
+ *
28
+ * ## Two sources
29
+ *
30
+ * The whole spec is read directly, because chant emits types for 257 of
31
+ * roughly a thousand definitions and the merge semantics of a definition are
32
+ * worth having whether or not it becomes a type. Reading only the parsed
33
+ * results finds 18 of the 32 properties and misses `ServicePort.port`.
34
+ *
35
+ * Parsed results are read too, because CRDs arrive that way — an operator CRD
36
+ * that declares these extensions contributes its keys for free.
37
+ */
38
+ function everyProperty(results) {
39
+ const out = [];
40
+ for (const r of results) {
41
+ out.push(...r.resource.properties);
42
+ for (const pt of r.propertyTypes)
43
+ out.push(...pt.properties);
44
+ }
45
+ return out;
46
+ }
47
+ /** The `(name, keys)` pairs carried on parsed properties — core types and every CRD. */
48
+ export function parsedListMapKeyPairs(results) {
49
+ const pairs = [];
50
+ for (const p of everyProperty(results)) {
51
+ if (p.listType === "map" && p.listMapKeys?.length)
52
+ pairs.push([p.name, p.listMapKeys]);
53
+ }
54
+ return pairs;
55
+ }
56
+ /**
57
+ * Fold `(name, keys)` pairs into a by-name table.
58
+ *
59
+ * Candidate sets are deduplicated by content and ordered longest-first, so a
60
+ * more specific key set is tried before a shorter one whose fields a longer
61
+ * element would also satisfy.
62
+ */
63
+ export function buildListMapKeyTable(...sources) {
64
+ const byName = new Map();
65
+ for (const pairs of sources) {
66
+ for (const [name, rawKeys] of pairs) {
67
+ const keys = [...rawKeys].sort();
68
+ const seen = byName.get(name) ?? new Map();
69
+ seen.set(keys.join(" "), keys);
70
+ byName.set(name, seen);
71
+ }
72
+ }
73
+ const table = {};
74
+ for (const name of [...byName.keys()].sort()) {
75
+ const variants = byName.get(name);
76
+ if (!variants)
77
+ continue;
78
+ const sets = [...variants.values()];
79
+ sets.sort((a, b) => b.length - a.length || a.join().localeCompare(b.join()));
80
+ table[name] = sets;
81
+ }
82
+ return table;
83
+ }
84
+ /** Serialize the `list-map-keys.json` generated artifact. */
85
+ export function generateListMapKeysJSON(specPairs, results) {
86
+ const table = buildListMapKeyTable(specPairs, parsedListMapKeyPairs(results));
87
+ return `${JSON.stringify(table, null, 2)}\n`;
88
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * The generated operation surface — chant #1074.
3
+ *
4
+ * `describeResources` used to reach the cluster through a hand-written
5
+ * `entityType → kubectl resource` map with twenty entries in it. Every one of
6
+ * the other ~180 generated resource types, and every CRD, fell off the end of
7
+ * it. The map was hand-maintained precisely because nothing derived it, and
8
+ * nothing derived it because the codegen pass that produces the types never
9
+ * emitted the addressing half.
10
+ *
11
+ * It does now. This artifact is written by the same `generate()` run that
12
+ * writes `lexicon-k8s.json` and `index.d.ts`, out of the same parsed results,
13
+ * so a resource that has a declarable class necessarily has an operation entry
14
+ * with the same apiVersion and kind. `operation-surface.test.ts` asserts that
15
+ * correspondence rather than trusting it.
16
+ *
17
+ * What it is not: an authority on what a given cluster serves. `plural` and
18
+ * `scope` are what the schema says; the live client confirms both against the
19
+ * cluster's own discovery before addressing anything, because a cluster can
20
+ * serve a different version of a CRD than the one chant generated from.
21
+ */
22
+ import { gvkToApiVersion } from "../spec/parse.js";
23
+ import { pluralizeKind } from "../api/operation-surface.js";
24
+ /** Build the operation table from the same parsed results the types come from. */
25
+ export function buildOperationTable(results) {
26
+ const table = {};
27
+ for (const result of results) {
28
+ if (result.isProperty)
29
+ continue;
30
+ const entityType = result.resource.typeName;
31
+ // A later result for the same type wins nothing — the first parse of a
32
+ // preferred version is canonical, matching the registry's own precedence.
33
+ if (table[entityType])
34
+ continue;
35
+ table[entityType] = {
36
+ entityType,
37
+ apiVersion: gvkToApiVersion(result.gvk),
38
+ kind: result.gvk.kind,
39
+ plural: result.operation?.plural ?? pluralizeKind(result.gvk.kind),
40
+ scope: result.operation?.scope ?? "Namespaced",
41
+ verbs: result.operation?.verbs ?? [],
42
+ };
43
+ }
44
+ return table;
45
+ }
46
+ /** Serialize the table, key-sorted so regeneration produces a stable diff. */
47
+ export function generateOperationsJSON(results) {
48
+ const table = buildOperationTable(results);
49
+ const sorted = {};
50
+ for (const key of Object.keys(table).sort())
51
+ sorted[key] = table[key];
52
+ return `${JSON.stringify(sorted, null, 2)}\n`;
53
+ }