@homeflare/alchemy 0.5.0 → 0.7.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 (288) hide show
  1. package/README.md +68 -55
  2. package/dist/caddy/admin-calls.d.ts +48 -0
  3. package/dist/caddy/admin-calls.d.ts.map +1 -0
  4. package/dist/caddy/admin-guard.d.ts +40 -0
  5. package/dist/caddy/admin-guard.d.ts.map +1 -0
  6. package/dist/caddy/admin.d.ts +73 -0
  7. package/dist/caddy/admin.d.ts.map +1 -0
  8. package/dist/caddy/caddyfile-tripwire.d.ts +5 -0
  9. package/dist/caddy/caddyfile-tripwire.d.ts.map +1 -0
  10. package/dist/caddy/config-form.d.ts +27 -0
  11. package/dist/caddy/config-form.d.ts.map +1 -0
  12. package/dist/caddy/config-lifecycle.d.ts +45 -0
  13. package/dist/caddy/config-lifecycle.d.ts.map +1 -0
  14. package/dist/caddy/config.d.ts +30 -0
  15. package/dist/caddy/config.d.ts.map +1 -0
  16. package/dist/caddy/digest.d.ts +7 -0
  17. package/dist/caddy/digest.d.ts.map +1 -0
  18. package/dist/caddy/fake-caddy.d.ts +43 -0
  19. package/dist/caddy/fake-caddy.d.ts.map +1 -0
  20. package/dist/caddy/index.d.ts +21 -0
  21. package/dist/caddy/index.d.ts.map +1 -0
  22. package/dist/caddy/index.js +912 -0
  23. package/dist/caddy/index.js.map +28 -0
  24. package/dist/caddy/local-admin.d.ts +45 -0
  25. package/dist/caddy/local-admin.d.ts.map +1 -0
  26. package/dist/caddy/providers.d.ts +14 -0
  27. package/dist/caddy/providers.d.ts.map +1 -0
  28. package/dist/caddy/with-file.d.ts +47 -0
  29. package/dist/caddy/with-file.d.ts.map +1 -0
  30. package/dist/cloudflare/fake-mesh.d.ts +58 -0
  31. package/dist/cloudflare/fake-mesh.d.ts.map +1 -0
  32. package/dist/cloudflare/index.d.ts +9 -3
  33. package/dist/cloudflare/index.d.ts.map +1 -1
  34. package/dist/cloudflare/index.js +218 -7
  35. package/dist/cloudflare/index.js.map +9 -4
  36. package/dist/cloudflare/mesh-node-api.d.ts +75 -0
  37. package/dist/cloudflare/mesh-node-api.d.ts.map +1 -0
  38. package/dist/cloudflare/mesh-node-form.d.ts +82 -0
  39. package/dist/cloudflare/mesh-node-form.d.ts.map +1 -0
  40. package/dist/cloudflare/mesh-node-harness.d.ts +46 -0
  41. package/dist/cloudflare/mesh-node-harness.d.ts.map +1 -0
  42. package/dist/cloudflare/mesh-node-lifecycle.d.ts +22 -0
  43. package/dist/cloudflare/mesh-node-lifecycle.d.ts.map +1 -0
  44. package/dist/cloudflare/mesh-node-token.d.ts +23 -0
  45. package/dist/cloudflare/mesh-node-token.d.ts.map +1 -0
  46. package/dist/cloudflare/mesh-node.d.ts +48 -0
  47. package/dist/cloudflare/mesh-node.d.ts.map +1 -0
  48. package/dist/cloudflare/providers.d.ts +1 -12
  49. package/dist/cloudflare/providers.d.ts.map +1 -1
  50. package/dist/index.d.ts +2 -0
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/launchd/fake-runner.d.ts +36 -0
  53. package/dist/launchd/fake-runner.d.ts.map +1 -0
  54. package/dist/launchd/host-effect.d.ts +19 -0
  55. package/dist/launchd/host-effect.d.ts.map +1 -0
  56. package/dist/launchd/host-file-form.d.ts +24 -0
  57. package/dist/launchd/host-file-form.d.ts.map +1 -0
  58. package/dist/launchd/host-file-lifecycle.d.ts +29 -0
  59. package/dist/launchd/host-file-lifecycle.d.ts.map +1 -0
  60. package/dist/launchd/host-file.d.ts +25 -0
  61. package/dist/launchd/host-file.d.ts.map +1 -0
  62. package/dist/launchd/host-lookup.d.ts +27 -0
  63. package/dist/launchd/host-lookup.d.ts.map +1 -0
  64. package/dist/launchd/index.d.ts +21 -0
  65. package/dist/launchd/index.d.ts.map +1 -0
  66. package/dist/launchd/index.js +969 -0
  67. package/dist/launchd/index.js.map +25 -0
  68. package/dist/launchd/job-form.d.ts +90 -0
  69. package/dist/launchd/job-form.d.ts.map +1 -0
  70. package/dist/launchd/job-lifecycle.d.ts +34 -0
  71. package/dist/launchd/job-lifecycle.d.ts.map +1 -0
  72. package/dist/launchd/job-preflight.d.ts +50 -0
  73. package/dist/launchd/job-preflight.d.ts.map +1 -0
  74. package/dist/launchd/job-validate.d.ts +25 -0
  75. package/dist/launchd/job-validate.d.ts.map +1 -0
  76. package/dist/launchd/job.d.ts +34 -0
  77. package/dist/launchd/job.d.ts.map +1 -0
  78. package/dist/launchd/launchctl.d.ts +62 -0
  79. package/dist/launchd/launchctl.d.ts.map +1 -0
  80. package/dist/launchd/local-runner.d.ts +7 -0
  81. package/dist/launchd/local-runner.d.ts.map +1 -0
  82. package/dist/launchd/plist.d.ts +41 -0
  83. package/dist/launchd/plist.d.ts.map +1 -0
  84. package/dist/launchd/providers.d.ts +15 -0
  85. package/dist/launchd/providers.d.ts.map +1 -0
  86. package/dist/launchd/runner.d.ts +91 -0
  87. package/dist/launchd/runner.d.ts.map +1 -0
  88. package/dist/launchd/secret-tripwire.d.ts +24 -0
  89. package/dist/launchd/secret-tripwire.d.ts.map +1 -0
  90. package/dist/openbao/auth-method-form.d.ts +7 -0
  91. package/dist/openbao/auth-method-form.d.ts.map +1 -1
  92. package/dist/openbao/auth-method-reconcile.d.ts +15 -0
  93. package/dist/openbao/auth-method-reconcile.d.ts.map +1 -0
  94. package/dist/openbao/auth-method.d.ts +6 -0
  95. package/dist/openbao/auth-method.d.ts.map +1 -1
  96. package/dist/openbao/auth-role-form.d.ts +9 -0
  97. package/dist/openbao/auth-role-form.d.ts.map +1 -1
  98. package/dist/openbao/auth-role.d.ts.map +1 -1
  99. package/dist/openbao/auth-token-form.d.ts +48 -0
  100. package/dist/openbao/auth-token-form.d.ts.map +1 -0
  101. package/dist/openbao/bao-identity.d.ts +81 -0
  102. package/dist/openbao/bao-identity.d.ts.map +1 -0
  103. package/dist/openbao/bao-namespace.d.ts +20 -0
  104. package/dist/openbao/bao-namespace.d.ts.map +1 -0
  105. package/dist/openbao/cloudflare-role.d.ts.map +1 -1
  106. package/dist/openbao/cloudflare-roles-expand.d.ts.map +1 -1
  107. package/dist/openbao/fake-engines.d.ts +30 -0
  108. package/dist/openbao/fake-engines.d.ts.map +1 -0
  109. package/dist/openbao/fake-stack.d.ts +29 -0
  110. package/dist/openbao/fake-stack.d.ts.map +1 -0
  111. package/dist/openbao/host-approles.d.ts +62 -0
  112. package/dist/openbao/host-approles.d.ts.map +1 -0
  113. package/dist/openbao/index.d.ts +19 -0
  114. package/dist/openbao/index.d.ts.map +1 -1
  115. package/dist/openbao/index.js +1518 -310
  116. package/dist/openbao/index.js.map +40 -17
  117. package/dist/openbao/jwt-config-form.d.ts +51 -0
  118. package/dist/openbao/jwt-config-form.d.ts.map +1 -0
  119. package/dist/openbao/jwt-config.d.ts +25 -0
  120. package/dist/openbao/jwt-config.d.ts.map +1 -0
  121. package/dist/openbao/jwt-role-form.d.ts +91 -0
  122. package/dist/openbao/jwt-role-form.d.ts.map +1 -0
  123. package/dist/openbao/jwt-role.d.ts +31 -0
  124. package/dist/openbao/jwt-role.d.ts.map +1 -0
  125. package/dist/openbao/kubernetes-role-form.d.ts +68 -0
  126. package/dist/openbao/kubernetes-role-form.d.ts.map +1 -0
  127. package/dist/openbao/kubernetes-role.d.ts +25 -0
  128. package/dist/openbao/kubernetes-role.d.ts.map +1 -0
  129. package/dist/openbao/mfa-enforcement-form.d.ts +46 -0
  130. package/dist/openbao/mfa-enforcement-form.d.ts.map +1 -0
  131. package/dist/openbao/mfa-enforcement.d.ts +41 -0
  132. package/dist/openbao/mfa-enforcement.d.ts.map +1 -0
  133. package/dist/openbao/mfa-totp-form.d.ts +52 -0
  134. package/dist/openbao/mfa-totp-form.d.ts.map +1 -0
  135. package/dist/openbao/mfa-totp.d.ts +33 -0
  136. package/dist/openbao/mfa-totp.d.ts.map +1 -0
  137. package/dist/openbao/mfa-wire.d.ts +63 -0
  138. package/dist/openbao/mfa-wire.d.ts.map +1 -0
  139. package/dist/openbao/mount-form.d.ts +7 -0
  140. package/dist/openbao/mount-form.d.ts.map +1 -1
  141. package/dist/openbao/mount-move.d.ts +49 -0
  142. package/dist/openbao/mount-move.d.ts.map +1 -0
  143. package/dist/openbao/mount-path.d.ts +8 -4
  144. package/dist/openbao/mount-path.d.ts.map +1 -1
  145. package/dist/openbao/mount-reconcile.d.ts +16 -0
  146. package/dist/openbao/mount-reconcile.d.ts.map +1 -0
  147. package/dist/openbao/mount.d.ts +9 -0
  148. package/dist/openbao/mount.d.ts.map +1 -1
  149. package/dist/openbao/pki-role-canonical.d.ts +46 -0
  150. package/dist/openbao/pki-role-canonical.d.ts.map +1 -0
  151. package/dist/openbao/pki-role-form.d.ts +38 -30
  152. package/dist/openbao/pki-role-form.d.ts.map +1 -1
  153. package/dist/openbao/pki-role.d.ts.map +1 -1
  154. package/dist/openbao/policy.d.ts +2 -0
  155. package/dist/openbao/policy.d.ts.map +1 -1
  156. package/dist/openbao/proxmox-role-form.d.ts +18 -8
  157. package/dist/openbao/proxmox-role-form.d.ts.map +1 -1
  158. package/dist/openbao/proxmox-role-wire.d.ts +18 -0
  159. package/dist/openbao/proxmox-role-wire.d.ts.map +1 -0
  160. package/dist/openbao/proxmox-role.d.ts.map +1 -1
  161. package/dist/openbao/remount-wire.d.ts +44 -0
  162. package/dist/openbao/remount-wire.d.ts.map +1 -0
  163. package/dist/openbao/rename.d.ts +106 -0
  164. package/dist/openbao/rename.d.ts.map +1 -0
  165. package/dist/openbao/role-reconcile.d.ts +40 -0
  166. package/dist/openbao/role-reconcile.d.ts.map +1 -0
  167. package/docs/caddy.md +180 -0
  168. package/docs/launchd.md +167 -0
  169. package/docs/mesh-node.md +188 -0
  170. package/package.json +12 -2
  171. package/src/caddy/admin-calls.ts +128 -0
  172. package/src/caddy/admin-guard.ts +170 -0
  173. package/src/caddy/admin.ts +74 -0
  174. package/src/caddy/caddyfile-tripwire.ts +133 -0
  175. package/src/caddy/config-form.ts +64 -0
  176. package/src/caddy/config-lifecycle.test.ts +218 -0
  177. package/src/caddy/config-lifecycle.ts +148 -0
  178. package/src/caddy/config.ts +116 -0
  179. package/src/caddy/digest.ts +44 -0
  180. package/src/caddy/fake-caddy.ts +160 -0
  181. package/src/caddy/guards.test.ts +156 -0
  182. package/src/caddy/index.ts +25 -0
  183. package/src/caddy/local-admin.test.ts +160 -0
  184. package/src/caddy/local-admin.ts +189 -0
  185. package/src/caddy/providers.test.ts +244 -0
  186. package/src/caddy/providers.ts +17 -0
  187. package/src/caddy/with-file.ts +62 -0
  188. package/src/cloudflare/fake-mesh.ts +181 -0
  189. package/src/cloudflare/index.ts +9 -3
  190. package/src/cloudflare/mesh-node-api.ts +127 -0
  191. package/src/cloudflare/mesh-node-edges.test.ts +103 -0
  192. package/src/cloudflare/mesh-node-form.test.ts +80 -0
  193. package/src/cloudflare/mesh-node-form.ts +115 -0
  194. package/src/cloudflare/mesh-node-harness.ts +124 -0
  195. package/src/cloudflare/mesh-node-lifecycle.ts +187 -0
  196. package/src/cloudflare/mesh-node-policy.test.ts +151 -0
  197. package/src/cloudflare/mesh-node-token.test.ts +117 -0
  198. package/src/cloudflare/mesh-node-token.ts +125 -0
  199. package/src/cloudflare/mesh-node.test.ts +172 -0
  200. package/src/cloudflare/mesh-node.ts +120 -0
  201. package/src/cloudflare/providers.ts +20 -2
  202. package/src/index.ts +2 -0
  203. package/src/launchd/fake-runner.ts +176 -0
  204. package/src/launchd/host-effect.ts +28 -0
  205. package/src/launchd/host-file-form.ts +71 -0
  206. package/src/launchd/host-file-lifecycle.test.ts +198 -0
  207. package/src/launchd/host-file-lifecycle.ts +164 -0
  208. package/src/launchd/host-file.ts +64 -0
  209. package/src/launchd/host-lookup.test.ts +92 -0
  210. package/src/launchd/host-lookup.ts +94 -0
  211. package/src/launchd/index.ts +26 -0
  212. package/src/launchd/job-form.test.ts +171 -0
  213. package/src/launchd/job-form.ts +180 -0
  214. package/src/launchd/job-identity.test.ts +147 -0
  215. package/src/launchd/job-lifecycle.test.ts +220 -0
  216. package/src/launchd/job-lifecycle.ts +194 -0
  217. package/src/launchd/job-preflight.ts +152 -0
  218. package/src/launchd/job-validate.ts +134 -0
  219. package/src/launchd/job.ts +96 -0
  220. package/src/launchd/launchctl.test.ts +181 -0
  221. package/src/launchd/launchctl.ts +188 -0
  222. package/src/launchd/local-runner.test.ts +125 -0
  223. package/src/launchd/local-runner.ts +143 -0
  224. package/src/launchd/plist.test.ts +186 -0
  225. package/src/launchd/plist.ts +156 -0
  226. package/src/launchd/providers.test.ts +222 -0
  227. package/src/launchd/providers.ts +21 -0
  228. package/src/launchd/runner.ts +95 -0
  229. package/src/launchd/secret-tripwire.test.ts +110 -0
  230. package/src/launchd/secret-tripwire.ts +81 -0
  231. package/src/openbao/README.md +181 -0
  232. package/src/openbao/REPLACE.md +163 -0
  233. package/src/openbao/auth-method-form.ts +7 -0
  234. package/src/openbao/auth-method-reconcile.ts +74 -0
  235. package/src/openbao/auth-method.ts +20 -41
  236. package/src/openbao/auth-role-form.test.ts +16 -1
  237. package/src/openbao/auth-role-form.ts +11 -0
  238. package/src/openbao/auth-role.ts +12 -0
  239. package/src/openbao/auth-token-form.ts +161 -0
  240. package/src/openbao/bao-identity.test.ts +131 -0
  241. package/src/openbao/bao-identity.ts +188 -0
  242. package/src/openbao/bao-namespace.ts +32 -0
  243. package/src/openbao/cloudflare-role-wire.test.ts +8 -34
  244. package/src/openbao/cloudflare-role.ts +30 -8
  245. package/src/openbao/cloudflare-roles-expand.ts +2 -5
  246. package/src/openbao/fake-engines.ts +119 -0
  247. package/src/openbao/fake-stack.ts +112 -0
  248. package/src/openbao/host-approles.test.ts +121 -0
  249. package/src/openbao/host-approles.ts +126 -0
  250. package/src/openbao/index.ts +19 -0
  251. package/src/openbao/jwt-config-form.ts +161 -0
  252. package/src/openbao/jwt-config.ts +85 -0
  253. package/src/openbao/jwt-role-form.test.ts +130 -0
  254. package/src/openbao/jwt-role-form.ts +231 -0
  255. package/src/openbao/jwt-role.ts +102 -0
  256. package/src/openbao/kubernetes-role-form.test.ts +82 -0
  257. package/src/openbao/kubernetes-role-form.ts +149 -0
  258. package/src/openbao/kubernetes-role.ts +94 -0
  259. package/src/openbao/mfa-enforcement-form.ts +147 -0
  260. package/src/openbao/mfa-enforcement.ts +143 -0
  261. package/src/openbao/mfa-totp-form.ts +155 -0
  262. package/src/openbao/mfa-totp.ts +118 -0
  263. package/src/openbao/mfa-wire.ts +117 -0
  264. package/src/openbao/mfa.test.ts +200 -0
  265. package/src/openbao/mount-form.ts +9 -1
  266. package/src/openbao/mount-move.test.ts +164 -0
  267. package/src/openbao/mount-move.ts +113 -0
  268. package/src/openbao/mount-path.test.ts +11 -1
  269. package/src/openbao/mount-path.ts +17 -11
  270. package/src/openbao/mount-reconcile.ts +75 -0
  271. package/src/openbao/mount.ts +26 -43
  272. package/src/openbao/pki-role-canonical.ts +163 -0
  273. package/src/openbao/pki-role-form.test.ts +112 -0
  274. package/src/openbao/pki-role-form.ts +76 -110
  275. package/src/openbao/pki-role.ts +12 -27
  276. package/src/openbao/policy-rename.test.ts +112 -0
  277. package/src/openbao/policy-wire.test.ts +7 -26
  278. package/src/openbao/policy.ts +27 -4
  279. package/src/openbao/proxmox-role-form.ts +27 -8
  280. package/src/openbao/proxmox-role-wire.ts +24 -0
  281. package/src/openbao/proxmox-role.ts +62 -37
  282. package/src/openbao/remount-wire.test.ts +87 -0
  283. package/src/openbao/remount-wire.ts +112 -0
  284. package/src/openbao/rename-occupied.test.ts +166 -0
  285. package/src/openbao/rename.ts +162 -0
  286. package/src/openbao/role-reconcile.test.ts +104 -0
  287. package/src/openbao/role-reconcile.ts +84 -0
  288. package/src/openbao/role-rename.test.ts +205 -0
@@ -140,18 +140,20 @@ var baoWrite = (method, path, body) => Effect.asVoid(baoCall("write", method, pa
140
140
  var baoDelete = (path) => Effect.asVoid(baoCall("delete", "DELETE", path));
141
141
 
142
142
  // src/openbao/mount-path.ts
143
- var trimTrailingSlashes = (text) => {
143
+ var trimTrailing = (text, char) => {
144
144
  let end = text.length;
145
- while (end > 0 && text[end - 1] === "/")
145
+ while (end > 0 && text[end - 1] === char)
146
146
  end -= 1;
147
147
  return text.slice(0, end);
148
148
  };
149
- var trimSlashes = (text) => {
149
+ var trimRuns = (text, char) => {
150
150
  let start = 0;
151
- while (start < text.length && text[start] === "/")
151
+ while (start < text.length && text[start] === char)
152
152
  start += 1;
153
- return trimTrailingSlashes(text.slice(start));
153
+ return trimTrailing(text.slice(start), char);
154
154
  };
155
+ var trimTrailingSlashes = (text) => trimTrailing(text, "/");
156
+ var trimSlashes = (text) => trimRuns(text, "/");
155
157
  var sshMountOf = (mount) => trimTrailingSlashes(mount ?? "ssh");
156
158
 
157
159
  // src/openbao/approle-login-form.ts
@@ -295,14 +297,14 @@ var revokeSelf = (token, env) => runWith(env, revokeSelfEffect(token));
295
297
  import { Resource } from "alchemy";
296
298
  import { isResolved } from "alchemy/Diff";
297
299
  import * as Provider from "alchemy/Provider";
298
- import * as Effect4 from "effect/Effect";
300
+ import * as Effect7 from "effect/Effect";
299
301
 
300
302
  // src/openbao/digest.ts
301
303
  var canonical = (text) => text.trimEnd();
302
304
  var sha256 = (text) => new Bun.CryptoHasher("sha256").update(canonical(text)).digest("hex");
303
305
 
304
306
  // src/openbao/mount-form.ts
305
- var mountPath = (path) => path.replace(/\/+$/, "");
307
+ var mountPath = (path) => trimTrailingSlashes(path);
306
308
  var DURATION = /^(\d+)([smhd])$/;
307
309
  var parseDuration = (text) => {
308
310
  const trimmed = text.trim();
@@ -435,6 +437,9 @@ var sameTtl2 = (want, have) => {
435
437
  var matches2 = (attributes, props) => attributes.type === props.type && (props.description === undefined || attributes.description === props.description) && sameTtl2(props.defaultLeaseTtl, attributes.defaultLeaseTtl) && sameTtl2(props.maxLeaseTtl, attributes.maxLeaseTtl);
436
438
  var wantsTune2 = (props) => props.description !== undefined || props.defaultLeaseTtl !== undefined || props.maxLeaseTtl !== undefined;
437
439
 
440
+ // src/openbao/auth-method-reconcile.ts
441
+ import * as Effect6 from "effect/Effect";
442
+
438
443
  // src/openbao/auth-method-wire.ts
439
444
  import * as Effect3 from "effect/Effect";
440
445
  var absentFromTable = (path, asked) => baoRead("sys/auth").pipe(Effect3.mapError((listing) => new BaoError(asked.status, asked.method, asked.path, [
@@ -446,24 +451,150 @@ var enableAuthMethod = (props) => baoWrite("POST", readPath2(props.path), enable
446
451
  var tuneAuthMethod = (props) => baoWrite("POST", tunePath2(props.path), tuneBody2(props));
447
452
  var disableAuthMethod = (path) => baoDelete(readPath2(path));
448
453
 
449
- // src/openbao/auth-method.ts
450
- var BaoAuthMethod = Resource("Bao.AuthMethod", {
451
- defaultRemovalPolicy: "retain"
454
+ // src/openbao/mount-move.ts
455
+ import * as Effect5 from "effect/Effect";
456
+
457
+ // src/openbao/remount-wire.ts
458
+ import * as Effect4 from "effect/Effect";
459
+ class RemountError extends Error {
460
+ constructor(message) {
461
+ super(message);
462
+ this.name = "RemountError";
463
+ }
464
+ }
465
+ var record = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
466
+ var startRemount = (from, to) => Effect4.flatMap(baoCall("write", "POST", "sys/remount", { from, to }), (body) => {
467
+ const id = record(body?.["data"])?.["migration_id"];
468
+ return typeof id === "string" && id !== "" ? Effect4.succeed(id) : Effect4.fail(new RemountError(`sys/remount ${from} -> ${to} answered no migration_id.`));
469
+ });
470
+ var remountStatus = (id) => Effect4.map(baoRead(`sys/remount/status/${encodeURIComponent(id)}`), (data) => {
471
+ if (data === undefined)
472
+ return;
473
+ const status = record(data["migration_info"])?.["status"];
474
+ return typeof status === "string" ? status : "unknown";
475
+ });
476
+ var remountAndWait = (from, to, confirm, options = {}) => Effect4.gen(function* () {
477
+ const interval = options.intervalMillis ?? 1000;
478
+ const attempts = Math.max(1, options.attempts ?? 120);
479
+ const id = yield* startRemount(from, to);
480
+ const named = `sys/remount ${from} -> ${to} (migration ${id})`;
481
+ for (let attempt = 1;attempt <= attempts; attempt += 1) {
482
+ const status = yield* remountStatus(id);
483
+ if (status === "success")
484
+ return;
485
+ if (status === "failure") {
486
+ return yield* Effect4.fail(new RemountError(`${named} FAILED. The server log carries the reason, tagged with that migration_id. ` + "Read sys/mounts (or sys/auth) before retrying: the source may be tainted."));
487
+ }
488
+ if (status === undefined) {
489
+ if (yield* confirm)
490
+ return;
491
+ return yield* Effect4.fail(new RemountError(`${named}: the server no longer knows the migration (its tracker is in memory, lost ` + "on a restart or leader change), and the mount table does not show the move done."));
492
+ }
493
+ if (attempt < attempts)
494
+ yield* Effect4.sleep(interval);
495
+ }
496
+ return yield* Effect4.fail(new RemountError(`${named} is still in progress after ${String(attempts)} status reads. Do not re-run ` + `until sys/remount/status/${id} settles.`));
497
+ });
498
+
499
+ // src/openbao/mount-move.ts
500
+ var planMove = (family, stated, path, remountFrom) => {
501
+ const to = mountPath(path);
502
+ const from = remountFrom === undefined ? undefined : mountPath(remountFrom);
503
+ if (stated === undefined) {
504
+ return from === undefined || from === to ? { kind: "stay" } : { kind: "move", from };
505
+ }
506
+ const was = mountPath(stated);
507
+ if (was === to)
508
+ return { kind: "stay" };
509
+ if (from === undefined) {
510
+ return {
511
+ kind: "refuse",
512
+ message: `${family}: path changed from ${was} to ${to}. That would enable an EMPTY mount at ${to} ` + `and strand everything under ${was}. To move it with its data, declare ` + `\`remountFrom: '${was}'\` (leases under it are revoked; policies are not rewritten). ` + "For a new, empty mount, give it a new resource id instead."
513
+ };
514
+ }
515
+ if (from !== was) {
516
+ return {
517
+ kind: "refuse",
518
+ message: `${family}: remountFrom is ${from}, but the mount in state is ${was}.`
519
+ };
520
+ }
521
+ return { kind: "move", from };
522
+ };
523
+ var refuse = (message) => Effect5.die(new Error(message));
524
+ var performMove = (target, from, options) => Effect5.gen(function* () {
525
+ const to = mountPath(target.path);
526
+ const source = yield* target.read(from);
527
+ const landed = yield* target.read(to);
528
+ if (source !== undefined && landed !== undefined) {
529
+ return yield* refuse(`${target.family}: both ${from} and ${to} exist, so remountFrom cannot move one onto the ` + "other. Decide which holds the data, and retire the other by hand.");
530
+ }
531
+ if (source === undefined && landed === undefined) {
532
+ return yield* refuse(`${target.family}: remountFrom ${from} names no mount, and ${to} does not exist either. ` + "Refusing to enable an empty mount where data was expected.");
533
+ }
534
+ if (source === undefined)
535
+ return;
536
+ if (source.type !== target.type) {
537
+ return yield* refuse(`${target.family}: ${from} is type ${source.type}, not ${target.type}. A remount keeps the ` + "type; fix the declaration first.");
538
+ }
539
+ const confirm = Effect5.map(Effect5.all([target.read(from), target.read(to)]), ([gone, there]) => gone === undefined && there !== undefined);
540
+ yield* remountAndWait(target.wire(from), target.wire(to), confirm, options);
452
541
  });
453
- var readMethod = (props) => Effect4.gen(function* () {
542
+
543
+ // src/openbao/auth-method-reconcile.ts
544
+ var readMethod = (props) => Effect6.gen(function* () {
454
545
  const live = yield* readAuthMethodData(props.path);
455
546
  if (live === undefined)
456
547
  return;
457
548
  return attributesOf2(props, live);
458
549
  });
459
- var BaoAuthMethodProvider = () => Provider.effect(BaoAuthMethod, Effect4.succeed(BaoAuthMethod.Provider.of({
460
- list: () => Effect4.succeed([]),
461
- read: Effect4.fn(function* ({ olds }) {
550
+ var moveTarget = (props) => ({
551
+ family: "Bao.AuthMethod",
552
+ path: props.path,
553
+ read: (path) => readMethod({ ...props, path }),
554
+ type: props.type,
555
+ wire: (path) => `auth/${authPath(path)}`
556
+ });
557
+ var reconcileAuthMethod = (news, stated, options) => Effect6.gen(function* () {
558
+ const move = planMove("Bao.AuthMethod", stated, news.path, news.remountFrom);
559
+ if (move.kind === "refuse")
560
+ return yield* Effect6.die(new Error(move.message));
561
+ if (move.kind === "move")
562
+ yield* performMove(moveTarget(news), move.from, options);
563
+ let live = yield* readMethod(news);
564
+ if (live === undefined) {
565
+ yield* enableAuthMethod(news);
566
+ if (wantsTune2(news))
567
+ yield* tuneAuthMethod(news);
568
+ } else if (!matches2(live, news)) {
569
+ if (live.type !== news.type) {
570
+ return yield* Effect6.die(new Error(`Bao.AuthMethod ${authPath(news.path)}: live type ${live.type} != ${news.type}. ` + "Auth method type is immutable — replace manually."));
571
+ }
572
+ yield* tuneAuthMethod(news);
573
+ }
574
+ live = yield* readMethod(news);
575
+ if (live === undefined) {
576
+ return yield* Effect6.die(new Error(`Bao.AuthMethod ${authPath(news.path)}: write returned no error but the method is still absent.`));
577
+ }
578
+ return live;
579
+ });
580
+
581
+ // src/openbao/auth-method.ts
582
+ var BaoAuthMethod = Resource("Bao.AuthMethod", {
583
+ defaultRemovalPolicy: "retain"
584
+ });
585
+ var BaoAuthMethodProvider = () => Provider.effect(BaoAuthMethod, Effect7.succeed(BaoAuthMethod.Provider.of({
586
+ list: () => Effect7.succeed([]),
587
+ read: Effect7.fn(function* ({ olds }) {
462
588
  return yield* readMethod(olds);
463
589
  }),
464
- diff: Effect4.fn(function* ({ news, output }) {
590
+ diff: Effect7.fn(function* ({ news, output }) {
465
591
  if (output === undefined || !isResolved(news))
466
592
  return;
593
+ const move = planMove("Bao.AuthMethod", output.path, news.path, news.remountFrom);
594
+ if (move.kind === "refuse")
595
+ return yield* Effect7.die(new Error(move.message));
596
+ if (move.kind === "move")
597
+ return { action: "update" };
467
598
  const live = yield* readMethod(news);
468
599
  if (live === undefined)
469
600
  return { action: "update" };
@@ -473,25 +604,10 @@ var BaoAuthMethodProvider = () => Provider.effect(BaoAuthMethod, Effect4.succeed
473
604
  return { action: "noop" };
474
605
  return { action: "update" };
475
606
  }),
476
- reconcile: Effect4.fn(function* ({ news }) {
477
- let live = yield* readMethod(news);
478
- if (live === undefined) {
479
- yield* enableAuthMethod(news);
480
- if (wantsTune2(news))
481
- yield* tuneAuthMethod(news);
482
- } else if (!matches2(live, news)) {
483
- if (live.type !== news.type) {
484
- return yield* Effect4.die(new Error(`Bao.AuthMethod ${authPath(news.path)}: live type ${live.type} != ${news.type}. ` + "Auth method type is immutable — replace manually."));
485
- }
486
- yield* tuneAuthMethod(news);
487
- }
488
- live = yield* readMethod(news);
489
- if (live === undefined) {
490
- return yield* Effect4.die(new Error(`Bao.AuthMethod ${authPath(news.path)}: write returned no error but the method is still absent.`));
491
- }
492
- return live;
607
+ reconcile: Effect7.fn(function* ({ news, output }) {
608
+ return yield* reconcileAuthMethod(news, output?.path);
493
609
  }),
494
- delete: Effect4.fn(function* ({ output }) {
610
+ delete: Effect7.fn(function* ({ output }) {
495
611
  yield* disableAuthMethod(output.path);
496
612
  return;
497
613
  })
@@ -500,7 +616,7 @@ var BaoAuthMethodProvider = () => Provider.effect(BaoAuthMethod, Effect4.succeed
500
616
  import { Resource as Resource2 } from "alchemy";
501
617
  import { isResolved as isResolved2 } from "alchemy/Diff";
502
618
  import * as Provider2 from "alchemy/Provider";
503
- import * as Effect5 from "effect/Effect";
619
+ import * as Effect8 from "effect/Effect";
504
620
 
505
621
  // src/openbao/auth-role-form.ts
506
622
  var policiesOf = (live) => {
@@ -549,6 +665,7 @@ var writeBody = (props) => {
549
665
  }
550
666
  return body;
551
667
  };
668
+ var isRename = (from, to) => from.toLowerCase() !== to.toLowerCase();
552
669
  var samePolicies = (want, have) => want.length === have.length && want.every((policy, index) => policy === have[index]);
553
670
  var sameTtl3 = (want, have) => {
554
671
  const wantSeconds = parseDuration(want);
@@ -564,20 +681,22 @@ var matches3 = (attributes, props) => {
564
681
  var BaoAuthRole = Resource2("Bao.AuthRole", {
565
682
  defaultRemovalPolicy: "retain"
566
683
  });
567
- var readRole = (props) => Effect5.gen(function* () {
684
+ var readRole = (props) => Effect8.gen(function* () {
568
685
  const live = yield* baoRead(readPath3(props.name));
569
686
  if (live === undefined)
570
687
  return;
571
688
  return attributesOf3(props, live);
572
689
  });
573
- var BaoAuthRoleProvider = () => Provider2.effect(BaoAuthRole, Effect5.succeed(BaoAuthRole.Provider.of({
574
- list: () => Effect5.succeed([]),
575
- read: Effect5.fn(function* ({ olds }) {
690
+ var BaoAuthRoleProvider = () => Provider2.effect(BaoAuthRole, Effect8.succeed(BaoAuthRole.Provider.of({
691
+ list: () => Effect8.succeed([]),
692
+ read: Effect8.fn(function* ({ olds }) {
576
693
  return yield* readRole(olds);
577
694
  }),
578
- diff: Effect5.fn(function* ({ news, output }) {
695
+ diff: Effect8.fn(function* ({ news, output }) {
579
696
  if (output === undefined || !isResolved2(news))
580
697
  return;
698
+ if (isRename(output.name, news.name))
699
+ return { action: "replace" };
581
700
  const live = yield* readRole(news);
582
701
  if (live === undefined)
583
702
  return { action: "update" };
@@ -585,27 +704,107 @@ var BaoAuthRoleProvider = () => Provider2.effect(BaoAuthRole, Effect5.succeed(Ba
585
704
  return { action: "noop" };
586
705
  return { action: "update" };
587
706
  }),
588
- reconcile: Effect5.fn(function* ({ news }) {
707
+ reconcile: Effect8.fn(function* ({ news }) {
589
708
  const live = yield* readRole(news);
590
709
  if (live === undefined || !matches3(live, news)) {
591
710
  yield* baoWrite("PUT", readPath3(news.name), writeBody(news));
592
711
  }
593
712
  const after = yield* readRole(news);
594
713
  if (after === undefined) {
595
- return yield* Effect5.die(new Error(`Bao.AuthRole ${news.name}: write returned no error but the role is still absent.`));
714
+ return yield* Effect8.die(new Error(`Bao.AuthRole ${news.name}: write returned no error but the role is still absent.`));
596
715
  }
597
716
  return after;
598
717
  }),
599
- delete: Effect5.fn(function* ({ output }) {
718
+ delete: Effect8.fn(function* ({ output }) {
600
719
  yield* baoDelete(readPath3(output.name));
601
720
  return;
602
721
  })
603
722
  })));
723
+ // src/openbao/bao-identity.ts
724
+ import * as Effect9 from "effect/Effect";
725
+ import * as FetchHttpClient3 from "effect/unstable/http/FetchHttpClient";
726
+
727
+ // src/openbao/bao-namespace.ts
728
+ var canonicalNamespace = (namespace) => {
729
+ const bare = trimSlashes((namespace ?? "").trim());
730
+ return bare === "root" ? "" : bare;
731
+ };
732
+ var namespacePath = (namespace) => {
733
+ const bare = canonicalNamespace(namespace);
734
+ return bare === "" ? "" : `${bare}/`;
735
+ };
736
+ var envNamespace = (env) => canonicalNamespace(resolveAddress(env).namespace);
737
+ var namespaceLabel = (namespace) => namespace === "" ? "root" : namespace;
738
+
739
+ // src/openbao/bao-identity.ts
740
+ class BaoIdentityError extends Error {
741
+ reason;
742
+ constructor(reason, detail) {
743
+ super(`REFUSING: ${detail}`);
744
+ this.reason = reason;
745
+ this.name = "BaoIdentityError";
746
+ }
747
+ }
748
+ var HEALTH_PATH = "sys/health?standbyok=true&sealedcode=200&uninitcode=200";
749
+ var NAMESPACE_PROBE_PATH = "sys/internal/ui/mounts";
750
+ var addressOf = (env) => {
751
+ const address = resolveAddress(env);
752
+ return address.socket === undefined ? address.base : `unix://${address.socket}`;
753
+ };
754
+ var unreachable = (address, what) => (error) => new BaoIdentityError("unreachable", `${address} did not answer ${what}: ${error.message}`);
755
+ var text3 = (value) => typeof value === "string" ? value : "";
756
+ var checkHealth = (address, expectedCluster, body) => {
757
+ if (body === undefined || typeof body["sealed"] !== "boolean") {
758
+ return new BaoIdentityError("unconfirmed", `${address} answered sys/health, but not as OpenBao.`);
759
+ }
760
+ if (body["initialized"] === false) {
761
+ return new BaoIdentityError("unconfirmed", `${address} is not initialised.`);
762
+ }
763
+ if (body["sealed"] === true) {
764
+ return new BaoIdentityError("unconfirmed", `${address} is sealed, and a sealed vault does not report its cluster_name.`);
765
+ }
766
+ const clusterName = text3(body["cluster_name"]);
767
+ if (clusterName !== expectedCluster) {
768
+ return new BaoIdentityError("mismatch", `${address} answers as cluster \`${clusterName || "(none)"}\`, but this stack expects ` + `\`${expectedCluster}\`. Check BAO_ADDR and BAO_AGENT_ADDR (the agent address wins).`);
769
+ }
770
+ return { clusterId: text3(body["cluster_id"]), version: text3(body["version"]) };
771
+ };
772
+ var assertBaoIdentityEffect = (expected) => Effect9.gen(function* () {
773
+ const env = yield* BaoEnv;
774
+ const address = addressOf(env);
775
+ const clusterName = expected.clusterName.trim();
776
+ if (clusterName === "") {
777
+ return yield* Effect9.fail(new BaoIdentityError("input", "clusterName is empty, so there is nothing to compare."));
778
+ }
779
+ const namespace = canonicalNamespace(expected.namespace);
780
+ const sent = envNamespace(env);
781
+ if (sent !== namespace) {
782
+ return yield* Effect9.fail(new BaoIdentityError("mismatch", `BAO_NAMESPACE resolves to \`${namespaceLabel(sent)}\`, but this stack expects ` + `\`${namespaceLabel(namespace)}\`.`));
783
+ }
784
+ const anonymous = { ...env, BAO_NAMESPACE: "", BAO_TOKEN: "" };
785
+ const body = yield* baoCall("read", "GET", HEALTH_PATH).pipe(Effect9.provideService(BaoEnv, anonymous), Effect9.mapError(unreachable(address, "sys/health")));
786
+ const health = checkHealth(address, clusterName, body);
787
+ if (health instanceof BaoIdentityError)
788
+ return yield* Effect9.fail(health);
789
+ if (namespace !== "") {
790
+ const scoped = { ...env, BAO_NAMESPACE: namespace, BAO_TOKEN: "" };
791
+ const probe = yield* baoCall("read", "GET", NAMESPACE_PROBE_PATH).pipe(Effect9.provideService(BaoEnv, scoped), Effect9.mapError(unreachable(address, `the namespace probe for \`${namespace}\``)));
792
+ if (probe === undefined) {
793
+ return yield* Effect9.fail(new BaoIdentityError("mismatch", `cluster \`${clusterName}\` at ${address} has no namespace \`${namespace}\`.`));
794
+ }
795
+ }
796
+ return { address, clusterName, namespace, ...health };
797
+ });
798
+ var assertBaoIdentity = (input) => {
799
+ const effect3 = assertBaoIdentityEffect(input);
800
+ const scoped = input.env === undefined ? effect3 : Effect9.provideService(effect3, BaoEnv, input.env);
801
+ return Effect9.runPromise(scoped.pipe(Effect9.provide(FetchHttpClient3.layer)));
802
+ };
604
803
  // src/openbao/cloudflare-role.ts
605
804
  import { Resource as Resource3 } from "alchemy";
606
805
  import { isResolved as isResolved3 } from "alchemy/Diff";
607
806
  import * as Provider3 from "alchemy/Provider";
608
- import * as Effect8 from "effect/Effect";
807
+ import * as Effect13 from "effect/Effect";
609
808
 
610
809
  // src/openbao/cloudflare-group-scope.ts
611
810
  var zoneResource = (zoneId) => `com.cloudflare.api.account.zone.${zoneId}`;
@@ -625,7 +824,7 @@ var groupKey = (name, scope) => `${scope} ${name}`;
625
824
 
626
825
  // src/openbao/cloudflare-permission-groups.ts
627
826
  import * as Context2 from "effect/Context";
628
- import * as Effect7 from "effect/Effect";
827
+ import * as Effect11 from "effect/Effect";
629
828
 
630
829
  // src/openbao/cloudflare-permission-groups-solve.ts
631
830
  var observationsOf = (declared, live) => declared.flatMap((role) => {
@@ -795,7 +994,7 @@ var samePolicies2 = (want, have) => want.length === have.length && want.every((w
795
994
  });
796
995
 
797
996
  // src/openbao/cloudflare-role-wire.ts
798
- import * as Effect6 from "effect/Effect";
997
+ import * as Effect10 from "effect/Effect";
799
998
 
800
999
  // src/openbao/cloudflare-role-form.ts
801
1000
  var rolePath = (mount, name) => `${mountPath(mount)}/roles/${name}`;
@@ -860,7 +1059,7 @@ var differences = (props, policies, live) => {
860
1059
  };
861
1060
 
862
1061
  // src/openbao/cloudflare-role-wire.ts
863
- var readCloudflareRole = (mount, name) => Effect6.map(baoRead(rolePath(mount, name)), (data) => data === undefined ? undefined : liveRoleOf(data));
1062
+ var readCloudflareRole = (mount, name) => Effect10.map(baoRead(rolePath(mount, name)), (data) => data === undefined ? undefined : liveRoleOf(data));
864
1063
  var writeCloudflareRole = (mount, name, body) => baoWrite("PUT", rolePath(mount, name), body);
865
1064
  var deleteCloudflareRole = (mount, name) => baoDelete(rolePath(mount, name));
866
1065
 
@@ -882,9 +1081,9 @@ var pick = (mount, groups, refs, hint) => {
882
1081
  const unknown = refs.filter((ref) => !groups.has(groupKey(ref.name, ref.scope)));
883
1082
  if (unknown.length > 0) {
884
1083
  const problems = unknown.map((ref) => `unknown group ${JSON.stringify(ref.name)} at scope ${ref.scope} — ${hint}`);
885
- return Effect7.fail(new CloudflareGroupsError(mount, problems));
1084
+ return Effect11.fail(new CloudflareGroupsError(mount, problems));
886
1085
  }
887
- return Effect7.succeed(new Map(refs.flatMap((ref) => {
1086
+ return Effect11.succeed(new Map(refs.flatMap((ref) => {
888
1087
  const key = groupKey(ref.name, ref.scope);
889
1088
  const id = groups.get(key);
890
1089
  return id === undefined ? [] : [[key, id]];
@@ -892,15 +1091,15 @@ var pick = (mount, groups, refs, hint) => {
892
1091
  };
893
1092
  var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
894
1093
  var deniedHint = (mount) => `GET ${mountPath(mount)}/permission-groups answered 403 — this token's policy carries no ` + `\`path "${mountPath(mount)}/permission-groups" { capabilities = ["read"] }\` grant. ` + "It is declared in ../platform/secrets/vault/policies/homeflare-admin/" + "20-cloudflare-permission-groups.hcl and applies with `alchemy deploy` of this stack";
895
- var deniedNamed = (mount, read) => Effect7.catchIf(read, (error) => error.status === 403, () => Effect7.fail(new CloudflareGroupsError(mount, [deniedHint(mount)])));
896
- var engineGroups = (mount) => Effect7.flatMap(deniedNamed(mount, baoRead(`${mountPath(mount)}/permission-groups`)), (data) => {
1094
+ var deniedNamed = (mount, read) => Effect11.catchIf(read, (error) => error.status === 403, () => Effect11.fail(new CloudflareGroupsError(mount, [deniedHint(mount)])));
1095
+ var engineGroups = (mount) => Effect11.flatMap(deniedNamed(mount, baoRead(`${mountPath(mount)}/permission-groups`)), (data) => {
897
1096
  const list = data?.["permission_groups"];
898
1097
  if (data === undefined) {
899
1098
  const why = "GET <mount>/permission-groups answered 404 — the endpoint is not deployed here";
900
- return Effect7.fail(new CloudflareGroupsError(mount, [why]));
1099
+ return Effect11.fail(new CloudflareGroupsError(mount, [why]));
901
1100
  }
902
1101
  if (!Array.isArray(list)) {
903
- return Effect7.fail(new CloudflareGroupsError(mount, ["no permission_groups array in reply"]));
1102
+ return Effect11.fail(new CloudflareGroupsError(mount, ["no permission_groups array in reply"]));
904
1103
  }
905
1104
  const scoped = new Map;
906
1105
  const idsOf = new Map;
@@ -927,24 +1126,24 @@ var engineGroups = (mount) => Effect7.flatMap(deniedNamed(mount, baoRead(`${moun
927
1126
  const only = [...ids];
928
1127
  return ids.size === 1 && only[0] !== undefined ? [[name, only[0]]] : [];
929
1128
  }));
930
- return problems.length > 0 ? Effect7.fail(new CloudflareGroupsError(mount, problems)) : Effect7.succeed(new Map([...atEveryScope(sole), ...scoped]));
1129
+ return problems.length > 0 ? Effect11.fail(new CloudflareGroupsError(mount, problems)) : Effect11.succeed(new Map([...atEveryScope(sole), ...scoped]));
931
1130
  });
932
- var permissionGroupsFromEngine = () => Effect7.sync(() => {
1131
+ var permissionGroupsFromEngine = () => Effect11.sync(() => {
933
1132
  const cache = new Map;
934
- const cachedFor = (mount) => Effect7.suspend(() => {
1133
+ const cachedFor = (mount) => Effect11.suspend(() => {
935
1134
  const hit = cache.get(mountPath(mount));
936
1135
  if (hit !== undefined)
937
1136
  return hit;
938
- return Effect7.flatMap(Effect7.cached(engineGroups(mount)), (lookup) => {
1137
+ return Effect11.flatMap(Effect11.cached(engineGroups(mount)), (lookup) => {
939
1138
  cache.set(mountPath(mount), lookup);
940
1139
  return lookup;
941
1140
  });
942
1141
  });
943
1142
  return CloudflarePermissionGroups.of({
944
- resolve: (mount, refs) => Effect7.flatMap(cachedFor(mount), (groups) => pick(mount, groups, refs, "the engine does not list it for this account at that scope"))
1143
+ resolve: (mount, refs) => Effect11.flatMap(cachedFor(mount), (groups) => pick(mount, groups, refs, "the engine does not list it for this account at that scope"))
945
1144
  });
946
1145
  });
947
- var permissionGroupsFromLiveRoles = (declared) => Effect7.gen(function* () {
1146
+ var permissionGroupsFromLiveRoles = (declared) => Effect11.gen(function* () {
948
1147
  const accountOf = new Map;
949
1148
  const byAccount = new Map;
950
1149
  for (const [mount, roles] of declared) {
@@ -955,9 +1154,9 @@ var permissionGroupsFromLiveRoles = (declared) => Effect7.gen(function* () {
955
1154
  }
956
1155
  const solved = new Map;
957
1156
  for (const [account, roles] of byAccount) {
958
- const solve = Effect7.gen(function* () {
1157
+ const solve = Effect11.gen(function* () {
959
1158
  const live = new Map;
960
- yield* Effect7.all(roles.map((role) => Effect7.map(readCloudflareRole(role.mount, role.name), (read) => {
1159
+ yield* Effect11.all(roles.map((role) => Effect11.map(readCloudflareRole(role.mount, role.name), (read) => {
961
1160
  if (read?.policies !== undefined) {
962
1161
  live.set(`${role.mount}/${role.name}`, read.policies);
963
1162
  }
@@ -965,11 +1164,11 @@ var permissionGroupsFromLiveRoles = (declared) => Effect7.gen(function* () {
965
1164
  const result = solveGroups(observationsOf(roles, live));
966
1165
  const problems = describeProblems(result);
967
1166
  if (problems.length > 0) {
968
- return yield* Effect7.fail(new CloudflareGroupsError(`account ${account}`, problems));
1167
+ return yield* Effect11.fail(new CloudflareGroupsError(`account ${account}`, problems));
969
1168
  }
970
1169
  return atEveryScope(result.groups);
971
1170
  });
972
- solved.set(account, yield* Effect7.cached(solve));
1171
+ solved.set(account, yield* Effect11.cached(solve));
973
1172
  }
974
1173
  return CloudflarePermissionGroups.of({
975
1174
  resolve: (mount, refs) => {
@@ -977,21 +1176,54 @@ var permissionGroupsFromLiveRoles = (declared) => Effect7.gen(function* () {
977
1176
  const lookup = account === undefined ? undefined : solved.get(account);
978
1177
  if (account === undefined || lookup === undefined) {
979
1178
  const why = "no declared role on this mount, so no live role can vouch for its groups";
980
- return Effect7.fail(new CloudflareGroupsError(mount, [why]));
1179
+ return Effect11.fail(new CloudflareGroupsError(mount, [why]));
981
1180
  }
982
1181
  const hint = `no live role on account ${account} carries it; a NEW group needs the engine endpoint`;
983
- return Effect7.flatMap(lookup, (groups) => pick(mount, groups, refs, hint));
1182
+ return Effect11.flatMap(lookup, (groups) => pick(mount, groups, refs, hint));
984
1183
  }
985
1184
  });
986
1185
  });
987
1186
 
1187
+ // src/openbao/rename.ts
1188
+ import * as Effect12 from "effect/Effect";
1189
+ var exact = (identity) => identity;
1190
+ var declaredString = (news, key) => {
1191
+ if (typeof news !== "object" || news === null)
1192
+ return;
1193
+ const value = news[key];
1194
+ return typeof value === "string" ? value : undefined;
1195
+ };
1196
+ var isPendingProp = (news, key) => {
1197
+ if (typeof news !== "object" || news === null)
1198
+ return false;
1199
+ const value = news[key];
1200
+ return value !== undefined && typeof value !== "string";
1201
+ };
1202
+ var declaredRolePath = (news, rolePath2) => {
1203
+ const mount = declaredString(news, "mount");
1204
+ const name = declaredString(news, "name");
1205
+ return mount === undefined || name === undefined ? undefined : rolePath2(mount, name);
1206
+ };
1207
+ var policyKey = (name) => name.trim().toLowerCase();
1208
+ var isMoved = (stored, declared, key = exact) => declared === undefined ? undefined : key(stored) !== key(declared);
1209
+ var triedRolePath = (output, olds, rolePath2) => output === undefined ? declaredRolePath(olds, rolePath2) : rolePath2(output.mount, output.name);
1210
+ var judgeMove = (family, tried, declared, pathOf, key = exact) => Effect12.gen(function* () {
1211
+ if (tried === undefined || declared === undefined || key(tried) === key(declared)) {
1212
+ return;
1213
+ }
1214
+ if ((yield* baoRead(pathOf(declared))) === undefined)
1215
+ return { from: tried, to: declared };
1216
+ return yield* Effect12.die(new Error(`${family}: ${tried} → ${declared} would land on an object that already exists at ` + `${declared}. Under RemovalPolicy.destroy() a resource moving off that name (a swap, a ` + "shift, or a reverted move that did not finish) deletes it after this one writes it. " + "Nothing was written. Move through a name nothing holds, in two deploys, or remove " + `${declared} by hand first.`));
1217
+ });
1218
+ var refuseMovedUpdate = (family, from, to) => Effect12.die(new Error(`${family}: planned as an update, but its identity moved from ${from} to ${to}. Writing ` + `${to} now would leave ${from} live with no state record. Nothing was written. Run the ` + "deploy again: once the value resolves at plan time it plans as a replace."));
1219
+
988
1220
  // src/openbao/cloudflare-role.ts
989
1221
  var BaoCloudflareRole = Resource3("Bao.CloudflareRole", {
990
1222
  defaultRemovalPolicy: "retain"
991
1223
  });
992
- var BaoCloudflareRoleProvider = () => Provider3.effect(BaoCloudflareRole, Effect8.gen(function* () {
1224
+ var BaoCloudflareRoleProvider = () => Provider3.effect(BaoCloudflareRole, Effect13.gen(function* () {
993
1225
  const groups = yield* CloudflarePermissionGroups;
994
- const resolved = (props) => Effect8.gen(function* () {
1226
+ const resolved = (props) => Effect13.gen(function* () {
995
1227
  const refs = new Map;
996
1228
  for (const policy of props.policies) {
997
1229
  const scope = policy.scope ?? scopeOfResource(policy.resource);
@@ -1001,18 +1233,25 @@ var BaoCloudflareRoleProvider = () => Provider3.effect(BaoCloudflareRole, Effect
1001
1233
  const ids = yield* groups.resolve(props.mount, [...refs.values()]);
1002
1234
  const { missing, policies } = resolvePolicies(props.policies, ids);
1003
1235
  if (missing.length > 0) {
1004
- return yield* Effect8.die(new Error(`Bao.CloudflareRole ${rolePath(props.mount, props.name)}: groups ` + `${missing.join(", ")} came back unresolved from a lookup that reported success.`));
1236
+ return yield* Effect13.die(new Error(`Bao.CloudflareRole ${rolePath(props.mount, props.name)}: groups ` + `${missing.join(", ")} came back unresolved from a lookup that reported success.`));
1005
1237
  }
1006
1238
  return policies;
1007
1239
  });
1008
1240
  return BaoCloudflareRole.Provider.of({
1009
- list: () => Effect8.succeed([]),
1010
- read: Effect8.fn(function* ({ olds }) {
1241
+ list: () => Effect13.succeed([]),
1242
+ read: Effect13.fn(function* ({ olds }) {
1011
1243
  const live = yield* readCloudflareRole(olds.mount, olds.name);
1012
1244
  return live === undefined ? undefined : attributesOf4(olds, live);
1013
1245
  }),
1014
- diff: Effect8.fn(function* ({ news, output }) {
1015
- if (output === undefined || !isResolved3(news))
1246
+ diff: Effect13.fn(function* ({ news, olds, output }) {
1247
+ const tried = triedRolePath(output, olds, rolePath);
1248
+ const declared = declaredRolePath(news, rolePath);
1249
+ const move = yield* judgeMove("Bao.CloudflareRole", tried, declared, (path) => path);
1250
+ if (output === undefined)
1251
+ return;
1252
+ if (move !== undefined)
1253
+ return { action: "replace" };
1254
+ if (!isResolved3(news))
1016
1255
  return;
1017
1256
  if (refusalOf(news) !== undefined)
1018
1257
  return { action: "update" };
@@ -1022,11 +1261,15 @@ var BaoCloudflareRoleProvider = () => Provider3.effect(BaoCloudflareRole, Effect
1022
1261
  return { action: "update" };
1023
1262
  return differences(news, policies, live).length === 0 ? { action: "noop" } : { action: "update" };
1024
1263
  }),
1025
- reconcile: Effect8.fn(function* ({ news }) {
1264
+ reconcile: Effect13.fn(function* ({ news, output }) {
1026
1265
  const path = rolePath(news.mount, news.name);
1266
+ const before = output === undefined ? path : rolePath(output.mount, output.name);
1267
+ if (isMoved(before, path) === true) {
1268
+ return yield* refuseMovedUpdate("Bao.CloudflareRole", before, path);
1269
+ }
1027
1270
  const refusal2 = refusalOf(news);
1028
1271
  if (refusal2 !== undefined) {
1029
- return yield* Effect8.die(new Error(`Bao.CloudflareRole ${path}: ${refusal2}.`));
1272
+ return yield* Effect13.die(new Error(`Bao.CloudflareRole ${path}: ${refusal2}.`));
1030
1273
  }
1031
1274
  const policies = yield* resolved(news);
1032
1275
  const live = yield* readCloudflareRole(news.mount, news.name);
@@ -1035,22 +1278,22 @@ var BaoCloudflareRoleProvider = () => Provider3.effect(BaoCloudflareRole, Effect
1035
1278
  }
1036
1279
  const after = yield* readCloudflareRole(news.mount, news.name);
1037
1280
  if (after === undefined) {
1038
- return yield* Effect8.die(new Error(`Bao.CloudflareRole ${path}: the write succeeded but the role is absent.`));
1281
+ return yield* Effect13.die(new Error(`Bao.CloudflareRole ${path}: the write succeeded but the role is absent.`));
1039
1282
  }
1040
1283
  const left = differences(news, policies, after);
1041
1284
  if (left.length > 0) {
1042
- return yield* Effect8.die(new Error(`Bao.CloudflareRole ${path}: wrote cleanly but reads back different in ` + `${left.map((d) => d.field).join(", ")}. Inspect the role before retrying.`));
1285
+ return yield* Effect13.die(new Error(`Bao.CloudflareRole ${path}: wrote cleanly but reads back different in ` + `${left.map((d) => d.field).join(", ")}. Inspect the role before retrying.`));
1043
1286
  }
1044
1287
  return attributesOf4(news, after);
1045
1288
  }),
1046
- delete: Effect8.fn(function* ({ output }) {
1289
+ delete: Effect13.fn(function* ({ output }) {
1047
1290
  yield* deleteCloudflareRole(output.mount, output.name);
1048
1291
  return;
1049
1292
  })
1050
1293
  });
1051
1294
  }));
1052
1295
  // src/openbao/cloudflare-roles-expand.ts
1053
- var slug = (name) => name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
1296
+ var slug = (name) => trimRuns(name.toLowerCase().replace(/[^a-z0-9]+/g, "-"), "-");
1054
1297
  var zoneSlug = (zone) => `${slug(zone.name)}${zone.internal ? "-internal" : ""}`;
1055
1298
  var mountName = (account, surface) => `cloudflare-${account}-${surface}`;
1056
1299
  var policiesFor = (tpl, accountId, zone, accountGroups) => {
@@ -1145,12 +1388,12 @@ class RolesConfigError extends Error {
1145
1388
  }
1146
1389
  }
1147
1390
  var isRaw = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1148
- var record = (value, where) => {
1391
+ var record2 = (value, where) => {
1149
1392
  if (!isRaw(value))
1150
1393
  throw new RolesConfigError(where, "expected a mapping");
1151
1394
  return value;
1152
1395
  };
1153
- var text3 = (raw, key, where) => {
1396
+ var text4 = (raw, key, where) => {
1154
1397
  const value = raw[key];
1155
1398
  if (typeof value === "string")
1156
1399
  return value;
@@ -1174,35 +1417,35 @@ var entries = (raw, key, where) => {
1174
1417
  return value;
1175
1418
  };
1176
1419
  var template = (value, where) => {
1177
- const raw = record(value, where);
1420
+ const raw = record2(value, where);
1178
1421
  const groups = texts(raw, "groups", where);
1179
1422
  if (groups === undefined)
1180
1423
  throw new RolesConfigError(`${where}.groups`, "is required");
1181
- const bucket = raw["bucket"] === undefined ? undefined : text3(raw, "bucket", where);
1424
+ const bucket = raw["bucket"] === undefined ? undefined : text4(raw, "bucket", where);
1182
1425
  if (bucket !== undefined && bucket.length === 0) {
1183
1426
  throw new RolesConfigError(`${where}.bucket`, "is empty; omit it to stay account-wide");
1184
1427
  }
1185
- const groupsScope = raw["groups_scope"] === undefined ? undefined : groupScopeNamed(text3(raw, "groups_scope", where));
1428
+ const groupsScope = raw["groups_scope"] === undefined ? undefined : groupScopeNamed(text4(raw, "groups_scope", where));
1186
1429
  if (raw["groups_scope"] !== undefined && groupsScope === undefined) {
1187
1430
  throw new RolesConfigError(`${where}.groups_scope`, `expected one of ${Object.keys(GROUP_SCOPES).join(", ")}`);
1188
1431
  }
1189
1432
  return {
1190
1433
  accountGroups: texts(raw, "account_groups", where) ?? [],
1191
1434
  bucket,
1192
- bucketJurisdiction: raw["bucket_jurisdiction"] === undefined ? "default" : text3(raw, "bucket_jurisdiction", where),
1193
- desc: raw["desc"] === undefined ? "" : text3(raw, "desc", where),
1435
+ bucketJurisdiction: raw["bucket_jurisdiction"] === undefined ? "default" : text4(raw, "bucket_jurisdiction", where),
1436
+ desc: raw["desc"] === undefined ? "" : text4(raw, "desc", where),
1194
1437
  groups,
1195
1438
  groupsScope,
1196
- maxTtl: text3(raw, "max_ttl", where),
1197
- name: text3(raw, "name", where),
1198
- ttl: text3(raw, "ttl", where),
1439
+ maxTtl: text4(raw, "max_ttl", where),
1440
+ name: text4(raw, "name", where),
1441
+ ttl: text4(raw, "ttl", where),
1199
1442
  zoneTypes: texts(raw, "zone_types", where) ?? [],
1200
1443
  zones: texts(raw, "zones", where)
1201
1444
  };
1202
1445
  };
1203
1446
  var surface = (name, value) => {
1204
1447
  const where = `surfaces.${name}`;
1205
- const raw = record(value, where);
1448
+ const raw = record2(value, where);
1206
1449
  return {
1207
1450
  accountRoles: entries(raw, "account_roles", where).map((item, i) => template(item, `${where}.account_roles[${String(i)}]`)),
1208
1451
  zoneRoles: entries(raw, "zone_roles", where).map((item, i) => template(item, `${where}.zone_roles[${String(i)}]`))
@@ -1210,7 +1453,7 @@ var surface = (name, value) => {
1210
1453
  };
1211
1454
  var account = (key, value, surfaces) => {
1212
1455
  const where = `accounts.${key}`;
1213
- const raw = record(value, where);
1456
+ const raw = record2(value, where);
1214
1457
  const names = texts(raw, "surfaces", where) ?? [];
1215
1458
  for (const name of names) {
1216
1459
  if (surfaces[name] === undefined) {
@@ -1218,56 +1461,941 @@ var account = (key, value, surfaces) => {
1218
1461
  }
1219
1462
  }
1220
1463
  const zones = raw["zones"] === undefined || raw["zones"] === "all" ? "all" : texts(raw, "zones", where);
1221
- return { id: text3(raw, "id", where), key, surfaces: names, zones: zones ?? "all" };
1464
+ return { id: text4(raw, "id", where), key, surfaces: names, zones: zones ?? "all" };
1222
1465
  };
1223
1466
  var parseRolesConfig = (yaml) => {
1224
- const root = record(Bun.YAML.parse(yaml), "(document)");
1467
+ const root = record2(Bun.YAML.parse(yaml), "(document)");
1225
1468
  const surfaces = {};
1226
- for (const [name, spec] of Object.entries(record(root["surfaces"], "surfaces"))) {
1469
+ for (const [name, spec] of Object.entries(record2(root["surfaces"], "surfaces"))) {
1227
1470
  surfaces[name] = surface(name, spec);
1228
1471
  }
1229
- const accounts = Object.entries(record(root["accounts"], "accounts")).map(([key, value]) => account(key, value, surfaces));
1472
+ const accounts = Object.entries(record2(root["accounts"], "accounts")).map(([key, value]) => account(key, value, surfaces));
1230
1473
  return {
1231
1474
  accounts,
1232
- namespace: text3(record(root["defaults"], "defaults"), "namespace", "defaults"),
1475
+ namespace: text4(record2(root["defaults"], "defaults"), "namespace", "defaults"),
1233
1476
  surfaces
1234
1477
  };
1235
1478
  };
1236
- // src/openbao/mount.ts
1479
+ // src/openbao/host-approles.ts
1480
+ var NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
1481
+ var HOST_ROLE_SEPARATOR = "--";
1482
+ var hostRoleName = (className, host) => `${className}${HOST_ROLE_SEPARATOR}${host}`;
1483
+ var classProblems = (name, spec) => {
1484
+ const found = [];
1485
+ if (!NAME.test(name))
1486
+ found.push(`class \`${name}\` is not lowercase letters, digits, single hyphens`);
1487
+ if (spec.policies.length === 0)
1488
+ found.push(`class \`${name}\` has no policies`);
1489
+ if (spec.policies.includes("root"))
1490
+ found.push(`class \`${name}\` grants \`root\``);
1491
+ const secretId = parseDuration(spec.secretIdTtl);
1492
+ if (secretId === undefined || secretId <= 0) {
1493
+ found.push(`class \`${name}\` secretIdTtl \`${spec.secretIdTtl}\` must be a duration above zero ` + "(0 never expires)");
1494
+ }
1495
+ const ttl = parseDuration(spec.tokenTtl);
1496
+ const max = parseDuration(spec.tokenMaxTtl);
1497
+ if (ttl === undefined || max === undefined) {
1498
+ found.push(`class \`${name}\` token TTLs must be durations like 15m or 1h`);
1499
+ } else if (max > 0 && ttl > max) {
1500
+ found.push(`class \`${name}\` tokenTtl is longer than tokenMaxTtl`);
1501
+ }
1502
+ return found;
1503
+ };
1504
+ var hostAppRoles = (input) => {
1505
+ const problems = Object.entries(input.classes).flatMap(([name, spec]) => classProblems(name, spec));
1506
+ const seen = new Set;
1507
+ const roles = [];
1508
+ for (const host of input.hosts) {
1509
+ if (!NAME.test(host.name)) {
1510
+ problems.push(`host \`${host.name}\` is not lowercase letters, digits, single hyphens`);
1511
+ }
1512
+ if (seen.has(host.name))
1513
+ problems.push(`host \`${host.name}\` is listed twice`);
1514
+ seen.add(host.name);
1515
+ const spec = input.classes[host.class];
1516
+ if (spec === undefined) {
1517
+ problems.push(`host \`${host.name}\` names unknown class \`${host.class}\``);
1518
+ continue;
1519
+ }
1520
+ roles.push({
1521
+ bindSecretId: true,
1522
+ name: hostRoleName(host.class, host.name),
1523
+ secretIdNumUses: spec.secretIdNumUses ?? 0,
1524
+ secretIdTtl: spec.secretIdTtl,
1525
+ tokenMaxTtl: spec.tokenMaxTtl,
1526
+ tokenPolicies: [...new Set(spec.policies)].sort(),
1527
+ tokenTtl: spec.tokenTtl
1528
+ });
1529
+ }
1530
+ if (problems.length > 0) {
1531
+ throw new Error(`hostAppRoles refused the input: ${problems.join("; ")}.`);
1532
+ }
1533
+ return roles.sort((a, b) => a.name.localeCompare(b.name));
1534
+ };
1535
+ // src/openbao/jwt-config.ts
1237
1536
  import { Resource as Resource4 } from "alchemy";
1238
1537
  import { isResolved as isResolved4 } from "alchemy/Diff";
1239
1538
  import * as Provider4 from "alchemy/Provider";
1240
- import * as Effect10 from "effect/Effect";
1539
+ import * as Effect15 from "effect/Effect";
1540
+
1541
+ // src/openbao/jwt-config-form.ts
1542
+ var text5 = (value) => typeof value === "string" ? value.trim() : "";
1543
+ var strings2 = (value) => Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
1544
+ var sortedSet = (values) => [...new Set(values.map((value) => value.trim()))].sort();
1545
+ var canonical2 = (input) => ({
1546
+ boundIssuer: input.boundIssuer.trim(),
1547
+ defaultRole: input.defaultRole.trim(),
1548
+ jwksCaPem: input.jwksCaPem.trim(),
1549
+ jwksUrl: input.jwksUrl.trim(),
1550
+ jwtSupportedAlgs: sortedSet(input.jwtSupportedAlgs),
1551
+ jwtValidationPubkeys: sortedSet(input.jwtValidationPubkeys),
1552
+ oidcDiscoveryCaPem: input.oidcDiscoveryCaPem.trim(),
1553
+ oidcDiscoveryUrl: input.oidcDiscoveryUrl.trim()
1554
+ });
1555
+ var canonicalFromProps = (props) => canonical2({
1556
+ boundIssuer: props.boundIssuer ?? "",
1557
+ defaultRole: props.defaultRole ?? "",
1558
+ jwksCaPem: props.jwksCaPem ?? "",
1559
+ jwksUrl: props.jwksUrl ?? "",
1560
+ jwtSupportedAlgs: props.jwtSupportedAlgs ?? [],
1561
+ jwtValidationPubkeys: props.jwtValidationPubkeys ?? [],
1562
+ oidcDiscoveryCaPem: props.oidcDiscoveryCaPem ?? "",
1563
+ oidcDiscoveryUrl: props.oidcDiscoveryUrl ?? ""
1564
+ });
1565
+ var canonicalFromLive = (live) => canonical2({
1566
+ boundIssuer: text5(live["bound_issuer"]),
1567
+ defaultRole: text5(live["default_role"]),
1568
+ jwksCaPem: text5(live["jwks_ca_pem"]),
1569
+ jwksUrl: text5(live["jwks_url"]),
1570
+ jwtSupportedAlgs: strings2(live["jwt_supported_algs"]),
1571
+ jwtValidationPubkeys: strings2(live["jwt_validation_pubkeys"]),
1572
+ oidcDiscoveryCaPem: text5(live["oidc_discovery_ca_pem"]),
1573
+ oidcDiscoveryUrl: text5(live["oidc_discovery_url"])
1574
+ });
1575
+ var mountOf = (props) => mountPath(props.mount ?? "jwt");
1576
+ var configPath = (props) => `auth/${mountOf(props)}/config`;
1577
+ var attributesOf5 = (props, live) => {
1578
+ const form = canonicalFromLive(live);
1579
+ return { ...form, digest: sha256(JSON.stringify(form)), mount: mountOf(props) };
1580
+ };
1581
+ var matches4 = (attributes, props) => attributes.digest === sha256(JSON.stringify(canonicalFromProps(props)));
1582
+ var writeBody3 = (props) => ({
1583
+ bound_issuer: props.boundIssuer ?? "",
1584
+ default_role: props.defaultRole ?? "",
1585
+ jwks_ca_pem: props.jwksCaPem ?? "",
1586
+ jwks_url: props.jwksUrl ?? "",
1587
+ jwt_supported_algs: [...props.jwtSupportedAlgs ?? []],
1588
+ jwt_validation_pubkeys: [...props.jwtValidationPubkeys ?? []],
1589
+ oidc_discovery_ca_pem: props.oidcDiscoveryCaPem ?? "",
1590
+ oidc_discovery_url: props.oidcDiscoveryUrl ?? ""
1591
+ });
1592
+ var wouldErase = (live) => {
1593
+ const lost = [];
1594
+ if (text5(live["oidc_client_id"]) !== "")
1595
+ lost.push("oidc_client_id (and its unreadable secret)");
1596
+ const provider = live["provider_config"];
1597
+ if (typeof provider === "object" && provider !== null && Object.keys(provider).length > 0) {
1598
+ lost.push("provider_config");
1599
+ }
1600
+ if (text5(live["oidc_response_mode"]) !== "")
1601
+ lost.push("oidc_response_mode");
1602
+ if (strings2(live["oidc_response_types"]).length > 0)
1603
+ lost.push("oidc_response_types");
1604
+ if (strings2(live["override_allowed_server_names"]).length > 0) {
1605
+ lost.push("override_allowed_server_names");
1606
+ }
1607
+ return lost;
1608
+ };
1609
+ var problems = (props) => {
1610
+ const found = [];
1611
+ const sources = [
1612
+ (props.oidcDiscoveryUrl ?? "") !== "",
1613
+ (props.jwksUrl ?? "") !== "",
1614
+ (props.jwtValidationPubkeys?.length ?? 0) > 0
1615
+ ].filter(Boolean).length;
1616
+ if (sources !== 1) {
1617
+ found.push("exactly one of oidcDiscoveryUrl, jwksUrl or jwtValidationPubkeys must be set");
1618
+ }
1619
+ if ((props.oidcDiscoveryUrl ?? "").includes(".well-known")) {
1620
+ found.push("oidcDiscoveryUrl must be the issuer, without /.well-known/openid-configuration");
1621
+ }
1622
+ const role = props.defaultRole ?? "";
1623
+ if (role !== role.toLowerCase())
1624
+ found.push(`defaultRole \`${role}\` must be lower case`);
1625
+ return found;
1626
+ };
1627
+
1628
+ // src/openbao/role-reconcile.ts
1629
+ import * as Effect14 from "effect/Effect";
1630
+ var readRoleAt = (spec) => Effect14.map(baoRead(spec.path), (live) => live === undefined ? undefined : { attributes: spec.attributesOf(live), live });
1631
+ var planRole = (spec) => Effect14.gen(function* () {
1632
+ if (spec.problems.length > 0)
1633
+ return "update";
1634
+ const found = yield* readRoleAt(spec);
1635
+ return found !== undefined && spec.matches(found.attributes) ? "noop" : "update";
1636
+ });
1637
+ var refuse2 = (spec, message) => Effect14.die(new Error(`${spec.family} ${spec.path}: ${message}`));
1638
+ var reconcileRole = (spec) => Effect14.gen(function* () {
1639
+ if (spec.problems.length > 0)
1640
+ return yield* refuse2(spec, spec.problems.join("; "));
1641
+ const found = yield* readRoleAt(spec);
1642
+ if (found === undefined || !spec.matches(found.attributes)) {
1643
+ const lost = found === undefined ? [] : spec.wouldErase?.(found.live) ?? [];
1644
+ if (lost.length > 0) {
1645
+ return yield* refuse2(spec, `live carries ${lost.join(", ")}, which this resource does not declare and the write ` + "would reset. Fold it into the declaration, or clear it by hand first.");
1646
+ }
1647
+ yield* baoWrite("PUT", spec.path, spec.body);
1648
+ }
1649
+ const after = yield* readRoleAt(spec);
1650
+ if (after === undefined)
1651
+ return yield* refuse2(spec, "write reported success but it is absent.");
1652
+ if (!spec.matches(after.attributes)) {
1653
+ return yield* refuse2(spec, "wrote cleanly but read back different — OpenBao stored something other than what was " + "declared. Read it by hand before retrying.");
1654
+ }
1655
+ return after.attributes;
1656
+ });
1657
+
1658
+ // src/openbao/jwt-config.ts
1659
+ var BaoJwtAuthConfig = Resource4("Bao.JwtAuthConfig", {
1660
+ defaultRemovalPolicy: "retain"
1661
+ });
1662
+ var jwtConfigSpec = (props) => ({
1663
+ attributesOf: (live) => attributesOf5(props, live),
1664
+ body: writeBody3(props),
1665
+ family: "Bao.JwtAuthConfig",
1666
+ matches: (attributes) => matches4(attributes, props),
1667
+ path: configPath(props),
1668
+ problems: problems(props),
1669
+ wouldErase
1670
+ });
1671
+ var BaoJwtAuthConfigProvider = () => Provider4.effect(BaoJwtAuthConfig, Effect15.succeed(BaoJwtAuthConfig.Provider.of({
1672
+ list: () => Effect15.succeed([]),
1673
+ read: Effect15.fn(function* ({ olds }) {
1674
+ const found = yield* readRoleAt(jwtConfigSpec(olds));
1675
+ return found?.attributes;
1676
+ }),
1677
+ diff: Effect15.fn(function* ({ news, output }) {
1678
+ if (output === undefined || !isResolved4(news))
1679
+ return;
1680
+ if (mountOf(news) !== output.mount)
1681
+ return { action: "replace" };
1682
+ return { action: yield* planRole(jwtConfigSpec(news)) };
1683
+ }),
1684
+ reconcile: Effect15.fn(function* ({ news }) {
1685
+ return yield* reconcileRole(jwtConfigSpec(news));
1686
+ }),
1687
+ delete: () => Effect15.void
1688
+ })));
1689
+ // src/openbao/jwt-role.ts
1690
+ import { Resource as Resource5 } from "alchemy";
1691
+ import { isResolved as isResolved5 } from "alchemy/Diff";
1692
+ import * as Provider5 from "alchemy/Provider";
1693
+ import * as Effect16 from "effect/Effect";
1694
+
1695
+ // src/openbao/auth-token-form.ts
1696
+ var sortedSet2 = (values) => [...new Set(values)].sort();
1697
+ var cidr = (value) => value.trim().replace(/\/(32|128)$/, "");
1698
+ var strings3 = (value) => Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
1699
+ var seconds2 = (text6) => parseDuration(text6 ?? "0") ?? -1;
1700
+ var tokenFields = (input) => ({
1701
+ tokenBoundCidrs: input.tokenBoundCidrs,
1702
+ tokenExplicitMaxTtlSeconds: input.tokenExplicitMaxTtlSeconds,
1703
+ tokenMaxTtlSeconds: input.tokenMaxTtlSeconds,
1704
+ tokenNoDefaultPolicy: input.tokenNoDefaultPolicy,
1705
+ tokenNumUses: input.tokenNumUses,
1706
+ tokenPeriodSeconds: input.tokenPeriodSeconds,
1707
+ tokenPolicies: input.tokenPolicies,
1708
+ tokenTtlSeconds: input.tokenTtlSeconds,
1709
+ tokenType: input.tokenType
1710
+ });
1711
+ var tokenForm = (input) => ({
1712
+ tokenBoundCidrs: sortedSet2(input.tokenBoundCidrs.map(cidr)),
1713
+ tokenExplicitMaxTtlSeconds: input.tokenExplicitMaxTtlSeconds,
1714
+ tokenMaxTtlSeconds: input.tokenMaxTtlSeconds,
1715
+ tokenNoDefaultPolicy: input.tokenNoDefaultPolicy,
1716
+ tokenNumUses: input.tokenNumUses,
1717
+ tokenPeriodSeconds: input.tokenPeriodSeconds,
1718
+ tokenPolicies: sortedSet2(input.tokenPolicies),
1719
+ tokenTtlSeconds: input.tokenTtlSeconds,
1720
+ tokenType: input.tokenType
1721
+ });
1722
+ var tokenFormOfProps = (props) => tokenForm({
1723
+ tokenBoundCidrs: props.tokenBoundCidrs ?? [],
1724
+ tokenExplicitMaxTtlSeconds: seconds2(props.tokenExplicitMaxTtl),
1725
+ tokenMaxTtlSeconds: seconds2(props.tokenMaxTtl),
1726
+ tokenNoDefaultPolicy: props.tokenNoDefaultPolicy ?? false,
1727
+ tokenNumUses: props.tokenNumUses ?? 0,
1728
+ tokenPeriodSeconds: seconds2(props.tokenPeriod),
1729
+ tokenPolicies: props.tokenPolicies,
1730
+ tokenTtlSeconds: seconds2(props.tokenTtl),
1731
+ tokenType: props.tokenType ?? "default"
1732
+ });
1733
+ var tokenFormOfLive = (live) => tokenForm({
1734
+ tokenBoundCidrs: strings3(live["token_bound_cidrs"]),
1735
+ tokenExplicitMaxTtlSeconds: ttlSeconds(live["token_explicit_max_ttl"]) ?? -1,
1736
+ tokenMaxTtlSeconds: ttlSeconds(live["token_max_ttl"]) ?? -1,
1737
+ tokenNoDefaultPolicy: live["token_no_default_policy"] === true,
1738
+ tokenNumUses: typeof live["token_num_uses"] === "number" ? live["token_num_uses"] : -1,
1739
+ tokenPeriodSeconds: ttlSeconds(live["token_period"]) ?? -1,
1740
+ tokenPolicies: strings3(live["token_policies"]),
1741
+ tokenTtlSeconds: ttlSeconds(live["token_ttl"]) ?? -1,
1742
+ tokenType: typeof live["token_type"] === "string" ? live["token_type"] : ""
1743
+ });
1744
+ var tokenBody = (props) => ({
1745
+ token_bound_cidrs: [...props.tokenBoundCidrs ?? []],
1746
+ token_explicit_max_ttl: props.tokenExplicitMaxTtl ?? "0",
1747
+ token_max_ttl: props.tokenMaxTtl ?? "0",
1748
+ token_no_default_policy: props.tokenNoDefaultPolicy ?? false,
1749
+ token_num_uses: props.tokenNumUses ?? 0,
1750
+ token_period: props.tokenPeriod ?? "0",
1751
+ token_policies: [...props.tokenPolicies],
1752
+ token_ttl: props.tokenTtl ?? "0",
1753
+ token_type: props.tokenType ?? "default"
1754
+ });
1755
+ var tokenProblems = (props) => {
1756
+ const found = [];
1757
+ const durations = {
1758
+ tokenExplicitMaxTtl: props.tokenExplicitMaxTtl,
1759
+ tokenMaxTtl: props.tokenMaxTtl,
1760
+ tokenPeriod: props.tokenPeriod,
1761
+ tokenTtl: props.tokenTtl
1762
+ };
1763
+ for (const [field, text6] of Object.entries(durations)) {
1764
+ if (seconds2(text6) < 0)
1765
+ found.push(`${field}=${text6 ?? ""} is not a duration (0 or <n>s|m|h|d)`);
1766
+ }
1767
+ const ttl = seconds2(props.tokenTtl);
1768
+ const max = seconds2(props.tokenMaxTtl);
1769
+ if (ttl > 0 && max > 0 && ttl > max)
1770
+ found.push("tokenTtl is longer than tokenMaxTtl");
1771
+ if (props.tokenType === "batch" && (seconds2(props.tokenPeriod) > 0 || (props.tokenNumUses ?? 0) > 0)) {
1772
+ found.push("a batch token cannot be periodic or use-limited (tokenutil.go:234-241)");
1773
+ }
1774
+ return found;
1775
+ };
1776
+
1777
+ // src/openbao/jwt-role-form.ts
1778
+ var sortedSet3 = (values) => [...new Set(values)].sort();
1779
+ var text6 = (value) => typeof value === "string" ? value : "";
1780
+ var strings4 = (value) => Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
1781
+ var record3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
1782
+ var claimsOf = (value) => {
1783
+ const source = record3(value);
1784
+ const out = {};
1785
+ for (const key of Object.keys(source).sort()) {
1786
+ const raw = source[key];
1787
+ out[key] = sortedSet3(typeof raw === "string" ? [raw] : strings4(raw));
1788
+ }
1789
+ return out;
1790
+ };
1791
+ var mappingsOf = (value) => {
1792
+ const source = record3(value);
1793
+ const out = {};
1794
+ for (const key of Object.keys(source).sort())
1795
+ out[key] = text6(source[key]);
1796
+ return out;
1797
+ };
1798
+ var canonical3 = (input) => ({
1799
+ allowedRedirectUris: sortedSet3(input.allowedRedirectUris),
1800
+ boundAudiences: sortedSet3(input.boundAudiences),
1801
+ boundClaims: claimsOf(input.boundClaims),
1802
+ boundClaimsType: input.boundClaimsType,
1803
+ boundSubject: input.boundSubject,
1804
+ callbackMode: input.callbackMode,
1805
+ claimMappings: mappingsOf(input.claimMappings),
1806
+ groupsClaim: input.groupsClaim,
1807
+ oidcDisableConfirmation: input.oidcDisableConfirmation,
1808
+ oidcScopes: sortedSet3(input.oidcScopes),
1809
+ roleType: input.roleType,
1810
+ userClaim: input.userClaim,
1811
+ verboseOidcLogging: input.verboseOidcLogging,
1812
+ ...tokenFields(input)
1813
+ });
1814
+ var canonicalFromProps2 = (props) => canonical3({
1815
+ ...tokenFormOfProps(props),
1816
+ allowedRedirectUris: props.allowedRedirectUris ?? [],
1817
+ boundAudiences: props.boundAudiences ?? [],
1818
+ boundClaims: claimsOf(props.boundClaims ?? {}),
1819
+ boundClaimsType: props.boundClaimsType ?? "string",
1820
+ boundSubject: props.boundSubject ?? "",
1821
+ callbackMode: props.callbackMode ?? "client",
1822
+ claimMappings: props.claimMappings ?? {},
1823
+ groupsClaim: props.groupsClaim ?? "",
1824
+ oidcDisableConfirmation: false,
1825
+ oidcScopes: props.oidcScopes ?? [],
1826
+ roleType: props.roleType,
1827
+ userClaim: props.userClaim,
1828
+ verboseOidcLogging: false
1829
+ });
1830
+ var canonicalFromLive2 = (live) => canonical3({
1831
+ ...tokenFormOfLive(live),
1832
+ allowedRedirectUris: strings4(live["allowed_redirect_uris"]),
1833
+ boundAudiences: strings4(live["bound_audiences"]),
1834
+ boundClaims: claimsOf(live["bound_claims"]),
1835
+ boundClaimsType: text6(live["bound_claims_type"]),
1836
+ boundSubject: text6(live["bound_subject"]),
1837
+ callbackMode: text6(live["callback_mode"]),
1838
+ claimMappings: mappingsOf(live["claim_mappings"]),
1839
+ groupsClaim: text6(live["groups_claim"]),
1840
+ oidcDisableConfirmation: live["oidc_disable_confirmation"] === true,
1841
+ oidcScopes: strings4(live["oidc_scopes"]),
1842
+ roleType: text6(live["role_type"]),
1843
+ userClaim: text6(live["user_claim"]),
1844
+ verboseOidcLogging: live["verbose_oidc_logging"] === true
1845
+ });
1846
+ var mountOf2 = (props) => mountPath(props.mount ?? "jwt");
1847
+ var rolePath2 = (props) => `auth/${mountOf2(props)}/role/${props.name}`;
1848
+ var attributesOf6 = (props, live) => {
1849
+ const form = canonicalFromLive2(live);
1850
+ return { ...form, digest: sha256(JSON.stringify(form)), mount: mountOf2(props), name: props.name };
1851
+ };
1852
+ var matches5 = (attributes, props) => attributes.digest === sha256(JSON.stringify(canonicalFromProps2(props)));
1853
+ var writeBody4 = (props) => ({
1854
+ allowed_redirect_uris: [...props.allowedRedirectUris ?? []],
1855
+ bound_audiences: [...props.boundAudiences ?? []],
1856
+ bound_claims: { ...props.boundClaims },
1857
+ bound_claims_type: props.boundClaimsType ?? "string",
1858
+ bound_subject: props.boundSubject ?? "",
1859
+ callback_mode: props.callbackMode ?? "client",
1860
+ claim_mappings: { ...props.claimMappings },
1861
+ groups_claim: props.groupsClaim ?? "",
1862
+ oidc_disable_confirmation: false,
1863
+ oidc_scopes: [...props.oidcScopes ?? []],
1864
+ role_type: props.roleType,
1865
+ user_claim: props.userClaim,
1866
+ verbose_oidc_logging: false,
1867
+ ...tokenBody(props)
1868
+ });
1869
+ var NAME2 = /^[a-z0-9_](?:[a-z0-9_.-]*[a-z0-9_])?$/;
1870
+ var problems2 = (props) => {
1871
+ const found = [...tokenProblems(props)];
1872
+ if (!NAME2.test(props.name))
1873
+ found.push(`name \`${props.name}\` must be lower case (the server folds it)`);
1874
+ if (props.userClaim.trim() === "")
1875
+ found.push("userClaim is empty; the server requires one");
1876
+ const bound = (props.boundAudiences?.length ?? 0) > 0 || (props.boundSubject ?? "") !== "" || Object.keys(props.boundClaims ?? {}).length > 0 || (props.tokenBoundCidrs?.length ?? 0) > 0;
1877
+ if (props.roleType === "jwt" && !bound) {
1878
+ found.push("a jwt role needs a bound constraint: boundAudiences, boundSubject, boundClaims or tokenBoundCidrs");
1879
+ }
1880
+ if (props.roleType === "oidc" && (props.allowedRedirectUris?.length ?? 0) === 0) {
1881
+ found.push("an oidc role needs allowedRedirectUris");
1882
+ }
1883
+ const targets = Object.values(props.claimMappings ?? {});
1884
+ if (targets.includes("role"))
1885
+ found.push("claimMappings may not map to the reserved key `role`");
1886
+ if (new Set(targets).size !== targets.length)
1887
+ found.push("claimMappings maps two claims to one key");
1888
+ return found;
1889
+ };
1890
+
1891
+ // src/openbao/jwt-role.ts
1892
+ var BaoJwtRole = Resource5("Bao.JwtRole", {
1893
+ defaultRemovalPolicy: "retain"
1894
+ });
1895
+ var jwtRoleSpec = (props) => ({
1896
+ attributesOf: (live) => attributesOf6(props, live),
1897
+ body: writeBody4(props),
1898
+ family: "Bao.JwtRole",
1899
+ matches: (attributes) => matches5(attributes, props),
1900
+ path: rolePath2(props),
1901
+ problems: problems2(props)
1902
+ });
1903
+ var BaoJwtRoleProvider = () => Provider5.effect(BaoJwtRole, Effect16.succeed(BaoJwtRole.Provider.of({
1904
+ list: () => Effect16.succeed([]),
1905
+ read: Effect16.fn(function* ({ olds }) {
1906
+ const found = yield* readRoleAt(jwtRoleSpec(olds));
1907
+ return found?.attributes;
1908
+ }),
1909
+ diff: Effect16.fn(function* ({ news, output }) {
1910
+ if (output === undefined || !isResolved5(news))
1911
+ return;
1912
+ if (mountOf2(news) !== output.mount || news.name !== output.name) {
1913
+ return { action: "replace" };
1914
+ }
1915
+ return { action: yield* planRole(jwtRoleSpec(news)) };
1916
+ }),
1917
+ reconcile: Effect16.fn(function* ({ news }) {
1918
+ return yield* reconcileRole(jwtRoleSpec(news));
1919
+ }),
1920
+ delete: Effect16.fn(function* ({ output }) {
1921
+ yield* baoDelete(rolePath2(output));
1922
+ return;
1923
+ })
1924
+ })));
1925
+ // src/openbao/kubernetes-role.ts
1926
+ import { Resource as Resource6 } from "alchemy";
1927
+ import { isResolved as isResolved6 } from "alchemy/Diff";
1928
+ import * as Provider6 from "alchemy/Provider";
1929
+ import * as Effect17 from "effect/Effect";
1930
+
1931
+ // src/openbao/kubernetes-role-form.ts
1932
+ var sortedSet4 = (values) => [...new Set(values)].sort();
1933
+ var text7 = (value) => typeof value === "string" ? value : "";
1934
+ var strings5 = (value) => Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
1935
+ var canonical4 = (input) => ({
1936
+ aliasNameSource: input.aliasNameSource,
1937
+ audience: input.audience,
1938
+ boundServiceAccountNames: sortedSet4(input.boundServiceAccountNames),
1939
+ boundServiceAccountNamespaceSelector: input.boundServiceAccountNamespaceSelector.trim(),
1940
+ boundServiceAccountNamespaces: sortedSet4(input.boundServiceAccountNamespaces),
1941
+ ...tokenFields(input)
1942
+ });
1943
+ var canonicalFromProps3 = (props) => canonical4({
1944
+ ...tokenFormOfProps(props),
1945
+ aliasNameSource: props.aliasNameSource,
1946
+ audience: props.audience ?? "",
1947
+ boundServiceAccountNames: props.boundServiceAccountNames,
1948
+ boundServiceAccountNamespaceSelector: props.boundServiceAccountNamespaceSelector ?? "",
1949
+ boundServiceAccountNamespaces: props.boundServiceAccountNamespaces ?? []
1950
+ });
1951
+ var canonicalFromLive3 = (live) => canonical4({
1952
+ ...tokenFormOfLive(live),
1953
+ aliasNameSource: text7(live["alias_name_source"]),
1954
+ audience: text7(live["audience"]),
1955
+ boundServiceAccountNames: strings5(live["bound_service_account_names"]),
1956
+ boundServiceAccountNamespaceSelector: text7(live["bound_service_account_namespace_selector"]),
1957
+ boundServiceAccountNamespaces: strings5(live["bound_service_account_namespaces"])
1958
+ });
1959
+ var mountOf3 = (props) => mountPath(props.mount ?? "kubernetes");
1960
+ var rolePath3 = (props) => `auth/${mountOf3(props)}/role/${props.name}`;
1961
+ var attributesOf7 = (props, live) => {
1962
+ const form = canonicalFromLive3(live);
1963
+ return { ...form, digest: sha256(JSON.stringify(form)), mount: mountOf3(props), name: props.name };
1964
+ };
1965
+ var matches6 = (attributes, props) => attributes.digest === sha256(JSON.stringify(canonicalFromProps3(props)));
1966
+ var writeBody5 = (props) => ({
1967
+ alias_name_source: props.aliasNameSource,
1968
+ audience: props.audience ?? "",
1969
+ bound_service_account_names: [...props.boundServiceAccountNames],
1970
+ bound_service_account_namespace_selector: props.boundServiceAccountNamespaceSelector ?? "",
1971
+ bound_service_account_namespaces: [...props.boundServiceAccountNamespaces ?? []],
1972
+ ...tokenBody(props)
1973
+ });
1974
+ var NAME3 = /^[a-z0-9_](?:[a-z0-9_.-]*[a-z0-9_])?$/;
1975
+ var starMixed = (values) => values.length > 1 && values.includes("*");
1976
+ var problems3 = (props) => {
1977
+ const found = [...tokenProblems(props)];
1978
+ const namespaces = props.boundServiceAccountNamespaces ?? [];
1979
+ const selector = (props.boundServiceAccountNamespaceSelector ?? "").trim();
1980
+ if (!NAME3.test(props.name))
1981
+ found.push(`name \`${props.name}\` must be lower case (stored folded)`);
1982
+ if (props.boundServiceAccountNames.length === 0)
1983
+ found.push("boundServiceAccountNames is empty");
1984
+ if (starMixed(props.boundServiceAccountNames))
1985
+ found.push("`*` mixed with other account names");
1986
+ if (namespaces.length === 0 && selector === "") {
1987
+ found.push("boundServiceAccountNamespaces is empty and no namespace selector is set");
1988
+ }
1989
+ if (starMixed(namespaces))
1990
+ found.push("`*` mixed with other namespaces");
1991
+ return found;
1992
+ };
1993
+
1994
+ // src/openbao/kubernetes-role.ts
1995
+ var BaoKubernetesRole = Resource6("Bao.KubernetesRole", {
1996
+ defaultRemovalPolicy: "retain"
1997
+ });
1998
+ var kubernetesRoleSpec = (props) => ({
1999
+ attributesOf: (live) => attributesOf7(props, live),
2000
+ body: writeBody5(props),
2001
+ family: "Bao.KubernetesRole",
2002
+ matches: (attributes) => matches6(attributes, props),
2003
+ path: rolePath3(props),
2004
+ problems: problems3(props)
2005
+ });
2006
+ var BaoKubernetesRoleProvider = () => Provider6.effect(BaoKubernetesRole, Effect17.succeed(BaoKubernetesRole.Provider.of({
2007
+ list: () => Effect17.succeed([]),
2008
+ read: Effect17.fn(function* ({ olds }) {
2009
+ const found = yield* readRoleAt(kubernetesRoleSpec(olds));
2010
+ return found?.attributes;
2011
+ }),
2012
+ diff: Effect17.fn(function* ({ news, output }) {
2013
+ if (output === undefined || !isResolved6(news))
2014
+ return;
2015
+ if (mountOf3(news) !== output.mount || news.name !== output.name) {
2016
+ return { action: "replace" };
2017
+ }
2018
+ return { action: yield* planRole(kubernetesRoleSpec(news)) };
2019
+ }),
2020
+ reconcile: Effect17.fn(function* ({ news }) {
2021
+ return yield* reconcileRole(kubernetesRoleSpec(news));
2022
+ }),
2023
+ delete: Effect17.fn(function* ({ output }) {
2024
+ yield* baoDelete(rolePath3(output));
2025
+ return;
2026
+ })
2027
+ })));
2028
+ // src/openbao/mfa-enforcement.ts
2029
+ import { Resource as Resource7 } from "alchemy";
2030
+ import { isResolved as isResolved7 } from "alchemy/Diff";
2031
+ import * as Provider7 from "alchemy/Provider";
2032
+ import * as Effect19 from "effect/Effect";
2033
+
2034
+ // src/openbao/mfa-enforcement-form.ts
2035
+ var sortedSet5 = (values) => [...new Set(values.map((value) => value.trim()))].filter((value) => value !== "").sort();
2036
+ var strings6 = (value) => Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
2037
+ var text8 = (value) => typeof value === "string" ? value : "";
2038
+ var canonical5 = (input) => ({
2039
+ authMethodAccessors: sortedSet5(input.authMethodAccessors),
2040
+ authMethodTypes: sortedSet5(input.authMethodTypes),
2041
+ identityEntityIds: sortedSet5(input.identityEntityIds),
2042
+ identityGroupIds: sortedSet5(input.identityGroupIds),
2043
+ mfaMethodIds: sortedSet5(input.mfaMethodIds)
2044
+ });
2045
+ var canonicalFromProps4 = (props, resolved) => canonical5({
2046
+ authMethodAccessors: [...props.authMethodAccessors ?? [], ...resolved],
2047
+ authMethodTypes: props.authMethodTypes ?? [],
2048
+ identityEntityIds: props.identityEntityIds ?? [],
2049
+ identityGroupIds: props.identityGroupIds ?? [],
2050
+ mfaMethodIds: props.mfaMethodIds
2051
+ });
2052
+ var canonicalFromLive4 = (live) => canonical5({
2053
+ authMethodAccessors: strings6(live["auth_method_accessors"]),
2054
+ authMethodTypes: strings6(live["auth_method_types"]),
2055
+ identityEntityIds: strings6(live["identity_entity_ids"]),
2056
+ identityGroupIds: strings6(live["identity_group_ids"]),
2057
+ mfaMethodIds: strings6(live["mfa_method_ids"])
2058
+ });
2059
+ var attributesOf8 = (name, live) => {
2060
+ const form = canonicalFromLive4(live);
2061
+ return {
2062
+ ...form,
2063
+ digest: sha256(JSON.stringify(form)),
2064
+ enforcementId: text8(live["id"]),
2065
+ name,
2066
+ namespacePath: text8(live["namespace_path"])
2067
+ };
2068
+ };
2069
+ var matches7 = (attributes, props, resolved) => attributes.digest === sha256(JSON.stringify(canonicalFromProps4(props, resolved)));
2070
+ var writeBody6 = (form) => ({
2071
+ auth_method_accessors: [...form.authMethodAccessors],
2072
+ auth_method_types: [...form.authMethodTypes],
2073
+ identity_entity_ids: [...form.identityEntityIds],
2074
+ identity_group_ids: [...form.identityGroupIds],
2075
+ mfa_method_ids: [...form.mfaMethodIds]
2076
+ });
2077
+ var NAME4 = /^\w(?:[\w.-]*\w)?$/;
2078
+ var problems4 = (props, missingPaths) => {
2079
+ const found = [];
2080
+ if (!NAME4.test(props.name))
2081
+ found.push(`name \`${props.name}\` is not a valid enforcement name`);
2082
+ if (sortedSet5(props.mfaMethodIds).length === 0)
2083
+ found.push("mfaMethodIds is empty");
2084
+ const targets = [
2085
+ props.authMethodPaths,
2086
+ props.authMethodAccessors,
2087
+ props.authMethodTypes,
2088
+ props.identityGroupIds,
2089
+ props.identityEntityIds
2090
+ ].some((list) => sortedSet5(list ?? []).length > 0);
2091
+ if (!targets)
2092
+ found.push("no target: an enforcement with none is accepted and enforces NOTHING");
2093
+ if (missingPaths.length > 0) {
2094
+ found.push(`no auth mount at ${missingPaths.map((path) => mountPath(path)).join(", ")}`);
2095
+ }
2096
+ return found;
2097
+ };
2098
+
2099
+ // src/openbao/mfa-wire.ts
2100
+ import * as Effect18 from "effect/Effect";
2101
+ var TOTP_PATH = "identity/mfa/method/totp";
2102
+ var METHODS_PATH = "identity/mfa/method";
2103
+ var enforcementPath = (name) => `identity/mfa/login-enforcement/${name}`;
2104
+ var record4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
2105
+ var ownNamespacePath = Effect18.gen(function* () {
2106
+ const env = yield* BaoEnv;
2107
+ return namespacePath(envNamespace(env));
2108
+ });
2109
+ var findTotp = (name) => Effect18.gen(function* () {
2110
+ const listing = yield* baoRead(`${METHODS_PATH}?list=true`);
2111
+ const info = record4(listing?.["key_info"]) ?? {};
2112
+ const own = yield* ownNamespacePath;
2113
+ for (const [id, raw] of Object.entries(info)) {
2114
+ const live = record4(raw);
2115
+ if (live === undefined || live["name"] !== name || live["namespace_path"] !== own)
2116
+ continue;
2117
+ if (live["type"] !== "totp") {
2118
+ return yield* Effect18.die(new Error(`Bao.MfaTotpMethod ${name}: that name belongs to a ${String(live["type"])} method ` + `(${id}) in this namespace, and a TOTP write would convert it. Pick another name.`));
2119
+ }
2120
+ return { id, live };
2121
+ }
2122
+ return;
2123
+ });
2124
+ var writeTotp = (body) => Effect18.asVoid(baoCall("write", "POST", TOTP_PATH, body));
2125
+ var deleteTotp = (id) => baoDelete(`${TOTP_PATH}/${id}`);
2126
+ var readEnforcement = (name) => baoRead(enforcementPath(name));
2127
+ var writeEnforcement = (name, body) => Effect18.asVoid(baoCall("write", "POST", enforcementPath(name), body));
2128
+ var authAccessors = (paths) => Effect18.gen(function* () {
2129
+ if (paths.length === 0)
2130
+ return { accessors: [], missing: [] };
2131
+ const table = (yield* baoRead("sys/auth")) ?? {};
2132
+ const accessors = [];
2133
+ const missing = [];
2134
+ for (const path of paths) {
2135
+ const accessor = record4(table[`${mountPath(path)}/`])?.["accessor"];
2136
+ if (typeof accessor === "string" && accessor !== "")
2137
+ accessors.push(accessor);
2138
+ else
2139
+ missing.push(mountPath(path));
2140
+ }
2141
+ return { accessors, missing };
2142
+ });
2143
+
2144
+ // src/openbao/mfa-enforcement.ts
2145
+ var BaoMfaLoginEnforcement = Resource7("Bao.MfaLoginEnforcement", {
2146
+ defaultRemovalPolicy: "retain"
2147
+ });
2148
+ var refuse3 = (name, message) => Effect19.die(new Error(`Bao.MfaLoginEnforcement ${name}: ${message}`));
2149
+ var resolveTargets = (props) => Effect19.map(authAccessors(props.authMethodPaths ?? []), ({ accessors, missing }) => ({
2150
+ bad: problems4(props, missing),
2151
+ resolved: accessors
2152
+ }));
2153
+ var readLive = (name) => Effect19.map(readEnforcement(name), (live) => live === undefined ? undefined : attributesOf8(name, live));
2154
+ var planEnforcement = (props) => Effect19.gen(function* () {
2155
+ const { bad, resolved } = yield* resolveTargets(props);
2156
+ if (bad.length > 0)
2157
+ return "update";
2158
+ const live = yield* readLive(props.name);
2159
+ return live !== undefined && matches7(live, props, resolved) ? "noop" : "update";
2160
+ });
2161
+ var reconcileEnforcement = (props) => Effect19.gen(function* () {
2162
+ const { bad, resolved } = yield* resolveTargets(props);
2163
+ if (bad.length > 0)
2164
+ return yield* refuse3(props.name, bad.join("; "));
2165
+ const live = yield* readLive(props.name);
2166
+ if (live === undefined || !matches7(live, props, resolved)) {
2167
+ yield* writeEnforcement(props.name, writeBody6(canonicalFromProps4(props, resolved)));
2168
+ }
2169
+ const after = yield* readLive(props.name);
2170
+ if (after === undefined)
2171
+ return yield* refuse3(props.name, "the write succeeded but it is absent.");
2172
+ if (!matches7(after, props, resolved)) {
2173
+ return yield* refuse3(props.name, "the enforcement read back different.");
2174
+ }
2175
+ return after;
2176
+ });
2177
+ var BaoMfaLoginEnforcementProvider = () => Provider7.effect(BaoMfaLoginEnforcement, Effect19.succeed(BaoMfaLoginEnforcement.Provider.of({
2178
+ list: () => Effect19.succeed([]),
2179
+ read: Effect19.fn(function* ({ olds }) {
2180
+ return yield* readLive(olds.name);
2181
+ }),
2182
+ diff: Effect19.fn(function* ({ news, output }) {
2183
+ if (output === undefined || !isResolved7(news))
2184
+ return;
2185
+ if (news.name !== output.name)
2186
+ return { action: "replace" };
2187
+ return { action: yield* planEnforcement(news) };
2188
+ }),
2189
+ reconcile: Effect19.fn(function* ({ news }) {
2190
+ return yield* reconcileEnforcement(news);
2191
+ }),
2192
+ delete: Effect19.fn(function* ({ output }) {
2193
+ return yield* refuse3(output.name, "REFUSING to delete: OpenBao 2.6.2 drops a login enforcement from memory only, and it " + "returns on the next unseal (openbao/openbao#4030). Set this resource back to " + "retain to drop it from state; remove it for good only once the server is fixed.");
2194
+ })
2195
+ })));
2196
+ // src/openbao/mfa-totp.ts
2197
+ import { Resource as Resource8 } from "alchemy";
2198
+ import { isResolved as isResolved8 } from "alchemy/Diff";
2199
+ import * as Provider8 from "alchemy/Provider";
2200
+ import * as Effect20 from "effect/Effect";
2201
+
2202
+ // src/openbao/mfa-totp-form.ts
2203
+ var canonical6 = (input) => ({
2204
+ algorithm: input.algorithm,
2205
+ digits: input.digits,
2206
+ issuer: input.issuer,
2207
+ keySize: input.keySize,
2208
+ maxValidationAttempts: input.maxValidationAttempts,
2209
+ periodSeconds: input.periodSeconds,
2210
+ qrSize: input.qrSize,
2211
+ skew: input.skew
2212
+ });
2213
+ var canonicalFromProps5 = (props) => canonical6({
2214
+ algorithm: props.algorithm ?? "SHA1",
2215
+ digits: props.digits ?? 6,
2216
+ issuer: props.issuer,
2217
+ keySize: props.keySize ?? 20,
2218
+ maxValidationAttempts: props.maxValidationAttempts ?? 5,
2219
+ periodSeconds: parseDuration(props.period ?? "30s") ?? -1,
2220
+ qrSize: props.qrSize ?? 200,
2221
+ skew: props.skew ?? 1
2222
+ });
2223
+ var num = (value) => typeof value === "number" && Number.isFinite(value) ? value : -1;
2224
+ var text9 = (value) => typeof value === "string" ? value : "";
2225
+ var canonicalFromLive5 = (live) => canonical6({
2226
+ algorithm: text9(live["algorithm"]),
2227
+ digits: num(live["digits"]),
2228
+ issuer: text9(live["issuer"]),
2229
+ keySize: num(live["key_size"]),
2230
+ maxValidationAttempts: num(live["max_validation_attempts"]),
2231
+ periodSeconds: ttlSeconds(live["period"]) ?? -1,
2232
+ qrSize: num(live["qr_size"]),
2233
+ skew: num(live["skew"])
2234
+ });
2235
+ var attributesOf9 = (methodId, live) => {
2236
+ const form = canonicalFromLive5(live);
2237
+ return {
2238
+ ...form,
2239
+ digest: sha256(JSON.stringify(form)),
2240
+ methodId,
2241
+ name: text9(live["name"]),
2242
+ namespacePath: text9(live["namespace_path"])
2243
+ };
2244
+ };
2245
+ var matches8 = (attributes, props) => attributes.digest === sha256(JSON.stringify(canonicalFromProps5(props)));
2246
+ var writeBody7 = (props) => {
2247
+ const form = canonicalFromProps5(props);
2248
+ return {
2249
+ algorithm: form.algorithm,
2250
+ digits: form.digits,
2251
+ issuer: form.issuer,
2252
+ key_size: form.keySize,
2253
+ max_validation_attempts: form.maxValidationAttempts,
2254
+ method_name: props.name,
2255
+ period: props.period ?? "30s",
2256
+ qr_size: form.qrSize,
2257
+ skew: form.skew
2258
+ };
2259
+ };
2260
+ var renameProblem = (from, to) => from === to ? undefined : `renaming from \`${from}\` would strand every enrolled secret. Declare the new method as a ` + "second resource, list both ids on the enforcement, enrol, then remove the old one " + "(mfa-totp.ts).";
2261
+ var problems5 = (props) => {
2262
+ const found = [];
2263
+ const form = canonicalFromProps5(props);
2264
+ if (props.name.trim() === "")
2265
+ found.push("name is empty; it is the only key a declaration has");
2266
+ if (props.issuer.trim() === "")
2267
+ found.push("issuer is empty; the server requires one");
2268
+ if (form.periodSeconds <= 0)
2269
+ found.push(`period \`${props.period ?? ""}\` must be a duration above zero`);
2270
+ if (form.keySize <= 0)
2271
+ found.push("keySize must be above zero");
2272
+ if (form.maxValidationAttempts < 1)
2273
+ found.push("maxValidationAttempts must be at least 1 (0 reads back as 5)");
2274
+ return found;
2275
+ };
2276
+
2277
+ // src/openbao/mfa-totp.ts
2278
+ var BaoMfaTotpMethod = Resource8("Bao.MfaTotpMethod", {
2279
+ defaultRemovalPolicy: "retain"
2280
+ });
2281
+ var refuse4 = (props, message) => Effect20.die(new Error(`Bao.MfaTotpMethod ${props.name}: ${message}`));
2282
+ var readTotp = (props) => Effect20.map(findTotp(props.name), (found) => found === undefined ? undefined : attributesOf9(found.id, found.live));
2283
+ var planTotp = (props) => Effect20.gen(function* () {
2284
+ if (problems5(props).length > 0)
2285
+ return "update";
2286
+ const live = yield* readTotp(props);
2287
+ return live !== undefined && matches8(live, props) ? "noop" : "update";
2288
+ });
2289
+ var reconcileTotp = (props) => Effect20.gen(function* () {
2290
+ const bad = problems5(props);
2291
+ if (bad.length > 0)
2292
+ return yield* refuse4(props, bad.join("; "));
2293
+ const live = yield* readTotp(props);
2294
+ if (live === undefined || !matches8(live, props))
2295
+ yield* writeTotp(writeBody7(props));
2296
+ const after = yield* readTotp(props);
2297
+ if (after === undefined)
2298
+ return yield* refuse4(props, "the write succeeded but no method has that name.");
2299
+ if (!matches8(after, props))
2300
+ return yield* refuse4(props, "the method read back different.");
2301
+ return after;
2302
+ });
2303
+ var BaoMfaTotpMethodProvider = () => Provider8.effect(BaoMfaTotpMethod, Effect20.succeed(BaoMfaTotpMethod.Provider.of({
2304
+ list: () => Effect20.succeed([]),
2305
+ read: Effect20.fn(function* ({ olds }) {
2306
+ return yield* readTotp(olds);
2307
+ }),
2308
+ diff: Effect20.fn(function* ({ news, output }) {
2309
+ if (output === undefined || !isResolved8(news))
2310
+ return;
2311
+ const rename = renameProblem(output.name, news.name);
2312
+ if (rename !== undefined)
2313
+ return yield* refuse4(news, rename);
2314
+ return { action: yield* planTotp(news) };
2315
+ }),
2316
+ reconcile: Effect20.fn(function* ({ news }) {
2317
+ return yield* reconcileTotp(news);
2318
+ }),
2319
+ delete: Effect20.fn(function* ({ output }) {
2320
+ yield* deleteTotp(output.methodId);
2321
+ return;
2322
+ })
2323
+ })));
2324
+ // src/openbao/mount.ts
2325
+ import { Resource as Resource9 } from "alchemy";
2326
+ import { isResolved as isResolved9 } from "alchemy/Diff";
2327
+ import * as Provider9 from "alchemy/Provider";
2328
+ import * as Effect23 from "effect/Effect";
2329
+
2330
+ // src/openbao/mount-reconcile.ts
2331
+ import * as Effect22 from "effect/Effect";
1241
2332
 
1242
2333
  // src/openbao/mount-wire.ts
1243
- import * as Effect9 from "effect/Effect";
1244
- var absentFromTable2 = (path, asked) => baoRead("sys/mounts").pipe(Effect9.mapError((listing) => new BaoError(asked.status, asked.method, asked.path, [
2334
+ import * as Effect21 from "effect/Effect";
2335
+ var absentFromTable2 = (path, asked) => baoRead("sys/mounts").pipe(Effect21.mapError((listing) => new BaoError(asked.status, asked.method, asked.path, [
1245
2336
  ...asked.errors,
1246
2337
  `sys/mounts could not confirm the mount is absent: ${listing.message}`
1247
- ])), Effect9.flatMap((table) => table !== undefined && !Object.hasOwn(table, `${mountPath(path)}/`) ? Effect9.succeed(undefined) : Effect9.fail(asked)));
1248
- var readMountData = (path) => baoRead(readPath(path)).pipe(Effect9.catchIf((error) => error.status === 400, (error) => absentFromTable2(path, error)));
2338
+ ])), Effect21.flatMap((table) => table !== undefined && !Object.hasOwn(table, `${mountPath(path)}/`) ? Effect21.succeed(undefined) : Effect21.fail(asked)));
2339
+ var readMountData = (path) => baoRead(readPath(path)).pipe(Effect21.catchIf((error) => error.status === 400, (error) => absentFromTable2(path, error)));
1249
2340
  var enableMount = (props) => baoWrite("POST", readPath(props.path), enableBody(props));
1250
2341
  var tuneMount = (props) => baoWrite("POST", tunePath(props.path), tuneBody(props));
1251
2342
  var disableMount = (path) => baoDelete(readPath(path));
1252
2343
 
1253
- // src/openbao/mount.ts
1254
- var BaoMount = Resource4("Bao.Mount", {
1255
- defaultRemovalPolicy: "retain"
1256
- });
1257
- var readMount = (props) => Effect10.gen(function* () {
2344
+ // src/openbao/mount-reconcile.ts
2345
+ var readMount = (props) => Effect22.gen(function* () {
1258
2346
  const live = yield* readMountData(props.path);
1259
2347
  if (live === undefined)
1260
2348
  return;
1261
2349
  return attributesOf(props, live);
1262
2350
  });
1263
- var BaoMountProvider = () => Provider4.effect(BaoMount, Effect10.succeed(BaoMount.Provider.of({
1264
- list: () => Effect10.succeed([]),
1265
- read: Effect10.fn(function* ({ olds }) {
2351
+ var moveTarget2 = (props) => ({
2352
+ family: "Bao.Mount",
2353
+ path: props.path,
2354
+ read: (path) => readMount({ ...props, path }),
2355
+ type: props.type,
2356
+ wire: mountPath
2357
+ });
2358
+ var reconcileMount = (news, stated, options) => Effect22.gen(function* () {
2359
+ const move = planMove("Bao.Mount", stated, news.path, news.remountFrom);
2360
+ if (move.kind === "refuse")
2361
+ return yield* Effect22.die(new Error(move.message));
2362
+ if (move.kind === "move")
2363
+ yield* performMove(moveTarget2(news), move.from, options);
2364
+ let live = yield* readMount(news);
2365
+ if (live === undefined) {
2366
+ yield* enableMount(news);
2367
+ if (wantsTune(news))
2368
+ yield* tuneMount(news);
2369
+ } else if (!matches(live, news)) {
2370
+ if (live.type !== news.type) {
2371
+ return yield* Effect22.die(new Error(`Bao.Mount ${mountPath(news.path)}: live type ${live.type} != ${news.type}. ` + "Secrets engine type is immutable — replace manually."));
2372
+ }
2373
+ yield* tuneMount(news);
2374
+ }
2375
+ live = yield* readMount(news);
2376
+ if (live === undefined) {
2377
+ return yield* Effect22.die(new Error(`Bao.Mount ${mountPath(news.path)}: write returned no error but the mount is still absent.`));
2378
+ }
2379
+ return live;
2380
+ });
2381
+
2382
+ // src/openbao/mount.ts
2383
+ var BaoMount = Resource9("Bao.Mount", {
2384
+ defaultRemovalPolicy: "retain"
2385
+ });
2386
+ var BaoMountProvider = () => Provider9.effect(BaoMount, Effect23.succeed(BaoMount.Provider.of({
2387
+ list: () => Effect23.succeed([]),
2388
+ read: Effect23.fn(function* ({ olds }) {
1266
2389
  return yield* readMount(olds);
1267
2390
  }),
1268
- diff: Effect10.fn(function* ({ news, output }) {
1269
- if (output === undefined || !isResolved4(news))
2391
+ diff: Effect23.fn(function* ({ news, output }) {
2392
+ if (output === undefined || !isResolved9(news))
1270
2393
  return;
2394
+ const move = planMove("Bao.Mount", output.path, news.path, news.remountFrom);
2395
+ if (move.kind === "refuse")
2396
+ return yield* Effect23.die(new Error(move.message));
2397
+ if (move.kind === "move")
2398
+ return { action: "update" };
1271
2399
  const live = yield* readMount(news);
1272
2400
  if (live === undefined)
1273
2401
  return { action: "update" };
@@ -1277,39 +2405,29 @@ var BaoMountProvider = () => Provider4.effect(BaoMount, Effect10.succeed(BaoMoun
1277
2405
  return { action: "noop" };
1278
2406
  return { action: "update" };
1279
2407
  }),
1280
- reconcile: Effect10.fn(function* ({ news }) {
1281
- let live = yield* readMount(news);
1282
- if (live === undefined) {
1283
- yield* enableMount(news);
1284
- if (wantsTune(news))
1285
- yield* tuneMount(news);
1286
- } else if (!matches(live, news)) {
1287
- if (live.type !== news.type) {
1288
- return yield* Effect10.die(new Error(`Bao.Mount ${mountPath(news.path)}: live type ${live.type} != ${news.type}. ` + "Secrets engine type is immutable — replace manually."));
1289
- }
1290
- yield* tuneMount(news);
1291
- }
1292
- live = yield* readMount(news);
1293
- if (live === undefined) {
1294
- return yield* Effect10.die(new Error(`Bao.Mount ${mountPath(news.path)}: write returned no error but the mount is still absent.`));
1295
- }
1296
- return live;
2408
+ reconcile: Effect23.fn(function* ({ news, output }) {
2409
+ return yield* reconcileMount(news, output?.path);
1297
2410
  }),
1298
- delete: Effect10.fn(function* ({ output }) {
2411
+ delete: Effect23.fn(function* ({ output }) {
1299
2412
  yield* disableMount(output.path);
1300
2413
  return;
1301
2414
  })
1302
2415
  })));
1303
2416
  // src/openbao/pki-role.ts
1304
- import { Resource as Resource5 } from "alchemy";
1305
- import { isResolved as isResolved5 } from "alchemy/Diff";
1306
- import * as Provider5 from "alchemy/Provider";
1307
- import * as Effect11 from "effect/Effect";
2417
+ import { Resource as Resource10 } from "alchemy";
2418
+ import { isResolved as isResolved10 } from "alchemy/Diff";
2419
+ import * as Provider10 from "alchemy/Provider";
2420
+ import * as Effect24 from "effect/Effect";
1308
2421
 
1309
- // src/openbao/pki-role-form.ts
2422
+ // src/openbao/pki-role-canonical.ts
2423
+ var DEFAULT_KEY_USAGE = [
2424
+ "DigitalSignature",
2425
+ "KeyAgreement",
2426
+ "KeyEncipherment"
2427
+ ];
1310
2428
  var sorted = (values) => [...values].sort();
1311
2429
  var foldedUsages = (values) => sorted(values.map((v) => v.toLowerCase()));
1312
- var canonical2 = (input) => ({
2430
+ var canonical7 = (input) => ({
1313
2431
  allowBareDomains: input.allowBareDomains,
1314
2432
  allowGlobDomains: input.allowGlobDomains,
1315
2433
  allowIpSans: input.allowIpSans,
@@ -1317,18 +2435,24 @@ var canonical2 = (input) => ({
1317
2435
  allowSubdomains: input.allowSubdomains,
1318
2436
  allowWildcardCertificates: input.allowWildcardCertificates,
1319
2437
  allowedDomains: sorted(input.allowedDomains),
2438
+ allowedDomainsTemplate: input.allowedDomainsTemplate,
1320
2439
  clientFlag: input.clientFlag,
1321
2440
  cnValidations: sorted(input.cnValidations),
2441
+ enforceHostnames: input.enforceHostnames,
1322
2442
  extKeyUsage: foldedUsages(input.extKeyUsage),
2443
+ generateLease: input.generateLease,
1323
2444
  keyBits: input.keyBits,
1324
2445
  keyType: input.keyType,
2446
+ keyUsage: foldedUsages(input.keyUsage),
1325
2447
  maxTtlSeconds: input.maxTtlSeconds,
2448
+ noStore: input.noStore,
2449
+ requireCn: input.requireCn,
1326
2450
  serverFlag: input.serverFlag,
1327
2451
  signatureBits: input.signatureBits,
1328
2452
  ttlSeconds: input.ttlSeconds
1329
2453
  });
1330
- var seconds2 = (text4) => parseDuration(text4) ?? -1;
1331
- var canonicalFromProps = (props) => canonical2({
2454
+ var seconds3 = (text10) => parseDuration(text10) ?? -1;
2455
+ var canonicalFromProps6 = (props) => canonical7({
1332
2456
  allowBareDomains: props.allowBareDomains ?? false,
1333
2457
  allowGlobDomains: props.allowGlobDomains ?? false,
1334
2458
  allowIpSans: props.allowIpSans ?? false,
@@ -1336,21 +2460,27 @@ var canonicalFromProps = (props) => canonical2({
1336
2460
  allowSubdomains: props.allowSubdomains ?? false,
1337
2461
  allowWildcardCertificates: props.allowWildcardCertificates ?? false,
1338
2462
  allowedDomains: props.allowedDomains,
2463
+ allowedDomainsTemplate: props.allowedDomainsTemplate ?? false,
1339
2464
  clientFlag: props.clientFlag ?? true,
1340
2465
  cnValidations: props.cnValidations ?? ["hostname"],
2466
+ enforceHostnames: props.enforceHostnames ?? true,
1341
2467
  extKeyUsage: props.extKeyUsage ?? [],
2468
+ generateLease: props.generateLease ?? false,
1342
2469
  keyBits: props.keyBits ?? 0,
1343
2470
  keyType: props.keyType ?? "rsa",
1344
- maxTtlSeconds: seconds2(props.maxTtl),
2471
+ keyUsage: props.keyUsage ?? DEFAULT_KEY_USAGE,
2472
+ maxTtlSeconds: seconds3(props.maxTtl),
2473
+ noStore: props.noStore ?? false,
2474
+ requireCn: props.requireCn ?? true,
1345
2475
  serverFlag: props.serverFlag ?? true,
1346
2476
  signatureBits: props.signatureBits ?? 0,
1347
- ttlSeconds: seconds2(props.ttl)
2477
+ ttlSeconds: seconds3(props.ttl)
1348
2478
  });
1349
2479
  var bool2 = (value, fallback) => typeof value === "boolean" ? value : fallback;
1350
2480
  var int2 = (value, fallback) => typeof value === "number" && Number.isFinite(value) ? value : fallback;
1351
2481
  var list = (value) => Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
1352
2482
  var str = (value, fallback) => typeof value === "string" ? value : fallback;
1353
- var canonicalFromLive = (live) => canonical2({
2483
+ var canonicalFromLive6 = (live) => canonical7({
1354
2484
  allowBareDomains: bool2(live["allow_bare_domains"], false),
1355
2485
  allowGlobDomains: bool2(live["allow_glob_domains"], false),
1356
2486
  allowIpSans: bool2(live["allow_ip_sans"], false),
@@ -1358,16 +2488,25 @@ var canonicalFromLive = (live) => canonical2({
1358
2488
  allowSubdomains: bool2(live["allow_subdomains"], false),
1359
2489
  allowWildcardCertificates: bool2(live["allow_wildcard_certificates"], false),
1360
2490
  allowedDomains: list(live["allowed_domains"]),
2491
+ allowedDomainsTemplate: bool2(live["allowed_domains_template"], false),
1361
2492
  clientFlag: bool2(live["client_flag"], true),
1362
2493
  cnValidations: list(live["cn_validations"]),
2494
+ enforceHostnames: bool2(live["enforce_hostnames"], false),
1363
2495
  extKeyUsage: list(live["ext_key_usage"]),
2496
+ generateLease: bool2(live["generate_lease"], false),
1364
2497
  keyBits: int2(live["key_bits"], 0),
1365
2498
  keyType: str(live["key_type"], "rsa"),
2499
+ keyUsage: list(live["key_usage"]),
1366
2500
  maxTtlSeconds: ttlSeconds(live["max_ttl"]) ?? -1,
2501
+ noStore: bool2(live["no_store"], false),
2502
+ requireCn: bool2(live["require_cn"], false),
1367
2503
  serverFlag: bool2(live["server_flag"], true),
1368
2504
  signatureBits: int2(live["signature_bits"], 0),
1369
2505
  ttlSeconds: ttlSeconds(live["ttl"]) ?? -1
1370
2506
  });
2507
+ var digestOf = (form) => sha256(JSON.stringify(form));
2508
+
2509
+ // src/openbao/pki-role-form.ts
1371
2510
  var ttlText2 = (value) => {
1372
2511
  if (value < 0)
1373
2512
  return "";
@@ -1379,10 +2518,9 @@ var ttlText2 = (value) => {
1379
2518
  return `${String(value / 60)}m`;
1380
2519
  return `${String(value)}s`;
1381
2520
  };
1382
- var digestOf = (form) => sha256(JSON.stringify(form));
1383
- var rolePath2 = (props) => `${mountPath(props.mount ?? "pki")}/roles/${props.name}`;
1384
- var attributesOf5 = (props, live) => {
1385
- const form = canonicalFromLive(live);
2521
+ var rolePath4 = (props) => `${mountPath(props.mount ?? "pki")}/roles/${props.name}`;
2522
+ var attributesOf10 = (props, live) => {
2523
+ const form = canonicalFromLive6(live);
1386
2524
  return {
1387
2525
  ...form,
1388
2526
  name: props.name,
@@ -1392,8 +2530,8 @@ var attributesOf5 = (props, live) => {
1392
2530
  digest: digestOf(form)
1393
2531
  };
1394
2532
  };
1395
- var writeBody3 = (props) => {
1396
- const form = canonicalFromProps(props);
2533
+ var writeBody8 = (props) => {
2534
+ const form = canonicalFromProps6(props);
1397
2535
  const body = {
1398
2536
  allowed_domains: form.allowedDomains.join(","),
1399
2537
  allow_bare_domains: String(form.allowBareDomains),
@@ -1409,76 +2547,94 @@ var writeBody3 = (props) => {
1409
2547
  key_bits: String(form.keyBits),
1410
2548
  signature_bits: String(form.signatureBits),
1411
2549
  ttl: props.ttl,
1412
- max_ttl: props.maxTtl
2550
+ max_ttl: props.maxTtl,
2551
+ require_cn: String(form.requireCn),
2552
+ enforce_hostnames: String(form.enforceHostnames),
2553
+ key_usage: (props.keyUsage ?? DEFAULT_KEY_USAGE).join(","),
2554
+ allowed_domains_template: String(form.allowedDomainsTemplate),
2555
+ no_store: String(form.noStore),
2556
+ generate_lease: String(form.generateLease)
1413
2557
  };
1414
2558
  const usages = props.extKeyUsage ?? [];
1415
2559
  if (usages.length > 0)
1416
2560
  body["ext_key_usage"] = usages.join(",");
1417
2561
  return body;
1418
2562
  };
1419
- var matches4 = (attributes, props) => attributes.digest === digestOf(canonicalFromProps(props));
2563
+ var matches9 = (attributes, props) => attributes.digest === digestOf(canonicalFromProps6(props));
2564
+ var problems6 = (props) => {
2565
+ const found = [];
2566
+ if (props.allowedDomains.length === 0) {
2567
+ found.push("allowedDomains is empty. With allow_any_name false that role can issue no certificate at all.");
2568
+ }
2569
+ const bad = [
2570
+ ["ttl", props.ttl],
2571
+ ["maxTtl", props.maxTtl]
2572
+ ].filter(([, text10]) => parseDuration(text10 ?? "") === undefined);
2573
+ if (bad.length > 0) {
2574
+ found.push(`${bad.map(([k, v]) => `${k ?? ""}=${v ?? ""}`).join(", ")} is not a duration OpenBao ` + "parses (expect 30m, 720h, 8760h or 0).");
2575
+ }
2576
+ if (props.noStore === true && props.generateLease === true) {
2577
+ found.push("noStore and generateLease are both true; OpenBao keeps no_store and drops the lease.");
2578
+ }
2579
+ return found;
2580
+ };
1420
2581
 
1421
2582
  // src/openbao/pki-role.ts
1422
- var BaoPkiRole = Resource5("Bao.PkiRole", {
2583
+ var BaoPkiRole = Resource10("Bao.PkiRole", {
1423
2584
  defaultRemovalPolicy: "retain"
1424
2585
  });
1425
- var readRole2 = (props) => Effect11.gen(function* () {
1426
- const live = yield* baoRead(rolePath2(props));
2586
+ var readRole2 = (props) => Effect24.gen(function* () {
2587
+ const live = yield* baoRead(rolePath4(props));
1427
2588
  if (live === undefined)
1428
2589
  return;
1429
- return attributesOf5(props, live);
2590
+ return attributesOf10(props, live);
1430
2591
  });
1431
- var badDurations = (props) => [["ttl", props.ttl], ["maxTtl", props.maxTtl]].filter(([, text4]) => parseDuration(text4) === undefined);
1432
- var BaoPkiRoleProvider = () => Provider5.effect(BaoPkiRole, Effect11.succeed(BaoPkiRole.Provider.of({
1433
- list: () => Effect11.succeed([]),
1434
- read: Effect11.fn(function* ({ olds }) {
2592
+ var BaoPkiRoleProvider = () => Provider10.effect(BaoPkiRole, Effect24.succeed(BaoPkiRole.Provider.of({
2593
+ list: () => Effect24.succeed([]),
2594
+ read: Effect24.fn(function* ({ olds }) {
1435
2595
  return yield* readRole2(olds);
1436
2596
  }),
1437
- diff: Effect11.fn(function* ({ news, output }) {
1438
- if (output === undefined || !isResolved5(news))
2597
+ diff: Effect24.fn(function* ({ news, output }) {
2598
+ if (output === undefined || !isResolved10(news))
1439
2599
  return;
1440
- if (mountPath(news.mount ?? "pki") !== output.mount)
2600
+ if (mountPath(news.mount ?? "pki") !== output.mount || news.name !== output.name)
1441
2601
  return { action: "replace" };
1442
- if (news.allowedDomains.length === 0 || badDurations(news).length > 0) {
2602
+ if (problems6(news).length > 0)
1443
2603
  return { action: "update" };
1444
- }
1445
2604
  const live = yield* readRole2(news);
1446
2605
  if (live === undefined)
1447
2606
  return { action: "update" };
1448
- return matches4(live, news) ? { action: "noop" } : { action: "update" };
2607
+ return matches9(live, news) ? { action: "noop" } : { action: "update" };
1449
2608
  }),
1450
- reconcile: Effect11.fn(function* ({ news }) {
1451
- const path = rolePath2(news);
1452
- if (news.allowedDomains.length === 0) {
1453
- return yield* Effect11.die(new Error(`Bao.PkiRole ${path}: allowedDomains is empty. With allow_any_name false ` + "that role can issue no certificate at all."));
1454
- }
1455
- const bad = badDurations(news);
2609
+ reconcile: Effect24.fn(function* ({ news }) {
2610
+ const path = rolePath4(news);
2611
+ const bad = problems6(news);
1456
2612
  if (bad.length > 0) {
1457
- return yield* Effect11.die(new Error(`Bao.PkiRole ${path}: ${bad.map(([k, v]) => `${k}=${v}`).join(", ")} ` + "is not a duration OpenBao parses (expect 30m, 720h, 8760h or 0)."));
2613
+ return yield* Effect24.die(new Error(`Bao.PkiRole ${path}: ${bad.join(" ")}`));
1458
2614
  }
1459
2615
  const live = yield* readRole2(news);
1460
- if (live === undefined || !matches4(live, news)) {
1461
- yield* baoWrite("PUT", path, writeBody3(news));
2616
+ if (live === undefined || !matches9(live, news)) {
2617
+ yield* baoWrite("PUT", path, writeBody8(news));
1462
2618
  }
1463
2619
  const after = yield* readRole2(news);
1464
2620
  if (after === undefined) {
1465
- return yield* Effect11.die(new Error(`Bao.PkiRole ${path}: write reported success but the role is absent.`));
2621
+ return yield* Effect24.die(new Error(`Bao.PkiRole ${path}: write reported success but the role is absent.`));
1466
2622
  }
1467
- if (!matches4(after, news)) {
1468
- return yield* Effect11.die(new Error(`Bao.PkiRole ${path}: wrote cleanly but the role read back different. ` + "OpenBao stored something other than what was declared — inspect with " + `\`bao read ${path}\` before retrying.`));
2623
+ if (!matches9(after, news)) {
2624
+ return yield* Effect24.die(new Error(`Bao.PkiRole ${path}: wrote cleanly but the role read back different. ` + "OpenBao stored something other than what was declared — inspect with " + `\`bao read ${path}\` before retrying.`));
1469
2625
  }
1470
2626
  return after;
1471
2627
  }),
1472
- delete: Effect11.fn(function* ({ output }) {
1473
- yield* baoDelete(rolePath2(output));
2628
+ delete: Effect24.fn(function* ({ output }) {
2629
+ yield* baoDelete(rolePath4(output));
1474
2630
  return;
1475
2631
  })
1476
2632
  })));
1477
2633
  // src/openbao/plugin.ts
1478
- import { Resource as Resource6 } from "alchemy";
1479
- import { isResolved as isResolved6 } from "alchemy/Diff";
1480
- import * as Provider6 from "alchemy/Provider";
1481
- import * as Effect13 from "effect/Effect";
2634
+ import { Resource as Resource11 } from "alchemy";
2635
+ import { isResolved as isResolved11 } from "alchemy/Diff";
2636
+ import * as Provider11 from "alchemy/Provider";
2637
+ import * as Effect26 from "effect/Effect";
1482
2638
 
1483
2639
  // src/openbao/plugin-form.ts
1484
2640
  var resolve = (props) => ({
@@ -1490,7 +2646,7 @@ var resolve = (props) => ({
1490
2646
  version: props.version ?? ""
1491
2647
  });
1492
2648
  var CANONICAL_VERSION = /^v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$/;
1493
- var problems = (form) => {
2649
+ var problems7 = (form) => {
1494
2650
  const found = [];
1495
2651
  if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(form.name) || form.name.includes("..")) {
1496
2652
  found.push(`name ${JSON.stringify(form.name)} is not a plain catalog name`);
@@ -1516,25 +2672,25 @@ var registerBody = (form) => ({
1516
2672
  ...form.args.length > 0 ? { args: form.args } : {},
1517
2673
  ...form.version === "" ? {} : { version: form.version }
1518
2674
  });
1519
- var text4 = (value) => typeof value === "string" ? value : "";
2675
+ var text10 = (value) => typeof value === "string" ? value : "";
1520
2676
  var list2 = (value) => Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
1521
- var attributesOf6 = (form, live) => {
2677
+ var attributesOf11 = (form, live) => {
1522
2678
  const attrs = {
1523
2679
  name: form.name,
1524
2680
  type: form.type,
1525
- version: text4(live["version"]),
1526
- sha256: text4(live["sha256"]).toLowerCase(),
1527
- command: text4(live["command"]),
2681
+ version: text10(live["version"]),
2682
+ sha256: text10(live["sha256"]).toLowerCase(),
2683
+ command: text10(live["command"]),
1528
2684
  args: list2(live["args"]),
1529
2685
  builtin: live["builtin"] === true
1530
2686
  };
1531
2687
  return { ...attrs, digest: sha256(JSON.stringify(attrs)) };
1532
2688
  };
1533
2689
  var sameArgs = (want, have) => want.length === have.length && want.every((entry, index) => entry === have[index]);
1534
- var matches5 = (attributes, form) => !attributes.builtin && attributes.version === form.version && attributes.sha256 === form.sha256 && attributes.command === form.command && sameArgs(form.args, attributes.args);
2690
+ var matches10 = (attributes, form) => !attributes.builtin && attributes.version === form.version && attributes.sha256 === form.sha256 && attributes.command === form.command && sameArgs(form.args, attributes.args);
1535
2691
 
1536
2692
  // src/openbao/plugin-reconcile.ts
1537
- import * as Effect12 from "effect/Effect";
2693
+ import * as Effect25 from "effect/Effect";
1538
2694
 
1539
2695
  // src/openbao/plugin-wire.ts
1540
2696
  var readPluginData = (form) => baoRead(versionedPath(form.type, form.name, form.version));
@@ -1542,44 +2698,44 @@ var registerPlugin = (form) => baoWrite("PUT", catalogPath(form.type, form.name)
1542
2698
  var deregisterPlugin = (type, name, version) => baoDelete(versionedPath(type, name, version));
1543
2699
 
1544
2700
  // src/openbao/plugin-reconcile.ts
1545
- var readEntry = (form) => Effect12.map(readPluginData(form), (live) => live === undefined ? undefined : { attributes: attributesOf6(form, live), live });
1546
- var refuse = (form, message) => Effect12.die(new Error(`Bao.Plugin ${versionedPath(form.type, form.name, form.version)}: ${message}`));
2701
+ var readEntry = (form) => Effect25.map(readPluginData(form), (live) => live === undefined ? undefined : { attributes: attributesOf11(form, live), live });
2702
+ var refuse5 = (form, message) => Effect25.die(new Error(`Bao.Plugin ${versionedPath(form.type, form.name, form.version)}: ${message}`));
1547
2703
  var SELF_REPORTED = "the write succeeded but the read-back does not match. If the binary reports its own version, " + "OpenBao filed the registration under it (plugin_catalog.go setInternal) — declare `version` " + "as exactly the version the binary reports; that adopts the entry this write just made.";
1548
- var reconcilePlugin = (props) => Effect12.gen(function* () {
2704
+ var reconcilePlugin = (props) => Effect25.gen(function* () {
1549
2705
  const form = resolve(props);
1550
- const bad = problems(form);
2706
+ const bad = problems7(form);
1551
2707
  if (bad.length > 0)
1552
- return yield* refuse(form, bad.join("; "));
2708
+ return yield* refuse5(form, bad.join("; "));
1553
2709
  const found = yield* readEntry(form);
1554
- if (found !== undefined && !matches5(found.attributes, form)) {
2710
+ if (found !== undefined && !matches10(found.attributes, form)) {
1555
2711
  if (found.live["declarative"] === true || found.live["oci"] === true) {
1556
- return yield* refuse(form, "the live entry is declarative/OCI; manage it in server config.");
2712
+ return yield* refuse5(form, "the live entry is declarative/OCI; manage it in server config.");
1557
2713
  }
1558
2714
  if (found.attributes.builtin) {
1559
- return yield* refuse(form, "this name is a builtin; an unversioned write would shadow it.");
2715
+ return yield* refuse5(form, "this name is a builtin; an unversioned write would shadow it.");
1560
2716
  }
1561
2717
  }
1562
- if (found === undefined || !matches5(found.attributes, form))
2718
+ if (found === undefined || !matches10(found.attributes, form))
1563
2719
  yield* registerPlugin(form);
1564
2720
  const after = yield* readEntry(form);
1565
- if (after === undefined || !matches5(after.attributes, form)) {
1566
- return yield* refuse(form, SELF_REPORTED);
2721
+ if (after === undefined || !matches10(after.attributes, form)) {
2722
+ return yield* refuse5(form, SELF_REPORTED);
1567
2723
  }
1568
2724
  return after.attributes;
1569
2725
  });
1570
2726
 
1571
2727
  // src/openbao/plugin.ts
1572
- var BaoPlugin = Resource6("Bao.Plugin", {
2728
+ var BaoPlugin = Resource11("Bao.Plugin", {
1573
2729
  defaultRemovalPolicy: "retain"
1574
2730
  });
1575
- var BaoPluginProvider = () => Provider6.effect(BaoPlugin, Effect13.succeed(BaoPlugin.Provider.of({
1576
- list: () => Effect13.succeed([]),
1577
- read: Effect13.fn(function* ({ olds }) {
2731
+ var BaoPluginProvider = () => Provider11.effect(BaoPlugin, Effect26.succeed(BaoPlugin.Provider.of({
2732
+ list: () => Effect26.succeed([]),
2733
+ read: Effect26.fn(function* ({ olds }) {
1578
2734
  const found = yield* readEntry(resolve(olds));
1579
2735
  return found?.attributes;
1580
2736
  }),
1581
- diff: Effect13.fn(function* ({ news, output }) {
1582
- if (output === undefined || !isResolved6(news))
2737
+ diff: Effect26.fn(function* ({ news, output }) {
2738
+ if (output === undefined || !isResolved11(news))
1583
2739
  return;
1584
2740
  const form = resolve(news);
1585
2741
  if (output.name !== form.name || output.type !== form.type || output.version !== form.version) {
@@ -1588,21 +2744,21 @@ var BaoPluginProvider = () => Provider6.effect(BaoPlugin, Effect13.succeed(BaoPl
1588
2744
  const found = yield* readEntry(form);
1589
2745
  if (found === undefined)
1590
2746
  return { action: "update" };
1591
- return matches5(found.attributes, form) ? { action: "noop" } : { action: "update" };
2747
+ return matches10(found.attributes, form) ? { action: "noop" } : { action: "update" };
1592
2748
  }),
1593
- reconcile: Effect13.fn(function* ({ news }) {
2749
+ reconcile: Effect26.fn(function* ({ news }) {
1594
2750
  return yield* reconcilePlugin(news);
1595
2751
  }),
1596
- delete: Effect13.fn(function* ({ output }) {
2752
+ delete: Effect26.fn(function* ({ output }) {
1597
2753
  yield* deregisterPlugin(output.type, output.name, output.version);
1598
2754
  return;
1599
2755
  })
1600
2756
  })));
1601
2757
  // src/openbao/policy.ts
1602
- import { Resource as Resource7 } from "alchemy";
1603
- import { isResolved as isResolved7 } from "alchemy/Diff";
1604
- import * as Provider7 from "alchemy/Provider";
1605
- import * as Effect15 from "effect/Effect";
2758
+ import { Resource as Resource12 } from "alchemy";
2759
+ import { isResolved as isResolved12 } from "alchemy/Diff";
2760
+ import * as Provider12 from "alchemy/Provider";
2761
+ import * as Effect28 from "effect/Effect";
1606
2762
  import * as FileSystem from "effect/FileSystem";
1607
2763
  import * as Path from "effect/Path";
1608
2764
 
@@ -1610,32 +2766,32 @@ import * as Path from "effect/Path";
1610
2766
  var isEmptyAssembly = (parts, joined) => parts === 0 || !/^\s*path\s+"/m.test(joined);
1611
2767
 
1612
2768
  // src/openbao/policy-wire.ts
1613
- import * as Effect14 from "effect/Effect";
2769
+ import * as Effect27 from "effect/Effect";
1614
2770
  var policyPath = (name) => `sys/policies/acl/${name}`;
1615
- var readPolicy = (name) => Effect14.flatMap(baoRead(policyPath(name)), (data) => {
2771
+ var readPolicy = (name) => Effect27.flatMap(baoRead(policyPath(name)), (data) => {
1616
2772
  if (data === undefined)
1617
- return Effect14.succeed("");
1618
- const text5 = data["policy"];
1619
- return typeof text5 === "string" ? Effect14.succeed(text5) : Effect14.fail(new BaoError(200, "GET", policyPath(name), ["no data.policy in the response"]));
2773
+ return Effect27.succeed("");
2774
+ const text11 = data["policy"];
2775
+ return typeof text11 === "string" ? Effect27.succeed(text11) : Effect27.fail(new BaoError(200, "GET", policyPath(name), ["no data.policy in the response"]));
1620
2776
  });
1621
2777
  var writePolicy = (name, hcl) => baoWrite("PUT", policyPath(name), { policy: hcl });
1622
2778
  var deletePolicy = (name) => baoDelete(policyPath(name));
1623
2779
 
1624
2780
  // src/openbao/policy.ts
1625
- var BaoPolicy = Resource7("Bao.Policy", {
2781
+ var BaoPolicy = Resource12("Bao.Policy", {
1626
2782
  defaultRemovalPolicy: "retain"
1627
2783
  });
1628
2784
  var grantsOf = (hcl) => (hcl.match(/^path /gm) ?? []).length;
1629
- var attributesOf7 = (name, hcl, parts) => ({
2785
+ var attributesOf12 = (name, hcl, parts) => ({
1630
2786
  name,
1631
2787
  digest: sha256(hcl),
1632
2788
  grants: grantsOf(hcl),
1633
2789
  parts
1634
2790
  });
1635
- var BaoPolicyProvider = () => Provider7.effect(BaoPolicy, Effect15.gen(function* () {
2791
+ var BaoPolicyProvider = () => Provider12.effect(BaoPolicy, Effect28.gen(function* () {
1636
2792
  const fs = yield* FileSystem.FileSystem;
1637
2793
  const path = yield* Path.Path;
1638
- const assemble = (dir) => Effect15.gen(function* () {
2794
+ const assemble = (dir) => Effect28.gen(function* () {
1639
2795
  const entries2 = (yield* fs.readDirectory(dir)).filter((f) => f.endsWith(".hcl")).sort();
1640
2796
  let joined = "";
1641
2797
  for (const f of entries2)
@@ -1644,67 +2800,77 @@ var BaoPolicyProvider = () => Provider7.effect(BaoPolicy, Effect15.gen(function*
1644
2800
  return { joined, parts: entries2.length };
1645
2801
  });
1646
2802
  return BaoPolicy.Provider.of({
1647
- list: () => Effect15.succeed([]),
1648
- read: Effect15.fn(function* ({ olds }) {
2803
+ list: () => Effect28.succeed([]),
2804
+ read: Effect28.fn(function* ({ olds }) {
1649
2805
  const live = yield* readPolicy(olds.name);
1650
- return live.length === 0 ? undefined : attributesOf7(olds.name, live, 0);
2806
+ return live.length === 0 ? undefined : attributesOf12(olds.name, live, 0);
1651
2807
  }),
1652
- diff: Effect15.fn(function* ({ news, output }) {
1653
- if (output === undefined || !isResolved7(news))
2808
+ diff: Effect28.fn(function* ({ news, olds, output }) {
2809
+ const tried = output?.name ?? declaredString(olds, "name");
2810
+ const declared = declaredString(news, "name");
2811
+ const move = yield* judgeMove("Bao.Policy", tried, declared, policyPath, policyKey);
2812
+ if (output === undefined)
2813
+ return;
2814
+ if (move !== undefined)
2815
+ return { action: "replace" };
2816
+ if (!isResolved12(news))
1654
2817
  return;
1655
2818
  const { joined } = yield* assemble(news.fragments);
1656
2819
  const live = yield* readPolicy(news.name);
1657
2820
  return live.length > 0 && sha256(live) === sha256(joined) ? { action: "noop" } : { action: "update" };
1658
2821
  }),
1659
- reconcile: Effect15.fn(function* ({ news }) {
2822
+ reconcile: Effect28.fn(function* ({ news, output }) {
1660
2823
  const name = news.name;
2824
+ if (output !== undefined && isMoved(output.name, name, policyKey) === true) {
2825
+ return yield* refuseMovedUpdate(`Bao.Policy ${name}`, output.name, name);
2826
+ }
1661
2827
  const { joined, parts } = yield* assemble(news.fragments);
1662
2828
  if (isEmptyAssembly(parts, joined))
1663
- return yield* Effect15.die(new Error(`Bao.Policy ${name}: the fragments under ${news.fragments} carry no path grant ` + `(${String(parts)} file(s)). Writing it would revoke every grant the policy carries.`));
2829
+ return yield* Effect28.die(new Error(`Bao.Policy ${name}: the fragments under ${news.fragments} carry no path grant ` + `(${String(parts)} file(s)). Writing it would revoke every grant the policy carries.`));
1664
2830
  const current = yield* readPolicy(name);
1665
2831
  if (current.length > 0 && sha256(current) === sha256(joined)) {
1666
- return attributesOf7(name, joined, parts);
2832
+ return attributesOf12(name, joined, parts);
1667
2833
  }
1668
2834
  yield* writePolicy(name, joined);
1669
- return attributesOf7(name, joined, parts);
2835
+ return attributesOf12(name, joined, parts);
1670
2836
  }),
1671
- delete: Effect15.fn(function* ({ output }) {
2837
+ delete: Effect28.fn(function* ({ output }) {
1672
2838
  yield* deletePolicy(output.name);
1673
2839
  return;
1674
2840
  })
1675
2841
  });
1676
2842
  }));
1677
2843
  // src/openbao/proxmox-role.ts
1678
- import { Resource as Resource8 } from "alchemy";
1679
- import { isResolved as isResolved8 } from "alchemy/Diff";
1680
- import * as Provider8 from "alchemy/Provider";
1681
- import * as Effect16 from "effect/Effect";
2844
+ import { Resource as Resource13 } from "alchemy";
2845
+ import { isResolved as isResolved13 } from "alchemy/Diff";
2846
+ import * as Provider13 from "alchemy/Provider";
2847
+ import * as Effect30 from "effect/Effect";
1682
2848
 
1683
2849
  // src/openbao/proxmox-role-form.ts
1684
- var text5 = (value) => typeof value === "string" ? value : "";
1685
- var ttlText3 = (seconds3, fallback = "0") => {
1686
- if (seconds3 === undefined)
2850
+ var text11 = (value) => typeof value === "string" ? value : "";
2851
+ var ttlText3 = (seconds4, fallback = "0") => {
2852
+ if (seconds4 === undefined)
1687
2853
  return fallback;
1688
- if (seconds3 % 86400 === 0)
1689
- return `${String(seconds3 / 86400)}d`;
1690
- if (seconds3 % 3600 === 0)
1691
- return `${String(seconds3 / 3600)}h`;
1692
- if (seconds3 % 60 === 0)
1693
- return `${String(seconds3 / 60)}m`;
1694
- return `${String(seconds3)}s`;
1695
- };
1696
- var rolePath3 = (mount, name) => `${mountPath(mount)}/roles/${name}`;
1697
- var attributesOf8 = (props, live) => {
2854
+ if (seconds4 % 86400 === 0)
2855
+ return `${String(seconds4 / 86400)}d`;
2856
+ if (seconds4 % 3600 === 0)
2857
+ return `${String(seconds4 / 3600)}h`;
2858
+ if (seconds4 % 60 === 0)
2859
+ return `${String(seconds4 / 60)}m`;
2860
+ return `${String(seconds4)}s`;
2861
+ };
2862
+ var rolePath5 = (mount, name) => `${mountPath(mount)}/roles/${name}`;
2863
+ var attributesOf13 = (props, live) => {
1698
2864
  const attrs = {
1699
2865
  mount: mountPath(props.mount),
1700
- name: text5(live["name"]) || props.name,
1701
- mintUser: text5(live["mint_user"]),
2866
+ name: text11(live["name"]) || props.name,
2867
+ mintUser: text11(live["mint_user"]),
1702
2868
  ttl: ttlText3(ttlSeconds(live["ttl"])),
1703
2869
  maxTtl: ttlText3(ttlSeconds(live["max_ttl"]))
1704
2870
  };
1705
2871
  return { ...attrs, digest: sha256(JSON.stringify(attrs)) };
1706
2872
  };
1707
- var writeBody4 = (props) => {
2873
+ var writeBody9 = (props) => {
1708
2874
  const ttl = parseDuration(props.ttl);
1709
2875
  const maxTtl = parseDuration(props.maxTtl);
1710
2876
  const bad = [];
@@ -1724,68 +2890,94 @@ var sameTtl5 = (want, have) => {
1724
2890
  const haveSeconds = ttlSeconds(have);
1725
2891
  return wantSeconds !== undefined && wantSeconds === haveSeconds;
1726
2892
  };
1727
- var matches6 = (attributes, props) => attributes.mintUser === props.mintUser && sameTtl5(props.ttl, attributes.ttl) && sameTtl5(props.maxTtl, attributes.maxTtl);
2893
+ var rescopeRefusal = (before, mintUser, allowMintUserChange) => before === mintUser || allowMintUserChange === before ? undefined : `re-scopes mint_user ${before} → ${mintUser}. Every token this role mints inherits ` + `the ACL of that PVE user. To re-scope deliberately, declare allowMintUserChange: '${before}'.`;
2894
+ var matches11 = (attributes, props) => attributes.mintUser === props.mintUser && sameTtl5(props.ttl, attributes.ttl) && sameTtl5(props.maxTtl, attributes.maxTtl);
1728
2895
 
1729
- // src/openbao/proxmox-role.ts
1730
- var BaoProxmoxRole = Resource8("Bao.ProxmoxRole", {
1731
- defaultRemovalPolicy: "retain"
1732
- });
1733
- var readRole3 = (props) => Effect16.gen(function* () {
1734
- const live = yield* baoRead(rolePath3(props.mount, props.name));
2896
+ // src/openbao/proxmox-role-wire.ts
2897
+ import * as Effect29 from "effect/Effect";
2898
+ var readRole3 = (props) => Effect29.gen(function* () {
2899
+ const live = yield* baoRead(rolePath5(props.mount, props.name));
1735
2900
  if (live === undefined)
1736
2901
  return;
1737
- return attributesOf8(props, live);
2902
+ return attributesOf13(props, live);
1738
2903
  });
1739
- var BaoProxmoxRoleProvider = () => Provider8.effect(BaoProxmoxRole, Effect16.succeed(BaoProxmoxRole.Provider.of({
1740
- list: () => Effect16.succeed([]),
1741
- read: Effect16.fn(function* ({ olds }) {
2904
+
2905
+ // src/openbao/proxmox-role.ts
2906
+ var BaoProxmoxRole = Resource13("Bao.ProxmoxRole", {
2907
+ defaultRemovalPolicy: "retain"
2908
+ });
2909
+ var BaoProxmoxRoleProvider = () => Provider13.effect(BaoProxmoxRole, Effect30.succeed(BaoProxmoxRole.Provider.of({
2910
+ list: () => Effect30.succeed([]),
2911
+ read: Effect30.fn(function* ({ olds }) {
1742
2912
  return yield* readRole3(olds);
1743
2913
  }),
1744
- diff: Effect16.fn(function* ({ news, output }) {
1745
- if (output === undefined || !isResolved8(news))
2914
+ diff: Effect30.fn(function* ({ news, olds, output }) {
2915
+ const tried = triedRolePath(output, olds, rolePath5);
2916
+ const declared = declaredRolePath(news, rolePath5);
2917
+ const move = yield* judgeMove("Bao.ProxmoxRole", tried, declared, (path) => path);
2918
+ if (output === undefined)
2919
+ return;
2920
+ if (move !== undefined) {
2921
+ const mintUser = declaredString(news, "mintUser");
2922
+ if (mintUser === undefined || isPendingProp(news, "allowMintUserChange"))
2923
+ return;
2924
+ const from = (yield* readRole3(output))?.mintUser ?? output.mintUser;
2925
+ const allow = declaredString(news, "allowMintUserChange");
2926
+ const refusal2 = rescopeRefusal(from, mintUser, allow);
2927
+ if (refusal2 === undefined)
2928
+ return { action: "replace" };
2929
+ return yield* Effect30.die(new Error(`Bao.ProxmoxRole ${move.from} → ${move.to}: the rename ${refusal2}`));
2930
+ }
2931
+ if (!isResolved13(news))
1746
2932
  return;
1747
2933
  const live = yield* readRole3(news);
1748
2934
  if (live === undefined)
1749
2935
  return { action: "update" };
1750
- if (matches6(live, news))
2936
+ if (matches11(live, news))
1751
2937
  return { action: "noop" };
1752
2938
  return { action: "update" };
1753
2939
  }),
1754
- reconcile: Effect16.fn(function* ({ news }) {
2940
+ reconcile: Effect30.fn(function* ({ news, output }) {
2941
+ const path = rolePath5(news.mount, news.name);
2942
+ const before = output === undefined ? path : rolePath5(output.mount, output.name);
2943
+ if (isMoved(before, path) === true) {
2944
+ return yield* refuseMovedUpdate("Bao.ProxmoxRole", before, path);
2945
+ }
1755
2946
  const live = yield* readRole3(news);
1756
- if (live !== undefined && live.mintUser !== news.mintUser && news.allowMintUserChange !== live.mintUser) {
1757
- return yield* Effect16.die(new Error(`Bao.ProxmoxRole ${rolePath3(news.mount, news.name)}: live mint_user ` + `${live.mintUser} != declared ${news.mintUser}. Every token this role mints ` + "inherits the ACL of that PVE user. To re-scope deliberately, declare " + `allowMintUserChange: '${live.mintUser}'.`));
2947
+ const rescope = live === undefined ? undefined : rescopeRefusal(live.mintUser, news.mintUser, news.allowMintUserChange);
2948
+ if (rescope !== undefined) {
2949
+ return yield* Effect30.die(new Error(`Bao.ProxmoxRole ${path}: ${rescope}`));
1758
2950
  }
1759
- if (live === undefined || !matches6(live, news)) {
1760
- const write = writeBody4(news);
2951
+ if (live === undefined || !matches11(live, news)) {
2952
+ const write = writeBody9(news);
1761
2953
  if (!write.ok) {
1762
- return yield* Effect16.die(new Error(`Bao.ProxmoxRole ${rolePath3(news.mount, news.name)}: unparseable duration ` + `${write.bad.join(", ")}. Use 30s / 5m / 1h / 1d, or 0.`));
2954
+ return yield* Effect30.die(new Error(`Bao.ProxmoxRole ${path}: unparseable duration ` + `${write.bad.join(", ")}. Use 30s / 5m / 1h / 1d, or 0.`));
1763
2955
  }
1764
- yield* baoWrite("PUT", rolePath3(news.mount, news.name), write.body);
2956
+ yield* baoWrite("PUT", path, write.body);
1765
2957
  }
1766
2958
  const after = yield* readRole3(news);
1767
2959
  if (after === undefined) {
1768
- return yield* Effect16.die(new Error(`Bao.ProxmoxRole ${rolePath3(news.mount, news.name)}: write returned no error ` + "but the role is still absent."));
2960
+ return yield* Effect30.die(new Error(`Bao.ProxmoxRole ${path}: write returned no error ` + "but the role is still absent."));
1769
2961
  }
1770
2962
  return after;
1771
2963
  }),
1772
- delete: Effect16.fn(function* ({ output }) {
1773
- yield* baoDelete(rolePath3(output.mount, output.name));
2964
+ delete: Effect30.fn(function* ({ output }) {
2965
+ yield* baoDelete(rolePath5(output.mount, output.name));
1774
2966
  return;
1775
2967
  })
1776
2968
  })));
1777
2969
  // src/openbao/ssh-role.ts
1778
- import { Resource as Resource9 } from "alchemy";
1779
- import { isResolved as isResolved9 } from "alchemy/Diff";
1780
- import * as Provider9 from "alchemy/Provider";
1781
- import * as Effect17 from "effect/Effect";
2970
+ import { Resource as Resource14 } from "alchemy";
2971
+ import { isResolved as isResolved14 } from "alchemy/Diff";
2972
+ import * as Provider14 from "alchemy/Provider";
2973
+ import * as Effect31 from "effect/Effect";
1782
2974
 
1783
2975
  // src/openbao/ssh-role-form.ts
1784
2976
  var readPath4 = (mount, name) => `${sshMountOf(mount)}/roles/${name}`;
1785
- var text6 = (value) => typeof value === "string" ? value : "";
2977
+ var text12 = (value) => typeof value === "string" ? value : "";
1786
2978
  var bool3 = (value, fallback) => typeof value === "boolean" ? value : fallback;
1787
2979
  var csvList = (value) => {
1788
- const parts = Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : text6(value).split(",");
2980
+ const parts = Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : text12(value).split(",");
1789
2981
  return [
1790
2982
  ...new Set(parts.map((entry) => entry.trim()).filter((entry) => entry.length > 0))
1791
2983
  ].sort();
@@ -1796,7 +2988,7 @@ var extMap = (value) => {
1796
2988
  return out;
1797
2989
  const source = value;
1798
2990
  for (const key of Object.keys(source).sort())
1799
- out[key] = text6(source[key]);
2991
+ out[key] = text12(source[key]);
1800
2992
  return out;
1801
2993
  };
1802
2994
  var resolve2 = (props) => ({
@@ -1817,12 +3009,12 @@ var resolve2 = (props) => ({
1817
3009
  ttl: props.ttl,
1818
3010
  maxTtl: props.maxTtl
1819
3011
  });
1820
- var attributesOf9 = (form, live) => {
3012
+ var attributesOf14 = (form, live) => {
1821
3013
  const attrs = {
1822
3014
  name: form.name,
1823
3015
  mount: form.mount,
1824
3016
  allowedUsers: csvList(live["allowed_users"]),
1825
- defaultUser: text6(live["default_user"]),
3017
+ defaultUser: text12(live["default_user"]),
1826
3018
  allowUserCertificates: bool3(live["allow_user_certificates"], false),
1827
3019
  allowHostCertificates: bool3(live["allow_host_certificates"], false),
1828
3020
  allowedDomains: csvList(live["allowed_domains"]),
@@ -1833,14 +3025,14 @@ var attributesOf9 = (form, live) => {
1833
3025
  allowedCriticalOptions: csvList(live["allowed_critical_options"]),
1834
3026
  defaultCriticalOptions: extMap(live["default_critical_options"]),
1835
3027
  allowEmptyPrincipals: bool3(live["allow_empty_principals"], false),
1836
- keyType: text6(live["key_type"]) || "ca",
3028
+ keyType: text12(live["key_type"]) || "ca",
1837
3029
  ttlSeconds: ttlSeconds(live["ttl"]) ?? 0,
1838
3030
  maxTtlSeconds: ttlSeconds(live["max_ttl"]) ?? 0
1839
3031
  };
1840
3032
  return { ...attrs, digest: sha256(JSON.stringify(attrs)) };
1841
3033
  };
1842
- var mapValue = (map5) => extMap(map5);
1843
- var writeBody5 = (form) => ({
3034
+ var mapValue = (map10) => extMap(map10);
3035
+ var writeBody10 = (form) => ({
1844
3036
  key_type: "ca",
1845
3037
  allowed_users: form.allowedUsers.join(","),
1846
3038
  default_user: form.defaultUser,
@@ -1863,18 +3055,18 @@ var sameMap = (want, have) => {
1863
3055
  return keys.length === Object.keys(have).length && keys.every((key) => have[key] === want[key]);
1864
3056
  };
1865
3057
  var sameTtl6 = (want, have) => parseDuration(want) === have;
1866
- var badDurations2 = (form) => ["ttl", "maxTtl"].filter((field) => parseDuration(form[field]) === undefined);
1867
- var matches7 = (attributes, form) => attributes.keyType === "ca" && sameList2(form.allowedUsers, attributes.allowedUsers) && attributes.defaultUser === form.defaultUser && attributes.allowUserCertificates === form.allowUserCertificates && attributes.allowHostCertificates === form.allowHostCertificates && sameList2(form.allowedDomains, attributes.allowedDomains) && attributes.allowSubdomains === form.allowSubdomains && attributes.allowBareDomains === form.allowBareDomains && sameList2(form.allowedExtensions, attributes.allowedExtensions) && sameMap(form.defaultExtensions, attributes.defaultExtensions) && sameList2(form.allowedCriticalOptions, attributes.allowedCriticalOptions) && sameMap(form.defaultCriticalOptions, attributes.defaultCriticalOptions) && attributes.allowEmptyPrincipals === form.allowEmptyPrincipals && sameTtl6(form.ttl, attributes.ttlSeconds) && sameTtl6(form.maxTtl, attributes.maxTtlSeconds);
3058
+ var badDurations = (form) => ["ttl", "maxTtl"].filter((field) => parseDuration(form[field]) === undefined);
3059
+ var matches12 = (attributes, form) => attributes.keyType === "ca" && sameList2(form.allowedUsers, attributes.allowedUsers) && attributes.defaultUser === form.defaultUser && attributes.allowUserCertificates === form.allowUserCertificates && attributes.allowHostCertificates === form.allowHostCertificates && sameList2(form.allowedDomains, attributes.allowedDomains) && attributes.allowSubdomains === form.allowSubdomains && attributes.allowBareDomains === form.allowBareDomains && sameList2(form.allowedExtensions, attributes.allowedExtensions) && sameMap(form.defaultExtensions, attributes.defaultExtensions) && sameList2(form.allowedCriticalOptions, attributes.allowedCriticalOptions) && sameMap(form.defaultCriticalOptions, attributes.defaultCriticalOptions) && attributes.allowEmptyPrincipals === form.allowEmptyPrincipals && sameTtl6(form.ttl, attributes.ttlSeconds) && sameTtl6(form.maxTtl, attributes.maxTtlSeconds);
1868
3060
 
1869
3061
  // src/openbao/ssh-role.ts
1870
- var BaoSshRole = Resource9("Bao.SshRole", {
3062
+ var BaoSshRole = Resource14("Bao.SshRole", {
1871
3063
  defaultRemovalPolicy: "retain"
1872
3064
  });
1873
- var readRole4 = (form) => Effect17.gen(function* () {
3065
+ var readRole4 = (form) => Effect31.gen(function* () {
1874
3066
  const live = yield* baoRead(readPath4(form.mount, form.name));
1875
3067
  if (live === undefined)
1876
3068
  return;
1877
- return { attributes: attributesOf9(form, live), live };
3069
+ return { attributes: attributesOf14(form, live), live };
1878
3070
  });
1879
3071
  var UNMANAGED_DEFAULTS = {
1880
3072
  algorithm_signer: "default",
@@ -1889,22 +3081,22 @@ var UNMANAGED_DEFAULTS = {
1889
3081
  not_before_duration: 30
1890
3082
  };
1891
3083
  var isDefault = (live, field) => live[field] === undefined || live[field] === "" || live[field] === UNMANAGED_DEFAULTS[field];
1892
- var wouldErase = (live) => {
3084
+ var wouldErase2 = (live) => {
1893
3085
  const lost = Object.keys(UNMANAGED_DEFAULTS).filter((field) => !isDefault(live, field));
1894
3086
  if (Object.keys(extMap(live["allowed_user_key_lengths"])).length > 0) {
1895
3087
  lost.push("allowed_user_key_lengths");
1896
3088
  }
1897
3089
  return lost.sort();
1898
3090
  };
1899
- var refuse2 = (message) => Effect17.die(new Error(message));
1900
- var BaoSshRoleProvider = () => Provider9.effect(BaoSshRole, Effect17.succeed(BaoSshRole.Provider.of({
1901
- list: () => Effect17.succeed([]),
1902
- read: Effect17.fn(function* ({ olds }) {
3091
+ var refuse6 = (message) => Effect31.die(new Error(message));
3092
+ var BaoSshRoleProvider = () => Provider14.effect(BaoSshRole, Effect31.succeed(BaoSshRole.Provider.of({
3093
+ list: () => Effect31.succeed([]),
3094
+ read: Effect31.fn(function* ({ olds }) {
1903
3095
  const found = yield* readRole4(resolve2(olds));
1904
3096
  return found?.attributes;
1905
3097
  }),
1906
- diff: Effect17.fn(function* ({ news, output }) {
1907
- if (output === undefined || !isResolved9(news))
3098
+ diff: Effect31.fn(function* ({ news, output }) {
3099
+ if (output === undefined || !isResolved14(news))
1908
3100
  return;
1909
3101
  const form = resolve2(news);
1910
3102
  if (output.mount !== form.mount || output.name !== form.name) {
@@ -1913,35 +3105,35 @@ var BaoSshRoleProvider = () => Provider9.effect(BaoSshRole, Effect17.succeed(Bao
1913
3105
  const found = yield* readRole4(form);
1914
3106
  if (found === undefined)
1915
3107
  return { action: "update" };
1916
- return matches7(found.attributes, form) ? { action: "noop" } : { action: "update" };
3108
+ return matches12(found.attributes, form) ? { action: "noop" } : { action: "update" };
1917
3109
  }),
1918
- reconcile: Effect17.fn(function* ({ news }) {
3110
+ reconcile: Effect31.fn(function* ({ news }) {
1919
3111
  const form = resolve2(news);
1920
3112
  const path = readPath4(form.mount, form.name);
1921
- const bad = badDurations2(form);
3113
+ const bad = badDurations(form);
1922
3114
  if (bad.length > 0) {
1923
- return yield* refuse2(`Bao.SshRole ${path}: ${bad.join(", ")} is not a duration parseDuration understands ` + "(`0` or `<n>s|m|h|d`). It would write, then report update on every plan forever.");
3115
+ return yield* refuse6(`Bao.SshRole ${path}: ${bad.join(", ")} is not a duration parseDuration understands ` + "(`0` or `<n>s|m|h|d`). It would write, then report update on every plan forever.");
1924
3116
  }
1925
3117
  if (form.allowUserCertificates && form.allowedUsers.length === 0 && !form.allowEmptyPrincipals) {
1926
- return yield* refuse2(`Bao.SshRole ${path}: allowUserCertificates with an empty allowedUsers and ` + "allowEmptyPrincipals false. Every sign request against it would be rejected.");
3118
+ return yield* refuse6(`Bao.SshRole ${path}: allowUserCertificates with an empty allowedUsers and ` + "allowEmptyPrincipals false. Every sign request against it would be rejected.");
1927
3119
  }
1928
3120
  const found = yield* readRole4(form);
1929
- if (found === undefined || !matches7(found.attributes, form)) {
3121
+ if (found === undefined || !matches12(found.attributes, form)) {
1930
3122
  if (found !== undefined) {
1931
- const lost = wouldErase(found.live);
3123
+ const lost = wouldErase2(found.live);
1932
3124
  if (lost.length > 0) {
1933
- return yield* refuse2(`Bao.SshRole ${path}: live role carries ${lost.join(", ")}, which this ` + "resource does not declare and the full-replace write would ERASE. " + "Fold those settings into the declaration, or clear them by hand first.");
3125
+ return yield* refuse6(`Bao.SshRole ${path}: live role carries ${lost.join(", ")}, which this ` + "resource does not declare and the full-replace write would ERASE. " + "Fold those settings into the declaration, or clear them by hand first.");
1934
3126
  }
1935
3127
  }
1936
- yield* baoWrite("PUT", path, writeBody5(form));
3128
+ yield* baoWrite("PUT", path, writeBody10(form));
1937
3129
  }
1938
3130
  const after = yield* readRole4(form);
1939
3131
  if (after === undefined) {
1940
- return yield* refuse2(`Bao.SshRole ${path}: write returned no error but the role is still absent.`);
3132
+ return yield* refuse6(`Bao.SshRole ${path}: write returned no error but the role is still absent.`);
1941
3133
  }
1942
3134
  return after.attributes;
1943
3135
  }),
1944
- delete: Effect17.fn(function* ({ output }) {
3136
+ delete: Effect31.fn(function* ({ output }) {
1945
3137
  yield* baoDelete(readPath4(output.mount, output.name));
1946
3138
  return;
1947
3139
  })
@@ -1953,7 +3145,18 @@ export {
1953
3145
  BaoAuthRoleProvider,
1954
3146
  BaoCloudflareRole,
1955
3147
  BaoCloudflareRoleProvider,
3148
+ BaoIdentityError,
3149
+ BaoJwtAuthConfig,
3150
+ BaoJwtAuthConfigProvider,
3151
+ BaoJwtRole,
3152
+ BaoJwtRoleProvider,
3153
+ BaoKubernetesRole,
3154
+ BaoKubernetesRoleProvider,
1956
3155
  BaoLoginError,
3156
+ BaoMfaLoginEnforcement,
3157
+ BaoMfaLoginEnforcementProvider,
3158
+ BaoMfaTotpMethod,
3159
+ BaoMfaTotpMethodProvider,
1957
3160
  BaoMount,
1958
3161
  BaoMountProvider,
1959
3162
  BaoPkiRole,
@@ -1967,10 +3170,15 @@ export {
1967
3170
  BaoSshRole,
1968
3171
  BaoSshRoleProvider,
1969
3172
  CloudflarePermissionGroups,
3173
+ HOST_ROLE_SEPARATOR,
1970
3174
  appRoleLogin,
1971
3175
  appRoleLoginEffect,
3176
+ assertBaoIdentity,
3177
+ assertBaoIdentityEffect,
1972
3178
  expandAccount,
1973
3179
  expandAll,
3180
+ hostAppRoles,
3181
+ hostRoleName,
1974
3182
  mountName,
1975
3183
  parseRolesConfig,
1976
3184
  permissionGroupsFromEngine,
@@ -1979,5 +3187,5 @@ export {
1979
3187
  revokeSelfEffect
1980
3188
  };
1981
3189
 
1982
- //# debugId=1C57EE110DEE3DA764756E2164756E21
3190
+ //# debugId=81D869587FC0B7AF64756E2164756E21
1983
3191
  //# sourceMappingURL=index.js.map