@homeflare/alchemy 0.47.1 → 0.48.1

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 (130) 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 +116 -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 +683 -170
  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 +29 -3
  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 +45 -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 +193 -0
  58. package/src/talos/cluster-adapter-move.test.ts +131 -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 +250 -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.test.ts +44 -0
  81. package/src/talos/mint-temp-file.ts +68 -0
  82. package/src/talos/node-connect.harness.ts +78 -0
  83. package/src/talos/node-connect.test.ts +27 -0
  84. package/src/talos/node-temp.harness.ts +40 -0
  85. package/src/talos/node-temp.test.ts +26 -0
  86. package/src/talos/talos-cluster-health-declare.test.ts +54 -0
  87. package/src/talos/talos-cluster-health-literal.test.ts +69 -0
  88. package/src/talos/talos-cluster-health.test.ts +104 -18
  89. package/src/talos/talos-cluster-health.ts +46 -14
  90. package/src/talos/talos-cluster-identity.test.ts +96 -0
  91. package/src/talos/talos-cluster-identity.ts +130 -0
  92. package/src/talos/talosctl-binary.ts +150 -0
  93. package/src/talos/talosctl-probe-kill.test.ts +56 -0
  94. package/src/talos/talosctl-vetting.test.ts +212 -0
  95. package/src/talos/talosctl.test.ts +163 -0
  96. package/src/talos/talosctl.ts +133 -15
  97. package/src/talos/trust-boundary.test.ts +17 -0
  98. package/src/talos/trust-boundary.ts +45 -0
  99. package/src/talos/values.ts +5 -12
  100. package/src/talos/yaml-parse.test.ts +41 -0
  101. package/src/talos/yaml-parse.ts +28 -0
  102. package/src/verify/fake-engine.ts +10 -0
  103. package/dist/talos/fake-process.d.ts +0 -56
  104. package/dist/talos/fake-process.d.ts.map +0 -1
  105. package/dist/talos/machine-config-fixtures.d.ts +0 -60
  106. package/dist/talos/machine-config-fixtures.d.ts.map +0 -1
  107. package/src/postgres/fixtures/pg-REL_18_6-acl.h.txt +0 -147
  108. package/src/postgres/fixtures/pg-REL_18_6-aclexplode-func.txt +0 -31
  109. package/src/postgres/fixtures/pg-REL_18_6-alter-default-privileges-synopsis.txt +0 -82
  110. package/src/postgres/fixtures/pg-REL_18_6-alter_role.sgml.txt +0 -44
  111. package/src/postgres/fixtures/pg-REL_18_6-create-database-synopsis.txt +0 -26
  112. package/src/postgres/fixtures/pg-REL_18_6-create_role.sgml.txt +0 -26
  113. package/src/postgres/fixtures/pg-REL_18_6-grant-synopsis.txt +0 -95
  114. package/src/postgres/fixtures/pg-REL_18_6-pg_attribute.h.txt +0 -24
  115. package/src/postgres/fixtures/pg-REL_18_6-pg_auth_members.h.txt +0 -62
  116. package/src/postgres/fixtures/pg-REL_18_6-pg_authid.h.txt +0 -70
  117. package/src/postgres/fixtures/pg-REL_18_6-pg_class.h.txt +0 -33
  118. package/src/postgres/fixtures/pg-REL_18_6-pg_database.h.txt +0 -67
  119. package/src/postgres/fixtures/pg-REL_18_6-pg_default_acl.h.txt +0 -81
  120. package/src/postgres/fixtures/pg-REL_18_6-pg_namespace.h.txt +0 -28
  121. package/src/postgres/fixtures/pg-REL_18_6-revoke-synopsis.txt +0 -124
  122. package/src/release/fixtures/openbao/checksums.txt +0 -83
  123. package/src/release/fixtures/openbao/checksums.txt.gpgsig +0 -0
  124. package/src/release/fixtures/openbao/darwin-assets.txt +0 -12
  125. package/src/release/fixtures/openbao/openbao-gpg-pub-20240618.asc +0 -109
  126. package/src/release/fixtures/victoria/darwin-arm64-assets.txt +0 -22
  127. package/src/release/fixtures/victoria/victoria-logs-darwin-arm64-v1.52.0_checksums.txt +0 -2
  128. package/src/release/fixtures/victoria/victoria-metrics-darwin-arm64-v1.151.0_checksums.txt +0 -2
  129. package/src/release/fixtures/victoria/victoria-traces-darwin-arm64-v0.10.0_checksums.txt +0 -2
  130. package/src/release/fixtures/victoria/vmutils-darwin-arm64-v1.151.0_checksums.txt +0 -8
@@ -1,89 +1,490 @@
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 (the identity read's own deadline, inside the 10 s connect deadline). ` + "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, unlinkSync, 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 {
41
- writeFileSync(fd, raw);
42
- } finally {
43
- closeSync(fd);
255
+ try {
256
+ writeFileSync(fd, raw);
257
+ } finally {
258
+ closeSync(fd);
259
+ }
260
+ } catch (cause) {
261
+ try {
262
+ unlinkSync(path);
263
+ } catch {}
264
+ throw cause;
44
265
  }
45
266
  },
46
267
  catch: (cause) => new Error(`writing ${label} temp file: ${String(cause)}`)
47
- }), () => Effect.tryPromise({ try: () => Bun.file(path).delete(), catch: () => {
268
+ }), () => Effect3.tryPromise({ try: () => unlink(path), catch: () => {
48
269
  return;
49
- } }).pipe(Effect.orElseSucceed(() => {
270
+ } }).pipe(Effect3.orElseSucceed(() => {
50
271
  return;
51
272
  })));
52
273
  return { path };
53
274
  });
275
+
276
+ // src/talos/credentials.ts
277
+ var DEFAULT_TALOSCONFIG_KEY = "talosconfig";
278
+ var DEFAULT_KUBECONFIG_KEY = "kubeconfig";
279
+ var readKvValue = (mount, key, fields) => Effect4.gen(function* () {
280
+ const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
281
+ const bao = yield* resolveBao();
282
+ const env = baoEnv();
283
+ if (env === undefined) {
284
+ 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."));
285
+ }
286
+ const result = yield* ChildProcess.make(bao, ["kv", "get", "-format=json", `${mount}/${key}`], {
287
+ env,
288
+ extendEnv: false,
289
+ forceKillAfter: Duration2.seconds(1),
290
+ stderr: "pipe",
291
+ stdin: "ignore",
292
+ stdout: "pipe"
293
+ }).pipe(spawner.spawn, Effect4.flatMap((child) => Effect4.all({
294
+ exitCode: child.exitCode,
295
+ stderr: child.stderr.pipe(Stream.decodeText, Stream.mkString, Effect4.map((text) => text.trim())),
296
+ stdout: child.stdout.pipe(Stream.decodeText, Stream.mkString, Effect4.map((text) => text.trim()))
297
+ }, { concurrency: "unbounded" })), Effect4.scoped);
298
+ if (result.exitCode !== 0) {
299
+ return yield* Effect4.fail(new Error(`bao kv get ${mount}/${key} exited ${String(result.exitCode)}: ${result.stderr}`));
300
+ }
301
+ let parsed;
302
+ try {
303
+ parsed = JSON.parse(result.stdout);
304
+ } catch {
305
+ return yield* Effect4.fail(new Error(`bao kv get ${mount}/${key} returned output that is not JSON (not echoed).`));
306
+ }
307
+ const data = parsed.data?.data;
308
+ for (const field of fields) {
309
+ const raw = data?.[field];
310
+ if (typeof raw === "string" && raw.trim() !== "")
311
+ return raw;
312
+ }
313
+ 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."));
314
+ });
315
+ var isVaultKeyAbsent = (error) => error instanceof Error && /no value found at/i.test(error.message);
54
316
  var mintTalosconfig = (target) => readKvValue(target.mount, target.talosconfigKey ?? DEFAULT_TALOSCONFIG_KEY, [
55
317
  "talosconfig",
56
318
  "config"
57
- ]).pipe(Effect.flatMap((raw) => mintKvTempFile(raw, `${target.cluster}-talosconfig`)), Effect.map(({ path }) => ({ talosconfigPath: path })));
319
+ ]).pipe(Effect4.flatMap((raw) => mintKvTempFile(raw, `${target.cluster}-talosconfig`)), Effect4.map(({ path }) => ({ talosconfigPath: path })));
320
+
321
+ // src/talos/yaml-parse.ts
322
+ import { YAMLParseError, parse } from "yaml";
323
+ var parseYaml = (text) => {
324
+ try {
325
+ return parse(text, { logLevel: "error", prettyErrors: false });
326
+ } catch (error) {
327
+ if (error instanceof YAMLParseError)
328
+ return;
329
+ throw error;
330
+ }
331
+ };
332
+
333
+ // src/talos/kubeconfig-doc.ts
334
+ var pem = (data) => {
335
+ if (data === undefined || data.trim() === "")
336
+ return;
337
+ const decoded = Buffer.from(data, "base64").toString("utf8");
338
+ return decoded.includes("BEGIN ") ? decoded : undefined;
339
+ };
340
+ var kubeconfigTransport = (yamlText, contextName) => {
341
+ const parsed = parseYaml(yamlText);
342
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
343
+ return;
344
+ const doc = parsed;
345
+ const ctx = doc.contexts?.find((row) => row.name === contextName)?.context;
346
+ const clusterName = ctx?.cluster;
347
+ const userName = ctx?.user;
348
+ if (clusterName === undefined || userName === undefined)
349
+ return;
350
+ const cluster = doc.clusters?.find((row) => row.name === clusterName)?.cluster;
351
+ const user = doc.users?.find((row) => row.name === userName)?.user;
352
+ const endpoint = cluster?.server;
353
+ const certificateAuthorityData = cluster?.["certificate-authority-data"];
354
+ const certificate = pem(user?.["client-certificate-data"]);
355
+ const key = pem(user?.["client-key-data"]);
356
+ if (endpoint === undefined || certificateAuthorityData === undefined || pem(certificateAuthorityData) === undefined || certificate === undefined || key === undefined) {
357
+ return;
358
+ }
359
+ return { certificateAuthorityData, clientCert: { certificate, key }, endpoint };
360
+ };
361
+
362
+ // src/talos/cluster-transport.ts
363
+ var talosVaultLocation = (props) => ({
364
+ context: props.context,
365
+ key: props.kubeconfigKey ?? DEFAULT_KUBECONFIG_KEY,
366
+ mount: props.target.mount
367
+ });
368
+ var talosOpenBaoCluster = (props, pin) => ({
369
+ ...talosVaultLocation(props),
370
+ uid: pin.uid,
371
+ ...pin.retired === true ? { retired: true } : {}
372
+ });
373
+ var CONNECT_TIMEOUT = Duration3.seconds(10);
374
+ var withConnectDeadline = (cluster, effect, timeout = CONNECT_TIMEOUT) => effect.pipe(Effect5.timeoutOrElse({
375
+ duration: timeout,
376
+ orElse: () => Effect5.fail(new TalosOpenBaoConnectTimeout({ cluster, seconds: Duration3.toSeconds(timeout) }))
377
+ }));
378
+ var openTransport = (config) => Effect5.gen(function* () {
379
+ const { context, key, mount } = config;
380
+ const raw = yield* readKvValue(mount, key, ["kubeconfig", "config"]).pipe(Effect5.mapError((error) => isVaultKeyAbsent(error) ? new TalosVaultKeyMissing({ key, mount }) : error));
381
+ const material = yield* Effect5.sync(() => kubeconfigTransport(raw, context));
382
+ if (material === undefined) {
383
+ return yield* Effect5.fail(new TalosKubeconfigUnreadable({ context, key, mount }));
384
+ }
385
+ return { ...material, headers: Effect5.succeed({}) };
386
+ });
387
+
388
+ // src/talos/cluster-adapter.ts
389
+ var talosOpenBaoConnection = (uid) => {
390
+ if (typeof uid !== "string" || uid === "")
391
+ throw new TalosUidNotLiteral({});
392
+ return { auth: { kind: "talos-openbao", uid } };
393
+ };
394
+ var connectTalosOpenBao = (configs, connection, timeout = CONNECT_TIMEOUT) => Effect6.gen(function* () {
395
+ if (connection === undefined || connection === null) {
396
+ return yield* Effect6.fail(new TalosClusterIdentityMissing({}));
397
+ }
398
+ const { auth } = connection;
399
+ if (auth === undefined || auth === null) {
400
+ return yield* Effect6.fail(new TalosClusterIdentityMissing({}));
401
+ }
402
+ if (auth.kind !== "talos-openbao") {
403
+ return yield* Effect6.fail(new TalosOpenBaoAuthKind({ kind: auth.kind }));
404
+ }
405
+ if (Object.hasOwn(auth, "cluster")) {
406
+ return yield* Effect6.fail(new TalosOpenBaoLegacyAuth({}));
407
+ }
408
+ const { uid } = auth;
409
+ if (typeof uid !== "string" || uid === "") {
410
+ return yield* Effect6.fail(new TalosClusterIdentityMissing({}));
411
+ }
412
+ const matches = Object.entries(configs).filter(([, entry]) => entry.uid === uid);
413
+ const [match] = matches;
414
+ if (match === undefined) {
415
+ return yield* Effect6.fail(new TalosOpenBaoUnknownCluster({
416
+ known: Object.values(configs).map((entry) => entry.uid),
417
+ uid
418
+ }));
419
+ }
420
+ if (matches.length > 1) {
421
+ return yield* Effect6.fail(new TalosOpenBaoAmbiguousUid({ aliases: matches.map(([alias2]) => alias2), uid }));
422
+ }
423
+ const [alias, config] = match;
424
+ if (config.retired === true) {
425
+ return yield* Effect6.fail(new ClusterNotFoundError({ message: `talos-openbao cluster '${alias}' is retired` }));
426
+ }
427
+ return yield* withConnectDeadline(alias, Effect6.gen(function* () {
428
+ const transport = yield* openTransport(config);
429
+ yield* assertClusterUid(alias, uid, transport);
430
+ return transport;
431
+ }), timeout);
432
+ });
433
+ var TalosOpenBaoAdapter = (config) => Layer.effect(ClusterAdapter("talos-openbao"), Effect6.gen(function* () {
434
+ const context2 = yield* Effect6.context();
435
+ const service = {
436
+ kind: "Kubernetes.ClusterAdapter",
437
+ connect: (connection) => connectTalosOpenBao(config, connection).pipe(Effect6.provideContext(context2))
438
+ };
439
+ return service;
440
+ }));
441
+ // src/talos/kubeconfig.ts
442
+ import { readFile } from "node:fs/promises";
443
+ import { chmodSync } from "node:fs";
444
+ import { Resource } from "alchemy";
445
+ import { isResolved } from "alchemy/Diff";
446
+ import * as Provider from "alchemy/Provider";
447
+ import * as Effect9 from "effect/Effect";
58
448
 
59
449
  // src/talos/credentials-write.ts
60
- import * as Effect2 from "effect/Effect";
450
+ import * as Effect7 from "effect/Effect";
451
+ import * as Duration4 from "effect/Duration";
61
452
  import * as Stream2 from "effect/Stream";
453
+ import { unlink as unlink2 } from "node:fs/promises";
454
+ import { randomUUID as randomUUID2 } from "node:crypto";
455
+ import { tmpdir as tmpdir2 } from "node:os";
456
+ import { join as join3 } from "node:path";
62
457
  import * as ChildProcess2 from "effect/process/ChildProcess";
63
458
  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: () => {
459
+ var reservedTempPath = (label) => Effect7.gen(function* () {
460
+ const path = join3(tmpdir2(), `hf-talos-${label}-${randomUUID2()}.yaml`);
461
+ yield* Effect7.addFinalizer(() => Effect7.tryPromise({ try: () => unlink2(path), catch: () => {
67
462
  return;
68
- } }).pipe(Effect2.orElseSucceed(() => {
463
+ } }).pipe(Effect7.orElseSucceed(() => {
69
464
  return;
70
465
  })));
71
466
  return { path };
72
467
  });
73
- var writeKvValue = (mount, key, field, value) => Effect2.gen(function* () {
468
+ var writeKvValue = (mount, key, field, value) => Effect7.gen(function* () {
74
469
  const spawner = yield* ChildProcessSpawner3.ChildProcessSpawner;
75
- const result = yield* ChildProcess2.make("bao", ["kv", "put", `${mount}/${key}`, `${field}=-`], {
76
- detached: false,
77
- extendEnv: true,
470
+ const bao = yield* resolveBao();
471
+ const env = baoEnv();
472
+ if (env === undefined) {
473
+ 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."));
474
+ }
475
+ const result = yield* ChildProcess2.make(bao, ["kv", "put", `${mount}/${key}`, `${field}=-`], {
476
+ env,
477
+ extendEnv: false,
478
+ forceKillAfter: Duration4.seconds(1),
78
479
  stderr: "pipe",
79
480
  stdin: Stream2.fromIterable([new TextEncoder().encode(value)]),
80
481
  stdout: "pipe"
81
- }).pipe(spawner.spawn, Effect2.flatMap((child) => Effect2.all({
482
+ }).pipe(spawner.spawn, Effect7.flatMap((child) => Effect7.all({
82
483
  exitCode: child.exitCode,
83
- stderr: child.stderr.pipe(Stream2.decodeText, Stream2.mkString, Effect2.map((text) => text.trim()))
84
- }, { concurrency: "unbounded" })), Effect2.scoped);
484
+ stderr: child.stderr.pipe(Stream2.decodeText, Stream2.mkString, Effect7.map((text) => text.trim()))
485
+ }, { concurrency: "unbounded" })), Effect7.scoped);
85
486
  if (result.exitCode !== 0) {
86
- return yield* Effect2.fail(new Error(`bao kv put ${mount}/${key} exited ${String(result.exitCode)}: ${result.stderr}`));
487
+ return yield* Effect7.fail(new Error(`bao kv put ${mount}/${key} exited ${String(result.exitCode)}: ${result.stderr}`));
87
488
  }
88
489
  });
89
490
 
@@ -93,12 +494,7 @@ var canonicalText = (text) => text.trimEnd();
93
494
  var sha256 = (text) => createHash("sha256").update(canonicalText(text)).digest("hex");
94
495
  var configDigest = (text) => sha256(text);
95
496
  var extractMachineConfigSpec = (wrapperYaml) => {
96
- let doc;
97
- try {
98
- doc = Bun.YAML.parse(wrapperYaml);
99
- } catch {
100
- return;
101
- }
497
+ let doc = parseYaml(wrapperYaml);
102
498
  if (Array.isArray(doc)) {
103
499
  if (doc.length !== 1)
104
500
  return;
@@ -110,12 +506,10 @@ var extractMachineConfigSpec = (wrapperYaml) => {
110
506
  return typeof spec === "string" ? spec : undefined;
111
507
  };
112
508
  var kubeconfigMetadata = (yamlText, contextName) => {
113
- let doc;
114
- try {
115
- doc = Bun.YAML.parse(yamlText);
116
- } catch {
509
+ const parsed = parseYaml(yamlText);
510
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
117
511
  return;
118
- }
512
+ const doc = parsed;
119
513
  const ctx = doc.contexts?.find((row) => row.name === contextName)?.context;
120
514
  const clusterName = ctx?.cluster;
121
515
  const userName = ctx?.user;
@@ -140,10 +534,6 @@ var generation = (meta) => sha256(`${meta.context}
140
534
  ${meta.endpoint}
141
535
  ${meta.caFingerprint}
142
536
  ${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
537
  var buildAttrs = (raw, props) => {
148
538
  const meta = kubeconfigMetadata(raw, props.context);
149
539
  if (meta === undefined)
@@ -151,7 +541,6 @@ var buildAttrs = (raw, props) => {
151
541
  return {
152
542
  certificateAuthorityFingerprint: meta.caFingerprint,
153
543
  clientCertificateFingerprint: meta.clientFingerprint,
154
- connection: toConnection(props),
155
544
  context: props.context,
156
545
  credentialGeneration: generation({ ...meta, context: props.context }),
157
546
  endpoint: meta.endpoint
@@ -159,23 +548,68 @@ var buildAttrs = (raw, props) => {
159
548
  };
160
549
 
161
550
  // src/talos/talosctl.ts
162
- import * as Effect3 from "effect/Effect";
551
+ import * as Duration5 from "effect/Duration";
552
+ import * as Effect8 from "effect/Effect";
163
553
  import * as Stream3 from "effect/Stream";
164
554
  import * as ChildProcess3 from "effect/process/ChildProcess";
165
555
  import * as ChildProcessSpawner5 from "effect/process/ChildProcessSpawner";
166
-
167
556
  class TalosError extends Error {
168
557
  command;
169
558
  exitCode;
170
- constructor(command, exitCode, detail) {
171
- super(`talosctl ${command} -> ${String(exitCode)}: ${detail}`);
559
+ constructor(command, exitCode, detail, binary = DEFAULT_TALOSCTL_BINARY) {
560
+ super(`${binary} ${command} -> ${String(exitCode)}: ${detail}`);
172
561
  this.command = command;
173
562
  this.exitCode = exitCode;
174
563
  this.name = "TalosError";
175
564
  }
176
565
  }
177
- var talosctl = (args, options) => Effect3.gen(function* () {
566
+ var TALOSCTL_PINNED_VERSION = "v1.14.2";
567
+ var talosctlBinary = (override) => {
568
+ const fromOption = override?.trim();
569
+ if (fromOption !== undefined && fromOption !== "")
570
+ return fromOption;
571
+ const fromEnv = process.env[TALOSCTL_BINARY_ENV]?.trim();
572
+ if (fromEnv !== undefined && fromEnv !== "")
573
+ return fromEnv;
574
+ return DEFAULT_TALOSCTL_BINARY;
575
+ };
576
+ var talosctlEnv = () => {
577
+ const env = {};
578
+ for (const name of ["PATH", "HOME", "TMPDIR"]) {
579
+ const value = process.env[name];
580
+ if (value !== undefined && value !== "")
581
+ env[name] = value;
582
+ }
583
+ return env;
584
+ };
585
+ var capture = (binary, argv) => Effect8.gen(function* () {
178
586
  const spawner = yield* ChildProcessSpawner5.ChildProcessSpawner;
587
+ return yield* ChildProcess3.make(binary, argv, {
588
+ env: talosctlEnv(),
589
+ extendEnv: false,
590
+ forceKillAfter: Duration5.seconds(1),
591
+ stderr: "pipe",
592
+ stdin: "ignore",
593
+ stdout: "pipe"
594
+ }).pipe(spawner.spawn, Effect8.flatMap((child) => Effect8.all({
595
+ exitCode: child.exitCode,
596
+ stderr: child.stderr.pipe(Stream3.decodeText, Stream3.mkString, Effect8.map((text) => text.trim())),
597
+ stdout: child.stdout.pipe(Stream3.decodeText, Stream3.mkString, Effect8.map((text) => text.trim()))
598
+ }, { concurrency: "unbounded" })), Effect8.scoped);
599
+ });
600
+ var VERSION_PROBE_TIMEOUT = Duration5.seconds(10);
601
+ var reportsPinnedVersion = (stdout) => stdout.split(/[^\w.+-]+/).includes(TALOSCTL_PINNED_VERSION);
602
+ var checkOverrideVersion = (binary, source, timeout = VERSION_PROBE_TIMEOUT) => capture(binary, ["version", "--client"]).pipe(Effect8.timeoutOrElse({
603
+ duration: timeout,
604
+ orElse: () => Effect8.fail(new TalosBinaryRefused("version --client", `${source} did not answer within ${String(Duration5.toSeconds(timeout))}s`, binary))
605
+ }), 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))));
606
+ var talosctl = (args, options) => Effect8.gen(function* () {
607
+ const requested = talosctlBinary(options.binary);
608
+ const resolved = requested === DEFAULT_TALOSCTL_BINARY ? yield* resolveDefault(requested) : requested;
609
+ const fromOption = options.binary?.trim();
610
+ const source = requested === DEFAULT_TALOSCTL_BINARY ? "talosctl on PATH" : fromOption !== undefined && fromOption !== "" ? "the `binary` option" : TALOSCTL_BINARY_ENV;
611
+ const binary = yield* checkBinaryPath(resolved, source);
612
+ yield* checkOverrideVersion(binary, source, options.versionProbeTimeout);
179
613
  const argv = [
180
614
  ...args,
181
615
  "--talosconfig",
@@ -184,119 +618,108 @@ var talosctl = (args, options) => Effect3.gen(function* () {
184
618
  ...options.endpoints === undefined ? [] : ["--endpoints", options.endpoints.join(",")],
185
619
  ...options.insecure === true ? ["--insecure"] : []
186
620
  ];
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);
621
+ const result = yield* capture(binary, argv);
198
622
  const command = args.join(" ");
199
623
  if (result.exitCode !== 0) {
200
- return yield* Effect3.fail(new TalosError(command, result.exitCode, result.stderr.slice(0, 300)));
624
+ return yield* Effect8.fail(new TalosError(command, result.exitCode, result.stderr.slice(0, 300), binary));
201
625
  }
202
626
  return result.stdout;
203
627
  });
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("")));
628
+ 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
629
 
206
630
  // src/talos/kubeconfig.ts
207
631
  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* () {
632
+ var readVaultMeta = (props, key) => readKvValue(props.target.mount, key, ["kubeconfig", "config"]).pipe(Effect9.catchIf(isVaultKeyAbsent, () => Effect9.succeed(undefined)));
633
+ var readKubeconfig = (props) => Effect9.gen(function* () {
211
634
  const key = props.kubeconfigKey ?? DEFAULT_KUBECONFIG_KEY;
212
635
  const raw = yield* readVaultMeta(props, key);
213
636
  if (raw === undefined)
214
637
  return;
215
638
  const meta = buildAttrs(raw, props);
216
639
  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."));
640
+ 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
641
  }
219
642
  return meta;
220
643
  });
221
- var diffKubeconfig = (news, output) => Effect4.gen(function* () {
644
+ var diffKubeconfig = (news, output) => Effect9.gen(function* () {
222
645
  if (output === undefined || !isResolved(news))
223
646
  return;
224
647
  const live = yield* readKubeconfig(news);
225
- if (live !== undefined && live.credentialGeneration === output.credentialGeneration && live.context === output.context && live.endpoint === output.endpoint) {
648
+ if (live !== undefined && live.credentialGeneration === output.credentialGeneration && live.context === output.context && !("connection" in output) && live.endpoint === output.endpoint) {
226
649
  return { action: "noop" };
227
650
  }
228
651
  return { action: "update" };
229
652
  });
230
- var reconcileKubeconfig = (props, output) => Effect4.scoped(Effect4.gen(function* () {
653
+ var reconcileKubeconfig = (props, output) => Effect9.scoped(Effect9.gen(function* () {
231
654
  const key = props.kubeconfigKey ?? DEFAULT_KUBECONFIG_KEY;
232
655
  if (output !== undefined && output.credentialGeneration !== "") {
233
656
  const raw2 = yield* readKvValue(props.target.mount, key, ["kubeconfig", "config"]);
234
657
  const meta2 = buildAttrs(raw2, props);
235
658
  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.`));
659
+ 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
660
  }
238
661
  return meta2;
239
662
  }
240
663
  const credential = yield* mintTalosconfig(props.target);
241
664
  const { path: outPath } = yield* reservedTempPath(`${props.target.cluster}-${props.node}-kubeconfig-out`);
242
665
  yield* talosctl(["kubeconfig", outPath, "--merge=false", "--force", "--force-context-name", props.context], { nodes: [props.node], talosconfigPath: credential.talosconfigPath });
243
- const raw = yield* Effect4.tryPromise({
666
+ const raw = yield* Effect9.tryPromise({
244
667
  catch: (cause) => new Error(`${outPath}: reading generated kubeconfig: ${String(cause)}`),
245
- try: () => Bun.file(outPath).text()
668
+ try: () => readFile(outPath, "utf8")
246
669
  });
247
670
  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."));
671
+ 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
672
  }
250
- yield* Effect4.try({
673
+ yield* Effect9.try({
251
674
  catch: (cause) => new Error(`${outPath}: chmod 0600: ${String(cause)}`),
252
675
  try: () => chmodSync(outPath, 384)
253
676
  });
254
677
  const meta = buildAttrs(raw, props);
255
678
  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}.`));
679
+ return yield* Effect9.die(new Error(`${props.node}: talosctl kubeconfig wrote a file that does not parse for context ` + `${props.context}.`));
257
680
  }
258
681
  yield* writeKvValue(props.target.mount, key, "kubeconfig", raw);
259
682
  return meta;
260
683
  }));
261
684
  var handlers = {
262
- delete: () => Effect4.void,
685
+ delete: () => Effect9.void,
263
686
  diff: ({
264
687
  news,
265
688
  output
266
689
  }) => diffKubeconfig(news, output),
267
- list: () => Effect4.succeed([]),
690
+ list: () => Effect9.succeed([]),
268
691
  read: ({ olds }) => readKubeconfig(olds),
269
692
  reconcile: ({
270
693
  news,
271
694
  output
272
695
  }) => reconcileKubeconfig(news, output)
273
696
  };
274
- var TalosKubeconfigProvider = () => Provider.effect(TalosKubeconfig, Effect4.succeed(TalosKubeconfig.Provider.of(handlers)));
697
+ var TalosKubeconfigProvider = () => Provider.effect(TalosKubeconfig, Effect9.succeed(TalosKubeconfig.Provider.of(handlers)));
275
698
  // src/talos/talos-bootstrap.ts
276
699
  import { Resource as Resource2 } from "alchemy";
277
700
  import { isResolved as isResolved2 } from "alchemy/Diff";
278
701
  import * as Provider2 from "alchemy/Provider";
279
- import * as Effect6 from "effect/Effect";
702
+ import * as Effect11 from "effect/Effect";
280
703
 
281
704
  // src/talos/talos-bootstrap-etcd.ts
282
- import * as Effect5 from "effect/Effect";
705
+ import * as Effect10 from "effect/Effect";
283
706
 
284
707
  // src/talos/talos-errors.ts
285
- import * as Data from "effect/Data";
708
+ import * as Data2 from "effect/Data";
286
709
 
287
- class TalosConfigDigestMismatch extends Data.TaggedError("TalosConfigDigestMismatch") {
710
+ class TalosConfigDigestMismatch extends Data2.TaggedError("TalosConfigDigestMismatch") {
288
711
  get message() {
289
712
  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
713
  }
291
714
  }
292
715
 
293
- class TalosConvergenceTimeout extends Data.TaggedError("TalosConvergenceTimeout") {
716
+ class TalosConvergenceTimeout extends Data2.TaggedError("TalosConvergenceTimeout") {
294
717
  get message() {
295
718
  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
719
  }
297
720
  }
298
721
 
299
- class TalosReBootstrapRefused extends Data.TaggedError("TalosReBootstrapRefused") {
722
+ class TalosReBootstrapRefused extends Data2.TaggedError("TalosReBootstrapRefused") {
300
723
  get message() {
301
724
  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
725
  }
@@ -306,28 +729,28 @@ class TalosReBootstrapRefused extends Data.TaggedError("TalosReBootstrapRefused"
306
729
  var isBootstrapped = (props, talosconfigPath) => talosctl(["get", "etcdmembers", "-o", "json"], {
307
730
  nodes: [props.node],
308
731
  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({
732
+ }).pipe(Effect10.map((text) => text.includes('"id"') || text.includes('"member"')));
733
+ var assertNoLivePeers = (props, talosconfigPath) => Effect10.all((props.peers ?? []).map((peer) => isBootstrapped({ ...props, node: peer }, talosconfigPath).pipe(Effect10.matchEffect({
734
+ onFailure: (cause) => Effect10.fail(new TalosReBootstrapRefused({
312
735
  detail: `peer ${peer}: live read failed before bootstrap: ${String(cause)}`,
313
736
  node: props.node
314
737
  })),
315
- onSuccess: (hasMembers) => hasMembers ? Effect5.fail(new TalosReBootstrapRefused({
738
+ onSuccess: (hasMembers) => hasMembers ? Effect10.fail(new TalosReBootstrapRefused({
316
739
  detail: `peer ${peer} already reports etcd members — a cluster may already exist; ` + "refusing to bootstrap a second one",
317
740
  node: props.node
318
- })) : Effect5.void
741
+ })) : Effect10.void
319
742
  }))), { discard: true });
320
743
 
321
744
  // src/talos/talos-bootstrap.ts
322
745
  var TalosBootstrap = Resource2("Talos.Bootstrap", {
323
746
  defaultRemovalPolicy: "retain"
324
747
  });
325
- var readBootstrap = (props) => Effect6.scoped(Effect6.gen(function* () {
748
+ var readBootstrap = (props) => Effect11.scoped(Effect11.gen(function* () {
326
749
  const credential = yield* mintTalosconfig(props.target);
327
750
  const bootstrapped = yield* isBootstrapped(props, credential.talosconfigPath);
328
751
  return bootstrapped ? { bootstrapped: true, node: props.node } : undefined;
329
752
  }));
330
- var diffBootstrap = (news, output) => Effect6.gen(function* () {
753
+ var diffBootstrap = (news, output) => Effect11.gen(function* () {
331
754
  if (output === undefined || !isResolved2(news))
332
755
  return;
333
756
  if (output.bootstrapped)
@@ -335,15 +758,15 @@ var diffBootstrap = (news, output) => Effect6.gen(function* () {
335
758
  const live = yield* readBootstrap(news);
336
759
  return live?.bootstrapped === true ? { action: "noop" } : { action: "update" };
337
760
  });
338
- var reconcileBootstrap = (props, output) => Effect6.scoped(Effect6.gen(function* () {
761
+ var reconcileBootstrap = (props, output) => Effect11.scoped(Effect11.gen(function* () {
339
762
  const credential = yield* mintTalosconfig(props.target);
340
763
  if (output?.bootstrapped === true) {
341
- const stillBootstrapped = yield* isBootstrapped(props, credential.talosconfigPath).pipe(Effect6.catchIf(() => true, (cause) => Effect6.fail(new TalosReBootstrapRefused({
764
+ const stillBootstrapped = yield* isBootstrapped(props, credential.talosconfigPath).pipe(Effect11.catchIf(() => true, (cause) => Effect11.fail(new TalosReBootstrapRefused({
342
765
  detail: `live read failed: ${String(cause)}`,
343
766
  node: props.node
344
767
  }))));
345
768
  if (!stillBootstrapped) {
346
- return yield* Effect6.fail(new TalosReBootstrapRefused({
769
+ return yield* Effect11.fail(new TalosReBootstrapRefused({
347
770
  detail: "etcd members are absent on a live read against already-bootstrapped state",
348
771
  node: props.node
349
772
  }));
@@ -360,31 +783,104 @@ var reconcileBootstrap = (props, output) => Effect6.scoped(Effect6.gen(function*
360
783
  }
361
784
  const after = yield* isBootstrapped(props, credential.talosconfigPath);
362
785
  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."));
786
+ 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
787
  }
365
788
  return { bootstrapped: true, node: props.node };
366
789
  }));
367
790
  var handlers2 = {
368
- delete: () => Effect6.void,
791
+ delete: () => Effect11.void,
369
792
  diff: ({
370
793
  news,
371
794
  output
372
795
  }) => diffBootstrap(news, output),
373
- list: () => Effect6.succeed([]),
796
+ list: () => Effect11.succeed([]),
374
797
  read: ({ olds }) => readBootstrap(olds),
375
798
  reconcile: ({
376
799
  news,
377
800
  output
378
801
  }) => reconcileBootstrap(news, output)
379
802
  };
380
- var TalosBootstrapProvider = () => Provider2.effect(TalosBootstrap, Effect6.succeed(TalosBootstrap.Provider.of(handlers2)));
381
- // src/talos/talos-cluster-health.ts
803
+ var TalosBootstrapProvider = () => Provider2.effect(TalosBootstrap, Effect11.succeed(TalosBootstrap.Provider.of(handlers2)));
804
+ // src/talos/talos-cluster-identity.ts
382
805
  import { Resource as Resource3 } from "alchemy";
383
806
  import { isResolved as isResolved3 } from "alchemy/Diff";
384
807
  import * as Provider3 from "alchemy/Provider";
385
- import * as Effect7 from "effect/Effect";
386
- var TalosClusterHealth = Resource3("Talos.ClusterHealth");
808
+ import * as Effect12 from "effect/Effect";
809
+ var TalosClusterIdentity = Resource3("Talos.ClusterIdentity");
810
+ var readClusterIdentity = (props, output) => Effect12.gen(function* () {
811
+ const cluster = props.target.cluster;
812
+ const uid = yield* withConnectDeadline(cluster, Effect12.gen(function* () {
813
+ const transport = yield* openTransport(talosVaultLocation(props));
814
+ return yield* readClusterUid(cluster, transport);
815
+ }));
816
+ if (output !== undefined && uid !== output.uid) {
817
+ return yield* Effect12.fail(new TalosClusterMoved({ cluster, live: uid, saved: output.uid }));
818
+ }
819
+ return { uid };
820
+ });
821
+ var reconcileClusterIdentity = (props, output) => readClusterIdentity(props, output);
822
+ var diffClusterIdentity = (news, output) => Effect12.gen(function* () {
823
+ if (output === undefined || !isResolved3(news))
824
+ return;
825
+ yield* reconcileClusterIdentity(news, output);
826
+ return { action: "noop" };
827
+ });
828
+ var handlers3 = {
829
+ delete: () => Effect12.void,
830
+ diff: ({
831
+ news,
832
+ output
833
+ }) => diffClusterIdentity(news, output),
834
+ list: () => Effect12.succeed([]),
835
+ read: ({
836
+ olds,
837
+ output
838
+ }) => readClusterIdentity(olds, output),
839
+ reconcile: ({
840
+ news,
841
+ output
842
+ }) => reconcileClusterIdentity(news, output)
843
+ };
844
+ var TalosClusterIdentityProvider = () => Provider3.effect(TalosClusterIdentity, Effect12.succeed(TalosClusterIdentity.Provider.of(handlers3)));
845
+ // src/talos/talos-cluster-health.ts
846
+ import { Resource as Resource4 } from "alchemy";
847
+ import { isResolved as isResolved4 } from "alchemy/Diff";
848
+ import * as Provider4 from "alchemy/Provider";
849
+ import * as Effect14 from "effect/Effect";
850
+
851
+ // src/talos/literal-connection.ts
852
+ import { hasUnresolvedInputs } from "alchemy/Diff";
853
+ import * as Effect13 from "effect/Effect";
854
+ var literalConnectionRefusal = (connection) => {
855
+ const auth = connection?.auth;
856
+ return hasUnresolvedInputs(connection) || auth?.kind !== "talos-openbao" || typeof auth?.uid !== "string" || auth.uid === "" ? new TalosUidNotLiteral({}) : undefined;
857
+ };
858
+ var withLiteralConnection = (resource) => new Proxy(resource, {
859
+ apply: (target, thisArg, args) => {
860
+ const refusal = (given) => literalConnectionRefusal(given?.connection);
861
+ const props = args[1];
862
+ if (Effect13.isEffect(props)) {
863
+ const checked = props.pipe(Effect13.flatMap((resolved) => {
864
+ const refused2 = refusal(resolved);
865
+ return refused2 === undefined ? Effect13.succeed(resolved) : Effect13.fail(refused2);
866
+ }));
867
+ return Reflect.apply(target, thisArg, [args[0], checked, ...args.slice(2)]);
868
+ }
869
+ const refused = refusal(props);
870
+ return refused === undefined ? Reflect.apply(target, thisArg, args) : Effect13.fail(refused);
871
+ }
872
+ });
873
+
874
+ // src/talos/talos-cluster-health.ts
875
+ var TalosClusterHealth = withLiteralConnection(Resource4("Talos.ClusterHealth"));
387
876
  var nodeCsv = (nodes) => nodes === undefined || nodes.length === 0 ? "" : nodes.join(",");
877
+ var attributes = (props, healthy) => ({
878
+ connection: props.connection,
879
+ controlPlaneNodes: nodeCsv(props.controlPlaneNodes),
880
+ healthy,
881
+ workerNodes: nodeCsv(props.workerNodes)
882
+ });
883
+ var sameConnection = (left, right) => right?.auth?.kind === left.auth.kind && right.auth.uid === left.auth.uid && !Object.hasOwn(right.auth, "cluster");
388
884
  var healthArgs = (props, waitTimeout) => {
389
885
  const args = ["health", "--wait-timeout", waitTimeout];
390
886
  const cp = nodeCsv(props.controlPlaneNodes);
@@ -395,79 +891,75 @@ var healthArgs = (props, waitTimeout) => {
395
891
  args.push("--worker-nodes", workers);
396
892
  return args;
397
893
  };
398
- var check = (props, waitTimeout) => Effect7.scoped(Effect7.gen(function* () {
894
+ var check = (props, waitTimeout) => Effect14.scoped(Effect14.gen(function* () {
399
895
  const credential = yield* mintTalosconfig(props.target);
400
896
  const contact = props.controlPlaneNodes[0];
401
897
  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."));
898
+ return yield* Effect14.fail(new Error(`Talos.ClusterHealth ${props.target.cluster}: controlPlaneNodes is empty — need at ` + "least one node to contact."));
403
899
  }
404
900
  yield* talosctl(healthArgs(props, waitTimeout), {
405
901
  endpoints: [contact],
406
902
  nodes: [contact],
407
903
  talosconfigPath: credential.talosconfigPath
408
904
  });
409
- return {
410
- controlPlaneNodes: nodeCsv(props.controlPlaneNodes),
411
- healthy: true,
412
- workerNodes: nodeCsv(props.workerNodes)
413
- };
905
+ return attributes(props, true);
414
906
  }));
415
907
  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))
908
+ var readClusterHealth = (props) => check(props, "5s").pipe(Effect14.catchIf(isTalosError, () => Effect14.succeed(attributes(props, false))));
909
+ var diffClusterHealth = (news, output) => Effect14.gen(function* () {
910
+ const refused = literalConnectionRefusal(news.connection);
911
+ if (refused !== undefined)
912
+ return yield* Effect14.fail(refused);
913
+ if (output === undefined || !isResolved4(news))
423
914
  return;
424
915
  const live = yield* readClusterHealth(news);
425
- if (live.healthy)
916
+ if (live.healthy && sameConnection(live.connection, output.connection)) {
426
917
  return { action: "noop" };
918
+ }
427
919
  return { action: "update" };
428
920
  });
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,
921
+ 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)}`))));
922
+ var handlers4 = {
923
+ delete: () => Effect14.void,
432
924
  diff: ({
433
925
  news,
434
926
  output
435
927
  }) => diffClusterHealth(news, output),
436
- list: () => Effect7.succeed([]),
928
+ list: () => Effect14.succeed([]),
437
929
  read: ({ olds }) => readClusterHealth(olds),
438
930
  reconcile: ({ news }) => reconcileClusterHealth(news)
439
931
  };
440
- var TalosClusterHealthProvider = () => Provider3.effect(TalosClusterHealth, Effect7.succeed(TalosClusterHealth.Provider.of(handlers3)));
932
+ var TalosClusterHealthProvider = () => Provider4.effect(TalosClusterHealth, Effect14.succeed(TalosClusterHealth.Provider.of(handlers4)));
441
933
  // 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";
934
+ import { Resource as Resource5 } from "alchemy";
935
+ import * as Provider5 from "alchemy/Provider";
936
+ import * as Effect17 from "effect/Effect";
445
937
 
446
938
  // src/talos/machine-config-read.ts
447
939
  import { Unowned } from "alchemy/AdoptPolicy";
448
- import { isResolved as isResolved4 } from "alchemy/Diff";
449
- import * as Effect8 from "effect/Effect";
940
+ import { isResolved as isResolved5 } from "alchemy/Diff";
941
+ import * as Effect15 from "effect/Effect";
450
942
  import * as Result from "effect/Result";
451
943
  var liveSpecDigest = (props, talosconfigPath) => talosctl(["get", "machineconfig", "v1alpha1", "-o", "yaml"], {
452
944
  endpoints: [props.node],
453
945
  nodes: [props.node],
454
946
  talosconfigPath
455
- }).pipe(Effect8.flatMap((wrapperYaml) => {
947
+ }).pipe(Effect15.flatMap((wrapperYaml) => {
456
948
  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));
949
+ 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
950
  }));
459
951
  var probeMaintenanceMode = (props, talosconfigPath) => talosctl(["version"], {
460
952
  endpoints: [props.node],
461
953
  insecure: true,
462
954
  nodes: [props.node],
463
955
  talosconfigPath
464
- }).pipe(Effect8.as(true), Effect8.orElseSucceed(() => false));
465
- var readMachineConfig = (props) => Effect8.scoped(Effect8.gen(function* () {
956
+ }).pipe(Effect15.as(true), Effect15.orElseSucceed(() => false));
957
+ var readMachineConfig = (props) => Effect15.scoped(Effect15.gen(function* () {
466
958
  const credential = yield* mintTalosconfig(props.target);
467
- const outcome = yield* Effect8.result(liveSpecDigest(props, credential.talosconfigPath));
959
+ const outcome = yield* Effect15.result(liveSpecDigest(props, credential.talosconfigPath));
468
960
  if (Result.isFailure(outcome)) {
469
961
  const reachable = yield* probeMaintenanceMode(props, credential.talosconfigPath);
470
- return reachable ? undefined : yield* Effect8.fail(outcome.failure);
962
+ return reachable ? undefined : yield* Effect15.fail(outcome.failure);
471
963
  }
472
964
  const attrs = {
473
965
  configDigest: props.configDigest,
@@ -477,8 +969,8 @@ var readMachineConfig = (props) => Effect8.scoped(Effect8.gen(function* () {
477
969
  };
478
970
  return attrs.converged === false ? Unowned(attrs) : attrs;
479
971
  }));
480
- var diffMachineConfig = (news, output) => Effect8.gen(function* () {
481
- if (output === undefined || !isResolved4(news))
972
+ var diffMachineConfig = (news, output) => Effect15.gen(function* () {
973
+ if (output === undefined || !isResolved5(news))
482
974
  return;
483
975
  const live = yield* readMachineConfig(news);
484
976
  if (live !== undefined && live.converged !== false)
@@ -487,40 +979,40 @@ var diffMachineConfig = (news, output) => Effect8.gen(function* () {
487
979
  });
488
980
 
489
981
  // src/talos/machine-config-poll.ts
490
- import * as Effect9 from "effect/Effect";
982
+ import * as Effect16 from "effect/Effect";
491
983
  var isRebootish = (mode) => mode === "reboot" || mode === "auto";
492
984
  var POLICY = {
493
985
  immediate: { intervalMs: 2000, maxAttempts: 5 },
494
986
  rebootish: { intervalMs: 5000, maxAttempts: 24 }
495
987
  };
496
- var pollUntilMatch = (check2, policy) => Effect9.gen(function* () {
988
+ var pollUntilMatch = (check2, policy) => Effect16.gen(function* () {
497
989
  for (let attempt = 1;attempt <= policy.maxAttempts; attempt++) {
498
- const matched = yield* check2.pipe(Effect9.orElseSucceed(() => false));
990
+ const matched = yield* check2.pipe(Effect16.orElseSucceed(() => false));
499
991
  if (matched)
500
992
  return true;
501
993
  if (attempt < policy.maxAttempts)
502
- yield* Effect9.sleep(policy.intervalMs);
994
+ yield* Effect16.sleep(policy.intervalMs);
503
995
  }
504
996
  return false;
505
997
  });
506
- var confirmConverged = (node, mode, check2, policies = POLICY) => Effect9.gen(function* () {
998
+ var confirmConverged = (node, mode, check2, policies = POLICY) => Effect16.gen(function* () {
507
999
  const rebootish = isRebootish(mode);
508
1000
  const policy = rebootish ? policies.rebootish : policies.immediate;
509
1001
  const matched = yield* pollUntilMatch(check2, policy);
510
1002
  if (!matched) {
511
- return yield* Effect9.fail(new TalosConvergenceTimeout({ attempts: policy.maxAttempts, mode, node }));
1003
+ return yield* Effect16.fail(new TalosConvergenceTimeout({ attempts: policy.maxAttempts, mode, node }));
512
1004
  }
513
1005
  return rebootish ? "accepted" : "read-back";
514
1006
  });
515
1007
 
516
1008
  // src/talos/talos-machine-config.ts
517
- var TalosMachineConfig = Resource4("Talos.MachineConfig");
518
- var reconcileMachineConfig = (props, output, pollPolicies) => Effect10.scoped(Effect10.gen(function* () {
1009
+ var TalosMachineConfig = Resource5("Talos.MachineConfig");
1010
+ var reconcileMachineConfig = (props, output, pollPolicies) => Effect17.scoped(Effect17.gen(function* () {
519
1011
  const mode = props.mode ?? "auto";
520
1012
  const raw = yield* readKvValue(props.target.mount, props.configKey, ["config"]);
521
1013
  const gotDigest = configDigest(raw);
522
1014
  if (gotDigest !== props.configDigest) {
523
- return yield* Effect10.fail(new TalosConfigDigestMismatch({
1015
+ return yield* Effect17.fail(new TalosConfigDigestMismatch({
524
1016
  expected: props.configDigest,
525
1017
  got: gotDigest,
526
1018
  key: props.configKey,
@@ -539,7 +1031,7 @@ var reconcileMachineConfig = (props, output, pollPolicies) => Effect10.scoped(Ef
539
1031
  ...isCreate ? ["--insecure"] : []
540
1032
  ];
541
1033
  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);
1034
+ const converged = yield* confirmConverged(props.node, mode, liveSpecDigest(props, credential.talosconfigPath).pipe(Effect17.map((live) => live === props.configDigest)), pollPolicies);
543
1035
  return {
544
1036
  configDigest: props.configDigest,
545
1037
  converged,
@@ -547,30 +1039,51 @@ var reconcileMachineConfig = (props, output, pollPolicies) => Effect10.scoped(Ef
547
1039
  node: props.node
548
1040
  };
549
1041
  }));
550
- var handlers4 = {
551
- delete: () => Effect10.void,
1042
+ var handlers5 = {
1043
+ delete: () => Effect17.void,
552
1044
  diff: ({
553
1045
  news,
554
1046
  output
555
1047
  }) => diffMachineConfig(news, output),
556
- list: () => Effect10.succeed([]),
1048
+ list: () => Effect17.succeed([]),
557
1049
  read: ({ olds }) => readMachineConfig(olds),
558
1050
  reconcile: ({
559
1051
  news,
560
1052
  output
561
1053
  }) => reconcileMachineConfig(news, output)
562
1054
  };
563
- var TalosMachineConfigProvider = () => Provider4.effect(TalosMachineConfig, Effect10.succeed(TalosMachineConfig.Provider.of(handlers4)));
1055
+ var TalosMachineConfigProvider = () => Provider5.effect(TalosMachineConfig, Effect17.succeed(TalosMachineConfig.Provider.of(handlers5)));
564
1056
  export {
1057
+ DEFAULT_TALOSCTL_BINARY,
1058
+ TALOSCTL_BINARY_ENV,
565
1059
  TalosBootstrap,
566
1060
  TalosBootstrapProvider,
567
1061
  TalosClusterHealth,
568
1062
  TalosClusterHealthProvider,
1063
+ TalosClusterIdentity,
1064
+ TalosClusterIdentityMismatch,
1065
+ TalosClusterIdentityMissing,
1066
+ TalosClusterIdentityProvider,
1067
+ TalosClusterIdentityTimeout,
1068
+ TalosClusterIdentityUnreadable,
1069
+ TalosClusterMoved,
569
1070
  TalosKubeconfig,
570
1071
  TalosKubeconfigProvider,
1072
+ TalosKubeconfigUnreadable,
571
1073
  TalosMachineConfig,
572
- TalosMachineConfigProvider
1074
+ TalosMachineConfigProvider,
1075
+ TalosOpenBaoAdapter,
1076
+ TalosOpenBaoAmbiguousUid,
1077
+ TalosOpenBaoAuthKind,
1078
+ TalosOpenBaoConnectTimeout,
1079
+ TalosOpenBaoLegacyAuth,
1080
+ TalosOpenBaoUnknownCluster,
1081
+ TalosUidNotLiteral,
1082
+ TalosVaultKeyMissing,
1083
+ connectTalosOpenBao,
1084
+ talosOpenBaoCluster,
1085
+ talosOpenBaoConnection
573
1086
  };
574
1087
 
575
- //# debugId=33FF194E493FB6EF64756E2164756E21
1088
+ //# debugId=E9E48AF60568B1C664756E2164756E21
576
1089
  //# sourceMappingURL=index.js.map