@intentius/chant-lexicon-k8s 0.124.31 → 0.124.32

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,56 @@
1
+ /**
2
+ * Kinds a project generated for itself with `chant generate` from `k8s.crds`
3
+ * (./crd/project-codegen.ts), as opposed to the kinds the lexicon ships in
4
+ * `generated/lexicon-k8s.json`.
5
+ *
6
+ * The serializer's GVK lookup and the CRD spec checks (WK8501/WK8502) consult
7
+ * this registry before the packaged JSON. Entries arrive two ways:
8
+ *
9
+ * - The generated module registers its kinds (type and GVK) when it is
10
+ * imported, so a project's own tests that construct and serialize a
11
+ * resource need nothing else.
12
+ * - A build registers every kind, spec schema included, from the committed
13
+ * `kinds.json` beside the module before it serializes (the plugin's
14
+ * `projectCodegen().load`). That covers a build whose discovery imported the
15
+ * module somewhere else, such as the sandboxed child process.
16
+ *
17
+ * The store hangs off `globalThis` under a registered symbol, so two copies
18
+ * of this module (the plugin the CLI loaded and the one a project's import
19
+ * resolved) share it. No Node imports: the post-synth barrel reaches this
20
+ * through ./lint/post-synth/crd-schema-helpers.ts and must stay edge-safe.
21
+ */
22
+ const STORE = Symbol.for("@intentius/chant-lexicon-k8s/project-kinds");
23
+ function store() {
24
+ const g = globalThis;
25
+ g[STORE] ?? (g[STORE] = new Map());
26
+ return g[STORE];
27
+ }
28
+ /**
29
+ * Register project-generated kinds. A later registration of the same type
30
+ * replaces the earlier one, keeping its spec schema when the new entry has
31
+ * none (the module's import-time registration carries no schema).
32
+ */
33
+ export function registerProjectKinds(kinds) {
34
+ const s = store();
35
+ for (const k of kinds) {
36
+ const previous = s.get(k.resourceType);
37
+ const specSchema = k.specSchema ?? (previous?.apiVersion === k.apiVersion && previous.kind === k.kind ? previous.specSchema : undefined);
38
+ s.set(k.resourceType, { ...k, ...(specSchema ? { specSchema } : {}) });
39
+ }
40
+ }
41
+ /** The project kind an entity type constructs, if a project registered one. */
42
+ export function projectKindForType(resourceType) {
43
+ return store().get(resourceType);
44
+ }
45
+ /** The spec schema a project registered for `apiVersion`/`kind`, if any. */
46
+ export function projectSpecSchema(apiVersion, kind) {
47
+ for (const k of store().values()) {
48
+ if (k.apiVersion === apiVersion && k.kind === kind && k.specSchema)
49
+ return k.specSchema;
50
+ }
51
+ return undefined;
52
+ }
53
+ /** Test seam: forget every registered kind. */
54
+ export function clearProjectKinds() {
55
+ store().clear();
56
+ }
@@ -0,0 +1,216 @@
1
+ /**
2
+ * The k8s `ReceiptStore` (#2074, epic #1703): core's injectable receipt seam
3
+ * (#1834, `@intentius/chant/op/receipt-store`) implemented over a core
4
+ * `ConfigMap`, at the address ./effect-receipt-row.ts derives from the
5
+ * ownership marker fields, plus the plan-side live read of the same rows.
6
+ *
7
+ * The transport is the lexicon's own typed client (./api/connect.ts), so a
8
+ * receipt read and write take the same environment-to-cluster binding
9
+ * (#1100/#1155), the same credential policy and the same field manager as
10
+ * every other k8s mutation. No `kubectl` binary is involved.
11
+ *
12
+ * Write discipline (epic decision 3): `write` exists for the `effect()` step
13
+ * alone, because the step's read-compare-run-write is the only path that reaches it,
14
+ * on success, last. It is a server-side apply as `chant:<stack>`, stamping the
15
+ * ownership marker labels and {@link RECEIPT_LABEL_KEY}, so a later write
16
+ * updates the value in place and the owned-only prune retains rather than
17
+ * deletes it (./op/activities/kubectl.ts).
18
+ *
19
+ * Identity: the ConfigMap name needs `<stack>` and `<env>`, which the activity
20
+ * args deliberately do not carry (the `EffectReceiptRef` is
21
+ * identity-of-the-effect, not identity-of-the-deployment). The store resolves
22
+ * them once, lazily, at first use: an explicit option, else `CHANT_ENV` (what
23
+ * `chant run --env` sets) and the project's `ownership` block, the same fields
24
+ * that stamp markers (epic decision 4). Nothing resolving is an error, never a
25
+ * guessed segment.
26
+ *
27
+ * Not exported from the package entry point. Like ./secret-store.ts and
28
+ * ./teardown.ts, this module names the API client, which must stay off the
29
+ * build path (chant #1074, examples/k8s-client-boundary.test.ts). Consumers
30
+ * reach it by subpath: `@intentius/chant-lexicon-k8s/receipt-store`.
31
+ */
32
+ import { loadChantConfigUpward, resolveOwnershipStack } from "@intentius/chant/config";
33
+ import { LABEL_OWNERSHIP_KEYS, ownershipEntries, classifyOwnership, readOwnership, } from "@intentius/chant/ownership";
34
+ import { defaultK8sConnector } from "./api/connect.js";
35
+ import { classifyApiFailure } from "./api/classify.js";
36
+ import { K8S_EFFECT_RECEIPT_ENTITY_TYPE, RECEIPT_CONFIGMAP_REF, RECEIPT_DATA_KEY, RECEIPT_LABEL_KEY, parseReceiptComment, receiptConfigMapRef, receiptNamespaceFrom, } from "./effect-receipt-row.js";
37
+ async function resolveIdentity(options) {
38
+ const processEnv = options.env ?? process.env;
39
+ let stack = options.stack;
40
+ let env = options.environment ?? processEnv.CHANT_ENV;
41
+ let namespace = options.namespace;
42
+ if (!stack || !env || !namespace) {
43
+ let config;
44
+ try {
45
+ config = (await loadChantConfigUpward(options.cwd ?? process.cwd())).config;
46
+ }
47
+ catch {
48
+ config = undefined;
49
+ }
50
+ if (config) {
51
+ stack = stack ?? resolveOwnershipStack(config);
52
+ // Only a literal env can answer here: a `{ param }` reference resolves
53
+ // per build, and an op run has no build parameters, and `--env` does.
54
+ const configEnv = config.ownership?.env;
55
+ env = env ?? (typeof configEnv === "string" ? configEnv : undefined);
56
+ namespace = namespace ?? receiptNamespaceFrom(config);
57
+ }
58
+ }
59
+ if (!stack) {
60
+ throw new Error("k8s receipt store: no stack identity. The receipt ConfigMap is named " +
61
+ "chant-receipt.<stack>.<env>.<effect>, derived from the same ownership fields that stamp " +
62
+ "markers (chant #1703, decision 4). Set ownership: { stack } in chant.config.ts.");
63
+ }
64
+ if (!env) {
65
+ throw new Error("k8s receipt store: no environment resolved. The receipt name's <env> segment is explicit " +
66
+ "(chant #1703, decision 4). Run with --env <name>, set CHANT_ENV, or set a literal " +
67
+ "ownership.env in chant.config.ts.");
68
+ }
69
+ return { stack, env, namespace: namespace ?? receiptNamespaceFrom(undefined) };
70
+ }
71
+ /** The stored expectation on a live receipt ConfigMap, or undefined when the
72
+ * object holds none. */
73
+ export function receiptValueOf(object) {
74
+ const data = object?.data;
75
+ const value = data?.[RECEIPT_DATA_KEY];
76
+ return typeof value === "string" ? value : undefined;
77
+ }
78
+ /**
79
+ * The `ReceiptStore` over ConfigMaps. Bind it once in the op activities barrel
80
+ * as `receiptActivities(k8sReceiptStore())`, and the registry resolves
81
+ * `receiptRead`/`receiptWrite`/`receiptStaleness` by name, exactly like
82
+ * `ensureSecret` (#1830). Identity and cluster resolve lazily at first use, so
83
+ * module load never reads the project or connects to anything.
84
+ */
85
+ export function k8sReceiptStore(options = {}) {
86
+ let identity;
87
+ const identityOf = () => (identity ?? (identity = resolveIdentity(options)));
88
+ let pending;
89
+ const clientOf = () => (pending ?? (pending = (options.connect ?? defaultK8sConnector)({
90
+ ...(options.environment !== undefined ? { environment: options.environment } : {}),
91
+ ...(options.context !== undefined ? { context: options.context } : {}),
92
+ ...(options.cwd !== undefined ? { cwd: options.cwd } : {}),
93
+ }).then((connected) => connected.client)));
94
+ return {
95
+ async read(receipt) {
96
+ const { stack, env, namespace } = await identityOf();
97
+ const ref = receiptConfigMapRef(stack, env, receipt.effect, namespace);
98
+ const client = await clientOf();
99
+ const live = await client.readIfPresent({ ...RECEIPT_CONFIGMAP_REF, ...ref });
100
+ return receiptValueOf(live);
101
+ },
102
+ async write(receipt, expectation) {
103
+ const { stack, env, namespace } = await identityOf();
104
+ const ref = receiptConfigMapRef(stack, env, receipt.effect, namespace);
105
+ const client = await clientOf();
106
+ const configMap = {
107
+ ...RECEIPT_CONFIGMAP_REF,
108
+ metadata: {
109
+ ...ref,
110
+ labels: {
111
+ ...ownershipEntries(LABEL_OWNERSHIP_KEYS, { stack, env }),
112
+ [RECEIPT_LABEL_KEY]: receipt.effect,
113
+ },
114
+ },
115
+ data: { [RECEIPT_DATA_KEY]: expectation },
116
+ };
117
+ await client.apply(configMap, { fieldManager: `chant:${stack}` });
118
+ },
119
+ };
120
+ }
121
+ /** The receipt rows a build output carries, keyed by entity name. Only the
122
+ * entities this observation was asked about. */
123
+ export function receiptRowsFor(entityNames, buildOutput) {
124
+ const rows = parseReceiptComment(buildOutput ?? "");
125
+ const wanted = new Set(entityNames);
126
+ const out = new Map();
127
+ for (const [name, row] of Object.entries(rows)) {
128
+ if (!wanted.has(name))
129
+ continue;
130
+ if (typeof row?.name !== "string" || typeof row?.namespace !== "string")
131
+ continue;
132
+ out.set(name, row);
133
+ }
134
+ return out;
135
+ }
136
+ /**
137
+ * The plan-side live read of the receipt rows (#2074's observation leg).
138
+ *
139
+ * A receipt is not a document the applier ever wrote (#1832), and it carries
140
+ * no `props` on the declared side, so the generic declared-entity sweep in
141
+ * ./describe-resources.ts has neither a `metadata.name` to query by nor an
142
+ * honest verdict to give. The serializer rendered each receipt's derived
143
+ * ConfigMap address into the build output's receipt comment, so this leg reads
144
+ * the addresses back from there, one derivation, decision 4, and asks the
145
+ * cluster for each.
146
+ *
147
+ * Present maps the stored value onto `attributes.value` (core's
148
+ * `RECEIPT_VALUE_ATTRIBUTE`); a genuine 404 is a real absence and stays one; a
149
+ * failed read is an `unobserved` hole, never a wrong answer: a receipt nobody
150
+ * could read must not arrive downstream as "the effect never ran".
151
+ */
152
+ export async function observeReceiptRows(client, rows) {
153
+ const out = { resources: {}, unobserved: {} };
154
+ await client.concurrently([...rows], async ([entityName, row]) => {
155
+ try {
156
+ const live = await client.read({
157
+ ...RECEIPT_CONFIGMAP_REF,
158
+ name: row.name,
159
+ namespace: row.namespace,
160
+ });
161
+ out.resources[entityName] = {
162
+ type: K8S_EFFECT_RECEIPT_ENTITY_TYPE,
163
+ physicalId: live.metadata?.uid,
164
+ // Live outside anything the applier wrote, by design. The same word
165
+ // the aws row's observation uses for a receipt parameter (#1835).
166
+ status: "EXTERNAL",
167
+ ownership: classifyOwnership(live.metadata?.labels, LABEL_OWNERSHIP_KEYS),
168
+ marker: readOwnership(live.metadata?.labels, LABEL_OWNERSHIP_KEYS),
169
+ attributes: {
170
+ namespace: row.namespace,
171
+ // Core's RECEIPT_VALUE_ATTRIBUTE, which is what `readReceiptValue` reads.
172
+ value: receiptValueOf(live) ?? "",
173
+ },
174
+ };
175
+ }
176
+ catch (err) {
177
+ const outcome = classifyApiFailure(err);
178
+ if (outcome.kind === "unobserved") {
179
+ out.unobserved[entityName] = {
180
+ type: K8S_EFFECT_RECEIPT_ENTITY_TYPE,
181
+ reason: outcome.reason,
182
+ detail: `reading receipt ConfigMap ${row.namespace}/${row.name}: ${outcome.detail}`,
183
+ };
184
+ }
185
+ // `absent` records nothing: in neither map is how the contract spells
186
+ // "asked, and it is not there", which is what the plan reads as "the
187
+ // effect has not fired for these inputs".
188
+ }
189
+ });
190
+ return out;
191
+ }
192
+ /**
193
+ * The deep read's answer for the receipt rows.
194
+ *
195
+ * A receipt is read back here for the same reason the thin path reads it: a
196
+ * declared entity nobody looked at is a hole, and a hole in the deep read is
197
+ * noise on every `lifecycle diff --live --deep` a project with receipts runs.
198
+ * What it deliberately contributes is an EMPTY property tree: the declaration
199
+ * has no `props`, so every live path would land outside the claimed-field set
200
+ * (`@intentius/chant/claimed-fields`) and be reported unclaimed, and the
201
+ * receipt's stored value is not drift on any reading, because a stale receipt is an
202
+ * `effect` row from `planReceipts` (#1832), never an update. Presence and the
203
+ * uid are the whole of what the deep read has to say about a receipt.
204
+ */
205
+ export async function observeReceiptRowsDeep(client, rows) {
206
+ const thin = await observeReceiptRows(client, rows);
207
+ const resources = {};
208
+ for (const [name, meta] of Object.entries(thin.resources)) {
209
+ resources[name] = {
210
+ type: K8S_EFFECT_RECEIPT_ENTITY_TYPE,
211
+ ...(meta.physicalId ? { physicalId: meta.physicalId } : {}),
212
+ properties: {},
213
+ };
214
+ }
215
+ return { resources, unobserved: thin.unobserved };
216
+ }
@@ -12,6 +12,7 @@
12
12
  */
13
13
 
14
14
  import { createRequire } from "module";
15
+ import { moduleSourceUrl } from "@intentius/chant/module-source";
15
16
  import type { PostSynthContext } from "@intentius/chant/lint/post-synth";
16
17
  import type { CrdFieldSchema } from "../../spec/parse";
17
18
  import { docsToManifests, type K8sManifest } from "./k8s-helpers";
@@ -44,7 +45,7 @@ export function getCrdSchemaRegistry(): Map<string, CrdFieldSchema> {
44
45
  // Built lazily so importing this module stays edge-safe: `createRequire`
45
46
  // throws where import.meta.url is undefined (bundled Workers), and there
46
47
  // the registry is simply empty.
47
- const require = createRequire(import.meta.url);
48
+ const require = createRequire(moduleSourceUrl(import.meta.url));
48
49
  const lexicon = require("../../generated/lexicon-k8s.json") as Record<string, LexiconEntry>;
49
50
  for (const entry of Object.values(lexicon)) {
50
51
  if (entry.kind === "resource" && entry.apiVersion && entry.gvkKind && entry.specSchema) {
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The `generated-once` marker label (chant #1830, epic #1365 decision 5).
3
+ *
4
+ * `k8sSecretStore` (./secret-store.ts) stamps this label onto every Secret it
5
+ * mints, next to the ownership marker. Both k8s prune paths key on it:
6
+ *
7
+ * - the apply-path prune (`applyManifest`'s owned-only sweep,
8
+ * op/activities/kubectl.ts) excludes a labeled Secret from the prunable set
9
+ * and reports it `retained` instead of deleting it;
10
+ * - env teardown (./teardown.ts `executeTeardown`) reports the same `retained`
11
+ * outcome for it, loudly, and never deletes it.
12
+ *
13
+ * The rationale is the materialization contract: the stored bytes are the ONLY
14
+ * copy of material chant never held, so no sweep may destroy them. Deletion is
15
+ * an explicit human act — `kubectl delete secret <name>`, or a future gated
16
+ * op — never a side effect of "no longer declared".
17
+ *
18
+ * Dependency-light on purpose: no client import, so the constants are usable
19
+ * from the build path, the prune paths, and the docs/tests alike.
20
+ */
21
+ /** The label key a generated-once Secret carries. */
22
+ export const GENERATED_ONCE_LABEL_KEY = "chant.intentius.io/generated-once";
23
+ /** The label value `k8sSecretStore` stamps. */
24
+ export const GENERATED_ONCE_LABEL_VALUE = "true";
25
+ /**
26
+ * True when a live object's labels mark it generated-once. Any non-empty
27
+ * value counts: the label's presence is the claim, and a sweep must err on
28
+ * the side of keeping.
29
+ */
30
+ export function isGeneratedOnce(labels) {
31
+ const value = labels?.[GENERATED_ONCE_LABEL_KEY];
32
+ return typeof value === "string" && value.length > 0;
33
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * The Kubernetes row of the secret-store seam (chant #1830, epic #1365) — a
3
+ * `SecretStoreAdapter` over the typed client, for the `ensure-secret`
4
+ * capability verb and the `ensureSecret(...)` op step (both #1829, both
5
+ * surfaces over core's one materialization engine).
6
+ *
7
+ * The constitutional line, held structurally:
8
+ *
9
+ * - `exists` and `describe` read the live Secret and report key NAMES and
10
+ * labels/annotations only. The `data` values are projected to their key
11
+ * names inside one function frame and the object is dropped — nothing
12
+ * value-shaped is returned, logged, or retained. The mismatch check in core
13
+ * therefore has nothing to compare but names, by construction.
14
+ * - `create` is the only writer. It redeems each key's material through
15
+ * `consumeSecretMaterial` AT the write — the engine hands the generator
16
+ * through this seam and never calls it — and returns nothing.
17
+ *
18
+ * Every minted Secret carries chant's ownership marker plus the
19
+ * `chant.intentius.io/generated-once` label (./secret-labels.ts), which is
20
+ * what both prune paths key their `retained` outcome on: a generated-once
21
+ * Secret is never in any prunable set, because the stored bytes are the only
22
+ * copy of material chant never held.
23
+ *
24
+ * Not exported from the package entry point — like ./teardown.ts, this module
25
+ * names the API client, which must stay off the build path (chant #1074,
26
+ * examples/k8s-client-boundary.test.ts). Consumers reach it by subpath:
27
+ * `@intentius/chant-lexicon-k8s/secret-store`.
28
+ */
29
+ import { consumeSecretMaterial } from "@intentius/chant/secret-materialization";
30
+ import { createEnsureSecretCapability, } from "@intentius/chant/components/verbs/ensure-secret";
31
+ import { LABEL_OWNERSHIP_KEYS, OWNERSHIP_MANAGED_BY_VALUE, ownershipEntries, } from "@intentius/chant/ownership";
32
+ import { defaultK8sConnector } from "./api/connect.js";
33
+ import { GENERATED_ONCE_LABEL_KEY, GENERATED_ONCE_LABEL_VALUE } from "./secret-labels.js";
34
+ const SECRET_REF = { apiVersion: "v1", kind: "Secret" };
35
+ /**
36
+ * Build the k8s `SecretStoreAdapter` for one namespace.
37
+ *
38
+ * Pass a `client` where the caller already holds one (an activity that
39
+ * connected for the rest of its work); omit it and the adapter connects
40
+ * lazily through the same environment→cluster binding every other k8s path
41
+ * uses (./api/connect.ts).
42
+ */
43
+ export function k8sSecretStore(namespace, client, options = {}) {
44
+ let pending = client ? Promise.resolve(client) : undefined;
45
+ const clientOf = () => (pending ?? (pending = (options.connect ?? defaultK8sConnector)({
46
+ ...(options.environment !== undefined ? { environment: options.environment } : {}),
47
+ ...(options.context !== undefined ? { context: options.context } : {}),
48
+ ...(options.cwd !== undefined ? { cwd: options.cwd } : {}),
49
+ }).then((connected) => connected.client)));
50
+ return {
51
+ async exists(name) {
52
+ const c = await clientOf();
53
+ return (await c.readIfPresent({ ...SECRET_REF, name, namespace })) !== undefined;
54
+ },
55
+ async describe(name) {
56
+ const c = await clientOf();
57
+ const live = await c.read({ ...SECRET_REF, name, namespace });
58
+ // Project key names and drop the object in the same frame. The values
59
+ // exist only in `live`, which nothing below touches again — the return
60
+ // carries names and metadata, never a byte of `data`.
61
+ const keys = Object.keys(live.data ?? {}).sort();
62
+ const metadata = {
63
+ ...(live.metadata?.annotations ?? {}),
64
+ ...(live.metadata?.labels ?? {}),
65
+ };
66
+ return { keys, metadata };
67
+ },
68
+ async create(name, keys, generate) {
69
+ const c = await clientOf();
70
+ // Mint and consume per key, at the write. `consumeSecretMaterial` burns
71
+ // each handle, so the material cannot be redeemed a second time by
72
+ // anything, this adapter included.
73
+ const data = {};
74
+ for (const key of keys) {
75
+ data[key] = Buffer.from(consumeSecretMaterial(await generate(key)), "utf-8").toString("base64");
76
+ }
77
+ const secret = {
78
+ apiVersion: SECRET_REF.apiVersion,
79
+ kind: SECRET_REF.kind,
80
+ metadata: {
81
+ name,
82
+ namespace,
83
+ labels: {
84
+ ...(options.marker
85
+ ? ownershipEntries(LABEL_OWNERSHIP_KEYS, options.marker)
86
+ : { [LABEL_OWNERSHIP_KEYS.managedBy]: OWNERSHIP_MANAGED_BY_VALUE }),
87
+ [GENERATED_ONCE_LABEL_KEY]: GENERATED_ONCE_LABEL_VALUE,
88
+ },
89
+ },
90
+ type: "Opaque",
91
+ data,
92
+ };
93
+ // Server-side apply as chant's field manager — the same write path every
94
+ // other chant mutation takes. The result (which echoes `data`) is
95
+ // deliberately discarded.
96
+ await c.apply(secret, {
97
+ ...(options.marker ? { fieldManager: `chant:${options.marker.stack}` } : {}),
98
+ });
99
+ },
100
+ };
101
+ }
102
+ /**
103
+ * The real `ensure-secret` capability for a Kubernetes namespace — core's
104
+ * factory (#1829) over this module's adapter. Replaces the starter set's
105
+ * typed stub for a project whose secret store is the cluster:
106
+ *
107
+ * ```ts
108
+ * registry.register(k8sEnsureSecretCapability("prod", undefined, {
109
+ * marker: { stack: "shop", env: "prod" },
110
+ * environment: "prod",
111
+ * }));
112
+ * ```
113
+ */
114
+ export function k8sEnsureSecretCapability(namespace, client, options = {}) {
115
+ return createEnsureSecretCapability({ store: k8sSecretStore(namespace, client, options) });
116
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"serializer.d.ts","sourceRoot":"","sources":["../src/serializer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAsC,MAAM,6BAA6B,CAAC;AAoXlG,eAAO,MAAM,aAAa,EAAE,UAyO3B,CAAC"}
1
+ {"version":3,"file":"serializer.d.ts","sourceRoot":"","sources":["../src/serializer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,KAAK,EAAE,UAAU,EAAsC,MAAM,6BAA6B,CAAC;AAoXlG,eAAO,MAAM,aAAa,EAAE,UAyO3B,CAAC"}