@homeflare/alchemy 0.7.0 → 0.10.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 (312) hide show
  1. package/README.md +45 -45
  2. package/dist/caddy/config-lifecycle.d.ts +49 -3
  3. package/dist/caddy/config-lifecycle.d.ts.map +1 -1
  4. package/dist/caddy/config.d.ts +6 -2
  5. package/dist/caddy/config.d.ts.map +1 -1
  6. package/dist/caddy/fake-caddy.d.ts +11 -0
  7. package/dist/caddy/fake-caddy.d.ts.map +1 -1
  8. package/dist/caddy/index.d.ts +2 -1
  9. package/dist/caddy/index.d.ts.map +1 -1
  10. package/dist/caddy/index.js +270 -44
  11. package/dist/caddy/index.js.map +15 -11
  12. package/dist/caddy/with-file.d.ts +7 -3
  13. package/dist/caddy/with-file.d.ts.map +1 -1
  14. package/dist/launchd/fake-runner.d.ts +1 -0
  15. package/dist/launchd/fake-runner.d.ts.map +1 -1
  16. package/dist/launchd/fake-sudo.d.ts +51 -0
  17. package/dist/launchd/fake-sudo.d.ts.map +1 -0
  18. package/dist/launchd/host-effect.d.ts +1 -1
  19. package/dist/launchd/host-file-form.d.ts +5 -0
  20. package/dist/launchd/host-file-form.d.ts.map +1 -1
  21. package/dist/launchd/host-file-lifecycle.d.ts +14 -1
  22. package/dist/launchd/host-file-lifecycle.d.ts.map +1 -1
  23. package/dist/launchd/host-file.d.ts +2 -1
  24. package/dist/launchd/host-file.d.ts.map +1 -1
  25. package/dist/launchd/index.d.ts +2 -0
  26. package/dist/launchd/index.d.ts.map +1 -1
  27. package/dist/launchd/index.js +642 -49
  28. package/dist/launchd/index.js.map +24 -14
  29. package/dist/launchd/job-lifecycle.d.ts +8 -1
  30. package/dist/launchd/job-lifecycle.d.ts.map +1 -1
  31. package/dist/launchd/job-preflight.d.ts +13 -1
  32. package/dist/launchd/job-preflight.d.ts.map +1 -1
  33. package/dist/launchd/job-validate.d.ts +2 -0
  34. package/dist/launchd/job-validate.d.ts.map +1 -1
  35. package/dist/launchd/job.d.ts +2 -1
  36. package/dist/launchd/job.d.ts.map +1 -1
  37. package/dist/launchd/local-runner.d.ts +2 -0
  38. package/dist/launchd/local-runner.d.ts.map +1 -1
  39. package/dist/launchd/providers.d.ts +2 -1
  40. package/dist/launchd/providers.d.ts.map +1 -1
  41. package/dist/launchd/runner.d.ts +14 -5
  42. package/dist/launchd/runner.d.ts.map +1 -1
  43. package/dist/launchd/sudo-acl.d.ts +25 -0
  44. package/dist/launchd/sudo-acl.d.ts.map +1 -0
  45. package/dist/launchd/sudo-allowlist.d.ts +47 -0
  46. package/dist/launchd/sudo-allowlist.d.ts.map +1 -0
  47. package/dist/launchd/sudo-guard.d.ts +83 -0
  48. package/dist/launchd/sudo-guard.d.ts.map +1 -0
  49. package/dist/launchd/sudo-runner.d.ts +60 -0
  50. package/dist/launchd/sudo-runner.d.ts.map +1 -0
  51. package/dist/launchd/sudo-said.d.ts +10 -0
  52. package/dist/launchd/sudo-said.d.ts.map +1 -0
  53. package/dist/launchd/sudo-stage.d.ts +7 -0
  54. package/dist/launchd/sudo-stage.d.ts.map +1 -0
  55. package/dist/openbao/auth-method-reconcile.d.ts +6 -2
  56. package/dist/openbao/auth-method-reconcile.d.ts.map +1 -1
  57. package/dist/openbao/auth-method.d.ts.map +1 -1
  58. package/dist/openbao/auth-role-form.d.ts +0 -9
  59. package/dist/openbao/auth-role-form.d.ts.map +1 -1
  60. package/dist/openbao/auth-role.d.ts +3 -2
  61. package/dist/openbao/auth-role.d.ts.map +1 -1
  62. package/dist/openbao/cloudflare-role.d.ts.map +1 -1
  63. package/dist/openbao/fake-bao.d.ts +18 -0
  64. package/dist/openbao/fake-bao.d.ts.map +1 -1
  65. package/dist/openbao/fake-core.d.ts +39 -0
  66. package/dist/openbao/fake-core.d.ts.map +1 -0
  67. package/dist/openbao/fake-engines-roles.d.ts +32 -0
  68. package/dist/openbao/fake-engines-roles.d.ts.map +1 -0
  69. package/dist/openbao/fake-engines.d.ts +8 -2
  70. package/dist/openbao/fake-engines.d.ts.map +1 -1
  71. package/dist/openbao/fake-families.d.ts +52 -0
  72. package/dist/openbao/fake-families.d.ts.map +1 -0
  73. package/dist/openbao/fake-mounts.d.ts +16 -0
  74. package/dist/openbao/fake-mounts.d.ts.map +1 -0
  75. package/dist/openbao/fake-stack.d.ts +5 -1
  76. package/dist/openbao/fake-stack.d.ts.map +1 -1
  77. package/dist/openbao/host-approles.d.ts.map +1 -1
  78. package/dist/openbao/index.js +709 -359
  79. package/dist/openbao/index.js.map +32 -25
  80. package/dist/openbao/jwt-config.d.ts +5 -0
  81. package/dist/openbao/jwt-config.d.ts.map +1 -1
  82. package/dist/openbao/jwt-role.d.ts +2 -1
  83. package/dist/openbao/jwt-role.d.ts.map +1 -1
  84. package/dist/openbao/kubernetes-role.d.ts +2 -1
  85. package/dist/openbao/kubernetes-role.d.ts.map +1 -1
  86. package/dist/openbao/mfa-enforcement.d.ts +12 -5
  87. package/dist/openbao/mfa-enforcement.d.ts.map +1 -1
  88. package/dist/openbao/mfa-totp.d.ts +6 -2
  89. package/dist/openbao/mfa-totp.d.ts.map +1 -1
  90. package/dist/openbao/mount-reconcile.d.ts +5 -1
  91. package/dist/openbao/mount-reconcile.d.ts.map +1 -1
  92. package/dist/openbao/mount.d.ts.map +1 -1
  93. package/dist/openbao/pki-role.d.ts.map +1 -1
  94. package/dist/openbao/plugin-reconcile.d.ts +6 -2
  95. package/dist/openbao/plugin-reconcile.d.ts.map +1 -1
  96. package/dist/openbao/plugin.d.ts.map +1 -1
  97. package/dist/openbao/policy.d.ts.map +1 -1
  98. package/dist/openbao/proxmox-role.d.ts.map +1 -1
  99. package/dist/openbao/rename-identity.d.ts +69 -0
  100. package/dist/openbao/rename-identity.d.ts.map +1 -0
  101. package/dist/openbao/rename.d.ts +13 -12
  102. package/dist/openbao/rename.d.ts.map +1 -1
  103. package/dist/openbao/role-reconcile.d.ts +11 -1
  104. package/dist/openbao/role-reconcile.d.ts.map +1 -1
  105. package/dist/openbao/ssh-role-erase.d.ts +11 -0
  106. package/dist/openbao/ssh-role-erase.d.ts.map +1 -0
  107. package/dist/openbao/ssh-role.d.ts.map +1 -1
  108. package/dist/ownership/adopt.d.ts +52 -0
  109. package/dist/ownership/adopt.d.ts.map +1 -0
  110. package/dist/ownership/probe.d.ts +13 -0
  111. package/dist/ownership/probe.d.ts.map +1 -0
  112. package/dist/ownership/resume.d.ts +40 -0
  113. package/dist/ownership/resume.d.ts.map +1 -0
  114. package/dist/ownership/rows.d.ts +33 -0
  115. package/dist/ownership/rows.d.ts.map +1 -0
  116. package/dist/ownership/whole.d.ts +14 -0
  117. package/dist/ownership/whole.d.ts.map +1 -0
  118. package/dist/proxmox/ceph-pool-form.d.ts +3 -3
  119. package/dist/proxmox/ceph-pool.d.ts.map +1 -1
  120. package/dist/proxmox/fake-bao-env.d.ts +3 -0
  121. package/dist/proxmox/fake-bao-env.d.ts.map +1 -0
  122. package/dist/proxmox/fake-pve-lxc.d.ts +42 -0
  123. package/dist/proxmox/fake-pve-lxc.d.ts.map +1 -0
  124. package/dist/proxmox/fake-pve.d.ts +38 -0
  125. package/dist/proxmox/fake-pve.d.ts.map +1 -0
  126. package/dist/proxmox/index.d.ts +6 -2
  127. package/dist/proxmox/index.d.ts.map +1 -1
  128. package/dist/proxmox/index.js +970 -186
  129. package/dist/proxmox/index.js.map +22 -9
  130. package/dist/proxmox/lxc-create-form.d.ts +6 -0
  131. package/dist/proxmox/lxc-create-form.d.ts.map +1 -0
  132. package/dist/proxmox/lxc-harness.d.ts +34 -0
  133. package/dist/proxmox/lxc-harness.d.ts.map +1 -0
  134. package/dist/proxmox/lxc-identity.d.ts +18 -0
  135. package/dist/proxmox/lxc-identity.d.ts.map +1 -0
  136. package/dist/proxmox/lxc-judge.d.ts +46 -0
  137. package/dist/proxmox/lxc-judge.d.ts.map +1 -0
  138. package/dist/proxmox/lxc-lifecycle.d.ts +68 -0
  139. package/dist/proxmox/lxc-lifecycle.d.ts.map +1 -0
  140. package/dist/proxmox/lxc-props.d.ts +139 -0
  141. package/dist/proxmox/lxc-props.d.ts.map +1 -0
  142. package/dist/proxmox/lxc-volume.d.ts +29 -0
  143. package/dist/proxmox/lxc-volume.d.ts.map +1 -0
  144. package/dist/proxmox/lxc-wire.d.ts +51 -0
  145. package/dist/proxmox/lxc-wire.d.ts.map +1 -0
  146. package/dist/proxmox/lxc.d.ts +27 -47
  147. package/dist/proxmox/lxc.d.ts.map +1 -1
  148. package/dist/proxmox/network-apply-read.d.ts +1 -1
  149. package/dist/proxmox/network-apply-read.d.ts.map +1 -1
  150. package/dist/proxmox/pbs-datastore.d.ts.map +1 -1
  151. package/dist/proxmox/resource.d.ts.map +1 -1
  152. package/dist/proxmox/update-guard.d.ts +27 -0
  153. package/dist/proxmox/update-guard.d.ts.map +1 -0
  154. package/dist/verify/args.d.ts +16 -0
  155. package/dist/verify/args.d.ts.map +1 -0
  156. package/dist/verify/cli.d.ts +3 -0
  157. package/dist/verify/cli.d.ts.map +1 -0
  158. package/dist/verify/cli.js +320 -0
  159. package/dist/verify/cli.js.map +15 -0
  160. package/dist/verify/fake-engine.d.ts +57 -0
  161. package/dist/verify/fake-engine.d.ts.map +1 -0
  162. package/dist/verify/index.d.ts +16 -0
  163. package/dist/verify/index.d.ts.map +1 -0
  164. package/dist/verify/index.js +297 -0
  165. package/dist/verify/index.js.map +14 -0
  166. package/dist/verify/report.d.ts +6 -0
  167. package/dist/verify/report.d.ts.map +1 -0
  168. package/dist/verify/rows.d.ts +57 -0
  169. package/dist/verify/rows.d.ts.map +1 -0
  170. package/dist/verify/spy.d.ts +31 -0
  171. package/dist/verify/spy.d.ts.map +1 -0
  172. package/dist/verify/verify.d.ts +43 -0
  173. package/dist/verify/verify.d.ts.map +1 -0
  174. package/docs/adopt-verify.md +108 -0
  175. package/docs/adopted-deploys.md +85 -0
  176. package/docs/caddy-admin.md +24 -0
  177. package/docs/caddy.md +73 -54
  178. package/docs/credentials.md +15 -0
  179. package/docs/launchd-sudo.md +147 -0
  180. package/docs/launchd.md +14 -7
  181. package/docs/ownership.md +113 -0
  182. package/docs/peers.md +39 -0
  183. package/docs/proxmox-lxc.md +196 -0
  184. package/docs/proxmox.md +12 -0
  185. package/package.json +9 -2
  186. package/src/caddy/adopt-scope.test.ts +82 -0
  187. package/src/caddy/config-adopt.test.ts +235 -0
  188. package/src/caddy/config-lifecycle.test.ts +31 -53
  189. package/src/caddy/config-lifecycle.ts +98 -7
  190. package/src/caddy/config.ts +48 -10
  191. package/src/caddy/fake-caddy.ts +19 -0
  192. package/src/caddy/index.ts +2 -1
  193. package/src/caddy/providers.test.ts +8 -23
  194. package/src/caddy/with-file.ts +7 -3
  195. package/src/launchd/adopt-apply.test.ts +166 -0
  196. package/src/launchd/fake-runner.ts +26 -4
  197. package/src/launchd/fake-sudo.ts +136 -0
  198. package/src/launchd/host-effect.ts +1 -1
  199. package/src/launchd/host-file-form.ts +10 -2
  200. package/src/launchd/host-file-lifecycle.ts +41 -15
  201. package/src/launchd/host-file.ts +13 -4
  202. package/src/launchd/index.ts +2 -0
  203. package/src/launchd/job-lifecycle.ts +22 -14
  204. package/src/launchd/job-preflight.ts +21 -3
  205. package/src/launchd/job-validate.ts +1 -1
  206. package/src/launchd/job.ts +13 -4
  207. package/src/launchd/local-runner.ts +6 -3
  208. package/src/launchd/providers.test.ts +3 -0
  209. package/src/launchd/providers.ts +2 -1
  210. package/src/launchd/runner.ts +14 -5
  211. package/src/launchd/sudo-acl.ts +68 -0
  212. package/src/launchd/sudo-allowlist.test.ts +191 -0
  213. package/src/launchd/sudo-allowlist.ts +213 -0
  214. package/src/launchd/sudo-chain.test.ts +121 -0
  215. package/src/launchd/sudo-guard.test.ts +100 -0
  216. package/src/launchd/sudo-guard.ts +230 -0
  217. package/src/launchd/sudo-lifecycle.test.ts +172 -0
  218. package/src/launchd/sudo-modes.test.ts +135 -0
  219. package/src/launchd/sudo-runner-files.test.ts +240 -0
  220. package/src/launchd/sudo-runner.test.ts +126 -0
  221. package/src/launchd/sudo-runner.ts +216 -0
  222. package/src/launchd/sudo-said.ts +58 -0
  223. package/src/launchd/sudo-stage.ts +28 -0
  224. package/src/openbao/README.md +11 -2
  225. package/src/openbao/REPLACE.md +67 -37
  226. package/src/openbao/adopt-core.test.ts +119 -0
  227. package/src/openbao/adopt-families.test.ts +97 -0
  228. package/src/openbao/adopt-holes.test.ts +168 -0
  229. package/src/openbao/adopt-recovery.test.ts +71 -0
  230. package/src/openbao/auth-method-reconcile.ts +15 -3
  231. package/src/openbao/auth-method.ts +15 -6
  232. package/src/openbao/auth-role-form.test.ts +2 -16
  233. package/src/openbao/auth-role-form.ts +0 -11
  234. package/src/openbao/auth-role.ts +48 -17
  235. package/src/openbao/cloudflare-role.ts +23 -18
  236. package/src/openbao/fake-bao.ts +52 -0
  237. package/src/openbao/fake-core.ts +141 -0
  238. package/src/openbao/fake-engines-roles.ts +114 -0
  239. package/src/openbao/fake-engines.ts +18 -11
  240. package/src/openbao/fake-families.ts +188 -0
  241. package/src/openbao/fake-mounts.ts +74 -0
  242. package/src/openbao/fake-stack.ts +17 -4
  243. package/src/openbao/host-approles.test.ts +15 -2
  244. package/src/openbao/host-approles.ts +19 -4
  245. package/src/openbao/jwt-config.ts +35 -11
  246. package/src/openbao/jwt-role.ts +33 -14
  247. package/src/openbao/kubernetes-role.ts +32 -14
  248. package/src/openbao/mfa-enforcement.ts +42 -13
  249. package/src/openbao/mfa-totp.ts +42 -14
  250. package/src/openbao/mount-move.test.ts +2 -35
  251. package/src/openbao/mount-reconcile.ts +13 -2
  252. package/src/openbao/mount.ts +15 -6
  253. package/src/openbao/pki-role.ts +33 -10
  254. package/src/openbao/plugin-reconcile.ts +16 -5
  255. package/src/openbao/plugin.ts +57 -20
  256. package/src/openbao/policy.ts +24 -11
  257. package/src/openbao/proxmox-role.ts +20 -20
  258. package/src/openbao/rename-adoption.test.ts +95 -0
  259. package/src/openbao/rename-families.test.ts +122 -0
  260. package/src/openbao/rename-identity.test.ts +129 -0
  261. package/src/openbao/rename-identity.ts +117 -0
  262. package/src/openbao/rename.ts +19 -15
  263. package/src/openbao/role-reconcile.ts +23 -0
  264. package/src/openbao/ssh-role-erase.ts +50 -0
  265. package/src/openbao/ssh-role.ts +29 -57
  266. package/src/ownership/adopt.ts +122 -0
  267. package/src/ownership/ownership.test.ts +200 -0
  268. package/src/ownership/probe.ts +65 -0
  269. package/src/ownership/resume.ts +118 -0
  270. package/src/ownership/rows.ts +117 -0
  271. package/src/ownership/whole.test.ts +39 -0
  272. package/src/ownership/whole.ts +60 -0
  273. package/src/proxmox/adopt-noop.test.ts +112 -0
  274. package/src/proxmox/ceph-pool-adopt.test.ts +88 -0
  275. package/src/proxmox/ceph-pool-form.ts +3 -3
  276. package/src/proxmox/ceph-pool.ts +10 -3
  277. package/src/proxmox/fake-bao-env.ts +28 -0
  278. package/src/proxmox/fake-pve-lxc.ts +210 -0
  279. package/src/proxmox/fake-pve.ts +75 -0
  280. package/src/proxmox/index.test.ts +6 -6
  281. package/src/proxmox/index.ts +6 -2
  282. package/src/proxmox/lxc-adopt.test.ts +170 -0
  283. package/src/proxmox/lxc-create-form.ts +76 -0
  284. package/src/proxmox/lxc-create.test.ts +126 -0
  285. package/src/proxmox/lxc-guards.test.ts +158 -0
  286. package/src/proxmox/lxc-harness.ts +150 -0
  287. package/src/proxmox/lxc-identity.ts +43 -0
  288. package/src/proxmox/lxc-judge.test.ts +175 -0
  289. package/src/proxmox/lxc-judge.ts +196 -0
  290. package/src/proxmox/lxc-lifecycle.ts +200 -0
  291. package/src/proxmox/lxc-ownership.test.ts +159 -0
  292. package/src/proxmox/lxc-props.ts +215 -0
  293. package/src/proxmox/lxc-read.test.ts +60 -0
  294. package/src/proxmox/lxc-volume.test.ts +96 -0
  295. package/src/proxmox/lxc-volume.ts +153 -0
  296. package/src/proxmox/lxc-wire.ts +210 -0
  297. package/src/proxmox/lxc.ts +216 -93
  298. package/src/proxmox/network-apply-read.ts +3 -1
  299. package/src/proxmox/pbs-datastore.ts +4 -3
  300. package/src/proxmox/resource.ts +7 -7
  301. package/src/proxmox/update-guard.ts +31 -0
  302. package/src/verify/args.ts +82 -0
  303. package/src/verify/cli.test.ts +86 -0
  304. package/src/verify/cli.ts +49 -0
  305. package/src/verify/fake-engine.ts +158 -0
  306. package/src/verify/index.ts +36 -0
  307. package/src/verify/report.ts +52 -0
  308. package/src/verify/rows.ts +169 -0
  309. package/src/verify/spy.test.ts +141 -0
  310. package/src/verify/spy.ts +149 -0
  311. package/src/verify/verify.test.ts +124 -0
  312. package/src/verify/verify.ts +172 -0
@@ -297,7 +297,202 @@ var revokeSelf = (token, env) => runWith(env, revokeSelfEffect(token));
297
297
  import { Resource } from "alchemy";
298
298
  import { isResolved } from "alchemy/Diff";
299
299
  import * as Provider from "alchemy/Provider";
300
+ import * as Effect12 from "effect/Effect";
301
+
302
+ // src/ownership/adopt.ts
303
+ import { AdoptPolicy } from "alchemy/AdoptPolicy";
304
+ import { AlchemyContext } from "alchemy/AlchemyContext";
305
+ import { Stack as Stack3 } from "alchemy/Stack";
306
+ import * as Effect6 from "effect/Effect";
307
+ import * as Option5 from "effect/Option";
308
+
309
+ // src/ownership/resume.ts
310
+ import { Unowned } from "alchemy/AdoptPolicy";
311
+ import { Artifacts } from "alchemy/Artifacts";
312
+ import * as Effect3 from "effect/Effect";
313
+ import * as Option2 from "effect/Option";
314
+ var PROVEN = "homeflare/ownership/resumes";
315
+ var UNFINISHED = "homeflare/ownership/unfinished";
316
+ var note = (key, instanceId) => Effect3.gen(function* () {
317
+ const bag = yield* Effect3.serviceOption(Artifacts);
318
+ if (Option2.isSome(bag))
319
+ yield* bag.value.set(key, instanceId);
320
+ });
321
+ var noted = (key, instanceId) => Effect3.gen(function* () {
322
+ const bag = yield* Effect3.serviceOption(Artifacts);
323
+ if (Option2.isNone(bag))
324
+ return false;
325
+ return (yield* bag.value.get(key)) === instanceId;
326
+ });
327
+ var noteUnfinished = (instanceId) => Effect3.as(note(UNFINISHED, instanceId), undefined);
328
+ var noteResume = (instanceId, recovered) => Effect3.gen(function* () {
329
+ yield* noteUnfinished(instanceId);
330
+ const unproven = () => Effect3.succeed(undefined);
331
+ const found = yield* recovered.pipe(Effect3.catch(unproven), Effect3.catchDefect(unproven));
332
+ if (found === undefined || found === null || Unowned.is(found))
333
+ return;
334
+ yield* note(PROVEN, instanceId);
335
+ });
336
+ var resumes = (instanceId) => noted(PROVEN, instanceId);
337
+ var unfinished = (instanceId) => noted(UNFINISHED, instanceId);
338
+ var provingResumes = (service) => {
339
+ const { diff, read } = service;
340
+ if (diff === undefined || read === undefined)
341
+ return service;
342
+ const proving = (input) => Effect3.gen(function* () {
343
+ if (input.output === undefined) {
344
+ yield* noteResume(input.instanceId, read({ ...input, output: undefined }));
345
+ }
346
+ return yield* diff(input);
347
+ });
348
+ return { ...service, diff: proving };
349
+ };
350
+
351
+ // src/ownership/rows.ts
352
+ import { Stack as Stack2 } from "alchemy/Stack";
353
+ import { State, isActionState } from "alchemy/State/State";
354
+ import * as Effect5 from "effect/Effect";
355
+ import * as Option4 from "effect/Option";
356
+
357
+ // src/ownership/whole.ts
358
+ import { Stack } from "alchemy/Stack";
359
+ import * as Effect4 from "effect/Effect";
360
+ import * as Option3 from "effect/Option";
361
+ var absent = (value) => value === undefined || value === null;
362
+ var isPlainObject = (value) => {
363
+ if (typeof value !== "object" || value === null || Array.isArray(value))
364
+ return false;
365
+ const proto = Object.getPrototypeOf(value);
366
+ return proto === Object.prototype || proto === null;
367
+ };
368
+ var carries = (declared, recorded) => {
369
+ if (absent(declared))
370
+ return true;
371
+ if (absent(recorded))
372
+ return false;
373
+ if (Array.isArray(declared)) {
374
+ if (!Array.isArray(recorded))
375
+ return false;
376
+ return declared.every((each, index) => carries(each, recorded[index]));
377
+ }
378
+ if (isPlainObject(declared)) {
379
+ if (!isPlainObject(recorded))
380
+ return false;
381
+ return Object.entries(declared).every(([key, each]) => carries(each, recorded[key]));
382
+ }
383
+ return true;
384
+ };
385
+ var wholeDeclaration = (fqn, props) => Effect4.map(Effect4.serviceOption(Stack), (stack) => {
386
+ const declared = Option3.isSome(stack) ? stack.value.resources[fqn] : undefined;
387
+ return declared !== undefined && carries(declared.Props, props);
388
+ });
389
+
390
+ // src/ownership/rows.ts
391
+ var storeOf = Effect5.gen(function* () {
392
+ const stack = yield* Effect5.serviceOption(Stack2);
393
+ const state = yield* Effect5.serviceOption(State);
394
+ if (Option4.isNone(stack) || Option4.isNone(state))
395
+ return;
396
+ const store = yield* state.value;
397
+ const where = { stack: stack.value.name, stage: stack.value.stage };
398
+ return { resources: stack.value.resources, store, where };
399
+ });
400
+ var rowAt = (store, where, fqn) => store.get({ ...where, fqn }).pipe(Effect5.orElseSucceed(() => {
401
+ return;
402
+ }));
403
+ var generationOf = (row, instanceId) => {
404
+ let at = row;
405
+ while (typeof at === "object" && at !== null && !isActionState(at)) {
406
+ const generation = at;
407
+ if (generation.instanceId === instanceId)
408
+ return generation;
409
+ at = generation.old;
410
+ }
411
+ return;
412
+ };
413
+ var recordedGeneration = (fqn, instanceId) => Effect5.gen(function* () {
414
+ const found = yield* storeOf;
415
+ if (found === undefined)
416
+ return;
417
+ for (const at of [fqn, ...found.resources[fqn]?.FormerFqns ?? []]) {
418
+ const generation = generationOf(yield* rowAt(found.store, found.where, at), instanceId);
419
+ if (generation !== undefined)
420
+ return generation;
421
+ }
422
+ return;
423
+ });
424
+ var recordedInstance = (fqn, instanceId) => Effect5.gen(function* () {
425
+ const generation = yield* recordedGeneration(fqn, instanceId);
426
+ if (generation === undefined)
427
+ return "absent";
428
+ return (yield* wholeDeclaration(fqn, generation.props)) ? "whole" : "partial";
429
+ });
430
+ var isCreate = (fqn, instanceId) => Effect5.map(recordedGeneration(fqn, instanceId), (generation) => generation?.status === "creating");
431
+ var forgetRefusedCreate = (fqn, instanceId) => Effect5.gen(function* () {
432
+ const found = yield* storeOf;
433
+ if (found === undefined)
434
+ return;
435
+ const row = yield* rowAt(found.store, found.where, fqn);
436
+ const mine = row;
437
+ if (mine?.status !== "creating" || mine.instanceId !== instanceId || mine.attr !== undefined) {
438
+ return;
439
+ }
440
+ yield* found.store.delete({ ...found.where, fqn }).pipe(Effect5.ignore);
441
+ });
442
+
443
+ // src/ownership/adopt.ts
444
+ var adoptEnabled = (fqn) => Effect6.gen(function* () {
445
+ const stack = yield* Effect6.serviceOption(Stack3);
446
+ const scoped = Option5.isSome(stack) ? stack.value.resources[fqn]?.Adopt : undefined;
447
+ if (scoped !== undefined)
448
+ return scoped;
449
+ const policy = yield* Effect6.serviceOption(AdoptPolicy);
450
+ if (Option5.isSome(policy))
451
+ return policy.value;
452
+ const context = yield* Effect6.serviceOption(AlchemyContext);
453
+ return Option5.isSome(context) ? context.value.adopt : false;
454
+ });
455
+ var adoptable = (owner) => Effect6.gen(function* () {
456
+ if (yield* isCreate(owner.fqn, owner.instanceId))
457
+ return true;
458
+ return yield* unfinished(owner.instanceId);
459
+ });
460
+ var adoptsAtApply = (owner) => Effect6.gen(function* () {
461
+ if (!(yield* adoptEnabled(owner.fqn)))
462
+ return false;
463
+ return yield* adoptable(owner);
464
+ });
465
+ var refuseTakeover = (owner, what) => Effect6.gen(function* () {
466
+ if (owner.output !== undefined)
467
+ return;
468
+ if (yield* resumes(owner.instanceId))
469
+ return;
470
+ const may = yield* adoptable(owner);
471
+ if (may && (yield* adoptEnabled(owner.fqn)))
472
+ return;
473
+ yield* forgetRefusedCreate(owner.fqn, owner.instanceId);
474
+ const remedy = may ? "Deploy with --adopt (or wrap the resource in adopt(true)) to take it over, or remove it first." : "It is the new identity of a replace, which --adopt does not cover: remove it first.";
475
+ return yield* Effect6.die(new Error(`${what}: already exists, and this stack holds no state for it — the plan could not ask ` + "first (a prop was still an Output, a replace moved onto it, or it appeared after the " + `plan). Nothing was written. ${remedy}`));
476
+ });
477
+ var claimFor = (owner) => (what) => refuseTakeover(owner, what);
478
+
479
+ // src/ownership/probe.ts
480
+ import { Unowned as Unowned2 } from "alchemy/AdoptPolicy";
300
481
  import * as Effect7 from "effect/Effect";
482
+ var ownedRead = (ask, found, settled) => Effect7.gen(function* () {
483
+ if (found === undefined || ask.output !== undefined)
484
+ return found;
485
+ const recorded = yield* recordedInstance(ask.fqn, ask.instanceId);
486
+ if (recorded === "absent")
487
+ return Unowned2(found);
488
+ if (recorded === "partial") {
489
+ yield* Effect7.logWarning(`${ask.fqn}: the interrupted create's state row lacks part of the declaration (a prop ` + "was still an Output when it was written), so the live object is not proven ours");
490
+ return Unowned2(found);
491
+ }
492
+ const unproven = (reason) => Effect7.as(Effect7.logWarning(`${ask.fqn}: the interrupted create's object could not be compared with its props ` + `(${reason instanceof Error ? reason.message : String(reason)}), so it is not treated as ours`), false);
493
+ const ours = yield* settled.pipe(Effect7.catch(unproven), Effect7.catchDefect(unproven));
494
+ return ours ? found : Unowned2(found);
495
+ });
301
496
 
302
497
  // src/openbao/digest.ts
303
498
  var canonical = (text) => text.trimEnd();
@@ -438,24 +633,24 @@ var matches2 = (attributes, props) => attributes.type === props.type && (props.d
438
633
  var wantsTune2 = (props) => props.description !== undefined || props.defaultLeaseTtl !== undefined || props.maxLeaseTtl !== undefined;
439
634
 
440
635
  // src/openbao/auth-method-reconcile.ts
441
- import * as Effect6 from "effect/Effect";
636
+ import * as Effect11 from "effect/Effect";
442
637
 
443
638
  // src/openbao/auth-method-wire.ts
444
- import * as Effect3 from "effect/Effect";
445
- var absentFromTable = (path, asked) => baoRead("sys/auth").pipe(Effect3.mapError((listing) => new BaoError(asked.status, asked.method, asked.path, [
639
+ import * as Effect8 from "effect/Effect";
640
+ var absentFromTable = (path, asked) => baoRead("sys/auth").pipe(Effect8.mapError((listing) => new BaoError(asked.status, asked.method, asked.path, [
446
641
  ...asked.errors,
447
642
  `sys/auth could not confirm the method is absent: ${listing.message}`
448
- ])), Effect3.flatMap((table) => table !== undefined && !Object.hasOwn(table, `${authPath(path)}/`) ? Effect3.succeed(undefined) : Effect3.fail(asked)));
449
- var readAuthMethodData = (path) => baoRead(readPath2(path)).pipe(Effect3.catchIf((error) => error.status === 400, (error) => absentFromTable(path, error)));
643
+ ])), Effect8.flatMap((table) => table !== undefined && !Object.hasOwn(table, `${authPath(path)}/`) ? Effect8.succeed(undefined) : Effect8.fail(asked)));
644
+ var readAuthMethodData = (path) => baoRead(readPath2(path)).pipe(Effect8.catchIf((error) => error.status === 400, (error) => absentFromTable(path, error)));
450
645
  var enableAuthMethod = (props) => baoWrite("POST", readPath2(props.path), enableBody2(props));
451
646
  var tuneAuthMethod = (props) => baoWrite("POST", tunePath2(props.path), tuneBody2(props));
452
647
  var disableAuthMethod = (path) => baoDelete(readPath2(path));
453
648
 
454
649
  // src/openbao/mount-move.ts
455
- import * as Effect5 from "effect/Effect";
650
+ import * as Effect10 from "effect/Effect";
456
651
 
457
652
  // src/openbao/remount-wire.ts
458
- import * as Effect4 from "effect/Effect";
653
+ import * as Effect9 from "effect/Effect";
459
654
  class RemountError extends Error {
460
655
  constructor(message) {
461
656
  super(message);
@@ -463,17 +658,17 @@ class RemountError extends Error {
463
658
  }
464
659
  }
465
660
  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) => {
661
+ var startRemount = (from, to) => Effect9.flatMap(baoCall("write", "POST", "sys/remount", { from, to }), (body) => {
467
662
  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.`));
663
+ return typeof id === "string" && id !== "" ? Effect9.succeed(id) : Effect9.fail(new RemountError(`sys/remount ${from} -> ${to} answered no migration_id.`));
469
664
  });
470
- var remountStatus = (id) => Effect4.map(baoRead(`sys/remount/status/${encodeURIComponent(id)}`), (data) => {
665
+ var remountStatus = (id) => Effect9.map(baoRead(`sys/remount/status/${encodeURIComponent(id)}`), (data) => {
471
666
  if (data === undefined)
472
667
  return;
473
668
  const status = record(data["migration_info"])?.["status"];
474
669
  return typeof status === "string" ? status : "unknown";
475
670
  });
476
- var remountAndWait = (from, to, confirm, options = {}) => Effect4.gen(function* () {
671
+ var remountAndWait = (from, to, confirm, options = {}) => Effect9.gen(function* () {
477
672
  const interval = options.intervalMillis ?? 1000;
478
673
  const attempts = Math.max(1, options.attempts ?? 120);
479
674
  const id = yield* startRemount(from, to);
@@ -483,17 +678,17 @@ var remountAndWait = (from, to, confirm, options = {}) => Effect4.gen(function*
483
678
  if (status === "success")
484
679
  return;
485
680
  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."));
681
+ return yield* Effect9.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
682
  }
488
683
  if (status === undefined) {
489
684
  if (yield* confirm)
490
685
  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."));
686
+ return yield* Effect9.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
687
  }
493
688
  if (attempt < attempts)
494
- yield* Effect4.sleep(interval);
689
+ yield* Effect9.sleep(interval);
495
690
  }
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.`));
691
+ return yield* Effect9.fail(new RemountError(`${named} is still in progress after ${String(attempts)} status reads. Do not re-run ` + `until sys/remount/status/${id} settles.`));
497
692
  });
498
693
 
499
694
  // src/openbao/mount-move.ts
@@ -520,8 +715,8 @@ var planMove = (family, stated, path, remountFrom) => {
520
715
  }
521
716
  return { kind: "move", from };
522
717
  };
523
- var refuse = (message) => Effect5.die(new Error(message));
524
- var performMove = (target, from, options) => Effect5.gen(function* () {
718
+ var refuse = (message) => Effect10.die(new Error(message));
719
+ var performMove = (target, from, options) => Effect10.gen(function* () {
525
720
  const to = mountPath(target.path);
526
721
  const source = yield* target.read(from);
527
722
  const landed = yield* target.read(to);
@@ -536,12 +731,12 @@ var performMove = (target, from, options) => Effect5.gen(function* () {
536
731
  if (source.type !== target.type) {
537
732
  return yield* refuse(`${target.family}: ${from} is type ${source.type}, not ${target.type}. A remount keeps the ` + "type; fix the declaration first.");
538
733
  }
539
- const confirm = Effect5.map(Effect5.all([target.read(from), target.read(to)]), ([gone, there]) => gone === undefined && there !== undefined);
734
+ const confirm = Effect10.map(Effect10.all([target.read(from), target.read(to)]), ([gone, there]) => gone === undefined && there !== undefined);
540
735
  yield* remountAndWait(target.wire(from), target.wire(to), confirm, options);
541
736
  });
542
737
 
543
738
  // src/openbao/auth-method-reconcile.ts
544
- var readMethod = (props) => Effect6.gen(function* () {
739
+ var readMethod = (props) => Effect11.gen(function* () {
545
740
  const live = yield* readAuthMethodData(props.path);
546
741
  if (live === undefined)
547
742
  return;
@@ -554,10 +749,16 @@ var moveTarget = (props) => ({
554
749
  type: props.type,
555
750
  wire: (path) => `auth/${authPath(path)}`
556
751
  });
557
- var reconcileAuthMethod = (news, stated, options) => Effect6.gen(function* () {
752
+ var reconcileAuthMethod = (news, stated, options, claim) => Effect11.gen(function* () {
558
753
  const move = planMove("Bao.AuthMethod", stated, news.path, news.remountFrom);
559
754
  if (move.kind === "refuse")
560
- return yield* Effect6.die(new Error(move.message));
755
+ return yield* Effect11.die(new Error(move.message));
756
+ const first = stated === undefined ? claim : undefined;
757
+ if (first !== undefined && move.kind === "move") {
758
+ const source = yield* readMethod({ ...news, path: move.from });
759
+ if (source !== undefined)
760
+ yield* first(`Bao.AuthMethod ${move.from} (the remountFrom source)`);
761
+ }
561
762
  if (move.kind === "move")
562
763
  yield* performMove(moveTarget(news), move.from, options);
563
764
  let live = yield* readMethod(news);
@@ -565,15 +766,18 @@ var reconcileAuthMethod = (news, stated, options) => Effect6.gen(function* () {
565
766
  yield* enableAuthMethod(news);
566
767
  if (wantsTune2(news))
567
768
  yield* tuneAuthMethod(news);
568
- } else if (!matches2(live, news)) {
769
+ } else {
569
770
  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."));
771
+ return yield* Effect11.die(new Error(`Bao.AuthMethod ${authPath(news.path)}: live type ${live.type} != ${news.type}. ` + "Auth method type is immutable — replace manually."));
571
772
  }
572
- yield* tuneAuthMethod(news);
773
+ if (first !== undefined)
774
+ yield* first(`Bao.AuthMethod ${authPath(news.path)}`);
775
+ if (!matches2(live, news))
776
+ yield* tuneAuthMethod(news);
573
777
  }
574
778
  live = yield* readMethod(news);
575
779
  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.`));
780
+ return yield* Effect11.die(new Error(`Bao.AuthMethod ${authPath(news.path)}: write returned no error but the method is still absent.`));
577
781
  }
578
782
  return live;
579
783
  });
@@ -582,17 +786,21 @@ var reconcileAuthMethod = (news, stated, options) => Effect6.gen(function* () {
582
786
  var BaoAuthMethod = Resource("Bao.AuthMethod", {
583
787
  defaultRemovalPolicy: "retain"
584
788
  });
585
- var BaoAuthMethodProvider = () => Provider.effect(BaoAuthMethod, Effect7.succeed(BaoAuthMethod.Provider.of({
586
- list: () => Effect7.succeed([]),
587
- read: Effect7.fn(function* ({ olds }) {
588
- return yield* readMethod(olds);
789
+ var BaoAuthMethodProvider = () => Provider.effect(BaoAuthMethod, Effect12.succeed(BaoAuthMethod.Provider.of({
790
+ list: () => Effect12.succeed([]),
791
+ read: Effect12.fn(function* ({ fqn, instanceId, olds, output }) {
792
+ const found = yield* readMethod(olds);
793
+ const ours = Effect12.sync(() => found !== undefined && matches2(found, olds));
794
+ return yield* ownedRead({ fqn, instanceId, output }, found, ours);
589
795
  }),
590
- diff: Effect7.fn(function* ({ news, output }) {
591
- if (output === undefined || !isResolved(news))
796
+ diff: Effect12.fn(function* ({ news, output }) {
797
+ if (output === undefined)
798
+ return;
799
+ if (!isResolved(news))
592
800
  return;
593
801
  const move = planMove("Bao.AuthMethod", output.path, news.path, news.remountFrom);
594
802
  if (move.kind === "refuse")
595
- return yield* Effect7.die(new Error(move.message));
803
+ return yield* Effect12.die(new Error(move.message));
596
804
  if (move.kind === "move")
597
805
  return { action: "update" };
598
806
  const live = yield* readMethod(news);
@@ -604,19 +812,20 @@ var BaoAuthMethodProvider = () => Provider.effect(BaoAuthMethod, Effect7.succeed
604
812
  return { action: "noop" };
605
813
  return { action: "update" };
606
814
  }),
607
- reconcile: Effect7.fn(function* ({ news, output }) {
608
- return yield* reconcileAuthMethod(news, output?.path);
815
+ reconcile: Effect12.fn(function* ({ fqn, instanceId, news, output }) {
816
+ const claim = claimFor({ fqn, instanceId, output });
817
+ return yield* reconcileAuthMethod(news, output?.path, undefined, claim);
609
818
  }),
610
- delete: Effect7.fn(function* ({ output }) {
819
+ delete: Effect12.fn(function* ({ output }) {
611
820
  yield* disableAuthMethod(output.path);
612
821
  return;
613
822
  })
614
- })));
823
+ })).pipe(Effect12.map(provingResumes)));
615
824
  // src/openbao/auth-role.ts
616
825
  import { Resource as Resource2 } from "alchemy";
617
826
  import { isResolved as isResolved2 } from "alchemy/Diff";
618
827
  import * as Provider2 from "alchemy/Provider";
619
- import * as Effect8 from "effect/Effect";
828
+ import * as Effect15 from "effect/Effect";
620
829
 
621
830
  // src/openbao/auth-role-form.ts
622
831
  var policiesOf = (live) => {
@@ -665,7 +874,6 @@ var writeBody = (props) => {
665
874
  }
666
875
  return body;
667
876
  };
668
- var isRename = (from, to) => from.toLowerCase() !== to.toLowerCase();
669
877
  var samePolicies = (want, have) => want.length === have.length && want.every((policy, index) => policy === have[index]);
670
878
  var sameTtl3 = (want, have) => {
671
879
  const wantSeconds = parseDuration(want);
@@ -677,26 +885,92 @@ var matches3 = (attributes, props) => {
677
885
  return samePolicies(wantPolicies, attributes.tokenPolicies) && sameTtl3(props.tokenTtl, attributes.tokenTtl) && sameTtl3(props.tokenMaxTtl, attributes.tokenMaxTtl) && sameTtl3(props.secretIdTtl, attributes.secretIdTtl) && (props.bindSecretId === undefined || attributes.bindSecretId === props.bindSecretId) && (props.secretIdNumUses === undefined || attributes.secretIdNumUses === props.secretIdNumUses);
678
886
  };
679
887
 
888
+ // src/openbao/rename-identity.ts
889
+ import * as Effect14 from "effect/Effect";
890
+
891
+ // src/openbao/rename.ts
892
+ import * as Effect13 from "effect/Effect";
893
+ var exact = (identity) => identity;
894
+ var declaredString = (news, key) => {
895
+ if (typeof news !== "object" || news === null)
896
+ return;
897
+ const value = news[key];
898
+ return typeof value === "string" ? value : undefined;
899
+ };
900
+ var isPendingProp = (news, key) => {
901
+ if (typeof news !== "object" || news === null)
902
+ return false;
903
+ const value = news[key];
904
+ return value !== undefined && typeof value !== "string";
905
+ };
906
+ var declaredOr = (news, key, fallback) => isPendingProp(news, key) ? undefined : declaredString(news, key) ?? fallback;
907
+ var declaredRolePath = (news, rolePath, defaultMount) => {
908
+ const mount = defaultMount === undefined ? declaredString(news, "mount") : declaredOr(news, "mount", defaultMount);
909
+ const name = declaredString(news, "name");
910
+ return mount === undefined || name === undefined ? undefined : rolePath(mount, name);
911
+ };
912
+ var policyKey = (name) => name.trim().toLowerCase();
913
+ var isMoved = (stored, declared, key = exact) => declared === undefined ? undefined : key(stored) !== key(declared);
914
+ var judgeMove = (family, tried, declared, pathOf, key = exact) => Effect13.gen(function* () {
915
+ if (tried === undefined || declared === undefined || key(tried) === key(declared)) {
916
+ return;
917
+ }
918
+ if ((yield* baoRead(pathOf(declared))) === undefined)
919
+ return { from: tried, to: declared };
920
+ return yield* Effect13.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.`));
921
+ });
922
+ var refuseMovedUpdate = (family, from, to) => Effect13.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."));
923
+
924
+ // src/openbao/rename-identity.ts
925
+ var nameIdentity = (family, pathOf, key) => ({
926
+ declared: (props) => declaredString(props, "name"),
927
+ family,
928
+ key,
929
+ pathOf,
930
+ recorded: (output) => output.name
931
+ });
932
+ var roleIdentity = (family, rolePath, defaultMount, key) => ({
933
+ declared: (props) => declaredRolePath(props, rolePath, defaultMount),
934
+ family,
935
+ key,
936
+ recorded: (output) => rolePath(output.mount, output.name)
937
+ });
938
+ var foldName = (path) => path.replace(/[^/]+$/, (name) => name.toLowerCase());
939
+ var judgeRename = (identity, olds, news, output) => judgeMove(identity.family, output === undefined ? identity.declared(olds) : identity.recorded(output), identity.declared(news), identity.pathOf ?? ((path) => path), identity.key);
940
+ var guardRename = (identity, news, output) => {
941
+ if (output === undefined)
942
+ return Effect14.void;
943
+ const from = identity.recorded(output);
944
+ const to = identity.declared(news);
945
+ return to !== undefined && isMoved(from, to, identity.key) === true ? refuseMovedUpdate(identity.family, from, to) : Effect14.void;
946
+ };
947
+
680
948
  // src/openbao/auth-role.ts
681
949
  var BaoAuthRole = Resource2("Bao.AuthRole", {
682
950
  defaultRemovalPolicy: "retain"
683
951
  });
684
- var readRole = (props) => Effect8.gen(function* () {
952
+ var IDENTITY = nameIdentity("Bao.AuthRole", readPath3, foldName);
953
+ var readRole = (props) => Effect15.gen(function* () {
685
954
  const live = yield* baoRead(readPath3(props.name));
686
955
  if (live === undefined)
687
956
  return;
688
957
  return attributesOf3(props, live);
689
958
  });
690
- var BaoAuthRoleProvider = () => Provider2.effect(BaoAuthRole, Effect8.succeed(BaoAuthRole.Provider.of({
691
- list: () => Effect8.succeed([]),
692
- read: Effect8.fn(function* ({ olds }) {
693
- return yield* readRole(olds);
959
+ var BaoAuthRoleProvider = () => Provider2.effect(BaoAuthRole, Effect15.succeed(BaoAuthRole.Provider.of({
960
+ list: () => Effect15.succeed([]),
961
+ read: Effect15.fn(function* ({ fqn, instanceId, olds, output }) {
962
+ const found = yield* readRole(olds);
963
+ const ours = Effect15.sync(() => found !== undefined && matches3(found, olds));
964
+ return yield* ownedRead({ fqn, instanceId, output }, found, ours);
694
965
  }),
695
- diff: Effect8.fn(function* ({ news, output }) {
696
- if (output === undefined || !isResolved2(news))
966
+ diff: Effect15.fn(function* ({ news, olds, output }) {
967
+ const move = yield* judgeRename(IDENTITY, olds, news, output);
968
+ if (output === undefined)
697
969
  return;
698
- if (isRename(output.name, news.name))
970
+ if (move !== undefined)
699
971
  return { action: "replace" };
972
+ if (!isResolved2(news))
973
+ return;
700
974
  const live = yield* readRole(news);
701
975
  if (live === undefined)
702
976
  return { action: "update" };
@@ -704,24 +978,28 @@ var BaoAuthRoleProvider = () => Provider2.effect(BaoAuthRole, Effect8.succeed(Ba
704
978
  return { action: "noop" };
705
979
  return { action: "update" };
706
980
  }),
707
- reconcile: Effect8.fn(function* ({ news }) {
981
+ reconcile: Effect15.fn(function* ({ fqn, instanceId, news, output }) {
982
+ yield* guardRename(IDENTITY, news, output);
708
983
  const live = yield* readRole(news);
984
+ const path = readPath3(news.name);
985
+ if (live !== undefined)
986
+ yield* refuseTakeover({ fqn, instanceId, output }, `Bao.AuthRole ${path}`);
709
987
  if (live === undefined || !matches3(live, news)) {
710
988
  yield* baoWrite("PUT", readPath3(news.name), writeBody(news));
711
989
  }
712
990
  const after = yield* readRole(news);
713
991
  if (after === undefined) {
714
- return yield* Effect8.die(new Error(`Bao.AuthRole ${news.name}: write returned no error but the role is still absent.`));
992
+ return yield* Effect15.die(new Error(`Bao.AuthRole ${news.name}: write returned no error but the role is still absent.`));
715
993
  }
716
994
  return after;
717
995
  }),
718
- delete: Effect8.fn(function* ({ output }) {
996
+ delete: Effect15.fn(function* ({ output }) {
719
997
  yield* baoDelete(readPath3(output.name));
720
998
  return;
721
999
  })
722
- })));
1000
+ })).pipe(Effect15.map(provingResumes)));
723
1001
  // src/openbao/bao-identity.ts
724
- import * as Effect9 from "effect/Effect";
1002
+ import * as Effect16 from "effect/Effect";
725
1003
  import * as FetchHttpClient3 from "effect/unstable/http/FetchHttpClient";
726
1004
 
727
1005
  // src/openbao/bao-namespace.ts
@@ -769,42 +1047,42 @@ var checkHealth = (address, expectedCluster, body) => {
769
1047
  }
770
1048
  return { clusterId: text3(body["cluster_id"]), version: text3(body["version"]) };
771
1049
  };
772
- var assertBaoIdentityEffect = (expected) => Effect9.gen(function* () {
1050
+ var assertBaoIdentityEffect = (expected) => Effect16.gen(function* () {
773
1051
  const env = yield* BaoEnv;
774
1052
  const address = addressOf(env);
775
1053
  const clusterName = expected.clusterName.trim();
776
1054
  if (clusterName === "") {
777
- return yield* Effect9.fail(new BaoIdentityError("input", "clusterName is empty, so there is nothing to compare."));
1055
+ return yield* Effect16.fail(new BaoIdentityError("input", "clusterName is empty, so there is nothing to compare."));
778
1056
  }
779
1057
  const namespace = canonicalNamespace(expected.namespace);
780
1058
  const sent = envNamespace(env);
781
1059
  if (sent !== namespace) {
782
- return yield* Effect9.fail(new BaoIdentityError("mismatch", `BAO_NAMESPACE resolves to \`${namespaceLabel(sent)}\`, but this stack expects ` + `\`${namespaceLabel(namespace)}\`.`));
1060
+ return yield* Effect16.fail(new BaoIdentityError("mismatch", `BAO_NAMESPACE resolves to \`${namespaceLabel(sent)}\`, but this stack expects ` + `\`${namespaceLabel(namespace)}\`.`));
783
1061
  }
784
1062
  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")));
1063
+ const body = yield* baoCall("read", "GET", HEALTH_PATH).pipe(Effect16.provideService(BaoEnv, anonymous), Effect16.mapError(unreachable(address, "sys/health")));
786
1064
  const health = checkHealth(address, clusterName, body);
787
1065
  if (health instanceof BaoIdentityError)
788
- return yield* Effect9.fail(health);
1066
+ return yield* Effect16.fail(health);
789
1067
  if (namespace !== "") {
790
1068
  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}\``)));
1069
+ const probe = yield* baoCall("read", "GET", NAMESPACE_PROBE_PATH).pipe(Effect16.provideService(BaoEnv, scoped), Effect16.mapError(unreachable(address, `the namespace probe for \`${namespace}\``)));
792
1070
  if (probe === undefined) {
793
- return yield* Effect9.fail(new BaoIdentityError("mismatch", `cluster \`${clusterName}\` at ${address} has no namespace \`${namespace}\`.`));
1071
+ return yield* Effect16.fail(new BaoIdentityError("mismatch", `cluster \`${clusterName}\` at ${address} has no namespace \`${namespace}\`.`));
794
1072
  }
795
1073
  }
796
1074
  return { address, clusterName, namespace, ...health };
797
1075
  });
798
1076
  var assertBaoIdentity = (input) => {
799
1077
  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)));
1078
+ const scoped = input.env === undefined ? effect3 : Effect16.provideService(effect3, BaoEnv, input.env);
1079
+ return Effect16.runPromise(scoped.pipe(Effect16.provide(FetchHttpClient3.layer)));
802
1080
  };
803
1081
  // src/openbao/cloudflare-role.ts
804
1082
  import { Resource as Resource3 } from "alchemy";
805
1083
  import { isResolved as isResolved3 } from "alchemy/Diff";
806
1084
  import * as Provider3 from "alchemy/Provider";
807
- import * as Effect13 from "effect/Effect";
1085
+ import * as Effect19 from "effect/Effect";
808
1086
 
809
1087
  // src/openbao/cloudflare-group-scope.ts
810
1088
  var zoneResource = (zoneId) => `com.cloudflare.api.account.zone.${zoneId}`;
@@ -824,7 +1102,7 @@ var groupKey = (name, scope) => `${scope} ${name}`;
824
1102
 
825
1103
  // src/openbao/cloudflare-permission-groups.ts
826
1104
  import * as Context2 from "effect/Context";
827
- import * as Effect11 from "effect/Effect";
1105
+ import * as Effect18 from "effect/Effect";
828
1106
 
829
1107
  // src/openbao/cloudflare-permission-groups-solve.ts
830
1108
  var observationsOf = (declared, live) => declared.flatMap((role) => {
@@ -994,7 +1272,7 @@ var samePolicies2 = (want, have) => want.length === have.length && want.every((w
994
1272
  });
995
1273
 
996
1274
  // src/openbao/cloudflare-role-wire.ts
997
- import * as Effect10 from "effect/Effect";
1275
+ import * as Effect17 from "effect/Effect";
998
1276
 
999
1277
  // src/openbao/cloudflare-role-form.ts
1000
1278
  var rolePath = (mount, name) => `${mountPath(mount)}/roles/${name}`;
@@ -1059,7 +1337,7 @@ var differences = (props, policies, live) => {
1059
1337
  };
1060
1338
 
1061
1339
  // src/openbao/cloudflare-role-wire.ts
1062
- var readCloudflareRole = (mount, name) => Effect10.map(baoRead(rolePath(mount, name)), (data) => data === undefined ? undefined : liveRoleOf(data));
1340
+ var readCloudflareRole = (mount, name) => Effect17.map(baoRead(rolePath(mount, name)), (data) => data === undefined ? undefined : liveRoleOf(data));
1063
1341
  var writeCloudflareRole = (mount, name, body) => baoWrite("PUT", rolePath(mount, name), body);
1064
1342
  var deleteCloudflareRole = (mount, name) => baoDelete(rolePath(mount, name));
1065
1343
 
@@ -1081,9 +1359,9 @@ var pick = (mount, groups, refs, hint) => {
1081
1359
  const unknown = refs.filter((ref) => !groups.has(groupKey(ref.name, ref.scope)));
1082
1360
  if (unknown.length > 0) {
1083
1361
  const problems = unknown.map((ref) => `unknown group ${JSON.stringify(ref.name)} at scope ${ref.scope} — ${hint}`);
1084
- return Effect11.fail(new CloudflareGroupsError(mount, problems));
1362
+ return Effect18.fail(new CloudflareGroupsError(mount, problems));
1085
1363
  }
1086
- return Effect11.succeed(new Map(refs.flatMap((ref) => {
1364
+ return Effect18.succeed(new Map(refs.flatMap((ref) => {
1087
1365
  const key = groupKey(ref.name, ref.scope);
1088
1366
  const id = groups.get(key);
1089
1367
  return id === undefined ? [] : [[key, id]];
@@ -1091,15 +1369,15 @@ var pick = (mount, groups, refs, hint) => {
1091
1369
  };
1092
1370
  var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1093
1371
  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";
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) => {
1372
+ var deniedNamed = (mount, read) => Effect18.catchIf(read, (error) => error.status === 403, () => Effect18.fail(new CloudflareGroupsError(mount, [deniedHint(mount)])));
1373
+ var engineGroups = (mount) => Effect18.flatMap(deniedNamed(mount, baoRead(`${mountPath(mount)}/permission-groups`)), (data) => {
1096
1374
  const list = data?.["permission_groups"];
1097
1375
  if (data === undefined) {
1098
1376
  const why = "GET <mount>/permission-groups answered 404 — the endpoint is not deployed here";
1099
- return Effect11.fail(new CloudflareGroupsError(mount, [why]));
1377
+ return Effect18.fail(new CloudflareGroupsError(mount, [why]));
1100
1378
  }
1101
1379
  if (!Array.isArray(list)) {
1102
- return Effect11.fail(new CloudflareGroupsError(mount, ["no permission_groups array in reply"]));
1380
+ return Effect18.fail(new CloudflareGroupsError(mount, ["no permission_groups array in reply"]));
1103
1381
  }
1104
1382
  const scoped = new Map;
1105
1383
  const idsOf = new Map;
@@ -1126,24 +1404,24 @@ var engineGroups = (mount) => Effect11.flatMap(deniedNamed(mount, baoRead(`${mou
1126
1404
  const only = [...ids];
1127
1405
  return ids.size === 1 && only[0] !== undefined ? [[name, only[0]]] : [];
1128
1406
  }));
1129
- return problems.length > 0 ? Effect11.fail(new CloudflareGroupsError(mount, problems)) : Effect11.succeed(new Map([...atEveryScope(sole), ...scoped]));
1407
+ return problems.length > 0 ? Effect18.fail(new CloudflareGroupsError(mount, problems)) : Effect18.succeed(new Map([...atEveryScope(sole), ...scoped]));
1130
1408
  });
1131
- var permissionGroupsFromEngine = () => Effect11.sync(() => {
1409
+ var permissionGroupsFromEngine = () => Effect18.sync(() => {
1132
1410
  const cache = new Map;
1133
- const cachedFor = (mount) => Effect11.suspend(() => {
1411
+ const cachedFor = (mount) => Effect18.suspend(() => {
1134
1412
  const hit = cache.get(mountPath(mount));
1135
1413
  if (hit !== undefined)
1136
1414
  return hit;
1137
- return Effect11.flatMap(Effect11.cached(engineGroups(mount)), (lookup) => {
1415
+ return Effect18.flatMap(Effect18.cached(engineGroups(mount)), (lookup) => {
1138
1416
  cache.set(mountPath(mount), lookup);
1139
1417
  return lookup;
1140
1418
  });
1141
1419
  });
1142
1420
  return CloudflarePermissionGroups.of({
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"))
1421
+ resolve: (mount, refs) => Effect18.flatMap(cachedFor(mount), (groups) => pick(mount, groups, refs, "the engine does not list it for this account at that scope"))
1144
1422
  });
1145
1423
  });
1146
- var permissionGroupsFromLiveRoles = (declared) => Effect11.gen(function* () {
1424
+ var permissionGroupsFromLiveRoles = (declared) => Effect18.gen(function* () {
1147
1425
  const accountOf = new Map;
1148
1426
  const byAccount = new Map;
1149
1427
  for (const [mount, roles] of declared) {
@@ -1154,9 +1432,9 @@ var permissionGroupsFromLiveRoles = (declared) => Effect11.gen(function* () {
1154
1432
  }
1155
1433
  const solved = new Map;
1156
1434
  for (const [account, roles] of byAccount) {
1157
- const solve = Effect11.gen(function* () {
1435
+ const solve = Effect18.gen(function* () {
1158
1436
  const live = new Map;
1159
- yield* Effect11.all(roles.map((role) => Effect11.map(readCloudflareRole(role.mount, role.name), (read) => {
1437
+ yield* Effect18.all(roles.map((role) => Effect18.map(readCloudflareRole(role.mount, role.name), (read) => {
1160
1438
  if (read?.policies !== undefined) {
1161
1439
  live.set(`${role.mount}/${role.name}`, read.policies);
1162
1440
  }
@@ -1164,11 +1442,11 @@ var permissionGroupsFromLiveRoles = (declared) => Effect11.gen(function* () {
1164
1442
  const result = solveGroups(observationsOf(roles, live));
1165
1443
  const problems = describeProblems(result);
1166
1444
  if (problems.length > 0) {
1167
- return yield* Effect11.fail(new CloudflareGroupsError(`account ${account}`, problems));
1445
+ return yield* Effect18.fail(new CloudflareGroupsError(`account ${account}`, problems));
1168
1446
  }
1169
1447
  return atEveryScope(result.groups);
1170
1448
  });
1171
- solved.set(account, yield* Effect11.cached(solve));
1449
+ solved.set(account, yield* Effect18.cached(solve));
1172
1450
  }
1173
1451
  return CloudflarePermissionGroups.of({
1174
1452
  resolve: (mount, refs) => {
@@ -1176,54 +1454,22 @@ var permissionGroupsFromLiveRoles = (declared) => Effect11.gen(function* () {
1176
1454
  const lookup = account === undefined ? undefined : solved.get(account);
1177
1455
  if (account === undefined || lookup === undefined) {
1178
1456
  const why = "no declared role on this mount, so no live role can vouch for its groups";
1179
- return Effect11.fail(new CloudflareGroupsError(mount, [why]));
1457
+ return Effect18.fail(new CloudflareGroupsError(mount, [why]));
1180
1458
  }
1181
1459
  const hint = `no live role on account ${account} carries it; a NEW group needs the engine endpoint`;
1182
- return Effect11.flatMap(lookup, (groups) => pick(mount, groups, refs, hint));
1460
+ return Effect18.flatMap(lookup, (groups) => pick(mount, groups, refs, hint));
1183
1461
  }
1184
1462
  });
1185
1463
  });
1186
1464
 
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
-
1220
1465
  // src/openbao/cloudflare-role.ts
1221
1466
  var BaoCloudflareRole = Resource3("Bao.CloudflareRole", {
1222
1467
  defaultRemovalPolicy: "retain"
1223
1468
  });
1224
- var BaoCloudflareRoleProvider = () => Provider3.effect(BaoCloudflareRole, Effect13.gen(function* () {
1469
+ var IDENTITY2 = roleIdentity("Bao.CloudflareRole", rolePath);
1470
+ var BaoCloudflareRoleProvider = () => Provider3.effect(BaoCloudflareRole, Effect19.gen(function* () {
1225
1471
  const groups = yield* CloudflarePermissionGroups;
1226
- const resolved = (props) => Effect13.gen(function* () {
1472
+ const resolved = (props) => Effect19.gen(function* () {
1227
1473
  const refs = new Map;
1228
1474
  for (const policy of props.policies) {
1229
1475
  const scope = policy.scope ?? scopeOfResource(policy.resource);
@@ -1233,20 +1479,24 @@ var BaoCloudflareRoleProvider = () => Provider3.effect(BaoCloudflareRole, Effect
1233
1479
  const ids = yield* groups.resolve(props.mount, [...refs.values()]);
1234
1480
  const { missing, policies } = resolvePolicies(props.policies, ids);
1235
1481
  if (missing.length > 0) {
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.`));
1482
+ return yield* Effect19.die(new Error(`Bao.CloudflareRole ${rolePath(props.mount, props.name)}: groups ` + `${missing.join(", ")} came back unresolved from a lookup that reported success.`));
1237
1483
  }
1238
1484
  return policies;
1239
1485
  });
1240
1486
  return BaoCloudflareRole.Provider.of({
1241
- list: () => Effect13.succeed([]),
1242
- read: Effect13.fn(function* ({ olds }) {
1487
+ list: () => Effect19.succeed([]),
1488
+ read: Effect19.fn(function* ({ fqn, instanceId, olds, output }) {
1243
1489
  const live = yield* readCloudflareRole(olds.mount, olds.name);
1244
- return live === undefined ? undefined : attributesOf4(olds, live);
1490
+ const found = live === undefined ? undefined : attributesOf4(olds, live);
1491
+ const ours = Effect19.gen(function* () {
1492
+ if (live === undefined || refusalOf(olds) !== undefined)
1493
+ return false;
1494
+ return differences(olds, yield* resolved(olds), live).length === 0;
1495
+ });
1496
+ return yield* ownedRead({ fqn, instanceId, output }, found, ours);
1245
1497
  }),
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);
1498
+ diff: Effect19.fn(function* ({ news, olds, output }) {
1499
+ const move = yield* judgeRename(IDENTITY2, olds, news, output);
1250
1500
  if (output === undefined)
1251
1501
  return;
1252
1502
  if (move !== undefined)
@@ -1261,37 +1511,37 @@ var BaoCloudflareRoleProvider = () => Provider3.effect(BaoCloudflareRole, Effect
1261
1511
  return { action: "update" };
1262
1512
  return differences(news, policies, live).length === 0 ? { action: "noop" } : { action: "update" };
1263
1513
  }),
1264
- reconcile: Effect13.fn(function* ({ news, output }) {
1514
+ reconcile: Effect19.fn(function* ({ fqn, instanceId, news, output }) {
1265
1515
  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
- }
1516
+ yield* guardRename(IDENTITY2, news, output);
1270
1517
  const refusal2 = refusalOf(news);
1271
1518
  if (refusal2 !== undefined) {
1272
- return yield* Effect13.die(new Error(`Bao.CloudflareRole ${path}: ${refusal2}.`));
1519
+ return yield* Effect19.die(new Error(`Bao.CloudflareRole ${path}: ${refusal2}.`));
1273
1520
  }
1274
1521
  const policies = yield* resolved(news);
1275
1522
  const live = yield* readCloudflareRole(news.mount, news.name);
1523
+ if (live !== undefined) {
1524
+ yield* refuseTakeover({ fqn, instanceId, output }, `Bao.CloudflareRole ${path}`);
1525
+ }
1276
1526
  if (live === undefined || differences(news, policies, live).length > 0) {
1277
1527
  yield* writeCloudflareRole(news.mount, news.name, writeBody2(news, policies));
1278
1528
  }
1279
1529
  const after = yield* readCloudflareRole(news.mount, news.name);
1280
1530
  if (after === undefined) {
1281
- return yield* Effect13.die(new Error(`Bao.CloudflareRole ${path}: the write succeeded but the role is absent.`));
1531
+ return yield* Effect19.die(new Error(`Bao.CloudflareRole ${path}: the write succeeded but the role is absent.`));
1282
1532
  }
1283
1533
  const left = differences(news, policies, after);
1284
1534
  if (left.length > 0) {
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.`));
1535
+ return yield* Effect19.die(new Error(`Bao.CloudflareRole ${path}: wrote cleanly but reads back different in ` + `${left.map((d) => d.field).join(", ")}. Inspect the role before retrying.`));
1286
1536
  }
1287
1537
  return attributesOf4(news, after);
1288
1538
  }),
1289
- delete: Effect13.fn(function* ({ output }) {
1539
+ delete: Effect19.fn(function* ({ output }) {
1290
1540
  yield* deleteCloudflareRole(output.mount, output.name);
1291
1541
  return;
1292
1542
  })
1293
1543
  });
1294
- }));
1544
+ }).pipe(Effect19.map(provingResumes)));
1295
1545
  // src/openbao/cloudflare-roles-expand.ts
1296
1546
  var slug = (name) => trimRuns(name.toLowerCase().replace(/[^a-z0-9]+/g, "-"), "-");
1297
1547
  var zoneSlug = (zone) => `${slug(zone.name)}${zone.internal ? "-internal" : ""}`;
@@ -1509,17 +1759,19 @@ var hostAppRoles = (input) => {
1509
1759
  if (!NAME.test(host.name)) {
1510
1760
  problems.push(`host \`${host.name}\` is not lowercase letters, digits, single hyphens`);
1511
1761
  }
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];
1762
+ const name = hostRoleName(host.class, host.name);
1763
+ if (seen.has(name)) {
1764
+ problems.push(`host \`${host.name}\` is listed twice in class \`${host.class}\``);
1765
+ }
1766
+ seen.add(name);
1767
+ const spec = Object.hasOwn(input.classes, host.class) ? input.classes[host.class] : undefined;
1516
1768
  if (spec === undefined) {
1517
1769
  problems.push(`host \`${host.name}\` names unknown class \`${host.class}\``);
1518
1770
  continue;
1519
1771
  }
1520
1772
  roles.push({
1521
1773
  bindSecretId: true,
1522
- name: hostRoleName(host.class, host.name),
1774
+ name,
1523
1775
  secretIdNumUses: spec.secretIdNumUses ?? 0,
1524
1776
  secretIdTtl: spec.secretIdTtl,
1525
1777
  tokenMaxTtl: spec.tokenMaxTtl,
@@ -1536,7 +1788,7 @@ var hostAppRoles = (input) => {
1536
1788
  import { Resource as Resource4 } from "alchemy";
1537
1789
  import { isResolved as isResolved4 } from "alchemy/Diff";
1538
1790
  import * as Provider4 from "alchemy/Provider";
1539
- import * as Effect15 from "effect/Effect";
1791
+ import * as Effect21 from "effect/Effect";
1540
1792
 
1541
1793
  // src/openbao/jwt-config-form.ts
1542
1794
  var text5 = (value) => typeof value === "string" ? value.trim() : "";
@@ -1626,19 +1878,25 @@ var problems = (props) => {
1626
1878
  };
1627
1879
 
1628
1880
  // 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* () {
1881
+ import * as Effect20 from "effect/Effect";
1882
+ var readRoleAt = (spec) => Effect20.map(baoRead(spec.path), (live) => live === undefined ? undefined : { attributes: spec.attributesOf(live), live });
1883
+ var readOwnedRole = (ask, spec) => Effect20.flatMap(readRoleAt(spec), (found) => {
1884
+ const ours = Effect20.sync(() => found !== undefined && spec.problems.length === 0 && spec.matches(found.attributes));
1885
+ return ownedRead(ask, found?.attributes, ours);
1886
+ });
1887
+ var planRole = (spec) => Effect20.gen(function* () {
1632
1888
  if (spec.problems.length > 0)
1633
1889
  return "update";
1634
1890
  const found = yield* readRoleAt(spec);
1635
1891
  return found !== undefined && spec.matches(found.attributes) ? "noop" : "update";
1636
1892
  });
1637
- var refuse2 = (spec, message) => Effect14.die(new Error(`${spec.family} ${spec.path}: ${message}`));
1638
- var reconcileRole = (spec) => Effect14.gen(function* () {
1893
+ var refuse2 = (spec, message) => Effect20.die(new Error(`${spec.family} ${spec.path}: ${message}`));
1894
+ var reconcileRole = (spec, claim) => Effect20.gen(function* () {
1639
1895
  if (spec.problems.length > 0)
1640
1896
  return yield* refuse2(spec, spec.problems.join("; "));
1641
1897
  const found = yield* readRoleAt(spec);
1898
+ if (found !== undefined && claim !== undefined)
1899
+ yield* claim(`${spec.family} ${spec.path}`);
1642
1900
  if (found === undefined || !spec.matches(found.attributes)) {
1643
1901
  const lost = found === undefined ? [] : spec.wouldErase?.(found.live) ?? [];
1644
1902
  if (lost.length > 0) {
@@ -1659,6 +1917,14 @@ var reconcileRole = (spec) => Effect14.gen(function* () {
1659
1917
  var BaoJwtAuthConfig = Resource4("Bao.JwtAuthConfig", {
1660
1918
  defaultRemovalPolicy: "retain"
1661
1919
  });
1920
+ var IDENTITY3 = {
1921
+ declared: (props) => {
1922
+ const mount = declaredOr(props, "mount", "jwt");
1923
+ return mount === undefined ? undefined : configPath({ mount });
1924
+ },
1925
+ family: "Bao.JwtAuthConfig",
1926
+ recorded: (output) => configPath(output)
1927
+ };
1662
1928
  var jwtConfigSpec = (props) => ({
1663
1929
  attributesOf: (live) => attributesOf5(props, live),
1664
1930
  body: writeBody3(props),
@@ -1668,29 +1934,32 @@ var jwtConfigSpec = (props) => ({
1668
1934
  problems: problems(props),
1669
1935
  wouldErase
1670
1936
  });
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;
1937
+ var BaoJwtAuthConfigProvider = () => Provider4.effect(BaoJwtAuthConfig, Effect21.succeed(BaoJwtAuthConfig.Provider.of({
1938
+ list: () => Effect21.succeed([]),
1939
+ read: Effect21.fn(function* ({ fqn, instanceId, olds, output }) {
1940
+ return yield* readOwnedRole({ fqn, instanceId, output }, jwtConfigSpec(olds));
1676
1941
  }),
1677
- diff: Effect15.fn(function* ({ news, output }) {
1678
- if (output === undefined || !isResolved4(news))
1942
+ diff: Effect21.fn(function* ({ news, olds, output }) {
1943
+ const move = yield* judgeRename(IDENTITY3, olds, news, output);
1944
+ if (output === undefined)
1679
1945
  return;
1680
- if (mountOf(news) !== output.mount)
1946
+ if (move !== undefined)
1681
1947
  return { action: "replace" };
1948
+ if (!isResolved4(news))
1949
+ return;
1682
1950
  return { action: yield* planRole(jwtConfigSpec(news)) };
1683
1951
  }),
1684
- reconcile: Effect15.fn(function* ({ news }) {
1685
- return yield* reconcileRole(jwtConfigSpec(news));
1952
+ reconcile: Effect21.fn(function* ({ fqn, instanceId, news, output }) {
1953
+ yield* guardRename(IDENTITY3, news, output);
1954
+ return yield* reconcileRole(jwtConfigSpec(news), claimFor({ fqn, instanceId, output }));
1686
1955
  }),
1687
- delete: () => Effect15.void
1688
- })));
1956
+ delete: () => Effect21.void
1957
+ })).pipe(Effect21.map(provingResumes)));
1689
1958
  // src/openbao/jwt-role.ts
1690
1959
  import { Resource as Resource5 } from "alchemy";
1691
1960
  import { isResolved as isResolved5 } from "alchemy/Diff";
1692
1961
  import * as Provider5 from "alchemy/Provider";
1693
- import * as Effect16 from "effect/Effect";
1962
+ import * as Effect22 from "effect/Effect";
1694
1963
 
1695
1964
  // src/openbao/auth-token-form.ts
1696
1965
  var sortedSet2 = (values) => [...new Set(values)].sort();
@@ -1892,6 +2161,7 @@ var problems2 = (props) => {
1892
2161
  var BaoJwtRole = Resource5("Bao.JwtRole", {
1893
2162
  defaultRemovalPolicy: "retain"
1894
2163
  });
2164
+ var IDENTITY4 = roleIdentity("Bao.JwtRole", (mount, name) => rolePath2({ mount, name }), "jwt", foldName);
1895
2165
  var jwtRoleSpec = (props) => ({
1896
2166
  attributesOf: (live) => attributesOf6(props, live),
1897
2167
  body: writeBody4(props),
@@ -1900,33 +2170,35 @@ var jwtRoleSpec = (props) => ({
1900
2170
  path: rolePath2(props),
1901
2171
  problems: problems2(props)
1902
2172
  });
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;
2173
+ var BaoJwtRoleProvider = () => Provider5.effect(BaoJwtRole, Effect22.succeed(BaoJwtRole.Provider.of({
2174
+ list: () => Effect22.succeed([]),
2175
+ read: Effect22.fn(function* ({ fqn, instanceId, olds, output }) {
2176
+ return yield* readOwnedRole({ fqn, instanceId, output }, jwtRoleSpec(olds));
1908
2177
  }),
1909
- diff: Effect16.fn(function* ({ news, output }) {
1910
- if (output === undefined || !isResolved5(news))
2178
+ diff: Effect22.fn(function* ({ news, olds, output }) {
2179
+ const move = yield* judgeRename(IDENTITY4, olds, news, output);
2180
+ if (output === undefined)
1911
2181
  return;
1912
- if (mountOf2(news) !== output.mount || news.name !== output.name) {
2182
+ if (move !== undefined)
1913
2183
  return { action: "replace" };
1914
- }
2184
+ if (!isResolved5(news))
2185
+ return;
1915
2186
  return { action: yield* planRole(jwtRoleSpec(news)) };
1916
2187
  }),
1917
- reconcile: Effect16.fn(function* ({ news }) {
1918
- return yield* reconcileRole(jwtRoleSpec(news));
2188
+ reconcile: Effect22.fn(function* ({ fqn, instanceId, news, output }) {
2189
+ yield* guardRename(IDENTITY4, news, output);
2190
+ return yield* reconcileRole(jwtRoleSpec(news), claimFor({ fqn, instanceId, output }));
1919
2191
  }),
1920
- delete: Effect16.fn(function* ({ output }) {
2192
+ delete: Effect22.fn(function* ({ output }) {
1921
2193
  yield* baoDelete(rolePath2(output));
1922
2194
  return;
1923
2195
  })
1924
- })));
2196
+ })).pipe(Effect22.map(provingResumes)));
1925
2197
  // src/openbao/kubernetes-role.ts
1926
2198
  import { Resource as Resource6 } from "alchemy";
1927
2199
  import { isResolved as isResolved6 } from "alchemy/Diff";
1928
2200
  import * as Provider6 from "alchemy/Provider";
1929
- import * as Effect17 from "effect/Effect";
2201
+ import * as Effect23 from "effect/Effect";
1930
2202
 
1931
2203
  // src/openbao/kubernetes-role-form.ts
1932
2204
  var sortedSet4 = (values) => [...new Set(values)].sort();
@@ -1995,6 +2267,7 @@ var problems3 = (props) => {
1995
2267
  var BaoKubernetesRole = Resource6("Bao.KubernetesRole", {
1996
2268
  defaultRemovalPolicy: "retain"
1997
2269
  });
2270
+ var IDENTITY5 = roleIdentity("Bao.KubernetesRole", (mount, name) => rolePath3({ mount, name }), "kubernetes", foldName);
1998
2271
  var kubernetesRoleSpec = (props) => ({
1999
2272
  attributesOf: (live) => attributesOf7(props, live),
2000
2273
  body: writeBody5(props),
@@ -2003,33 +2276,35 @@ var kubernetesRoleSpec = (props) => ({
2003
2276
  path: rolePath3(props),
2004
2277
  problems: problems3(props)
2005
2278
  });
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;
2279
+ var BaoKubernetesRoleProvider = () => Provider6.effect(BaoKubernetesRole, Effect23.succeed(BaoKubernetesRole.Provider.of({
2280
+ list: () => Effect23.succeed([]),
2281
+ read: Effect23.fn(function* ({ fqn, instanceId, olds, output }) {
2282
+ return yield* readOwnedRole({ fqn, instanceId, output }, kubernetesRoleSpec(olds));
2011
2283
  }),
2012
- diff: Effect17.fn(function* ({ news, output }) {
2013
- if (output === undefined || !isResolved6(news))
2284
+ diff: Effect23.fn(function* ({ news, olds, output }) {
2285
+ const move = yield* judgeRename(IDENTITY5, olds, news, output);
2286
+ if (output === undefined)
2014
2287
  return;
2015
- if (mountOf3(news) !== output.mount || news.name !== output.name) {
2288
+ if (move !== undefined)
2016
2289
  return { action: "replace" };
2017
- }
2290
+ if (!isResolved6(news))
2291
+ return;
2018
2292
  return { action: yield* planRole(kubernetesRoleSpec(news)) };
2019
2293
  }),
2020
- reconcile: Effect17.fn(function* ({ news }) {
2021
- return yield* reconcileRole(kubernetesRoleSpec(news));
2294
+ reconcile: Effect23.fn(function* ({ fqn, instanceId, news, output }) {
2295
+ yield* guardRename(IDENTITY5, news, output);
2296
+ return yield* reconcileRole(kubernetesRoleSpec(news), claimFor({ fqn, instanceId, output }));
2022
2297
  }),
2023
- delete: Effect17.fn(function* ({ output }) {
2298
+ delete: Effect23.fn(function* ({ output }) {
2024
2299
  yield* baoDelete(rolePath3(output));
2025
2300
  return;
2026
2301
  })
2027
- })));
2302
+ })).pipe(Effect23.map(provingResumes)));
2028
2303
  // src/openbao/mfa-enforcement.ts
2029
2304
  import { Resource as Resource7 } from "alchemy";
2030
2305
  import { isResolved as isResolved7 } from "alchemy/Diff";
2031
2306
  import * as Provider7 from "alchemy/Provider";
2032
- import * as Effect19 from "effect/Effect";
2307
+ import * as Effect25 from "effect/Effect";
2033
2308
 
2034
2309
  // src/openbao/mfa-enforcement-form.ts
2035
2310
  var sortedSet5 = (values) => [...new Set(values.map((value) => value.trim()))].filter((value) => value !== "").sort();
@@ -2097,16 +2372,16 @@ var problems4 = (props, missingPaths) => {
2097
2372
  };
2098
2373
 
2099
2374
  // src/openbao/mfa-wire.ts
2100
- import * as Effect18 from "effect/Effect";
2375
+ import * as Effect24 from "effect/Effect";
2101
2376
  var TOTP_PATH = "identity/mfa/method/totp";
2102
2377
  var METHODS_PATH = "identity/mfa/method";
2103
2378
  var enforcementPath = (name) => `identity/mfa/login-enforcement/${name}`;
2104
2379
  var record4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
2105
- var ownNamespacePath = Effect18.gen(function* () {
2380
+ var ownNamespacePath = Effect24.gen(function* () {
2106
2381
  const env = yield* BaoEnv;
2107
2382
  return namespacePath(envNamespace(env));
2108
2383
  });
2109
- var findTotp = (name) => Effect18.gen(function* () {
2384
+ var findTotp = (name) => Effect24.gen(function* () {
2110
2385
  const listing = yield* baoRead(`${METHODS_PATH}?list=true`);
2111
2386
  const info = record4(listing?.["key_info"]) ?? {};
2112
2387
  const own = yield* ownNamespacePath;
@@ -2115,17 +2390,17 @@ var findTotp = (name) => Effect18.gen(function* () {
2115
2390
  if (live === undefined || live["name"] !== name || live["namespace_path"] !== own)
2116
2391
  continue;
2117
2392
  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.`));
2393
+ return yield* Effect24.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
2394
  }
2120
2395
  return { id, live };
2121
2396
  }
2122
2397
  return;
2123
2398
  });
2124
- var writeTotp = (body) => Effect18.asVoid(baoCall("write", "POST", TOTP_PATH, body));
2399
+ var writeTotp = (body) => Effect24.asVoid(baoCall("write", "POST", TOTP_PATH, body));
2125
2400
  var deleteTotp = (id) => baoDelete(`${TOTP_PATH}/${id}`);
2126
2401
  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* () {
2402
+ var writeEnforcement = (name, body) => Effect24.asVoid(baoCall("write", "POST", enforcementPath(name), body));
2403
+ var authAccessors = (paths) => Effect24.gen(function* () {
2129
2404
  if (paths.length === 0)
2130
2405
  return { accessors: [], missing: [] };
2131
2406
  const table = (yield* baoRead("sys/auth")) ?? {};
@@ -2145,24 +2420,28 @@ var authAccessors = (paths) => Effect18.gen(function* () {
2145
2420
  var BaoMfaLoginEnforcement = Resource7("Bao.MfaLoginEnforcement", {
2146
2421
  defaultRemovalPolicy: "retain"
2147
2422
  });
2148
- var refuse3 = (name, message) => Effect19.die(new Error(`Bao.MfaLoginEnforcement ${name}: ${message}`));
2149
- var resolveTargets = (props) => Effect19.map(authAccessors(props.authMethodPaths ?? []), ({ accessors, missing }) => ({
2423
+ var IDENTITY6 = nameIdentity("Bao.MfaLoginEnforcement", enforcementPath);
2424
+ var refuse3 = (name, message) => Effect25.die(new Error(`Bao.MfaLoginEnforcement ${name}: ${message}`));
2425
+ var resolveTargets = (props) => Effect25.map(authAccessors(props.authMethodPaths ?? []), ({ accessors, missing }) => ({
2150
2426
  bad: problems4(props, missing),
2151
2427
  resolved: accessors
2152
2428
  }));
2153
- var readLive = (name) => Effect19.map(readEnforcement(name), (live) => live === undefined ? undefined : attributesOf8(name, live));
2154
- var planEnforcement = (props) => Effect19.gen(function* () {
2429
+ var readLive = (name) => Effect25.map(readEnforcement(name), (live) => live === undefined ? undefined : attributesOf8(name, live));
2430
+ var planEnforcement = (props) => Effect25.gen(function* () {
2155
2431
  const { bad, resolved } = yield* resolveTargets(props);
2156
2432
  if (bad.length > 0)
2157
2433
  return "update";
2158
2434
  const live = yield* readLive(props.name);
2159
2435
  return live !== undefined && matches7(live, props, resolved) ? "noop" : "update";
2160
2436
  });
2161
- var reconcileEnforcement = (props) => Effect19.gen(function* () {
2437
+ var reconcileEnforcement = (props, claim) => Effect25.gen(function* () {
2162
2438
  const { bad, resolved } = yield* resolveTargets(props);
2163
2439
  if (bad.length > 0)
2164
2440
  return yield* refuse3(props.name, bad.join("; "));
2165
2441
  const live = yield* readLive(props.name);
2442
+ if (live !== undefined && claim !== undefined) {
2443
+ yield* claim(`Bao.MfaLoginEnforcement ${props.name}`);
2444
+ }
2166
2445
  if (live === undefined || !matches7(live, props, resolved)) {
2167
2446
  yield* writeEnforcement(props.name, writeBody6(canonicalFromProps4(props, resolved)));
2168
2447
  }
@@ -2174,30 +2453,36 @@ var reconcileEnforcement = (props) => Effect19.gen(function* () {
2174
2453
  }
2175
2454
  return after;
2176
2455
  });
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);
2456
+ var BaoMfaLoginEnforcementProvider = () => Provider7.effect(BaoMfaLoginEnforcement, Effect25.succeed(BaoMfaLoginEnforcement.Provider.of({
2457
+ list: () => Effect25.succeed([]),
2458
+ read: Effect25.fn(function* ({ fqn, instanceId, olds, output }) {
2459
+ const found = yield* readLive(olds.name);
2460
+ const ours = Effect25.map(planEnforcement(olds), (plan) => plan === "noop");
2461
+ return yield* ownedRead({ fqn, instanceId, output }, found, ours);
2181
2462
  }),
2182
- diff: Effect19.fn(function* ({ news, output }) {
2183
- if (output === undefined || !isResolved7(news))
2463
+ diff: Effect25.fn(function* ({ news, olds, output }) {
2464
+ const move = yield* judgeRename(IDENTITY6, olds, news, output);
2465
+ if (output === undefined)
2184
2466
  return;
2185
- if (news.name !== output.name)
2467
+ if (move !== undefined)
2186
2468
  return { action: "replace" };
2469
+ if (!isResolved7(news))
2470
+ return;
2187
2471
  return { action: yield* planEnforcement(news) };
2188
2472
  }),
2189
- reconcile: Effect19.fn(function* ({ news }) {
2190
- return yield* reconcileEnforcement(news);
2473
+ reconcile: Effect25.fn(function* ({ fqn, instanceId, news, output }) {
2474
+ yield* guardRename(IDENTITY6, news, output);
2475
+ return yield* reconcileEnforcement(news, claimFor({ fqn, instanceId, output }));
2191
2476
  }),
2192
- delete: Effect19.fn(function* ({ output }) {
2477
+ delete: Effect25.fn(function* ({ output }) {
2193
2478
  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
2479
  })
2195
- })));
2480
+ })).pipe(Effect25.map(provingResumes)));
2196
2481
  // src/openbao/mfa-totp.ts
2197
2482
  import { Resource as Resource8 } from "alchemy";
2198
2483
  import { isResolved as isResolved8 } from "alchemy/Diff";
2199
2484
  import * as Provider8 from "alchemy/Provider";
2200
- import * as Effect20 from "effect/Effect";
2485
+ import * as Effect26 from "effect/Effect";
2201
2486
 
2202
2487
  // src/openbao/mfa-totp-form.ts
2203
2488
  var canonical6 = (input) => ({
@@ -2278,71 +2563,83 @@ var problems5 = (props) => {
2278
2563
  var BaoMfaTotpMethod = Resource8("Bao.MfaTotpMethod", {
2279
2564
  defaultRemovalPolicy: "retain"
2280
2565
  });
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* () {
2566
+ var refuse4 = (name, message) => Effect26.die(new Error(`Bao.MfaTotpMethod ${name}: ${message}`));
2567
+ var readTotp = (props) => Effect26.map(findTotp(props.name), (found) => found === undefined ? undefined : attributesOf9(found.id, found.live));
2568
+ var planTotp = (props) => Effect26.gen(function* () {
2284
2569
  if (problems5(props).length > 0)
2285
2570
  return "update";
2286
2571
  const live = yield* readTotp(props);
2287
2572
  return live !== undefined && matches8(live, props) ? "noop" : "update";
2288
2573
  });
2289
- var reconcileTotp = (props) => Effect20.gen(function* () {
2574
+ var reconcileTotp = (props, claim) => Effect26.gen(function* () {
2290
2575
  const bad = problems5(props);
2291
2576
  if (bad.length > 0)
2292
- return yield* refuse4(props, bad.join("; "));
2577
+ return yield* refuse4(props.name, bad.join("; "));
2293
2578
  const live = yield* readTotp(props);
2579
+ if (live !== undefined && claim !== undefined)
2580
+ yield* claim(`Bao.MfaTotpMethod ${props.name}`);
2294
2581
  if (live === undefined || !matches8(live, props))
2295
2582
  yield* writeTotp(writeBody7(props));
2296
2583
  const after = yield* readTotp(props);
2297
2584
  if (after === undefined)
2298
- return yield* refuse4(props, "the write succeeded but no method has that name.");
2585
+ return yield* refuse4(props.name, "the write succeeded but no method has that name.");
2299
2586
  if (!matches8(after, props))
2300
- return yield* refuse4(props, "the method read back different.");
2587
+ return yield* refuse4(props.name, "the method read back different.");
2301
2588
  return after;
2302
2589
  });
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);
2590
+ var BaoMfaTotpMethodProvider = () => Provider8.effect(BaoMfaTotpMethod, Effect26.succeed(BaoMfaTotpMethod.Provider.of({
2591
+ list: () => Effect26.succeed([]),
2592
+ read: Effect26.fn(function* ({ fqn, instanceId, olds, output }) {
2593
+ const found = yield* readTotp(olds);
2594
+ const ours = Effect26.sync(() => found !== undefined && problems5(olds).length === 0 && matches8(found, olds));
2595
+ return yield* ownedRead({ fqn, instanceId, output }, found, ours);
2307
2596
  }),
2308
- diff: Effect20.fn(function* ({ news, output }) {
2309
- if (output === undefined || !isResolved8(news))
2597
+ diff: Effect26.fn(function* ({ news, output }) {
2598
+ const name = declaredString(news, "name");
2599
+ if (output !== undefined && name !== undefined) {
2600
+ const rename = renameProblem(output.name, name);
2601
+ if (rename !== undefined)
2602
+ return yield* refuse4(name, rename);
2603
+ }
2604
+ if (output === undefined)
2605
+ return;
2606
+ if (!isResolved8(news))
2310
2607
  return;
2311
- const rename = renameProblem(output.name, news.name);
2312
- if (rename !== undefined)
2313
- return yield* refuse4(news, rename);
2314
2608
  return { action: yield* planTotp(news) };
2315
2609
  }),
2316
- reconcile: Effect20.fn(function* ({ news }) {
2317
- return yield* reconcileTotp(news);
2610
+ reconcile: Effect26.fn(function* ({ fqn, instanceId, news, output }) {
2611
+ const rename = output === undefined ? undefined : renameProblem(output.name, news.name);
2612
+ if (rename !== undefined)
2613
+ return yield* refuse4(news.name, rename);
2614
+ return yield* reconcileTotp(news, claimFor({ fqn, instanceId, output }));
2318
2615
  }),
2319
- delete: Effect20.fn(function* ({ output }) {
2616
+ delete: Effect26.fn(function* ({ output }) {
2320
2617
  yield* deleteTotp(output.methodId);
2321
2618
  return;
2322
2619
  })
2323
- })));
2620
+ })).pipe(Effect26.map(provingResumes)));
2324
2621
  // src/openbao/mount.ts
2325
2622
  import { Resource as Resource9 } from "alchemy";
2326
2623
  import { isResolved as isResolved9 } from "alchemy/Diff";
2327
2624
  import * as Provider9 from "alchemy/Provider";
2328
- import * as Effect23 from "effect/Effect";
2625
+ import * as Effect29 from "effect/Effect";
2329
2626
 
2330
2627
  // src/openbao/mount-reconcile.ts
2331
- import * as Effect22 from "effect/Effect";
2628
+ import * as Effect28 from "effect/Effect";
2332
2629
 
2333
2630
  // src/openbao/mount-wire.ts
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, [
2631
+ import * as Effect27 from "effect/Effect";
2632
+ var absentFromTable2 = (path, asked) => baoRead("sys/mounts").pipe(Effect27.mapError((listing) => new BaoError(asked.status, asked.method, asked.path, [
2336
2633
  ...asked.errors,
2337
2634
  `sys/mounts could not confirm the mount is absent: ${listing.message}`
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)));
2635
+ ])), Effect27.flatMap((table) => table !== undefined && !Object.hasOwn(table, `${mountPath(path)}/`) ? Effect27.succeed(undefined) : Effect27.fail(asked)));
2636
+ var readMountData = (path) => baoRead(readPath(path)).pipe(Effect27.catchIf((error) => error.status === 400, (error) => absentFromTable2(path, error)));
2340
2637
  var enableMount = (props) => baoWrite("POST", readPath(props.path), enableBody(props));
2341
2638
  var tuneMount = (props) => baoWrite("POST", tunePath(props.path), tuneBody(props));
2342
2639
  var disableMount = (path) => baoDelete(readPath(path));
2343
2640
 
2344
2641
  // src/openbao/mount-reconcile.ts
2345
- var readMount = (props) => Effect22.gen(function* () {
2642
+ var readMount = (props) => Effect28.gen(function* () {
2346
2643
  const live = yield* readMountData(props.path);
2347
2644
  if (live === undefined)
2348
2645
  return;
@@ -2355,10 +2652,16 @@ var moveTarget2 = (props) => ({
2355
2652
  type: props.type,
2356
2653
  wire: mountPath
2357
2654
  });
2358
- var reconcileMount = (news, stated, options) => Effect22.gen(function* () {
2655
+ var reconcileMount = (news, stated, options, claim) => Effect28.gen(function* () {
2359
2656
  const move = planMove("Bao.Mount", stated, news.path, news.remountFrom);
2360
2657
  if (move.kind === "refuse")
2361
- return yield* Effect22.die(new Error(move.message));
2658
+ return yield* Effect28.die(new Error(move.message));
2659
+ const first = stated === undefined ? claim : undefined;
2660
+ if (first !== undefined && move.kind === "move") {
2661
+ const source = yield* readMount({ ...news, path: move.from });
2662
+ if (source !== undefined)
2663
+ yield* first(`Bao.Mount ${move.from} (the remountFrom source)`);
2664
+ }
2362
2665
  if (move.kind === "move")
2363
2666
  yield* performMove(moveTarget2(news), move.from, options);
2364
2667
  let live = yield* readMount(news);
@@ -2366,15 +2669,18 @@ var reconcileMount = (news, stated, options) => Effect22.gen(function* () {
2366
2669
  yield* enableMount(news);
2367
2670
  if (wantsTune(news))
2368
2671
  yield* tuneMount(news);
2369
- } else if (!matches(live, news)) {
2672
+ } else {
2370
2673
  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."));
2674
+ return yield* Effect28.die(new Error(`Bao.Mount ${mountPath(news.path)}: live type ${live.type} != ${news.type}. ` + "Secrets engine type is immutable — replace manually."));
2372
2675
  }
2373
- yield* tuneMount(news);
2676
+ if (first !== undefined)
2677
+ yield* first(`Bao.Mount ${mountPath(news.path)}`);
2678
+ if (!matches(live, news))
2679
+ yield* tuneMount(news);
2374
2680
  }
2375
2681
  live = yield* readMount(news);
2376
2682
  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.`));
2683
+ return yield* Effect28.die(new Error(`Bao.Mount ${mountPath(news.path)}: write returned no error but the mount is still absent.`));
2378
2684
  }
2379
2685
  return live;
2380
2686
  });
@@ -2383,17 +2689,21 @@ var reconcileMount = (news, stated, options) => Effect22.gen(function* () {
2383
2689
  var BaoMount = Resource9("Bao.Mount", {
2384
2690
  defaultRemovalPolicy: "retain"
2385
2691
  });
2386
- var BaoMountProvider = () => Provider9.effect(BaoMount, Effect23.succeed(BaoMount.Provider.of({
2387
- list: () => Effect23.succeed([]),
2388
- read: Effect23.fn(function* ({ olds }) {
2389
- return yield* readMount(olds);
2692
+ var BaoMountProvider = () => Provider9.effect(BaoMount, Effect29.succeed(BaoMount.Provider.of({
2693
+ list: () => Effect29.succeed([]),
2694
+ read: Effect29.fn(function* ({ fqn, instanceId, olds, output }) {
2695
+ const found = yield* readMount(olds);
2696
+ const ours = Effect29.sync(() => found !== undefined && matches(found, olds));
2697
+ return yield* ownedRead({ fqn, instanceId, output }, found, ours);
2390
2698
  }),
2391
- diff: Effect23.fn(function* ({ news, output }) {
2392
- if (output === undefined || !isResolved9(news))
2699
+ diff: Effect29.fn(function* ({ news, output }) {
2700
+ if (output === undefined)
2701
+ return;
2702
+ if (!isResolved9(news))
2393
2703
  return;
2394
2704
  const move = planMove("Bao.Mount", output.path, news.path, news.remountFrom);
2395
2705
  if (move.kind === "refuse")
2396
- return yield* Effect23.die(new Error(move.message));
2706
+ return yield* Effect29.die(new Error(move.message));
2397
2707
  if (move.kind === "move")
2398
2708
  return { action: "update" };
2399
2709
  const live = yield* readMount(news);
@@ -2405,19 +2715,20 @@ var BaoMountProvider = () => Provider9.effect(BaoMount, Effect23.succeed(BaoMoun
2405
2715
  return { action: "noop" };
2406
2716
  return { action: "update" };
2407
2717
  }),
2408
- reconcile: Effect23.fn(function* ({ news, output }) {
2409
- return yield* reconcileMount(news, output?.path);
2718
+ reconcile: Effect29.fn(function* ({ fqn, instanceId, news, output }) {
2719
+ const claim = claimFor({ fqn, instanceId, output });
2720
+ return yield* reconcileMount(news, output?.path, undefined, claim);
2410
2721
  }),
2411
- delete: Effect23.fn(function* ({ output }) {
2722
+ delete: Effect29.fn(function* ({ output }) {
2412
2723
  yield* disableMount(output.path);
2413
2724
  return;
2414
2725
  })
2415
- })));
2726
+ })).pipe(Effect29.map(provingResumes)));
2416
2727
  // src/openbao/pki-role.ts
2417
2728
  import { Resource as Resource10 } from "alchemy";
2418
2729
  import { isResolved as isResolved10 } from "alchemy/Diff";
2419
2730
  import * as Provider10 from "alchemy/Provider";
2420
- import * as Effect24 from "effect/Effect";
2731
+ import * as Effect30 from "effect/Effect";
2421
2732
 
2422
2733
  // src/openbao/pki-role-canonical.ts
2423
2734
  var DEFAULT_KEY_USAGE = [
@@ -2583,22 +2894,28 @@ var problems6 = (props) => {
2583
2894
  var BaoPkiRole = Resource10("Bao.PkiRole", {
2584
2895
  defaultRemovalPolicy: "retain"
2585
2896
  });
2586
- var readRole2 = (props) => Effect24.gen(function* () {
2897
+ var IDENTITY7 = roleIdentity("Bao.PkiRole", (mount, name) => rolePath4({ mount, name }), "pki");
2898
+ var readRole2 = (props) => Effect30.gen(function* () {
2587
2899
  const live = yield* baoRead(rolePath4(props));
2588
2900
  if (live === undefined)
2589
2901
  return;
2590
2902
  return attributesOf10(props, live);
2591
2903
  });
2592
- var BaoPkiRoleProvider = () => Provider10.effect(BaoPkiRole, Effect24.succeed(BaoPkiRole.Provider.of({
2593
- list: () => Effect24.succeed([]),
2594
- read: Effect24.fn(function* ({ olds }) {
2595
- return yield* readRole2(olds);
2904
+ var BaoPkiRoleProvider = () => Provider10.effect(BaoPkiRole, Effect30.succeed(BaoPkiRole.Provider.of({
2905
+ list: () => Effect30.succeed([]),
2906
+ read: Effect30.fn(function* ({ fqn, instanceId, olds, output }) {
2907
+ const found = yield* readRole2(olds);
2908
+ const ours = Effect30.sync(() => found !== undefined && problems6(olds).length === 0 && matches9(found, olds));
2909
+ return yield* ownedRead({ fqn, instanceId, output }, found, ours);
2596
2910
  }),
2597
- diff: Effect24.fn(function* ({ news, output }) {
2598
- if (output === undefined || !isResolved10(news))
2911
+ diff: Effect30.fn(function* ({ news, olds, output }) {
2912
+ const move = yield* judgeRename(IDENTITY7, olds, news, output);
2913
+ if (output === undefined)
2599
2914
  return;
2600
- if (mountPath(news.mount ?? "pki") !== output.mount || news.name !== output.name)
2915
+ if (move !== undefined)
2601
2916
  return { action: "replace" };
2917
+ if (!isResolved10(news))
2918
+ return;
2602
2919
  if (problems6(news).length > 0)
2603
2920
  return { action: "update" };
2604
2921
  const live = yield* readRole2(news);
@@ -2606,35 +2923,38 @@ var BaoPkiRoleProvider = () => Provider10.effect(BaoPkiRole, Effect24.succeed(Ba
2606
2923
  return { action: "update" };
2607
2924
  return matches9(live, news) ? { action: "noop" } : { action: "update" };
2608
2925
  }),
2609
- reconcile: Effect24.fn(function* ({ news }) {
2926
+ reconcile: Effect30.fn(function* ({ fqn, instanceId, news, output }) {
2610
2927
  const path = rolePath4(news);
2928
+ yield* guardRename(IDENTITY7, news, output);
2611
2929
  const bad = problems6(news);
2612
2930
  if (bad.length > 0) {
2613
- return yield* Effect24.die(new Error(`Bao.PkiRole ${path}: ${bad.join(" ")}`));
2931
+ return yield* Effect30.die(new Error(`Bao.PkiRole ${path}: ${bad.join(" ")}`));
2614
2932
  }
2615
2933
  const live = yield* readRole2(news);
2934
+ if (live !== undefined)
2935
+ yield* refuseTakeover({ fqn, instanceId, output }, `Bao.PkiRole ${path}`);
2616
2936
  if (live === undefined || !matches9(live, news)) {
2617
2937
  yield* baoWrite("PUT", path, writeBody8(news));
2618
2938
  }
2619
2939
  const after = yield* readRole2(news);
2620
2940
  if (after === undefined) {
2621
- return yield* Effect24.die(new Error(`Bao.PkiRole ${path}: write reported success but the role is absent.`));
2941
+ return yield* Effect30.die(new Error(`Bao.PkiRole ${path}: write reported success but the role is absent.`));
2622
2942
  }
2623
2943
  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.`));
2944
+ return yield* Effect30.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.`));
2625
2945
  }
2626
2946
  return after;
2627
2947
  }),
2628
- delete: Effect24.fn(function* ({ output }) {
2948
+ delete: Effect30.fn(function* ({ output }) {
2629
2949
  yield* baoDelete(rolePath4(output));
2630
2950
  return;
2631
2951
  })
2632
- })));
2952
+ })).pipe(Effect30.map(provingResumes)));
2633
2953
  // src/openbao/plugin.ts
2634
2954
  import { Resource as Resource11 } from "alchemy";
2635
2955
  import { isResolved as isResolved11 } from "alchemy/Diff";
2636
2956
  import * as Provider11 from "alchemy/Provider";
2637
- import * as Effect26 from "effect/Effect";
2957
+ import * as Effect32 from "effect/Effect";
2638
2958
 
2639
2959
  // src/openbao/plugin-form.ts
2640
2960
  var resolve = (props) => ({
@@ -2690,7 +3010,7 @@ var sameArgs = (want, have) => want.length === have.length && want.every((entry,
2690
3010
  var matches10 = (attributes, form) => !attributes.builtin && attributes.version === form.version && attributes.sha256 === form.sha256 && attributes.command === form.command && sameArgs(form.args, attributes.args);
2691
3011
 
2692
3012
  // src/openbao/plugin-reconcile.ts
2693
- import * as Effect25 from "effect/Effect";
3013
+ import * as Effect31 from "effect/Effect";
2694
3014
 
2695
3015
  // src/openbao/plugin-wire.ts
2696
3016
  var readPluginData = (form) => baoRead(versionedPath(form.type, form.name, form.version));
@@ -2698,15 +3018,18 @@ var registerPlugin = (form) => baoWrite("PUT", catalogPath(form.type, form.name)
2698
3018
  var deregisterPlugin = (type, name, version) => baoDelete(versionedPath(type, name, version));
2699
3019
 
2700
3020
  // src/openbao/plugin-reconcile.ts
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}`));
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.";
2704
- var reconcilePlugin = (props) => Effect25.gen(function* () {
3021
+ var readEntry = (form) => Effect31.map(readPluginData(form), (live) => live === undefined ? undefined : { attributes: attributesOf11(form, live), live });
3022
+ var refuse5 = (form, message) => Effect31.die(new Error(`Bao.Plugin ${versionedPath(form.type, form.name, form.version)}: ${message}`));
3023
+ 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, and deploy once with --adopt: that adopts the " + "entry this write just made.";
3024
+ var reconcilePlugin = (props, claim) => Effect31.gen(function* () {
2705
3025
  const form = resolve(props);
2706
3026
  const bad = problems7(form);
2707
3027
  if (bad.length > 0)
2708
3028
  return yield* refuse5(form, bad.join("; "));
2709
3029
  const found = yield* readEntry(form);
3030
+ if (found !== undefined && claim !== undefined) {
3031
+ yield* claim(`Bao.Plugin ${versionedPath(form.type, form.name, form.version)}`);
3032
+ }
2710
3033
  if (found !== undefined && !matches10(found.attributes, form)) {
2711
3034
  if (found.live["declarative"] === true || found.live["oci"] === true) {
2712
3035
  return yield* refuse5(form, "the live entry is declarative/OCI; manage it in server config.");
@@ -2728,37 +3051,54 @@ var reconcilePlugin = (props) => Effect25.gen(function* () {
2728
3051
  var BaoPlugin = Resource11("Bao.Plugin", {
2729
3052
  defaultRemovalPolicy: "retain"
2730
3053
  });
2731
- var BaoPluginProvider = () => Provider11.effect(BaoPlugin, Effect26.succeed(BaoPlugin.Provider.of({
2732
- list: () => Effect26.succeed([]),
2733
- read: Effect26.fn(function* ({ olds }) {
2734
- const found = yield* readEntry(resolve(olds));
2735
- return found?.attributes;
3054
+ var IDENTITY8 = {
3055
+ declared: (props) => {
3056
+ const type = declaredString(props, "type");
3057
+ const name = declaredString(props, "name");
3058
+ const version = declaredOr(props, "version", "");
3059
+ if (type === undefined || name === undefined || version === undefined)
3060
+ return;
3061
+ return versionedPath(type, name, version);
3062
+ },
3063
+ family: "Bao.Plugin",
3064
+ recorded: (output) => versionedPath(output.type, output.name, output.version)
3065
+ };
3066
+ var BaoPluginProvider = () => Provider11.effect(BaoPlugin, Effect32.succeed(BaoPlugin.Provider.of({
3067
+ list: () => Effect32.succeed([]),
3068
+ read: Effect32.fn(function* ({ fqn, instanceId, olds, output }) {
3069
+ const form = resolve(olds);
3070
+ const found = yield* readEntry(form);
3071
+ const ours = Effect32.sync(() => found !== undefined && problems7(form).length === 0 && matches10(found.attributes, form));
3072
+ return yield* ownedRead({ fqn, instanceId, output }, found?.attributes, ours);
2736
3073
  }),
2737
- diff: Effect26.fn(function* ({ news, output }) {
2738
- if (output === undefined || !isResolved11(news))
3074
+ diff: Effect32.fn(function* ({ news, olds, output }) {
3075
+ const move = yield* judgeRename(IDENTITY8, olds, news, output);
3076
+ if (output === undefined)
2739
3077
  return;
2740
- const form = resolve(news);
2741
- if (output.name !== form.name || output.type !== form.type || output.version !== form.version) {
3078
+ if (move !== undefined)
2742
3079
  return { action: "replace" };
2743
- }
3080
+ if (!isResolved11(news))
3081
+ return;
3082
+ const form = resolve(news);
2744
3083
  const found = yield* readEntry(form);
2745
3084
  if (found === undefined)
2746
3085
  return { action: "update" };
2747
3086
  return matches10(found.attributes, form) ? { action: "noop" } : { action: "update" };
2748
3087
  }),
2749
- reconcile: Effect26.fn(function* ({ news }) {
2750
- return yield* reconcilePlugin(news);
3088
+ reconcile: Effect32.fn(function* ({ fqn, instanceId, news, output }) {
3089
+ yield* guardRename(IDENTITY8, news, output);
3090
+ return yield* reconcilePlugin(news, claimFor({ fqn, instanceId, output }));
2751
3091
  }),
2752
- delete: Effect26.fn(function* ({ output }) {
3092
+ delete: Effect32.fn(function* ({ output }) {
2753
3093
  yield* deregisterPlugin(output.type, output.name, output.version);
2754
3094
  return;
2755
3095
  })
2756
- })));
3096
+ })).pipe(Effect32.map(provingResumes)));
2757
3097
  // src/openbao/policy.ts
2758
3098
  import { Resource as Resource12 } from "alchemy";
2759
3099
  import { isResolved as isResolved12 } from "alchemy/Diff";
2760
3100
  import * as Provider12 from "alchemy/Provider";
2761
- import * as Effect28 from "effect/Effect";
3101
+ import * as Effect34 from "effect/Effect";
2762
3102
  import * as FileSystem from "effect/FileSystem";
2763
3103
  import * as Path from "effect/Path";
2764
3104
 
@@ -2766,13 +3106,13 @@ import * as Path from "effect/Path";
2766
3106
  var isEmptyAssembly = (parts, joined) => parts === 0 || !/^\s*path\s+"/m.test(joined);
2767
3107
 
2768
3108
  // src/openbao/policy-wire.ts
2769
- import * as Effect27 from "effect/Effect";
3109
+ import * as Effect33 from "effect/Effect";
2770
3110
  var policyPath = (name) => `sys/policies/acl/${name}`;
2771
- var readPolicy = (name) => Effect27.flatMap(baoRead(policyPath(name)), (data) => {
3111
+ var readPolicy = (name) => Effect33.flatMap(baoRead(policyPath(name)), (data) => {
2772
3112
  if (data === undefined)
2773
- return Effect27.succeed("");
3113
+ return Effect33.succeed("");
2774
3114
  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"]));
3115
+ return typeof text11 === "string" ? Effect33.succeed(text11) : Effect33.fail(new BaoError(200, "GET", policyPath(name), ["no data.policy in the response"]));
2776
3116
  });
2777
3117
  var writePolicy = (name, hcl) => baoWrite("PUT", policyPath(name), { policy: hcl });
2778
3118
  var deletePolicy = (name) => baoDelete(policyPath(name));
@@ -2781,6 +3121,7 @@ var deletePolicy = (name) => baoDelete(policyPath(name));
2781
3121
  var BaoPolicy = Resource12("Bao.Policy", {
2782
3122
  defaultRemovalPolicy: "retain"
2783
3123
  });
3124
+ var IDENTITY9 = nameIdentity("Bao.Policy", policyPath, policyKey);
2784
3125
  var grantsOf = (hcl) => (hcl.match(/^path /gm) ?? []).length;
2785
3126
  var attributesOf12 = (name, hcl, parts) => ({
2786
3127
  name,
@@ -2788,10 +3129,10 @@ var attributesOf12 = (name, hcl, parts) => ({
2788
3129
  grants: grantsOf(hcl),
2789
3130
  parts
2790
3131
  });
2791
- var BaoPolicyProvider = () => Provider12.effect(BaoPolicy, Effect28.gen(function* () {
3132
+ var BaoPolicyProvider = () => Provider12.effect(BaoPolicy, Effect34.gen(function* () {
2792
3133
  const fs = yield* FileSystem.FileSystem;
2793
3134
  const path = yield* Path.Path;
2794
- const assemble = (dir) => Effect28.gen(function* () {
3135
+ const assemble = (dir) => Effect34.gen(function* () {
2795
3136
  const entries2 = (yield* fs.readDirectory(dir)).filter((f) => f.endsWith(".hcl")).sort();
2796
3137
  let joined = "";
2797
3138
  for (const f of entries2)
@@ -2800,15 +3141,15 @@ var BaoPolicyProvider = () => Provider12.effect(BaoPolicy, Effect28.gen(function
2800
3141
  return { joined, parts: entries2.length };
2801
3142
  });
2802
3143
  return BaoPolicy.Provider.of({
2803
- list: () => Effect28.succeed([]),
2804
- read: Effect28.fn(function* ({ olds }) {
3144
+ list: () => Effect34.succeed([]),
3145
+ read: Effect34.fn(function* ({ fqn, instanceId, olds, output }) {
2805
3146
  const live = yield* readPolicy(olds.name);
2806
- return live.length === 0 ? undefined : attributesOf12(olds.name, live, 0);
3147
+ const found = live.length === 0 ? undefined : attributesOf12(olds.name, live, 0);
3148
+ const ours = Effect34.map(assemble(olds.fragments), (a) => sha256(a.joined) === found?.digest);
3149
+ return yield* ownedRead({ fqn, instanceId, output }, found, ours);
2807
3150
  }),
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);
3151
+ diff: Effect34.fn(function* ({ news, olds, output }) {
3152
+ const move = yield* judgeRename(IDENTITY9, olds, news, output);
2812
3153
  if (output === undefined)
2813
3154
  return;
2814
3155
  if (move !== undefined)
@@ -2819,32 +3160,32 @@ var BaoPolicyProvider = () => Provider12.effect(BaoPolicy, Effect28.gen(function
2819
3160
  const live = yield* readPolicy(news.name);
2820
3161
  return live.length > 0 && sha256(live) === sha256(joined) ? { action: "noop" } : { action: "update" };
2821
3162
  }),
2822
- reconcile: Effect28.fn(function* ({ news, output }) {
3163
+ reconcile: Effect34.fn(function* ({ fqn, instanceId, news, output }) {
2823
3164
  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
- }
3165
+ yield* guardRename(IDENTITY9, news, output);
2827
3166
  const { joined, parts } = yield* assemble(news.fragments);
2828
3167
  if (isEmptyAssembly(parts, joined))
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.`));
3168
+ return yield* Effect34.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.`));
2830
3169
  const current = yield* readPolicy(name);
3170
+ if (current.length > 0)
3171
+ yield* refuseTakeover({ fqn, instanceId, output }, `Bao.Policy ${name}`);
2831
3172
  if (current.length > 0 && sha256(current) === sha256(joined)) {
2832
3173
  return attributesOf12(name, joined, parts);
2833
3174
  }
2834
3175
  yield* writePolicy(name, joined);
2835
3176
  return attributesOf12(name, joined, parts);
2836
3177
  }),
2837
- delete: Effect28.fn(function* ({ output }) {
3178
+ delete: Effect34.fn(function* ({ output }) {
2838
3179
  yield* deletePolicy(output.name);
2839
3180
  return;
2840
3181
  })
2841
3182
  });
2842
- }));
3183
+ }).pipe(Effect34.map(provingResumes)));
2843
3184
  // src/openbao/proxmox-role.ts
2844
3185
  import { Resource as Resource13 } from "alchemy";
2845
3186
  import { isResolved as isResolved13 } from "alchemy/Diff";
2846
3187
  import * as Provider13 from "alchemy/Provider";
2847
- import * as Effect30 from "effect/Effect";
3188
+ import * as Effect36 from "effect/Effect";
2848
3189
 
2849
3190
  // src/openbao/proxmox-role-form.ts
2850
3191
  var text11 = (value) => typeof value === "string" ? value : "";
@@ -2894,8 +3235,8 @@ var rescopeRefusal = (before, mintUser, allowMintUserChange) => before === mintU
2894
3235
  var matches11 = (attributes, props) => attributes.mintUser === props.mintUser && sameTtl5(props.ttl, attributes.ttl) && sameTtl5(props.maxTtl, attributes.maxTtl);
2895
3236
 
2896
3237
  // src/openbao/proxmox-role-wire.ts
2897
- import * as Effect29 from "effect/Effect";
2898
- var readRole3 = (props) => Effect29.gen(function* () {
3238
+ import * as Effect35 from "effect/Effect";
3239
+ var readRole3 = (props) => Effect35.gen(function* () {
2899
3240
  const live = yield* baoRead(rolePath5(props.mount, props.name));
2900
3241
  if (live === undefined)
2901
3242
  return;
@@ -2906,15 +3247,16 @@ var readRole3 = (props) => Effect29.gen(function* () {
2906
3247
  var BaoProxmoxRole = Resource13("Bao.ProxmoxRole", {
2907
3248
  defaultRemovalPolicy: "retain"
2908
3249
  });
2909
- var BaoProxmoxRoleProvider = () => Provider13.effect(BaoProxmoxRole, Effect30.succeed(BaoProxmoxRole.Provider.of({
2910
- list: () => Effect30.succeed([]),
2911
- read: Effect30.fn(function* ({ olds }) {
2912
- return yield* readRole3(olds);
3250
+ var IDENTITY10 = roleIdentity("Bao.ProxmoxRole", rolePath5);
3251
+ var BaoProxmoxRoleProvider = () => Provider13.effect(BaoProxmoxRole, Effect36.succeed(BaoProxmoxRole.Provider.of({
3252
+ list: () => Effect36.succeed([]),
3253
+ read: Effect36.fn(function* ({ fqn, instanceId, olds, output }) {
3254
+ const found = yield* readRole3(olds);
3255
+ const ours = Effect36.sync(() => found !== undefined && matches11(found, olds));
3256
+ return yield* ownedRead({ fqn, instanceId, output }, found, ours);
2913
3257
  }),
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);
3258
+ diff: Effect36.fn(function* ({ news, olds, output }) {
3259
+ const move = yield* judgeRename(IDENTITY10, olds, news, output);
2918
3260
  if (output === undefined)
2919
3261
  return;
2920
3262
  if (move !== undefined) {
@@ -2926,7 +3268,7 @@ var BaoProxmoxRoleProvider = () => Provider13.effect(BaoProxmoxRole, Effect30.su
2926
3268
  const refusal2 = rescopeRefusal(from, mintUser, allow);
2927
3269
  if (refusal2 === undefined)
2928
3270
  return { action: "replace" };
2929
- return yield* Effect30.die(new Error(`Bao.ProxmoxRole ${move.from} → ${move.to}: the rename ${refusal2}`));
3271
+ return yield* Effect36.die(new Error(`Bao.ProxmoxRole ${move.from} → ${move.to}: the rename ${refusal2}`));
2930
3272
  }
2931
3273
  if (!isResolved13(news))
2932
3274
  return;
@@ -2937,40 +3279,39 @@ var BaoProxmoxRoleProvider = () => Provider13.effect(BaoProxmoxRole, Effect30.su
2937
3279
  return { action: "noop" };
2938
3280
  return { action: "update" };
2939
3281
  }),
2940
- reconcile: Effect30.fn(function* ({ news, output }) {
3282
+ reconcile: Effect36.fn(function* ({ fqn, instanceId, news, output }) {
2941
3283
  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
- }
3284
+ yield* guardRename(IDENTITY10, news, output);
2946
3285
  const live = yield* readRole3(news);
3286
+ if (live !== undefined)
3287
+ yield* refuseTakeover({ fqn, instanceId, output }, `Bao.ProxmoxRole ${path}`);
2947
3288
  const rescope = live === undefined ? undefined : rescopeRefusal(live.mintUser, news.mintUser, news.allowMintUserChange);
2948
3289
  if (rescope !== undefined) {
2949
- return yield* Effect30.die(new Error(`Bao.ProxmoxRole ${path}: ${rescope}`));
3290
+ return yield* Effect36.die(new Error(`Bao.ProxmoxRole ${path}: ${rescope}`));
2950
3291
  }
2951
3292
  if (live === undefined || !matches11(live, news)) {
2952
3293
  const write = writeBody9(news);
2953
3294
  if (!write.ok) {
2954
- return yield* Effect30.die(new Error(`Bao.ProxmoxRole ${path}: unparseable duration ` + `${write.bad.join(", ")}. Use 30s / 5m / 1h / 1d, or 0.`));
3295
+ return yield* Effect36.die(new Error(`Bao.ProxmoxRole ${path}: unparseable duration ` + `${write.bad.join(", ")}. Use 30s / 5m / 1h / 1d, or 0.`));
2955
3296
  }
2956
3297
  yield* baoWrite("PUT", path, write.body);
2957
3298
  }
2958
3299
  const after = yield* readRole3(news);
2959
3300
  if (after === undefined) {
2960
- return yield* Effect30.die(new Error(`Bao.ProxmoxRole ${path}: write returned no error ` + "but the role is still absent."));
3301
+ return yield* Effect36.die(new Error(`Bao.ProxmoxRole ${path}: write returned no error ` + "but the role is still absent."));
2961
3302
  }
2962
3303
  return after;
2963
3304
  }),
2964
- delete: Effect30.fn(function* ({ output }) {
3305
+ delete: Effect36.fn(function* ({ output }) {
2965
3306
  yield* baoDelete(rolePath5(output.mount, output.name));
2966
3307
  return;
2967
3308
  })
2968
- })));
3309
+ })).pipe(Effect36.map(provingResumes)));
2969
3310
  // src/openbao/ssh-role.ts
2970
3311
  import { Resource as Resource14 } from "alchemy";
2971
3312
  import { isResolved as isResolved14 } from "alchemy/Diff";
2972
3313
  import * as Provider14 from "alchemy/Provider";
2973
- import * as Effect31 from "effect/Effect";
3314
+ import * as Effect37 from "effect/Effect";
2974
3315
 
2975
3316
  // src/openbao/ssh-role-form.ts
2976
3317
  var readPath4 = (mount, name) => `${sshMountOf(mount)}/roles/${name}`;
@@ -3031,7 +3372,7 @@ var attributesOf14 = (form, live) => {
3031
3372
  };
3032
3373
  return { ...attrs, digest: sha256(JSON.stringify(attrs)) };
3033
3374
  };
3034
- var mapValue = (map10) => extMap(map10);
3375
+ var mapValue = (map23) => extMap(map23);
3035
3376
  var writeBody10 = (form) => ({
3036
3377
  key_type: "ca",
3037
3378
  allowed_users: form.allowedUsers.join(","),
@@ -3058,16 +3399,7 @@ var sameTtl6 = (want, have) => parseDuration(want) === have;
3058
3399
  var badDurations = (form) => ["ttl", "maxTtl"].filter((field) => parseDuration(form[field]) === undefined);
3059
3400
  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);
3060
3401
 
3061
- // src/openbao/ssh-role.ts
3062
- var BaoSshRole = Resource14("Bao.SshRole", {
3063
- defaultRemovalPolicy: "retain"
3064
- });
3065
- var readRole4 = (form) => Effect31.gen(function* () {
3066
- const live = yield* baoRead(readPath4(form.mount, form.name));
3067
- if (live === undefined)
3068
- return;
3069
- return { attributes: attributesOf14(form, live), live };
3070
- });
3402
+ // src/openbao/ssh-role-erase.ts
3071
3403
  var UNMANAGED_DEFAULTS = {
3072
3404
  algorithm_signer: "default",
3073
3405
  allow_commas_in_identity_templates: false,
@@ -3088,28 +3420,44 @@ var wouldErase2 = (live) => {
3088
3420
  }
3089
3421
  return lost.sort();
3090
3422
  };
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 }) {
3423
+
3424
+ // src/openbao/ssh-role.ts
3425
+ var BaoSshRole = Resource14("Bao.SshRole", {
3426
+ defaultRemovalPolicy: "retain"
3427
+ });
3428
+ var IDENTITY11 = roleIdentity("Bao.SshRole", readPath4, "ssh");
3429
+ var readRole4 = (form) => Effect37.gen(function* () {
3430
+ const live = yield* baoRead(readPath4(form.mount, form.name));
3431
+ if (live === undefined)
3432
+ return;
3433
+ return { attributes: attributesOf14(form, live), live };
3434
+ });
3435
+ var refuse6 = (message) => Effect37.die(new Error(message));
3436
+ var BaoSshRoleProvider = () => Provider14.effect(BaoSshRole, Effect37.succeed(BaoSshRole.Provider.of({
3437
+ list: () => Effect37.succeed([]),
3438
+ read: Effect37.fn(function* ({ fqn, instanceId, olds, output }) {
3095
3439
  const found = yield* readRole4(resolve2(olds));
3096
- return found?.attributes;
3440
+ const ours = Effect37.sync(() => found !== undefined && matches12(found.attributes, resolve2(olds)));
3441
+ return yield* ownedRead({ fqn, instanceId, output }, found?.attributes, ours);
3097
3442
  }),
3098
- diff: Effect31.fn(function* ({ news, output }) {
3099
- if (output === undefined || !isResolved14(news))
3443
+ diff: Effect37.fn(function* ({ news, olds, output }) {
3444
+ const move = yield* judgeRename(IDENTITY11, olds, news, output);
3445
+ if (output === undefined)
3100
3446
  return;
3101
- const form = resolve2(news);
3102
- if (output.mount !== form.mount || output.name !== form.name) {
3447
+ if (move !== undefined)
3103
3448
  return { action: "replace" };
3104
- }
3449
+ if (!isResolved14(news))
3450
+ return;
3451
+ const form = resolve2(news);
3105
3452
  const found = yield* readRole4(form);
3106
3453
  if (found === undefined)
3107
3454
  return { action: "update" };
3108
3455
  return matches12(found.attributes, form) ? { action: "noop" } : { action: "update" };
3109
3456
  }),
3110
- reconcile: Effect31.fn(function* ({ news }) {
3457
+ reconcile: Effect37.fn(function* ({ fqn, instanceId, news, output }) {
3111
3458
  const form = resolve2(news);
3112
3459
  const path = readPath4(form.mount, form.name);
3460
+ yield* guardRename(IDENTITY11, news, output);
3113
3461
  const bad = badDurations(form);
3114
3462
  if (bad.length > 0) {
3115
3463
  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.");
@@ -3118,6 +3466,8 @@ var BaoSshRoleProvider = () => Provider14.effect(BaoSshRole, Effect31.succeed(Ba
3118
3466
  return yield* refuse6(`Bao.SshRole ${path}: allowUserCertificates with an empty allowedUsers and ` + "allowEmptyPrincipals false. Every sign request against it would be rejected.");
3119
3467
  }
3120
3468
  const found = yield* readRole4(form);
3469
+ if (found !== undefined)
3470
+ yield* refuseTakeover({ fqn, instanceId, output }, `Bao.SshRole ${path}`);
3121
3471
  if (found === undefined || !matches12(found.attributes, form)) {
3122
3472
  if (found !== undefined) {
3123
3473
  const lost = wouldErase2(found.live);
@@ -3133,11 +3483,11 @@ var BaoSshRoleProvider = () => Provider14.effect(BaoSshRole, Effect31.succeed(Ba
3133
3483
  }
3134
3484
  return after.attributes;
3135
3485
  }),
3136
- delete: Effect31.fn(function* ({ output }) {
3486
+ delete: Effect37.fn(function* ({ output }) {
3137
3487
  yield* baoDelete(readPath4(output.mount, output.name));
3138
3488
  return;
3139
3489
  })
3140
- })));
3490
+ })).pipe(Effect37.map(provingResumes)));
3141
3491
  export {
3142
3492
  BaoAuthMethod,
3143
3493
  BaoAuthMethodProvider,
@@ -3187,5 +3537,5 @@ export {
3187
3537
  revokeSelfEffect
3188
3538
  };
3189
3539
 
3190
- //# debugId=81D869587FC0B7AF64756E2164756E21
3540
+ //# debugId=9AA407497A9014D964756E2164756E21
3191
3541
  //# sourceMappingURL=index.js.map