@homeflare/alchemy 0.47.1 → 0.48.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 (128) hide show
  1. package/dist/talos/bao-env.d.ts +7 -0
  2. package/dist/talos/bao-env.d.ts.map +1 -0
  3. package/dist/talos/cluster-adapter-errors.d.ts +134 -0
  4. package/dist/talos/cluster-adapter-errors.d.ts.map +1 -0
  5. package/dist/talos/cluster-adapter.d.ts +115 -0
  6. package/dist/talos/cluster-adapter.d.ts.map +1 -0
  7. package/dist/talos/cluster-identity.d.ts +28 -0
  8. package/dist/talos/cluster-identity.d.ts.map +1 -0
  9. package/dist/talos/cluster-transport.d.ts +64 -0
  10. package/dist/talos/cluster-transport.d.ts.map +1 -0
  11. package/dist/talos/credentials-write.d.ts.map +1 -1
  12. package/dist/talos/credentials.d.ts +10 -19
  13. package/dist/talos/credentials.d.ts.map +1 -1
  14. package/dist/talos/fake-apiserver.d.ts +9 -0
  15. package/dist/talos/fake-apiserver.d.ts.map +1 -0
  16. package/dist/talos/index.d.ts +7 -0
  17. package/dist/talos/index.d.ts.map +1 -1
  18. package/dist/talos/index.js +669 -167
  19. package/dist/talos/index.js.map +22 -10
  20. package/dist/talos/kubeconfig-attrs.d.ts +14 -0
  21. package/dist/talos/kubeconfig-attrs.d.ts.map +1 -1
  22. package/dist/talos/kubeconfig-doc.d.ts +23 -0
  23. package/dist/talos/kubeconfig-doc.d.ts.map +1 -0
  24. package/dist/talos/kubeconfig.d.ts +0 -12
  25. package/dist/talos/kubeconfig.d.ts.map +1 -1
  26. package/dist/talos/literal-connection.d.ts +16 -0
  27. package/dist/talos/literal-connection.d.ts.map +1 -0
  28. package/dist/talos/machine-config-read.d.ts +1 -1
  29. package/dist/talos/machine-config-read.d.ts.map +1 -1
  30. package/dist/talos/mint-temp-file.d.ts +20 -0
  31. package/dist/talos/mint-temp-file.d.ts.map +1 -0
  32. package/dist/talos/node-temp.harness.d.ts +2 -0
  33. package/dist/talos/node-temp.harness.d.ts.map +1 -0
  34. package/dist/talos/talos-bootstrap-etcd.d.ts +1 -1
  35. package/dist/talos/talos-bootstrap-etcd.d.ts.map +1 -1
  36. package/dist/talos/talos-cluster-health.d.ts +15 -15
  37. package/dist/talos/talos-cluster-health.d.ts.map +1 -1
  38. package/dist/talos/talos-cluster-identity.d.ts +65 -0
  39. package/dist/talos/talos-cluster-identity.d.ts.map +1 -0
  40. package/dist/talos/talosctl-binary.d.ts +43 -0
  41. package/dist/talos/talosctl-binary.d.ts.map +1 -0
  42. package/dist/talos/talosctl.d.ts +12 -15
  43. package/dist/talos/talosctl.d.ts.map +1 -1
  44. package/dist/talos/trust-boundary.d.ts +8 -0
  45. package/dist/talos/trust-boundary.d.ts.map +1 -0
  46. package/dist/talos/values.d.ts.map +1 -1
  47. package/dist/talos/yaml-parse.d.ts +3 -0
  48. package/dist/talos/yaml-parse.d.ts.map +1 -0
  49. package/dist/verify/fake-engine.d.ts +2 -0
  50. package/dist/verify/fake-engine.d.ts.map +1 -1
  51. package/docs/talos-openbao-adapter.md +44 -0
  52. package/docs/upstream-conformance-bun.md +61 -24
  53. package/package.json +6 -2
  54. package/src/talos/bao-env.ts +16 -0
  55. package/src/talos/bao-vetting.test.ts +97 -0
  56. package/src/talos/cluster-adapter-deadline.test.ts +110 -0
  57. package/src/talos/cluster-adapter-errors.ts +192 -0
  58. package/src/talos/cluster-adapter-move.test.ts +129 -0
  59. package/src/talos/cluster-adapter-reconcile.test.ts +165 -0
  60. package/src/talos/cluster-adapter-refusals.test.ts +102 -0
  61. package/src/talos/cluster-adapter-rename.test.ts +87 -0
  62. package/src/talos/cluster-adapter-secrecy.test.ts +243 -0
  63. package/src/talos/cluster-adapter.fixtures.ts +125 -0
  64. package/src/talos/cluster-adapter.test.ts +240 -0
  65. package/src/talos/cluster-adapter.ts +249 -0
  66. package/src/talos/cluster-identity.ts +63 -0
  67. package/src/talos/cluster-transport.ts +110 -0
  68. package/src/talos/credentials-write.ts +27 -5
  69. package/src/talos/credentials.ts +51 -45
  70. package/src/talos/fake-apiserver.ts +55 -0
  71. package/src/talos/fake-process.ts +47 -1
  72. package/src/talos/index.ts +32 -0
  73. package/src/talos/kubeconfig-adapter.test.ts +37 -0
  74. package/src/talos/kubeconfig-attrs.ts +12 -28
  75. package/src/talos/kubeconfig-doc.ts +74 -0
  76. package/src/talos/kubeconfig.fixtures.ts +52 -0
  77. package/src/talos/kubeconfig.test.ts +6 -54
  78. package/src/talos/kubeconfig.ts +18 -34
  79. package/src/talos/literal-connection.ts +59 -0
  80. package/src/talos/mint-temp-file.ts +56 -0
  81. package/src/talos/node-connect.harness.ts +78 -0
  82. package/src/talos/node-connect.test.ts +27 -0
  83. package/src/talos/node-temp.harness.ts +40 -0
  84. package/src/talos/node-temp.test.ts +26 -0
  85. package/src/talos/talos-cluster-health-declare.test.ts +54 -0
  86. package/src/talos/talos-cluster-health-literal.test.ts +69 -0
  87. package/src/talos/talos-cluster-health.test.ts +104 -18
  88. package/src/talos/talos-cluster-health.ts +46 -14
  89. package/src/talos/talos-cluster-identity.test.ts +96 -0
  90. package/src/talos/talos-cluster-identity.ts +130 -0
  91. package/src/talos/talosctl-binary.ts +150 -0
  92. package/src/talos/talosctl-vetting.test.ts +212 -0
  93. package/src/talos/talosctl.test.ts +110 -0
  94. package/src/talos/talosctl.ts +101 -15
  95. package/src/talos/trust-boundary.test.ts +17 -0
  96. package/src/talos/trust-boundary.ts +45 -0
  97. package/src/talos/values.ts +5 -12
  98. package/src/talos/yaml-parse.test.ts +41 -0
  99. package/src/talos/yaml-parse.ts +28 -0
  100. package/src/verify/fake-engine.ts +10 -0
  101. package/dist/talos/fake-process.d.ts +0 -56
  102. package/dist/talos/fake-process.d.ts.map +0 -1
  103. package/dist/talos/machine-config-fixtures.d.ts +0 -60
  104. package/dist/talos/machine-config-fixtures.d.ts.map +0 -1
  105. package/src/postgres/fixtures/pg-REL_18_6-acl.h.txt +0 -147
  106. package/src/postgres/fixtures/pg-REL_18_6-aclexplode-func.txt +0 -31
  107. package/src/postgres/fixtures/pg-REL_18_6-alter-default-privileges-synopsis.txt +0 -82
  108. package/src/postgres/fixtures/pg-REL_18_6-alter_role.sgml.txt +0 -44
  109. package/src/postgres/fixtures/pg-REL_18_6-create-database-synopsis.txt +0 -26
  110. package/src/postgres/fixtures/pg-REL_18_6-create_role.sgml.txt +0 -26
  111. package/src/postgres/fixtures/pg-REL_18_6-grant-synopsis.txt +0 -95
  112. package/src/postgres/fixtures/pg-REL_18_6-pg_attribute.h.txt +0 -24
  113. package/src/postgres/fixtures/pg-REL_18_6-pg_auth_members.h.txt +0 -62
  114. package/src/postgres/fixtures/pg-REL_18_6-pg_authid.h.txt +0 -70
  115. package/src/postgres/fixtures/pg-REL_18_6-pg_class.h.txt +0 -33
  116. package/src/postgres/fixtures/pg-REL_18_6-pg_database.h.txt +0 -67
  117. package/src/postgres/fixtures/pg-REL_18_6-pg_default_acl.h.txt +0 -81
  118. package/src/postgres/fixtures/pg-REL_18_6-pg_namespace.h.txt +0 -28
  119. package/src/postgres/fixtures/pg-REL_18_6-revoke-synopsis.txt +0 -124
  120. package/src/release/fixtures/openbao/checksums.txt +0 -83
  121. package/src/release/fixtures/openbao/checksums.txt.gpgsig +0 -0
  122. package/src/release/fixtures/openbao/darwin-assets.txt +0 -12
  123. package/src/release/fixtures/openbao/openbao-gpg-pub-20240618.asc +0 -109
  124. package/src/release/fixtures/victoria/darwin-arm64-assets.txt +0 -22
  125. package/src/release/fixtures/victoria/victoria-logs-darwin-arm64-v1.52.0_checksums.txt +0 -2
  126. package/src/release/fixtures/victoria/victoria-metrics-darwin-arm64-v1.151.0_checksums.txt +0 -2
  127. package/src/release/fixtures/victoria/victoria-traces-darwin-arm64-v0.10.0_checksums.txt +0 -2
  128. package/src/release/fixtures/victoria/vmutils-darwin-arm64-v1.151.0_checksums.txt +0 -8
@@ -1,40 +1,254 @@
1
- // src/talos/kubeconfig.ts
2
- import { chmodSync } from "node:fs";
3
- import { Resource } from "alchemy";
4
- import { isResolved } from "alchemy/Diff";
5
- import * as Provider from "alchemy/Provider";
6
- import * as Effect4 from "effect/Effect";
1
+ // src/talos/cluster-adapter.ts
2
+ import {
3
+ ClusterAdapter,
4
+ ClusterNotFoundError
5
+ } from "alchemy/Kubernetes/ClusterAdapter";
6
+ import * as Effect6 from "effect/Effect";
7
+ import * as Layer from "effect/Layer";
7
8
 
8
- // src/talos/credentials.ts
9
- import { closeSync, openSync, writeFileSync } from "node:fs";
9
+ // src/talos/cluster-adapter-errors.ts
10
+ import * as Data from "effect/Data";
11
+
12
+ class TalosOpenBaoUnknownCluster extends Data.TaggedError("TalosOpenBaoUnknownCluster") {
13
+ get message() {
14
+ return `talos-openbao adapter has no entry pinned to cluster uid ${this.uid} ` + `(configured uids: ${this.known.join(", ") || "none"}). Refused rather than reading ` + "another cluster's kubeconfig. If the cluster was retired, keep its entry with " + "`retired: true` and its uid until its rows are destroyed.";
15
+ }
16
+ }
17
+
18
+ class TalosOpenBaoAmbiguousUid extends Data.TaggedError("TalosOpenBaoAmbiguousUid") {
19
+ get message() {
20
+ return `talos-openbao adapter entries ${this.aliases.join(", ")} are all pinned to uid ` + `${this.uid}. Refused instead of picking one: a uid names exactly one entry.`;
21
+ }
22
+ }
23
+
24
+ class TalosOpenBaoLegacyAuth extends Data.TaggedError("TalosOpenBaoLegacyAuth") {
25
+ get message() {
26
+ return "talos-openbao connection carries a legacy `cluster` alias in its auth block. Refused " + "before any vault read or request, with no silent migration (old and new auth differ, so " + "upstream would PATCH then DELETE the same object). Remedy: edit the saved state ONCE so " + "each such row's `connection.auth` is `{ kind: 'talos-openbao', uid }` (drop `cluster`), " + "then redeploy.";
27
+ }
28
+ }
29
+
30
+ class TalosClusterMoved extends Data.TaggedError("TalosClusterMoved") {
31
+ get message() {
32
+ return `Talos.ClusterIdentity '${this.cluster}' published uid ${this.saved} but the vault key now ` + `answers as ${this.live}. Refused, not updated: an update cannot become a replace for the ` + "workloads downstream, so the new cluster would be force-applied and the old one orphaned. " + "A move is explicit: declare a NEW Talos.ClusterIdentity for the new cluster and leave this " + "one pointing at the old cluster until its workloads are destroyed.";
33
+ }
34
+ }
35
+
36
+ class TalosVaultKeyMissing extends Data.TaggedError("TalosVaultKeyMissing") {
37
+ get message() {
38
+ return `${this.mount}/${this.key}: OpenBao has no value at this key. Talos.Kubeconfig writes it ` + "once at bring-up. Connect refused instead of reading a kubeconfig from disk.";
39
+ }
40
+ }
41
+
42
+ class TalosKubeconfigUnreadable extends Data.TaggedError("TalosKubeconfigUnreadable") {
43
+ get message() {
44
+ return `${this.mount}/${this.key}: OpenBao kubeconfig does not parse for context ${this.context}. ` + "The document stayed in the vault. Connect wrote nothing to disk and nothing to state.";
45
+ }
46
+ }
47
+
48
+ class TalosOpenBaoAuthKind extends Data.TaggedError("TalosOpenBaoAuthKind") {
49
+ get message() {
50
+ return `talos-openbao adapter received auth kind '${this.kind}'. This adapter only connects ` + "`talos-openbao`.";
51
+ }
52
+ }
53
+
54
+ class TalosClusterIdentityMissing extends Data.TaggedError("TalosClusterIdentityMissing") {
55
+ get message() {
56
+ return "talos-openbao connection carries no uid, so it cannot say which physical cluster it " + "targets. Refused before any vault read or request: pin the uid " + "printed by `Talos.ClusterIdentity` as a literal in `TalosOpenBaoAdapter` and build " + "workloads from `talosOpenBaoConnection(<that literal>)`.";
57
+ }
58
+ }
59
+
60
+ class TalosClusterIdentityMismatch extends Data.TaggedError("TalosClusterIdentityMismatch") {
61
+ get message() {
62
+ return `talos-openbao cluster '${this.cluster}' answered as kube-system uid ${this.got}, but the ` + `saved connection expects ${this.expected}. Refused before any apply or delete: the name ` + "now resolves to a different physical cluster. If the original cluster is gone for good, " + "remove its rows from state deliberately.";
63
+ }
64
+ }
65
+
66
+ class TalosClusterIdentityUnreadable extends Data.TaggedError("TalosClusterIdentityUnreadable") {
67
+ get message() {
68
+ return `talos-openbao cluster '${this.cluster}': kube-system has no readable metadata.uid. ` + "Refused rather than assuming the cluster is the expected one.";
69
+ }
70
+ }
71
+
72
+ class TalosClusterIdentityTimeout extends Data.TaggedError("TalosClusterIdentityTimeout") {
73
+ get message() {
74
+ return `talos-openbao cluster '${this.cluster}': no answer to the kube-system uid read within ` + `${this.seconds}s. Refused (failed closed) rather than waiting on a silent apiserver.`;
75
+ }
76
+ }
77
+
78
+ class TalosOpenBaoConnectTimeout extends Data.TaggedError("TalosOpenBaoConnectTimeout") {
79
+ get message() {
80
+ return `talos-openbao cluster '${this.cluster}': vault read and identity check did not finish ` + `within ${this.seconds}s. Refused (failed closed); the bao child was killed.`;
81
+ }
82
+ }
83
+
84
+ class TalosUidNotLiteral extends Data.TaggedError("TalosUidNotLiteral") {
85
+ get message() {
86
+ return "talos-openbao connection uid must be a literal string taken from the adapter config, not " + "an Output (for example a Talos.ClusterIdentity attribute). An unresolved uid hides a " + "cluster change from the plan: upstream would plan an UPDATE and reconcile the old cluster's " + "objects on the new one. Pin the uid in `TalosOpenBaoAdapter` and take it from there.";
87
+ }
88
+ }
89
+
90
+ // src/talos/cluster-identity.ts
91
+ import { readObject } from "alchemy/Kubernetes/internal/client";
92
+ import * as Duration from "effect/Duration";
10
93
  import * as Effect from "effect/Effect";
94
+ var KUBE_SYSTEM = { apiVersion: "v1", kind: "Namespace", name: "kube-system" };
95
+ var UID_READ_TIMEOUT = Duration.seconds(5);
96
+ var readClusterUid = (cluster, transport, timeout = UID_READ_TIMEOUT) => readObject({ object: KUBE_SYSTEM, transport }).pipe(Effect.timeoutOrElse({
97
+ duration: timeout,
98
+ orElse: () => Effect.fail(new TalosClusterIdentityTimeout({ cluster, seconds: Duration.toSeconds(timeout) }))
99
+ }), Effect.flatMap((body) => {
100
+ const uid = body?.metadata?.uid;
101
+ return typeof uid === "string" && uid !== "" ? Effect.succeed(uid) : Effect.fail(new TalosClusterIdentityUnreadable({ cluster }));
102
+ }));
103
+ var assertClusterUid = (cluster, expected, transport) => readClusterUid(cluster, transport).pipe(Effect.flatMap((got) => got === expected ? Effect.void : Effect.fail(new TalosClusterIdentityMismatch({ cluster, expected, got }))));
104
+
105
+ // src/talos/cluster-transport.ts
106
+ import * as Duration3 from "effect/Duration";
107
+ import * as Effect5 from "effect/Effect";
108
+
109
+ // src/talos/credentials.ts
110
+ import * as Duration2 from "effect/Duration";
111
+ import * as Effect4 from "effect/Effect";
11
112
  import * as Stream from "effect/Stream";
12
113
  import * as ChildProcess from "effect/process/ChildProcess";
13
114
  import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
14
- var DEFAULT_TALOSCONFIG_KEY = "talosconfig";
15
- var DEFAULT_KUBECONFIG_KEY = "kubeconfig";
16
- var readKvValue = (mount, key, fields) => Effect.gen(function* () {
17
- const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
18
- const result = yield* ChildProcess.make("bao", ["kv", "get", "-format=json", `${mount}/${key}`], { detached: false, extendEnv: true, stderr: "pipe", stdin: "ignore", stdout: "pipe" }).pipe(spawner.spawn, Effect.flatMap((child) => Effect.all({
19
- exitCode: child.exitCode,
20
- stderr: child.stderr.pipe(Stream.decodeText, Stream.mkString, Effect.map((text) => text.trim())),
21
- stdout: child.stdout.pipe(Stream.decodeText, Stream.mkString, Effect.map((text) => text.trim()))
22
- }, { concurrency: "unbounded" })), Effect.scoped);
23
- if (result.exitCode !== 0) {
24
- return yield* Effect.fail(new Error(`bao kv get ${mount}/${key} exited ${String(result.exitCode)}: ${result.stderr}`));
115
+
116
+ // src/talos/bao-env.ts
117
+ var baoEnv = () => {
118
+ const addr = process.env["BAO_ADDR"];
119
+ const token = process.env["BAO_TOKEN"];
120
+ if (addr === undefined || addr === "" || token === undefined || token === "")
121
+ return;
122
+ const env = { BAO_ADDR: addr, BAO_TOKEN: token };
123
+ for (const name of ["PATH", "BAO_NAMESPACE", "BAO_CACERT"]) {
124
+ const value = process.env[name];
125
+ if (value !== undefined && value !== "")
126
+ env[name] = value;
25
127
  }
26
- const parsed = JSON.parse(result.stdout);
27
- const data = parsed.data?.data;
28
- for (const field of fields) {
29
- const raw = data?.[field];
30
- if (typeof raw === "string" && raw.trim() !== "")
31
- return raw;
128
+ return env;
129
+ };
130
+
131
+ // src/talos/talosctl-binary.ts
132
+ import { accessSync, constants, lstatSync, realpathSync as realpathSync2 } from "node:fs";
133
+ import { basename, delimiter, dirname, isAbsolute, join } from "node:path";
134
+ import * as Effect2 from "effect/Effect";
135
+
136
+ // src/talos/trust-boundary.ts
137
+ import { realpathSync } from "node:fs";
138
+ var boundaries = new Set;
139
+ var isTrustedBoundary = (dir) => {
140
+ try {
141
+ return boundaries.has(realpathSync(dir));
142
+ } catch {
143
+ return false;
144
+ }
145
+ };
146
+ var seamSpecifier = "./trust-boundary" + ".seam.ts";
147
+ import(seamSpecifier).then((seam) => {
148
+ boundaries = seam.boundaries;
149
+ }, () => {});
150
+
151
+ // src/talos/talosctl-binary.ts
152
+ var DEFAULT_TALOSCTL_BINARY = "talosctl";
153
+ var TALOSCTL_BINARY_ENV = "HF_TALOSCTL";
154
+ var DEFAULT_BAO_BINARY = "bao";
155
+ var BAO_BINARY_ENV = "HF_BAO";
156
+
157
+ class TalosBinaryRefused extends Error {
158
+ command;
159
+ binary;
160
+ constructor(command, detail, binary = DEFAULT_TALOSCTL_BINARY) {
161
+ super(`${binary} ${command} refused: ${detail}`);
162
+ this.name = "TalosBinaryRefused";
163
+ this.command = command;
164
+ this.binary = binary;
32
165
  }
33
- return yield* Effect.fail(new Error(`${mount}/${key} returned no ${fields.join("/")} field. Never store the value itself in ` + "an Alchemy prop or attribute — fix the KV entry, not the caller."));
166
+ }
167
+ var resolveDefault = (binary) => Effect2.try({
168
+ try: () => {
169
+ for (const entry of (process.env["PATH"] ?? "").split(delimiter)) {
170
+ if (!isAbsolute(entry))
171
+ continue;
172
+ try {
173
+ const candidate = join(entry, binary);
174
+ accessSync(candidate, constants.X_OK);
175
+ return realpathSync2(candidate);
176
+ } catch {}
177
+ }
178
+ throw new Error("not found");
179
+ },
180
+ catch: () => new TalosBinaryRefused("binary check", `${binary} was not found on PATH`, binary)
34
181
  });
35
- var mintKvTempFile = (raw, label) => Effect.gen(function* () {
36
- const path = `${Bun.env["TMPDIR"] ?? "/tmp"}/hf-talos-${label}-${Bun.randomUUIDv7()}.yaml`;
37
- yield* Effect.acquireRelease(Effect.try({
182
+ var untrustedAncestor = (file, uid) => {
183
+ let dir = realpathSync2(dirname(file));
184
+ for (;; ) {
185
+ const stat = lstatSync(dir);
186
+ const isBoundary = isTrustedBoundary(dir);
187
+ if (uid !== undefined && stat.uid !== uid && stat.uid !== 0) {
188
+ return `lives under a directory owned by another user (${dir})`;
189
+ }
190
+ const sticky = (stat.mode & 512) !== 0;
191
+ if ((stat.mode & 18) !== 0 && !sticky) {
192
+ return `lives in a group- or world-writable directory (${dir})`;
193
+ }
194
+ const up = dirname(dir);
195
+ if (up === dir || isBoundary)
196
+ return;
197
+ dir = up;
198
+ }
199
+ };
200
+ var checkBinaryPath = (binary, source) => {
201
+ const refuse = (why) => Effect2.fail(new TalosBinaryRefused("binary check", `${source} ${why}`, binary));
202
+ if (!isAbsolute(binary))
203
+ return refuse("must be an absolute path");
204
+ return Effect2.try({
205
+ try: () => {
206
+ const canonical = join(realpathSync2(dirname(binary)), basename(binary));
207
+ const file = lstatSync(canonical);
208
+ const uid = process.getuid?.();
209
+ return { canonical, file, uid, ancestor: untrustedAncestor(canonical, uid) };
210
+ },
211
+ catch: () => new TalosBinaryRefused("binary check", `${source} is not a readable file`, binary)
212
+ }).pipe(Effect2.flatMap(({ canonical, file, uid, ancestor }) => {
213
+ if (file.isSymbolicLink())
214
+ return refuse("must not be a symlink");
215
+ if (!file.isFile())
216
+ return refuse("is not a regular file");
217
+ if (uid === undefined)
218
+ return refuse("cannot be vetted: no process uid to check ownership");
219
+ if (ancestor !== undefined)
220
+ return refuse(ancestor);
221
+ if (uid !== undefined && file.uid !== uid && file.uid !== 0) {
222
+ return refuse("must be owned by the current user or root");
223
+ }
224
+ if ((file.mode & 18) !== 0)
225
+ return refuse("is group- or world-writable");
226
+ try {
227
+ accessSync(canonical, constants.X_OK);
228
+ } catch {
229
+ return refuse("is not executable");
230
+ }
231
+ return Effect2.succeed(canonical);
232
+ }));
233
+ };
234
+ var resolveBao = () => {
235
+ const override = process.env[BAO_BINARY_ENV]?.trim();
236
+ const requested = override !== undefined && override !== "" ? override : DEFAULT_BAO_BINARY;
237
+ const source = requested === DEFAULT_BAO_BINARY ? "bao on PATH" : BAO_BINARY_ENV;
238
+ const resolved = requested === DEFAULT_BAO_BINARY ? resolveDefault(requested) : Effect2.succeed(requested);
239
+ return Effect2.flatMap(resolved, (path) => checkBinaryPath(path, source));
240
+ };
241
+
242
+ // src/talos/mint-temp-file.ts
243
+ import { closeSync, openSync, writeFileSync } from "node:fs";
244
+ import { unlink } from "node:fs/promises";
245
+ import { randomUUID } from "node:crypto";
246
+ import { tmpdir } from "node:os";
247
+ import { join as join2 } from "node:path";
248
+ import * as Effect3 from "effect/Effect";
249
+ var mintKvTempFile = (raw, label) => Effect3.gen(function* () {
250
+ const path = join2(tmpdir(), `hf-talos-${label}-${randomUUID()}.yaml`);
251
+ yield* Effect3.acquireRelease(Effect3.try({
38
252
  try: () => {
39
253
  const fd = openSync(path, "wx", 384);
40
254
  try {
@@ -44,46 +258,226 @@ var mintKvTempFile = (raw, label) => Effect.gen(function* () {
44
258
  }
45
259
  },
46
260
  catch: (cause) => new Error(`writing ${label} temp file: ${String(cause)}`)
47
- }), () => Effect.tryPromise({ try: () => Bun.file(path).delete(), catch: () => {
261
+ }), () => Effect3.tryPromise({ try: () => unlink(path), catch: () => {
48
262
  return;
49
- } }).pipe(Effect.orElseSucceed(() => {
263
+ } }).pipe(Effect3.orElseSucceed(() => {
50
264
  return;
51
265
  })));
52
266
  return { path };
53
267
  });
268
+
269
+ // src/talos/credentials.ts
270
+ var DEFAULT_TALOSCONFIG_KEY = "talosconfig";
271
+ var DEFAULT_KUBECONFIG_KEY = "kubeconfig";
272
+ var readKvValue = (mount, key, fields) => Effect4.gen(function* () {
273
+ const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
274
+ const bao = yield* resolveBao();
275
+ const env = baoEnv();
276
+ if (env === undefined) {
277
+ return yield* Effect4.fail(new Error(`bao kv get ${mount}/${key} refused: BAO_ADDR and BAO_TOKEN must both be set. ` + "Without them bao would fall back to a cached login in the operator's home."));
278
+ }
279
+ const result = yield* ChildProcess.make(bao, ["kv", "get", "-format=json", `${mount}/${key}`], {
280
+ env,
281
+ extendEnv: false,
282
+ forceKillAfter: Duration2.seconds(1),
283
+ stderr: "pipe",
284
+ stdin: "ignore",
285
+ stdout: "pipe"
286
+ }).pipe(spawner.spawn, Effect4.flatMap((child) => Effect4.all({
287
+ exitCode: child.exitCode,
288
+ stderr: child.stderr.pipe(Stream.decodeText, Stream.mkString, Effect4.map((text) => text.trim())),
289
+ stdout: child.stdout.pipe(Stream.decodeText, Stream.mkString, Effect4.map((text) => text.trim()))
290
+ }, { concurrency: "unbounded" })), Effect4.scoped);
291
+ if (result.exitCode !== 0) {
292
+ return yield* Effect4.fail(new Error(`bao kv get ${mount}/${key} exited ${String(result.exitCode)}: ${result.stderr}`));
293
+ }
294
+ let parsed;
295
+ try {
296
+ parsed = JSON.parse(result.stdout);
297
+ } catch {
298
+ return yield* Effect4.fail(new Error(`bao kv get ${mount}/${key} returned output that is not JSON (not echoed).`));
299
+ }
300
+ const data = parsed.data?.data;
301
+ for (const field of fields) {
302
+ const raw = data?.[field];
303
+ if (typeof raw === "string" && raw.trim() !== "")
304
+ return raw;
305
+ }
306
+ return yield* Effect4.fail(new Error(`${mount}/${key} returned no ${fields.join("/")} field. Never store the value itself in ` + "an Alchemy prop or attribute — fix the KV entry, not the caller."));
307
+ });
308
+ var isVaultKeyAbsent = (error) => error instanceof Error && /no value found at/i.test(error.message);
54
309
  var mintTalosconfig = (target) => readKvValue(target.mount, target.talosconfigKey ?? DEFAULT_TALOSCONFIG_KEY, [
55
310
  "talosconfig",
56
311
  "config"
57
- ]).pipe(Effect.flatMap((raw) => mintKvTempFile(raw, `${target.cluster}-talosconfig`)), Effect.map(({ path }) => ({ talosconfigPath: path })));
312
+ ]).pipe(Effect4.flatMap((raw) => mintKvTempFile(raw, `${target.cluster}-talosconfig`)), Effect4.map(({ path }) => ({ talosconfigPath: path })));
313
+
314
+ // src/talos/yaml-parse.ts
315
+ import { YAMLParseError, parse } from "yaml";
316
+ var parseYaml = (text) => {
317
+ try {
318
+ return parse(text, { logLevel: "error", prettyErrors: false });
319
+ } catch (error) {
320
+ if (error instanceof YAMLParseError)
321
+ return;
322
+ throw error;
323
+ }
324
+ };
325
+
326
+ // src/talos/kubeconfig-doc.ts
327
+ var pem = (data) => {
328
+ if (data === undefined || data.trim() === "")
329
+ return;
330
+ const decoded = Buffer.from(data, "base64").toString("utf8");
331
+ return decoded.includes("BEGIN ") ? decoded : undefined;
332
+ };
333
+ var kubeconfigTransport = (yamlText, contextName) => {
334
+ const parsed = parseYaml(yamlText);
335
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
336
+ return;
337
+ const doc = parsed;
338
+ const ctx = doc.contexts?.find((row) => row.name === contextName)?.context;
339
+ const clusterName = ctx?.cluster;
340
+ const userName = ctx?.user;
341
+ if (clusterName === undefined || userName === undefined)
342
+ return;
343
+ const cluster = doc.clusters?.find((row) => row.name === clusterName)?.cluster;
344
+ const user = doc.users?.find((row) => row.name === userName)?.user;
345
+ const endpoint = cluster?.server;
346
+ const certificateAuthorityData = cluster?.["certificate-authority-data"];
347
+ const certificate = pem(user?.["client-certificate-data"]);
348
+ const key = pem(user?.["client-key-data"]);
349
+ if (endpoint === undefined || certificateAuthorityData === undefined || pem(certificateAuthorityData) === undefined || certificate === undefined || key === undefined) {
350
+ return;
351
+ }
352
+ return { certificateAuthorityData, clientCert: { certificate, key }, endpoint };
353
+ };
354
+
355
+ // src/talos/cluster-transport.ts
356
+ var talosVaultLocation = (props) => ({
357
+ context: props.context,
358
+ key: props.kubeconfigKey ?? DEFAULT_KUBECONFIG_KEY,
359
+ mount: props.target.mount
360
+ });
361
+ var talosOpenBaoCluster = (props, pin) => ({
362
+ ...talosVaultLocation(props),
363
+ uid: pin.uid,
364
+ ...pin.retired === true ? { retired: true } : {}
365
+ });
366
+ var CONNECT_TIMEOUT = Duration3.seconds(10);
367
+ var withConnectDeadline = (cluster, effect, timeout = CONNECT_TIMEOUT) => effect.pipe(Effect5.timeoutOrElse({
368
+ duration: timeout,
369
+ orElse: () => Effect5.fail(new TalosOpenBaoConnectTimeout({ cluster, seconds: Duration3.toSeconds(timeout) }))
370
+ }));
371
+ var openTransport = (config) => Effect5.gen(function* () {
372
+ const { context, key, mount } = config;
373
+ const raw = yield* readKvValue(mount, key, ["kubeconfig", "config"]).pipe(Effect5.mapError((error) => isVaultKeyAbsent(error) ? new TalosVaultKeyMissing({ key, mount }) : error));
374
+ const material = yield* Effect5.sync(() => kubeconfigTransport(raw, context));
375
+ if (material === undefined) {
376
+ return yield* Effect5.fail(new TalosKubeconfigUnreadable({ context, key, mount }));
377
+ }
378
+ return { ...material, headers: Effect5.succeed({}) };
379
+ });
380
+
381
+ // src/talos/cluster-adapter.ts
382
+ var talosOpenBaoConnection = (uid) => {
383
+ if (typeof uid !== "string" || uid === "")
384
+ throw new TalosUidNotLiteral({});
385
+ return { auth: { kind: "talos-openbao", uid } };
386
+ };
387
+ var connectTalosOpenBao = (configs, connection, timeout = CONNECT_TIMEOUT) => Effect6.gen(function* () {
388
+ if (connection === undefined || connection === null) {
389
+ return yield* Effect6.fail(new TalosClusterIdentityMissing({}));
390
+ }
391
+ const { auth } = connection;
392
+ if (auth === undefined || auth === null) {
393
+ return yield* Effect6.fail(new TalosClusterIdentityMissing({}));
394
+ }
395
+ if (auth.kind !== "talos-openbao") {
396
+ return yield* Effect6.fail(new TalosOpenBaoAuthKind({ kind: auth.kind }));
397
+ }
398
+ if (Object.hasOwn(auth, "cluster")) {
399
+ return yield* Effect6.fail(new TalosOpenBaoLegacyAuth({}));
400
+ }
401
+ const { uid } = auth;
402
+ if (typeof uid !== "string" || uid === "") {
403
+ return yield* Effect6.fail(new TalosClusterIdentityMissing({}));
404
+ }
405
+ const matches = Object.entries(configs).filter(([, entry]) => entry.uid === uid);
406
+ const [match] = matches;
407
+ if (match === undefined) {
408
+ return yield* Effect6.fail(new TalosOpenBaoUnknownCluster({
409
+ known: Object.values(configs).map((entry) => entry.uid),
410
+ uid
411
+ }));
412
+ }
413
+ if (matches.length > 1) {
414
+ return yield* Effect6.fail(new TalosOpenBaoAmbiguousUid({ aliases: matches.map(([alias2]) => alias2), uid }));
415
+ }
416
+ const [alias, config] = match;
417
+ if (config.retired === true) {
418
+ return yield* Effect6.fail(new ClusterNotFoundError({ message: `talos-openbao cluster '${alias}' is retired` }));
419
+ }
420
+ return yield* withConnectDeadline(alias, Effect6.gen(function* () {
421
+ const transport = yield* openTransport(config);
422
+ yield* assertClusterUid(alias, uid, transport);
423
+ return transport;
424
+ }), timeout);
425
+ });
426
+ var TalosOpenBaoAdapter = (config) => Layer.effect(ClusterAdapter("talos-openbao"), Effect6.gen(function* () {
427
+ const context2 = yield* Effect6.context();
428
+ const service = {
429
+ kind: "Kubernetes.ClusterAdapter",
430
+ connect: (connection) => connectTalosOpenBao(config, connection).pipe(Effect6.provideContext(context2))
431
+ };
432
+ return service;
433
+ }));
434
+ // src/talos/kubeconfig.ts
435
+ import { readFile } from "node:fs/promises";
436
+ import { chmodSync } from "node:fs";
437
+ import { Resource } from "alchemy";
438
+ import { isResolved } from "alchemy/Diff";
439
+ import * as Provider from "alchemy/Provider";
440
+ import * as Effect9 from "effect/Effect";
58
441
 
59
442
  // src/talos/credentials-write.ts
60
- import * as Effect2 from "effect/Effect";
443
+ import * as Effect7 from "effect/Effect";
444
+ import * as Duration4 from "effect/Duration";
61
445
  import * as Stream2 from "effect/Stream";
446
+ import { unlink as unlink2 } from "node:fs/promises";
447
+ import { randomUUID as randomUUID2 } from "node:crypto";
448
+ import { tmpdir as tmpdir2 } from "node:os";
449
+ import { join as join3 } from "node:path";
62
450
  import * as ChildProcess2 from "effect/process/ChildProcess";
63
451
  import * as ChildProcessSpawner3 from "effect/process/ChildProcessSpawner";
64
- var reservedTempPath = (label) => Effect2.gen(function* () {
65
- const path = `${Bun.env["TMPDIR"] ?? "/tmp"}/hf-talos-${label}-${Bun.randomUUIDv7()}.yaml`;
66
- yield* Effect2.addFinalizer(() => Effect2.tryPromise({ try: () => Bun.file(path).delete(), catch: () => {
452
+ var reservedTempPath = (label) => Effect7.gen(function* () {
453
+ const path = join3(tmpdir2(), `hf-talos-${label}-${randomUUID2()}.yaml`);
454
+ yield* Effect7.addFinalizer(() => Effect7.tryPromise({ try: () => unlink2(path), catch: () => {
67
455
  return;
68
- } }).pipe(Effect2.orElseSucceed(() => {
456
+ } }).pipe(Effect7.orElseSucceed(() => {
69
457
  return;
70
458
  })));
71
459
  return { path };
72
460
  });
73
- var writeKvValue = (mount, key, field, value) => Effect2.gen(function* () {
461
+ var writeKvValue = (mount, key, field, value) => Effect7.gen(function* () {
74
462
  const spawner = yield* ChildProcessSpawner3.ChildProcessSpawner;
75
- const result = yield* ChildProcess2.make("bao", ["kv", "put", `${mount}/${key}`, `${field}=-`], {
76
- detached: false,
77
- extendEnv: true,
463
+ const bao = yield* resolveBao();
464
+ const env = baoEnv();
465
+ if (env === undefined) {
466
+ return yield* Effect7.fail(new Error(`bao kv put ${mount}/${key} refused: BAO_ADDR and BAO_TOKEN must both be set. ` + "Without them bao would fall back to a cached login in the operator's home."));
467
+ }
468
+ const result = yield* ChildProcess2.make(bao, ["kv", "put", `${mount}/${key}`, `${field}=-`], {
469
+ env,
470
+ extendEnv: false,
471
+ forceKillAfter: Duration4.seconds(1),
78
472
  stderr: "pipe",
79
473
  stdin: Stream2.fromIterable([new TextEncoder().encode(value)]),
80
474
  stdout: "pipe"
81
- }).pipe(spawner.spawn, Effect2.flatMap((child) => Effect2.all({
475
+ }).pipe(spawner.spawn, Effect7.flatMap((child) => Effect7.all({
82
476
  exitCode: child.exitCode,
83
- stderr: child.stderr.pipe(Stream2.decodeText, Stream2.mkString, Effect2.map((text) => text.trim()))
84
- }, { concurrency: "unbounded" })), Effect2.scoped);
477
+ stderr: child.stderr.pipe(Stream2.decodeText, Stream2.mkString, Effect7.map((text) => text.trim()))
478
+ }, { concurrency: "unbounded" })), Effect7.scoped);
85
479
  if (result.exitCode !== 0) {
86
- return yield* Effect2.fail(new Error(`bao kv put ${mount}/${key} exited ${String(result.exitCode)}: ${result.stderr}`));
480
+ return yield* Effect7.fail(new Error(`bao kv put ${mount}/${key} exited ${String(result.exitCode)}: ${result.stderr}`));
87
481
  }
88
482
  });
89
483
 
@@ -93,12 +487,7 @@ var canonicalText = (text) => text.trimEnd();
93
487
  var sha256 = (text) => createHash("sha256").update(canonicalText(text)).digest("hex");
94
488
  var configDigest = (text) => sha256(text);
95
489
  var extractMachineConfigSpec = (wrapperYaml) => {
96
- let doc;
97
- try {
98
- doc = Bun.YAML.parse(wrapperYaml);
99
- } catch {
100
- return;
101
- }
490
+ let doc = parseYaml(wrapperYaml);
102
491
  if (Array.isArray(doc)) {
103
492
  if (doc.length !== 1)
104
493
  return;
@@ -110,12 +499,10 @@ var extractMachineConfigSpec = (wrapperYaml) => {
110
499
  return typeof spec === "string" ? spec : undefined;
111
500
  };
112
501
  var kubeconfigMetadata = (yamlText, contextName) => {
113
- let doc;
114
- try {
115
- doc = Bun.YAML.parse(yamlText);
116
- } catch {
502
+ const parsed = parseYaml(yamlText);
503
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
117
504
  return;
118
- }
505
+ const doc = parsed;
119
506
  const ctx = doc.contexts?.find((row) => row.name === contextName)?.context;
120
507
  const clusterName = ctx?.cluster;
121
508
  const userName = ctx?.user;
@@ -140,10 +527,6 @@ var generation = (meta) => sha256(`${meta.context}
140
527
  ${meta.endpoint}
141
528
  ${meta.caFingerprint}
142
529
  ${meta.clientFingerprint}`);
143
- var UNWIRED_KUBECONFIG_PATH = "/talos-first-boot-unwired/wire-Kubernetes.ClusterAdapter-to-mintKubeconfig";
144
- var toConnection = (props) => ({
145
- auth: { context: props.context, kind: "kubeconfig", path: UNWIRED_KUBECONFIG_PATH }
146
- });
147
530
  var buildAttrs = (raw, props) => {
148
531
  const meta = kubeconfigMetadata(raw, props.context);
149
532
  if (meta === undefined)
@@ -151,7 +534,6 @@ var buildAttrs = (raw, props) => {
151
534
  return {
152
535
  certificateAuthorityFingerprint: meta.caFingerprint,
153
536
  clientCertificateFingerprint: meta.clientFingerprint,
154
- connection: toConnection(props),
155
537
  context: props.context,
156
538
  credentialGeneration: generation({ ...meta, context: props.context }),
157
539
  endpoint: meta.endpoint
@@ -159,23 +541,64 @@ var buildAttrs = (raw, props) => {
159
541
  };
160
542
 
161
543
  // src/talos/talosctl.ts
162
- import * as Effect3 from "effect/Effect";
544
+ import * as Duration5 from "effect/Duration";
545
+ import * as Effect8 from "effect/Effect";
163
546
  import * as Stream3 from "effect/Stream";
164
547
  import * as ChildProcess3 from "effect/process/ChildProcess";
165
548
  import * as ChildProcessSpawner5 from "effect/process/ChildProcessSpawner";
166
-
167
549
  class TalosError extends Error {
168
550
  command;
169
551
  exitCode;
170
- constructor(command, exitCode, detail) {
171
- super(`talosctl ${command} -> ${String(exitCode)}: ${detail}`);
552
+ constructor(command, exitCode, detail, binary = DEFAULT_TALOSCTL_BINARY) {
553
+ super(`${binary} ${command} -> ${String(exitCode)}: ${detail}`);
172
554
  this.command = command;
173
555
  this.exitCode = exitCode;
174
556
  this.name = "TalosError";
175
557
  }
176
558
  }
177
- var talosctl = (args, options) => Effect3.gen(function* () {
559
+ var TALOSCTL_PINNED_VERSION = "v1.14.2";
560
+ var talosctlBinary = (override) => {
561
+ const fromOption = override?.trim();
562
+ if (fromOption !== undefined && fromOption !== "")
563
+ return fromOption;
564
+ const fromEnv = process.env[TALOSCTL_BINARY_ENV]?.trim();
565
+ if (fromEnv !== undefined && fromEnv !== "")
566
+ return fromEnv;
567
+ return DEFAULT_TALOSCTL_BINARY;
568
+ };
569
+ var talosctlEnv = () => {
570
+ const env = {};
571
+ for (const name of ["PATH", "HOME", "TMPDIR"]) {
572
+ const value = process.env[name];
573
+ if (value !== undefined && value !== "")
574
+ env[name] = value;
575
+ }
576
+ return env;
577
+ };
578
+ var capture = (binary, argv) => Effect8.gen(function* () {
178
579
  const spawner = yield* ChildProcessSpawner5.ChildProcessSpawner;
580
+ return yield* ChildProcess3.make(binary, argv, {
581
+ env: talosctlEnv(),
582
+ extendEnv: false,
583
+ forceKillAfter: Duration5.seconds(1),
584
+ stderr: "pipe",
585
+ stdin: "ignore",
586
+ stdout: "pipe"
587
+ }).pipe(spawner.spawn, Effect8.flatMap((child) => Effect8.all({
588
+ exitCode: child.exitCode,
589
+ stderr: child.stderr.pipe(Stream3.decodeText, Stream3.mkString, Effect8.map((text) => text.trim())),
590
+ stdout: child.stdout.pipe(Stream3.decodeText, Stream3.mkString, Effect8.map((text) => text.trim()))
591
+ }, { concurrency: "unbounded" })), Effect8.scoped);
592
+ });
593
+ var reportsPinnedVersion = (stdout) => stdout.split(/[^\w.+-]+/).includes(TALOSCTL_PINNED_VERSION);
594
+ var checkOverrideVersion = (binary, source) => capture(binary, ["version", "--client"]).pipe(Effect8.flatMap((out) => out.exitCode === 0 && reportsPinnedVersion(out.stdout) ? Effect8.void : Effect8.fail(new TalosBinaryRefused("version --client", `${source} is not talosctl ${TALOSCTL_PINNED_VERSION} (exit ${String(out.exitCode)})`, binary))));
595
+ var talosctl = (args, options) => Effect8.gen(function* () {
596
+ const requested = talosctlBinary(options.binary);
597
+ const resolved = requested === DEFAULT_TALOSCTL_BINARY ? yield* resolveDefault(requested) : requested;
598
+ const fromOption = options.binary?.trim();
599
+ const source = requested === DEFAULT_TALOSCTL_BINARY ? "talosctl on PATH" : fromOption !== undefined && fromOption !== "" ? "the `binary` option" : TALOSCTL_BINARY_ENV;
600
+ const binary = yield* checkBinaryPath(resolved, source);
601
+ yield* checkOverrideVersion(binary, source);
179
602
  const argv = [
180
603
  ...args,
181
604
  "--talosconfig",
@@ -184,119 +607,108 @@ var talosctl = (args, options) => Effect3.gen(function* () {
184
607
  ...options.endpoints === undefined ? [] : ["--endpoints", options.endpoints.join(",")],
185
608
  ...options.insecure === true ? ["--insecure"] : []
186
609
  ];
187
- const result = yield* ChildProcess3.make("talosctl", argv, {
188
- detached: false,
189
- extendEnv: true,
190
- stderr: "pipe",
191
- stdin: "ignore",
192
- stdout: "pipe"
193
- }).pipe(spawner.spawn, Effect3.flatMap((child) => Effect3.all({
194
- exitCode: child.exitCode,
195
- stderr: child.stderr.pipe(Stream3.decodeText, Stream3.mkString, Effect3.map((text) => text.trim())),
196
- stdout: child.stdout.pipe(Stream3.decodeText, Stream3.mkString, Effect3.map((text) => text.trim()))
197
- }, { concurrency: "unbounded" })), Effect3.scoped);
610
+ const result = yield* capture(binary, argv);
198
611
  const command = args.join(" ");
199
612
  if (result.exitCode !== 0) {
200
- return yield* Effect3.fail(new TalosError(command, result.exitCode, result.stderr.slice(0, 300)));
613
+ return yield* Effect8.fail(new TalosError(command, result.exitCode, result.stderr.slice(0, 300), binary));
201
614
  }
202
615
  return result.stdout;
203
616
  });
204
- var talosctlOrAlready = (args, options) => talosctl(args, options).pipe(Effect3.catchIf((cause) => cause instanceof TalosError && cause.exitCode !== 0 && /already|exists|bootstrapped/i.test(cause.message), () => Effect3.succeed("")));
617
+ var talosctlOrAlready = (args, options) => talosctl(args, options).pipe(Effect8.catchIf((cause) => cause instanceof TalosError && cause.exitCode !== 0 && /already|exists|bootstrapped/i.test(cause.message), () => Effect8.succeed("")));
205
618
 
206
619
  // src/talos/kubeconfig.ts
207
620
  var TalosKubeconfig = Resource("Talos.Kubeconfig");
208
- var isVaultKeyAbsent = (error) => error instanceof Error && /no value found at/i.test(error.message);
209
- var readVaultMeta = (props, key) => readKvValue(props.target.mount, key, ["kubeconfig", "config"]).pipe(Effect4.catchIf(isVaultKeyAbsent, () => Effect4.succeed(undefined)));
210
- var readKubeconfig = (props) => Effect4.gen(function* () {
621
+ var readVaultMeta = (props, key) => readKvValue(props.target.mount, key, ["kubeconfig", "config"]).pipe(Effect9.catchIf(isVaultKeyAbsent, () => Effect9.succeed(undefined)));
622
+ var readKubeconfig = (props) => Effect9.gen(function* () {
211
623
  const key = props.kubeconfigKey ?? DEFAULT_KUBECONFIG_KEY;
212
624
  const raw = yield* readVaultMeta(props, key);
213
625
  if (raw === undefined)
214
626
  return;
215
627
  const meta = buildAttrs(raw, props);
216
628
  if (meta === undefined) {
217
- return yield* Effect4.fail(new Error(`${props.target.mount}/${key}: vault content exists but does not parse as a kubeconfig ` + `for context ${props.context} — not a cold start. Fix the KV entry or the pinned ` + "context rather than trusting an empty read."));
629
+ return yield* Effect9.fail(new Error(`${props.target.mount}/${key}: vault content exists but does not parse as a kubeconfig ` + `for context ${props.context} — not a cold start. Fix the KV entry or the pinned ` + "context rather than trusting an empty read."));
218
630
  }
219
631
  return meta;
220
632
  });
221
- var diffKubeconfig = (news, output) => Effect4.gen(function* () {
633
+ var diffKubeconfig = (news, output) => Effect9.gen(function* () {
222
634
  if (output === undefined || !isResolved(news))
223
635
  return;
224
636
  const live = yield* readKubeconfig(news);
225
- if (live !== undefined && live.credentialGeneration === output.credentialGeneration && live.context === output.context && live.endpoint === output.endpoint) {
637
+ if (live !== undefined && live.credentialGeneration === output.credentialGeneration && live.context === output.context && !("connection" in output) && live.endpoint === output.endpoint) {
226
638
  return { action: "noop" };
227
639
  }
228
640
  return { action: "update" };
229
641
  });
230
- var reconcileKubeconfig = (props, output) => Effect4.scoped(Effect4.gen(function* () {
642
+ var reconcileKubeconfig = (props, output) => Effect9.scoped(Effect9.gen(function* () {
231
643
  const key = props.kubeconfigKey ?? DEFAULT_KUBECONFIG_KEY;
232
644
  if (output !== undefined && output.credentialGeneration !== "") {
233
645
  const raw2 = yield* readKvValue(props.target.mount, key, ["kubeconfig", "config"]);
234
646
  const meta2 = buildAttrs(raw2, props);
235
647
  if (meta2 === undefined) {
236
- return yield* Effect4.die(new Error(`${props.target.mount}/${key}: vault content no longer parses as a kubeconfig for ` + `context ${props.context}. Read back rather than trusting the write once.`));
648
+ return yield* Effect9.die(new Error(`${props.target.mount}/${key}: vault content no longer parses as a kubeconfig for ` + `context ${props.context}. Read back rather than trusting the write once.`));
237
649
  }
238
650
  return meta2;
239
651
  }
240
652
  const credential = yield* mintTalosconfig(props.target);
241
653
  const { path: outPath } = yield* reservedTempPath(`${props.target.cluster}-${props.node}-kubeconfig-out`);
242
654
  yield* talosctl(["kubeconfig", outPath, "--merge=false", "--force", "--force-context-name", props.context], { nodes: [props.node], talosconfigPath: credential.talosconfigPath });
243
- const raw = yield* Effect4.tryPromise({
655
+ const raw = yield* Effect9.tryPromise({
244
656
  catch: (cause) => new Error(`${outPath}: reading generated kubeconfig: ${String(cause)}`),
245
- try: () => Bun.file(outPath).text()
657
+ try: () => readFile(outPath, "utf8")
246
658
  });
247
659
  if (raw.trim() === "") {
248
- return yield* Effect4.die(new Error(`${props.node}: talosctl kubeconfig returned no error but wrote an empty file. Read ` + "back rather than trusting the exit code alone."));
660
+ return yield* Effect9.die(new Error(`${props.node}: talosctl kubeconfig returned no error but wrote an empty file. Read ` + "back rather than trusting the exit code alone."));
249
661
  }
250
- yield* Effect4.try({
662
+ yield* Effect9.try({
251
663
  catch: (cause) => new Error(`${outPath}: chmod 0600: ${String(cause)}`),
252
664
  try: () => chmodSync(outPath, 384)
253
665
  });
254
666
  const meta = buildAttrs(raw, props);
255
667
  if (meta === undefined) {
256
- return yield* Effect4.die(new Error(`${props.node}: talosctl kubeconfig wrote a file that does not parse for context ` + `${props.context}.`));
668
+ return yield* Effect9.die(new Error(`${props.node}: talosctl kubeconfig wrote a file that does not parse for context ` + `${props.context}.`));
257
669
  }
258
670
  yield* writeKvValue(props.target.mount, key, "kubeconfig", raw);
259
671
  return meta;
260
672
  }));
261
673
  var handlers = {
262
- delete: () => Effect4.void,
674
+ delete: () => Effect9.void,
263
675
  diff: ({
264
676
  news,
265
677
  output
266
678
  }) => diffKubeconfig(news, output),
267
- list: () => Effect4.succeed([]),
679
+ list: () => Effect9.succeed([]),
268
680
  read: ({ olds }) => readKubeconfig(olds),
269
681
  reconcile: ({
270
682
  news,
271
683
  output
272
684
  }) => reconcileKubeconfig(news, output)
273
685
  };
274
- var TalosKubeconfigProvider = () => Provider.effect(TalosKubeconfig, Effect4.succeed(TalosKubeconfig.Provider.of(handlers)));
686
+ var TalosKubeconfigProvider = () => Provider.effect(TalosKubeconfig, Effect9.succeed(TalosKubeconfig.Provider.of(handlers)));
275
687
  // src/talos/talos-bootstrap.ts
276
688
  import { Resource as Resource2 } from "alchemy";
277
689
  import { isResolved as isResolved2 } from "alchemy/Diff";
278
690
  import * as Provider2 from "alchemy/Provider";
279
- import * as Effect6 from "effect/Effect";
691
+ import * as Effect11 from "effect/Effect";
280
692
 
281
693
  // src/talos/talos-bootstrap-etcd.ts
282
- import * as Effect5 from "effect/Effect";
694
+ import * as Effect10 from "effect/Effect";
283
695
 
284
696
  // src/talos/talos-errors.ts
285
- import * as Data from "effect/Data";
697
+ import * as Data2 from "effect/Data";
286
698
 
287
- class TalosConfigDigestMismatch extends Data.TaggedError("TalosConfigDigestMismatch") {
699
+ class TalosConfigDigestMismatch extends Data2.TaggedError("TalosConfigDigestMismatch") {
288
700
  get message() {
289
701
  return `${this.mount}/${this.key}: live content digest ${this.got} does not match the pinned ` + `configDigest ${this.expected}. Fail closed — nothing was sent to talosctl. Either the KV ` + "value was rewritten without a matching PR, or the pinned digest is stale; check which one " + "moved before re-pinning either side.";
290
702
  }
291
703
  }
292
704
 
293
- class TalosConvergenceTimeout extends Data.TaggedError("TalosConvergenceTimeout") {
705
+ class TalosConvergenceTimeout extends Data2.TaggedError("TalosConvergenceTimeout") {
294
706
  get message() {
295
707
  return `${this.node}: apply-config (--mode ${this.mode}) was accepted but the live machineconfig ` + `digest never matched the pin within ${this.attempts} bounded poll attempts. Refusing to ` + "report converged — hand this node to the operator rather than silently pass.";
296
708
  }
297
709
  }
298
710
 
299
- class TalosReBootstrapRefused extends Data.TaggedError("TalosReBootstrapRefused") {
711
+ class TalosReBootstrapRefused extends Data2.TaggedError("TalosReBootstrapRefused") {
300
712
  get message() {
301
713
  return `${this.node}: state already recorded a successful bootstrap, and a live check could not ` + `reconfirm it (${this.detail}). Refusing to run 'talosctl bootstrap' again — that risks a ` + "second, isolated etcd cluster on a reset node. This needs an operator, not a reconcile.";
302
714
  }
@@ -306,28 +718,28 @@ class TalosReBootstrapRefused extends Data.TaggedError("TalosReBootstrapRefused"
306
718
  var isBootstrapped = (props, talosconfigPath) => talosctl(["get", "etcdmembers", "-o", "json"], {
307
719
  nodes: [props.node],
308
720
  talosconfigPath
309
- }).pipe(Effect5.map((text) => text.includes('"id"') || text.includes('"member"')));
310
- var assertNoLivePeers = (props, talosconfigPath) => Effect5.all((props.peers ?? []).map((peer) => isBootstrapped({ ...props, node: peer }, talosconfigPath).pipe(Effect5.matchEffect({
311
- onFailure: (cause) => Effect5.fail(new TalosReBootstrapRefused({
721
+ }).pipe(Effect10.map((text) => text.includes('"id"') || text.includes('"member"')));
722
+ var assertNoLivePeers = (props, talosconfigPath) => Effect10.all((props.peers ?? []).map((peer) => isBootstrapped({ ...props, node: peer }, talosconfigPath).pipe(Effect10.matchEffect({
723
+ onFailure: (cause) => Effect10.fail(new TalosReBootstrapRefused({
312
724
  detail: `peer ${peer}: live read failed before bootstrap: ${String(cause)}`,
313
725
  node: props.node
314
726
  })),
315
- onSuccess: (hasMembers) => hasMembers ? Effect5.fail(new TalosReBootstrapRefused({
727
+ onSuccess: (hasMembers) => hasMembers ? Effect10.fail(new TalosReBootstrapRefused({
316
728
  detail: `peer ${peer} already reports etcd members — a cluster may already exist; ` + "refusing to bootstrap a second one",
317
729
  node: props.node
318
- })) : Effect5.void
730
+ })) : Effect10.void
319
731
  }))), { discard: true });
320
732
 
321
733
  // src/talos/talos-bootstrap.ts
322
734
  var TalosBootstrap = Resource2("Talos.Bootstrap", {
323
735
  defaultRemovalPolicy: "retain"
324
736
  });
325
- var readBootstrap = (props) => Effect6.scoped(Effect6.gen(function* () {
737
+ var readBootstrap = (props) => Effect11.scoped(Effect11.gen(function* () {
326
738
  const credential = yield* mintTalosconfig(props.target);
327
739
  const bootstrapped = yield* isBootstrapped(props, credential.talosconfigPath);
328
740
  return bootstrapped ? { bootstrapped: true, node: props.node } : undefined;
329
741
  }));
330
- var diffBootstrap = (news, output) => Effect6.gen(function* () {
742
+ var diffBootstrap = (news, output) => Effect11.gen(function* () {
331
743
  if (output === undefined || !isResolved2(news))
332
744
  return;
333
745
  if (output.bootstrapped)
@@ -335,15 +747,15 @@ var diffBootstrap = (news, output) => Effect6.gen(function* () {
335
747
  const live = yield* readBootstrap(news);
336
748
  return live?.bootstrapped === true ? { action: "noop" } : { action: "update" };
337
749
  });
338
- var reconcileBootstrap = (props, output) => Effect6.scoped(Effect6.gen(function* () {
750
+ var reconcileBootstrap = (props, output) => Effect11.scoped(Effect11.gen(function* () {
339
751
  const credential = yield* mintTalosconfig(props.target);
340
752
  if (output?.bootstrapped === true) {
341
- const stillBootstrapped = yield* isBootstrapped(props, credential.talosconfigPath).pipe(Effect6.catchIf(() => true, (cause) => Effect6.fail(new TalosReBootstrapRefused({
753
+ const stillBootstrapped = yield* isBootstrapped(props, credential.talosconfigPath).pipe(Effect11.catchIf(() => true, (cause) => Effect11.fail(new TalosReBootstrapRefused({
342
754
  detail: `live read failed: ${String(cause)}`,
343
755
  node: props.node
344
756
  }))));
345
757
  if (!stillBootstrapped) {
346
- return yield* Effect6.fail(new TalosReBootstrapRefused({
758
+ return yield* Effect11.fail(new TalosReBootstrapRefused({
347
759
  detail: "etcd members are absent on a live read against already-bootstrapped state",
348
760
  node: props.node
349
761
  }));
@@ -360,31 +772,104 @@ var reconcileBootstrap = (props, output) => Effect6.scoped(Effect6.gen(function*
360
772
  }
361
773
  const after = yield* isBootstrapped(props, credential.talosconfigPath);
362
774
  if (!after) {
363
- return yield* Effect6.die(new Error(`${props.node}: bootstrap returned no error but etcd members are still absent. ` + "Read back rather than trusting the exit code alone."));
775
+ return yield* Effect11.die(new Error(`${props.node}: bootstrap returned no error but etcd members are still absent. ` + "Read back rather than trusting the exit code alone."));
364
776
  }
365
777
  return { bootstrapped: true, node: props.node };
366
778
  }));
367
779
  var handlers2 = {
368
- delete: () => Effect6.void,
780
+ delete: () => Effect11.void,
369
781
  diff: ({
370
782
  news,
371
783
  output
372
784
  }) => diffBootstrap(news, output),
373
- list: () => Effect6.succeed([]),
785
+ list: () => Effect11.succeed([]),
374
786
  read: ({ olds }) => readBootstrap(olds),
375
787
  reconcile: ({
376
788
  news,
377
789
  output
378
790
  }) => reconcileBootstrap(news, output)
379
791
  };
380
- var TalosBootstrapProvider = () => Provider2.effect(TalosBootstrap, Effect6.succeed(TalosBootstrap.Provider.of(handlers2)));
381
- // src/talos/talos-cluster-health.ts
792
+ var TalosBootstrapProvider = () => Provider2.effect(TalosBootstrap, Effect11.succeed(TalosBootstrap.Provider.of(handlers2)));
793
+ // src/talos/talos-cluster-identity.ts
382
794
  import { Resource as Resource3 } from "alchemy";
383
795
  import { isResolved as isResolved3 } from "alchemy/Diff";
384
796
  import * as Provider3 from "alchemy/Provider";
385
- import * as Effect7 from "effect/Effect";
386
- var TalosClusterHealth = Resource3("Talos.ClusterHealth");
797
+ import * as Effect12 from "effect/Effect";
798
+ var TalosClusterIdentity = Resource3("Talos.ClusterIdentity");
799
+ var readClusterIdentity = (props, output) => Effect12.gen(function* () {
800
+ const cluster = props.target.cluster;
801
+ const uid = yield* withConnectDeadline(cluster, Effect12.gen(function* () {
802
+ const transport = yield* openTransport(talosVaultLocation(props));
803
+ return yield* readClusterUid(cluster, transport);
804
+ }));
805
+ if (output !== undefined && uid !== output.uid) {
806
+ return yield* Effect12.fail(new TalosClusterMoved({ cluster, live: uid, saved: output.uid }));
807
+ }
808
+ return { uid };
809
+ });
810
+ var reconcileClusterIdentity = (props, output) => readClusterIdentity(props, output);
811
+ var diffClusterIdentity = (news, output) => Effect12.gen(function* () {
812
+ if (output === undefined || !isResolved3(news))
813
+ return;
814
+ yield* reconcileClusterIdentity(news, output);
815
+ return { action: "noop" };
816
+ });
817
+ var handlers3 = {
818
+ delete: () => Effect12.void,
819
+ diff: ({
820
+ news,
821
+ output
822
+ }) => diffClusterIdentity(news, output),
823
+ list: () => Effect12.succeed([]),
824
+ read: ({
825
+ olds,
826
+ output
827
+ }) => readClusterIdentity(olds, output),
828
+ reconcile: ({
829
+ news,
830
+ output
831
+ }) => reconcileClusterIdentity(news, output)
832
+ };
833
+ var TalosClusterIdentityProvider = () => Provider3.effect(TalosClusterIdentity, Effect12.succeed(TalosClusterIdentity.Provider.of(handlers3)));
834
+ // src/talos/talos-cluster-health.ts
835
+ import { Resource as Resource4 } from "alchemy";
836
+ import { isResolved as isResolved4 } from "alchemy/Diff";
837
+ import * as Provider4 from "alchemy/Provider";
838
+ import * as Effect14 from "effect/Effect";
839
+
840
+ // src/talos/literal-connection.ts
841
+ import { hasUnresolvedInputs } from "alchemy/Diff";
842
+ import * as Effect13 from "effect/Effect";
843
+ var literalConnectionRefusal = (connection) => {
844
+ const auth = connection?.auth;
845
+ return hasUnresolvedInputs(connection) || auth?.kind !== "talos-openbao" || typeof auth?.uid !== "string" || auth.uid === "" ? new TalosUidNotLiteral({}) : undefined;
846
+ };
847
+ var withLiteralConnection = (resource) => new Proxy(resource, {
848
+ apply: (target, thisArg, args) => {
849
+ const refusal = (given) => literalConnectionRefusal(given?.connection);
850
+ const props = args[1];
851
+ if (Effect13.isEffect(props)) {
852
+ const checked = props.pipe(Effect13.flatMap((resolved) => {
853
+ const refused2 = refusal(resolved);
854
+ return refused2 === undefined ? Effect13.succeed(resolved) : Effect13.fail(refused2);
855
+ }));
856
+ return Reflect.apply(target, thisArg, [args[0], checked, ...args.slice(2)]);
857
+ }
858
+ const refused = refusal(props);
859
+ return refused === undefined ? Reflect.apply(target, thisArg, args) : Effect13.fail(refused);
860
+ }
861
+ });
862
+
863
+ // src/talos/talos-cluster-health.ts
864
+ var TalosClusterHealth = withLiteralConnection(Resource4("Talos.ClusterHealth"));
387
865
  var nodeCsv = (nodes) => nodes === undefined || nodes.length === 0 ? "" : nodes.join(",");
866
+ var attributes = (props, healthy) => ({
867
+ connection: props.connection,
868
+ controlPlaneNodes: nodeCsv(props.controlPlaneNodes),
869
+ healthy,
870
+ workerNodes: nodeCsv(props.workerNodes)
871
+ });
872
+ var sameConnection = (left, right) => right?.auth?.kind === left.auth.kind && right.auth.uid === left.auth.uid && !Object.hasOwn(right.auth, "cluster");
388
873
  var healthArgs = (props, waitTimeout) => {
389
874
  const args = ["health", "--wait-timeout", waitTimeout];
390
875
  const cp = nodeCsv(props.controlPlaneNodes);
@@ -395,79 +880,75 @@ var healthArgs = (props, waitTimeout) => {
395
880
  args.push("--worker-nodes", workers);
396
881
  return args;
397
882
  };
398
- var check = (props, waitTimeout) => Effect7.scoped(Effect7.gen(function* () {
883
+ var check = (props, waitTimeout) => Effect14.scoped(Effect14.gen(function* () {
399
884
  const credential = yield* mintTalosconfig(props.target);
400
885
  const contact = props.controlPlaneNodes[0];
401
886
  if (contact === undefined) {
402
- return yield* Effect7.fail(new Error(`Talos.ClusterHealth ${props.target.cluster}: controlPlaneNodes is empty — need at ` + "least one node to contact."));
887
+ return yield* Effect14.fail(new Error(`Talos.ClusterHealth ${props.target.cluster}: controlPlaneNodes is empty — need at ` + "least one node to contact."));
403
888
  }
404
889
  yield* talosctl(healthArgs(props, waitTimeout), {
405
890
  endpoints: [contact],
406
891
  nodes: [contact],
407
892
  talosconfigPath: credential.talosconfigPath
408
893
  });
409
- return {
410
- controlPlaneNodes: nodeCsv(props.controlPlaneNodes),
411
- healthy: true,
412
- workerNodes: nodeCsv(props.workerNodes)
413
- };
894
+ return attributes(props, true);
414
895
  }));
415
896
  var isTalosError = (cause) => cause instanceof TalosError;
416
- var readClusterHealth = (props) => check(props, "5s").pipe(Effect7.catchIf(isTalosError, () => Effect7.succeed({
417
- controlPlaneNodes: nodeCsv(props.controlPlaneNodes),
418
- healthy: false,
419
- workerNodes: nodeCsv(props.workerNodes)
420
- })));
421
- var diffClusterHealth = (news, output) => Effect7.gen(function* () {
422
- if (output === undefined || !isResolved3(news))
897
+ var readClusterHealth = (props) => check(props, "5s").pipe(Effect14.catchIf(isTalosError, () => Effect14.succeed(attributes(props, false))));
898
+ var diffClusterHealth = (news, output) => Effect14.gen(function* () {
899
+ const refused = literalConnectionRefusal(news.connection);
900
+ if (refused !== undefined)
901
+ return yield* Effect14.fail(refused);
902
+ if (output === undefined || !isResolved4(news))
423
903
  return;
424
904
  const live = yield* readClusterHealth(news);
425
- if (live.healthy)
905
+ if (live.healthy && sameConnection(live.connection, output.connection)) {
426
906
  return { action: "noop" };
907
+ }
427
908
  return { action: "update" };
428
909
  });
429
- var reconcileClusterHealth = (props) => check(props, props.waitTimeout ?? "20m0s").pipe(Effect7.catchIf(isTalosError, (cause) => Effect7.fail(new Error(`Talos.ClusterHealth ${props.target.cluster}: cluster not healthy within ` + `${props.waitTimeout ?? "20m0s"} — ${String(cause)}`))));
430
- var handlers3 = {
431
- delete: () => Effect7.void,
910
+ var reconcileClusterHealth = (props) => check(props, props.waitTimeout ?? "20m0s").pipe(Effect14.catchIf(isTalosError, (cause) => Effect14.fail(new Error(`Talos.ClusterHealth ${props.target.cluster}: cluster not healthy within ` + `${props.waitTimeout ?? "20m0s"} — ${String(cause)}`))));
911
+ var handlers4 = {
912
+ delete: () => Effect14.void,
432
913
  diff: ({
433
914
  news,
434
915
  output
435
916
  }) => diffClusterHealth(news, output),
436
- list: () => Effect7.succeed([]),
917
+ list: () => Effect14.succeed([]),
437
918
  read: ({ olds }) => readClusterHealth(olds),
438
919
  reconcile: ({ news }) => reconcileClusterHealth(news)
439
920
  };
440
- var TalosClusterHealthProvider = () => Provider3.effect(TalosClusterHealth, Effect7.succeed(TalosClusterHealth.Provider.of(handlers3)));
921
+ var TalosClusterHealthProvider = () => Provider4.effect(TalosClusterHealth, Effect14.succeed(TalosClusterHealth.Provider.of(handlers4)));
441
922
  // src/talos/talos-machine-config.ts
442
- import { Resource as Resource4 } from "alchemy";
443
- import * as Provider4 from "alchemy/Provider";
444
- import * as Effect10 from "effect/Effect";
923
+ import { Resource as Resource5 } from "alchemy";
924
+ import * as Provider5 from "alchemy/Provider";
925
+ import * as Effect17 from "effect/Effect";
445
926
 
446
927
  // src/talos/machine-config-read.ts
447
928
  import { Unowned } from "alchemy/AdoptPolicy";
448
- import { isResolved as isResolved4 } from "alchemy/Diff";
449
- import * as Effect8 from "effect/Effect";
929
+ import { isResolved as isResolved5 } from "alchemy/Diff";
930
+ import * as Effect15 from "effect/Effect";
450
931
  import * as Result from "effect/Result";
451
932
  var liveSpecDigest = (props, talosconfigPath) => talosctl(["get", "machineconfig", "v1alpha1", "-o", "yaml"], {
452
933
  endpoints: [props.node],
453
934
  nodes: [props.node],
454
935
  talosconfigPath
455
- }).pipe(Effect8.flatMap((wrapperYaml) => {
936
+ }).pipe(Effect15.flatMap((wrapperYaml) => {
456
937
  const spec = extractMachineConfigSpec(wrapperYaml);
457
- return spec === undefined ? Effect8.fail(new Error(`${props.node}: get machineconfig v1alpha1 returned no string 'spec' field — see ` + "values.ts extractMachineConfigSpec for the expected shape.")) : Effect8.succeed(configDigest(spec));
938
+ return spec === undefined ? Effect15.fail(new Error(`${props.node}: get machineconfig v1alpha1 returned no string 'spec' field — see ` + "values.ts extractMachineConfigSpec for the expected shape.")) : Effect15.succeed(configDigest(spec));
458
939
  }));
459
940
  var probeMaintenanceMode = (props, talosconfigPath) => talosctl(["version"], {
460
941
  endpoints: [props.node],
461
942
  insecure: true,
462
943
  nodes: [props.node],
463
944
  talosconfigPath
464
- }).pipe(Effect8.as(true), Effect8.orElseSucceed(() => false));
465
- var readMachineConfig = (props) => Effect8.scoped(Effect8.gen(function* () {
945
+ }).pipe(Effect15.as(true), Effect15.orElseSucceed(() => false));
946
+ var readMachineConfig = (props) => Effect15.scoped(Effect15.gen(function* () {
466
947
  const credential = yield* mintTalosconfig(props.target);
467
- const outcome = yield* Effect8.result(liveSpecDigest(props, credential.talosconfigPath));
948
+ const outcome = yield* Effect15.result(liveSpecDigest(props, credential.talosconfigPath));
468
949
  if (Result.isFailure(outcome)) {
469
950
  const reachable = yield* probeMaintenanceMode(props, credential.talosconfigPath);
470
- return reachable ? undefined : yield* Effect8.fail(outcome.failure);
951
+ return reachable ? undefined : yield* Effect15.fail(outcome.failure);
471
952
  }
472
953
  const attrs = {
473
954
  configDigest: props.configDigest,
@@ -477,8 +958,8 @@ var readMachineConfig = (props) => Effect8.scoped(Effect8.gen(function* () {
477
958
  };
478
959
  return attrs.converged === false ? Unowned(attrs) : attrs;
479
960
  }));
480
- var diffMachineConfig = (news, output) => Effect8.gen(function* () {
481
- if (output === undefined || !isResolved4(news))
961
+ var diffMachineConfig = (news, output) => Effect15.gen(function* () {
962
+ if (output === undefined || !isResolved5(news))
482
963
  return;
483
964
  const live = yield* readMachineConfig(news);
484
965
  if (live !== undefined && live.converged !== false)
@@ -487,40 +968,40 @@ var diffMachineConfig = (news, output) => Effect8.gen(function* () {
487
968
  });
488
969
 
489
970
  // src/talos/machine-config-poll.ts
490
- import * as Effect9 from "effect/Effect";
971
+ import * as Effect16 from "effect/Effect";
491
972
  var isRebootish = (mode) => mode === "reboot" || mode === "auto";
492
973
  var POLICY = {
493
974
  immediate: { intervalMs: 2000, maxAttempts: 5 },
494
975
  rebootish: { intervalMs: 5000, maxAttempts: 24 }
495
976
  };
496
- var pollUntilMatch = (check2, policy) => Effect9.gen(function* () {
977
+ var pollUntilMatch = (check2, policy) => Effect16.gen(function* () {
497
978
  for (let attempt = 1;attempt <= policy.maxAttempts; attempt++) {
498
- const matched = yield* check2.pipe(Effect9.orElseSucceed(() => false));
979
+ const matched = yield* check2.pipe(Effect16.orElseSucceed(() => false));
499
980
  if (matched)
500
981
  return true;
501
982
  if (attempt < policy.maxAttempts)
502
- yield* Effect9.sleep(policy.intervalMs);
983
+ yield* Effect16.sleep(policy.intervalMs);
503
984
  }
504
985
  return false;
505
986
  });
506
- var confirmConverged = (node, mode, check2, policies = POLICY) => Effect9.gen(function* () {
987
+ var confirmConverged = (node, mode, check2, policies = POLICY) => Effect16.gen(function* () {
507
988
  const rebootish = isRebootish(mode);
508
989
  const policy = rebootish ? policies.rebootish : policies.immediate;
509
990
  const matched = yield* pollUntilMatch(check2, policy);
510
991
  if (!matched) {
511
- return yield* Effect9.fail(new TalosConvergenceTimeout({ attempts: policy.maxAttempts, mode, node }));
992
+ return yield* Effect16.fail(new TalosConvergenceTimeout({ attempts: policy.maxAttempts, mode, node }));
512
993
  }
513
994
  return rebootish ? "accepted" : "read-back";
514
995
  });
515
996
 
516
997
  // src/talos/talos-machine-config.ts
517
- var TalosMachineConfig = Resource4("Talos.MachineConfig");
518
- var reconcileMachineConfig = (props, output, pollPolicies) => Effect10.scoped(Effect10.gen(function* () {
998
+ var TalosMachineConfig = Resource5("Talos.MachineConfig");
999
+ var reconcileMachineConfig = (props, output, pollPolicies) => Effect17.scoped(Effect17.gen(function* () {
519
1000
  const mode = props.mode ?? "auto";
520
1001
  const raw = yield* readKvValue(props.target.mount, props.configKey, ["config"]);
521
1002
  const gotDigest = configDigest(raw);
522
1003
  if (gotDigest !== props.configDigest) {
523
- return yield* Effect10.fail(new TalosConfigDigestMismatch({
1004
+ return yield* Effect17.fail(new TalosConfigDigestMismatch({
524
1005
  expected: props.configDigest,
525
1006
  got: gotDigest,
526
1007
  key: props.configKey,
@@ -539,7 +1020,7 @@ var reconcileMachineConfig = (props, output, pollPolicies) => Effect10.scoped(Ef
539
1020
  ...isCreate ? ["--insecure"] : []
540
1021
  ];
541
1022
  yield* talosctl(args, { nodes: [props.node], talosconfigPath: credential.talosconfigPath });
542
- const converged = yield* confirmConverged(props.node, mode, liveSpecDigest(props, credential.talosconfigPath).pipe(Effect10.map((live) => live === props.configDigest)), pollPolicies);
1023
+ const converged = yield* confirmConverged(props.node, mode, liveSpecDigest(props, credential.talosconfigPath).pipe(Effect17.map((live) => live === props.configDigest)), pollPolicies);
543
1024
  return {
544
1025
  configDigest: props.configDigest,
545
1026
  converged,
@@ -547,30 +1028,51 @@ var reconcileMachineConfig = (props, output, pollPolicies) => Effect10.scoped(Ef
547
1028
  node: props.node
548
1029
  };
549
1030
  }));
550
- var handlers4 = {
551
- delete: () => Effect10.void,
1031
+ var handlers5 = {
1032
+ delete: () => Effect17.void,
552
1033
  diff: ({
553
1034
  news,
554
1035
  output
555
1036
  }) => diffMachineConfig(news, output),
556
- list: () => Effect10.succeed([]),
1037
+ list: () => Effect17.succeed([]),
557
1038
  read: ({ olds }) => readMachineConfig(olds),
558
1039
  reconcile: ({
559
1040
  news,
560
1041
  output
561
1042
  }) => reconcileMachineConfig(news, output)
562
1043
  };
563
- var TalosMachineConfigProvider = () => Provider4.effect(TalosMachineConfig, Effect10.succeed(TalosMachineConfig.Provider.of(handlers4)));
1044
+ var TalosMachineConfigProvider = () => Provider5.effect(TalosMachineConfig, Effect17.succeed(TalosMachineConfig.Provider.of(handlers5)));
564
1045
  export {
1046
+ DEFAULT_TALOSCTL_BINARY,
1047
+ TALOSCTL_BINARY_ENV,
565
1048
  TalosBootstrap,
566
1049
  TalosBootstrapProvider,
567
1050
  TalosClusterHealth,
568
1051
  TalosClusterHealthProvider,
1052
+ TalosClusterIdentity,
1053
+ TalosClusterIdentityMismatch,
1054
+ TalosClusterIdentityMissing,
1055
+ TalosClusterIdentityProvider,
1056
+ TalosClusterIdentityTimeout,
1057
+ TalosClusterIdentityUnreadable,
1058
+ TalosClusterMoved,
569
1059
  TalosKubeconfig,
570
1060
  TalosKubeconfigProvider,
1061
+ TalosKubeconfigUnreadable,
571
1062
  TalosMachineConfig,
572
- TalosMachineConfigProvider
1063
+ TalosMachineConfigProvider,
1064
+ TalosOpenBaoAdapter,
1065
+ TalosOpenBaoAmbiguousUid,
1066
+ TalosOpenBaoAuthKind,
1067
+ TalosOpenBaoConnectTimeout,
1068
+ TalosOpenBaoLegacyAuth,
1069
+ TalosOpenBaoUnknownCluster,
1070
+ TalosUidNotLiteral,
1071
+ TalosVaultKeyMissing,
1072
+ connectTalosOpenBao,
1073
+ talosOpenBaoCluster,
1074
+ talosOpenBaoConnection
573
1075
  };
574
1076
 
575
- //# debugId=33FF194E493FB6EF64756E2164756E21
1077
+ //# debugId=0069C805BB1B149764756E2164756E21
576
1078
  //# sourceMappingURL=index.js.map