@homeflare/alchemy 0.8.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (247) hide show
  1. package/README.md +22 -11
  2. package/dist/caddy/config-lifecycle.d.ts +1 -1
  3. package/dist/caddy/config.d.ts.map +1 -1
  4. package/dist/caddy/index.js +221 -53
  5. package/dist/caddy/index.js.map +13 -9
  6. package/dist/launchd/fake-runner.d.ts +1 -0
  7. package/dist/launchd/fake-runner.d.ts.map +1 -1
  8. package/dist/launchd/fake-sudo.d.ts +1 -0
  9. package/dist/launchd/fake-sudo.d.ts.map +1 -1
  10. package/dist/launchd/host-effect.d.ts +9 -18
  11. package/dist/launchd/host-effect.d.ts.map +1 -1
  12. package/dist/launchd/host-file-lifecycle.d.ts +14 -1
  13. package/dist/launchd/host-file-lifecycle.d.ts.map +1 -1
  14. package/dist/launchd/host-file.d.ts +2 -1
  15. package/dist/launchd/host-file.d.ts.map +1 -1
  16. package/dist/launchd/index.js +345 -86
  17. package/dist/launchd/index.js.map +18 -12
  18. package/dist/launchd/job-lifecycle.d.ts +8 -1
  19. package/dist/launchd/job-lifecycle.d.ts.map +1 -1
  20. package/dist/launchd/job-preflight.d.ts +13 -1
  21. package/dist/launchd/job-preflight.d.ts.map +1 -1
  22. package/dist/launchd/job.d.ts +2 -1
  23. package/dist/launchd/job.d.ts.map +1 -1
  24. package/dist/launchd/runner.d.ts +7 -0
  25. package/dist/launchd/runner.d.ts.map +1 -1
  26. package/dist/launchd/sudo-acl.d.ts +25 -0
  27. package/dist/launchd/sudo-acl.d.ts.map +1 -0
  28. package/dist/launchd/sudo-guard.d.ts +31 -4
  29. package/dist/launchd/sudo-guard.d.ts.map +1 -1
  30. package/dist/launchd/sudo-runner.d.ts +3 -3
  31. package/dist/launchd/sudo-runner.d.ts.map +1 -1
  32. package/dist/launchd/sudo-said.d.ts +10 -0
  33. package/dist/launchd/sudo-said.d.ts.map +1 -0
  34. package/dist/openbao/auth-method-reconcile.d.ts +6 -2
  35. package/dist/openbao/auth-method-reconcile.d.ts.map +1 -1
  36. package/dist/openbao/auth-method.d.ts.map +1 -1
  37. package/dist/openbao/auth-role.d.ts.map +1 -1
  38. package/dist/openbao/cloudflare-role.d.ts.map +1 -1
  39. package/dist/openbao/fake-bao.d.ts +18 -0
  40. package/dist/openbao/fake-bao.d.ts.map +1 -1
  41. package/dist/openbao/fake-core.d.ts +39 -0
  42. package/dist/openbao/fake-core.d.ts.map +1 -0
  43. package/dist/openbao/fake-families.d.ts +6 -3
  44. package/dist/openbao/fake-families.d.ts.map +1 -1
  45. package/dist/openbao/fake-mounts.d.ts +16 -0
  46. package/dist/openbao/fake-mounts.d.ts.map +1 -0
  47. package/dist/openbao/fake-stack.d.ts +5 -1
  48. package/dist/openbao/fake-stack.d.ts.map +1 -1
  49. package/dist/openbao/index.js +541 -270
  50. package/dist/openbao/index.js.map +26 -21
  51. package/dist/openbao/jwt-config.d.ts.map +1 -1
  52. package/dist/openbao/jwt-role.d.ts.map +1 -1
  53. package/dist/openbao/kubernetes-role.d.ts.map +1 -1
  54. package/dist/openbao/mfa-enforcement.d.ts +6 -2
  55. package/dist/openbao/mfa-enforcement.d.ts.map +1 -1
  56. package/dist/openbao/mfa-totp.d.ts +6 -2
  57. package/dist/openbao/mfa-totp.d.ts.map +1 -1
  58. package/dist/openbao/mount-reconcile.d.ts +5 -1
  59. package/dist/openbao/mount-reconcile.d.ts.map +1 -1
  60. package/dist/openbao/mount.d.ts.map +1 -1
  61. package/dist/openbao/pki-role.d.ts.map +1 -1
  62. package/dist/openbao/plugin-reconcile.d.ts +6 -2
  63. package/dist/openbao/plugin-reconcile.d.ts.map +1 -1
  64. package/dist/openbao/plugin.d.ts.map +1 -1
  65. package/dist/openbao/policy.d.ts.map +1 -1
  66. package/dist/openbao/proxmox-role.d.ts.map +1 -1
  67. package/dist/openbao/role-reconcile.d.ts +11 -1
  68. package/dist/openbao/role-reconcile.d.ts.map +1 -1
  69. package/dist/openbao/ssh-role.d.ts.map +1 -1
  70. package/dist/ownership/adopt.d.ts +52 -0
  71. package/dist/ownership/adopt.d.ts.map +1 -0
  72. package/dist/ownership/probe.d.ts +13 -0
  73. package/dist/ownership/probe.d.ts.map +1 -0
  74. package/dist/ownership/resume.d.ts +40 -0
  75. package/dist/ownership/resume.d.ts.map +1 -0
  76. package/dist/ownership/rows.d.ts +33 -0
  77. package/dist/ownership/rows.d.ts.map +1 -0
  78. package/dist/ownership/whole.d.ts +14 -0
  79. package/dist/ownership/whole.d.ts.map +1 -0
  80. package/dist/proxmox/ceph-pool-form.d.ts +3 -3
  81. package/dist/proxmox/ceph-pool.d.ts.map +1 -1
  82. package/dist/proxmox/fake-bao-env.d.ts +3 -0
  83. package/dist/proxmox/fake-bao-env.d.ts.map +1 -0
  84. package/dist/proxmox/fake-pve-lxc.d.ts +42 -0
  85. package/dist/proxmox/fake-pve-lxc.d.ts.map +1 -0
  86. package/dist/proxmox/fake-pve.d.ts +38 -0
  87. package/dist/proxmox/fake-pve.d.ts.map +1 -0
  88. package/dist/proxmox/index.d.ts +6 -2
  89. package/dist/proxmox/index.d.ts.map +1 -1
  90. package/dist/proxmox/index.js +970 -186
  91. package/dist/proxmox/index.js.map +22 -9
  92. package/dist/proxmox/lxc-create-form.d.ts +6 -0
  93. package/dist/proxmox/lxc-create-form.d.ts.map +1 -0
  94. package/dist/proxmox/lxc-harness.d.ts +34 -0
  95. package/dist/proxmox/lxc-harness.d.ts.map +1 -0
  96. package/dist/proxmox/lxc-identity.d.ts +18 -0
  97. package/dist/proxmox/lxc-identity.d.ts.map +1 -0
  98. package/dist/proxmox/lxc-judge.d.ts +46 -0
  99. package/dist/proxmox/lxc-judge.d.ts.map +1 -0
  100. package/dist/proxmox/lxc-lifecycle.d.ts +68 -0
  101. package/dist/proxmox/lxc-lifecycle.d.ts.map +1 -0
  102. package/dist/proxmox/lxc-props.d.ts +139 -0
  103. package/dist/proxmox/lxc-props.d.ts.map +1 -0
  104. package/dist/proxmox/lxc-volume.d.ts +29 -0
  105. package/dist/proxmox/lxc-volume.d.ts.map +1 -0
  106. package/dist/proxmox/lxc-wire.d.ts +51 -0
  107. package/dist/proxmox/lxc-wire.d.ts.map +1 -0
  108. package/dist/proxmox/lxc.d.ts +27 -47
  109. package/dist/proxmox/lxc.d.ts.map +1 -1
  110. package/dist/proxmox/network-apply-read.d.ts +1 -1
  111. package/dist/proxmox/network-apply-read.d.ts.map +1 -1
  112. package/dist/proxmox/pbs-datastore.d.ts.map +1 -1
  113. package/dist/proxmox/resource.d.ts.map +1 -1
  114. package/dist/proxmox/update-guard.d.ts +27 -0
  115. package/dist/proxmox/update-guard.d.ts.map +1 -0
  116. package/dist/verify/args.d.ts +16 -0
  117. package/dist/verify/args.d.ts.map +1 -0
  118. package/dist/verify/cli.d.ts +3 -0
  119. package/dist/verify/cli.d.ts.map +1 -0
  120. package/dist/verify/cli.js +370 -0
  121. package/dist/verify/cli.js.map +17 -0
  122. package/dist/verify/fake-engine.d.ts +65 -0
  123. package/dist/verify/fake-engine.d.ts.map +1 -0
  124. package/dist/verify/index.d.ts +16 -0
  125. package/dist/verify/index.d.ts.map +1 -0
  126. package/dist/verify/index.js +347 -0
  127. package/dist/verify/index.js.map +16 -0
  128. package/dist/verify/plan-scope.d.ts +14 -0
  129. package/dist/verify/plan-scope.d.ts.map +1 -0
  130. package/dist/verify/recheck.d.ts +42 -0
  131. package/dist/verify/recheck.d.ts.map +1 -0
  132. package/dist/verify/report.d.ts +6 -0
  133. package/dist/verify/report.d.ts.map +1 -0
  134. package/dist/verify/rows.d.ts +67 -0
  135. package/dist/verify/rows.d.ts.map +1 -0
  136. package/dist/verify/spy.d.ts +43 -0
  137. package/dist/verify/spy.d.ts.map +1 -0
  138. package/dist/verify/verify.d.ts +43 -0
  139. package/dist/verify/verify.d.ts.map +1 -0
  140. package/docs/adopt-verify.md +119 -0
  141. package/docs/adopted-deploys.md +85 -0
  142. package/docs/credentials.md +15 -0
  143. package/docs/launchd-sudo.md +31 -6
  144. package/docs/launchd.md +7 -3
  145. package/docs/ownership.md +113 -0
  146. package/docs/proxmox-lxc.md +196 -0
  147. package/docs/proxmox.md +12 -0
  148. package/package.json +9 -2
  149. package/src/caddy/adopt-scope.test.ts +1 -1
  150. package/src/caddy/config-lifecycle.ts +1 -1
  151. package/src/caddy/config.ts +2 -1
  152. package/src/launchd/adopt-apply.test.ts +166 -0
  153. package/src/launchd/fake-runner.ts +26 -4
  154. package/src/launchd/host-effect.ts +1 -33
  155. package/src/launchd/host-file-lifecycle.ts +39 -14
  156. package/src/launchd/host-file.ts +13 -4
  157. package/src/launchd/job-lifecycle.ts +22 -14
  158. package/src/launchd/job-preflight.ts +18 -1
  159. package/src/launchd/job.ts +13 -4
  160. package/src/launchd/runner.ts +7 -0
  161. package/src/launchd/sudo-acl.ts +68 -0
  162. package/src/launchd/sudo-chain.test.ts +121 -0
  163. package/src/launchd/sudo-guard.ts +89 -10
  164. package/src/launchd/sudo-modes.test.ts +135 -0
  165. package/src/launchd/sudo-runner.ts +40 -59
  166. package/src/launchd/sudo-said.ts +58 -0
  167. package/src/openbao/README.md +5 -2
  168. package/src/openbao/REPLACE.md +13 -11
  169. package/src/openbao/adopt-core.test.ts +119 -0
  170. package/src/openbao/adopt-families.test.ts +97 -0
  171. package/src/openbao/adopt-holes.test.ts +168 -0
  172. package/src/openbao/adopt-recovery.test.ts +71 -0
  173. package/src/openbao/auth-method-reconcile.ts +15 -3
  174. package/src/openbao/auth-method.ts +15 -6
  175. package/src/openbao/auth-role.ts +14 -4
  176. package/src/openbao/cloudflare-role.ts +17 -4
  177. package/src/openbao/fake-bao.ts +52 -0
  178. package/src/openbao/fake-core.ts +141 -0
  179. package/src/openbao/fake-families.ts +11 -5
  180. package/src/openbao/fake-mounts.ts +74 -0
  181. package/src/openbao/fake-stack.ts +17 -4
  182. package/src/openbao/jwt-config.ts +10 -7
  183. package/src/openbao/jwt-role.ts +10 -7
  184. package/src/openbao/kubernetes-role.ts +13 -7
  185. package/src/openbao/mfa-enforcement.ts +20 -6
  186. package/src/openbao/mfa-totp.ts +22 -7
  187. package/src/openbao/mount-move.test.ts +2 -35
  188. package/src/openbao/mount-reconcile.ts +13 -2
  189. package/src/openbao/mount.ts +15 -6
  190. package/src/openbao/pki-role.ts +15 -4
  191. package/src/openbao/plugin-reconcile.ts +16 -5
  192. package/src/openbao/plugin.ts +17 -6
  193. package/src/openbao/policy.ts +17 -4
  194. package/src/openbao/proxmox-role.ts +13 -4
  195. package/src/openbao/rename-adoption.test.ts +32 -19
  196. package/src/openbao/role-reconcile.ts +23 -0
  197. package/src/openbao/ssh-role.ts +14 -4
  198. package/src/ownership/adopt.ts +122 -0
  199. package/src/ownership/ownership.test.ts +200 -0
  200. package/src/ownership/probe.ts +65 -0
  201. package/src/ownership/resume.ts +118 -0
  202. package/src/ownership/rows.ts +117 -0
  203. package/src/ownership/whole.test.ts +39 -0
  204. package/src/ownership/whole.ts +60 -0
  205. package/src/proxmox/adopt-noop.test.ts +112 -0
  206. package/src/proxmox/ceph-pool-adopt.test.ts +120 -0
  207. package/src/proxmox/ceph-pool-form.ts +3 -3
  208. package/src/proxmox/ceph-pool.ts +10 -3
  209. package/src/proxmox/fake-bao-env.ts +28 -0
  210. package/src/proxmox/fake-pve-lxc.ts +210 -0
  211. package/src/proxmox/fake-pve.ts +75 -0
  212. package/src/proxmox/index.test.ts +6 -6
  213. package/src/proxmox/index.ts +6 -2
  214. package/src/proxmox/lxc-adopt.test.ts +170 -0
  215. package/src/proxmox/lxc-create-form.ts +76 -0
  216. package/src/proxmox/lxc-create.test.ts +126 -0
  217. package/src/proxmox/lxc-guards.test.ts +158 -0
  218. package/src/proxmox/lxc-harness.ts +150 -0
  219. package/src/proxmox/lxc-identity.ts +43 -0
  220. package/src/proxmox/lxc-judge.test.ts +175 -0
  221. package/src/proxmox/lxc-judge.ts +196 -0
  222. package/src/proxmox/lxc-lifecycle.ts +200 -0
  223. package/src/proxmox/lxc-ownership.test.ts +159 -0
  224. package/src/proxmox/lxc-props.ts +215 -0
  225. package/src/proxmox/lxc-read.test.ts +60 -0
  226. package/src/proxmox/lxc-volume.test.ts +96 -0
  227. package/src/proxmox/lxc-volume.ts +153 -0
  228. package/src/proxmox/lxc-wire.ts +210 -0
  229. package/src/proxmox/lxc.ts +216 -93
  230. package/src/proxmox/network-apply-read.ts +3 -1
  231. package/src/proxmox/pbs-datastore.ts +4 -3
  232. package/src/proxmox/resource.ts +7 -7
  233. package/src/proxmox/update-guard.ts +31 -0
  234. package/src/verify/args.ts +82 -0
  235. package/src/verify/cli.test.ts +86 -0
  236. package/src/verify/cli.ts +49 -0
  237. package/src/verify/fake-engine.ts +182 -0
  238. package/src/verify/index.ts +37 -0
  239. package/src/verify/plan-scope.ts +34 -0
  240. package/src/verify/recheck.test.ts +56 -0
  241. package/src/verify/recheck.ts +71 -0
  242. package/src/verify/report.ts +52 -0
  243. package/src/verify/rows.ts +210 -0
  244. package/src/verify/spy.test.ts +141 -0
  245. package/src/verify/spy.ts +160 -0
  246. package/src/verify/verify.test.ts +146 -0
  247. package/src/verify/verify.ts +185 -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 Effect10 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) => {
@@ -677,10 +886,10 @@ var matches3 = (attributes, props) => {
677
886
  };
678
887
 
679
888
  // src/openbao/rename-identity.ts
680
- import * as Effect9 from "effect/Effect";
889
+ import * as Effect14 from "effect/Effect";
681
890
 
682
891
  // src/openbao/rename.ts
683
- import * as Effect8 from "effect/Effect";
892
+ import * as Effect13 from "effect/Effect";
684
893
  var exact = (identity) => identity;
685
894
  var declaredString = (news, key) => {
686
895
  if (typeof news !== "object" || news === null)
@@ -702,15 +911,15 @@ var declaredRolePath = (news, rolePath, defaultMount) => {
702
911
  };
703
912
  var policyKey = (name) => name.trim().toLowerCase();
704
913
  var isMoved = (stored, declared, key = exact) => declared === undefined ? undefined : key(stored) !== key(declared);
705
- var judgeMove = (family, tried, declared, pathOf, key = exact) => Effect8.gen(function* () {
914
+ var judgeMove = (family, tried, declared, pathOf, key = exact) => Effect13.gen(function* () {
706
915
  if (tried === undefined || declared === undefined || key(tried) === key(declared)) {
707
916
  return;
708
917
  }
709
918
  if ((yield* baoRead(pathOf(declared))) === undefined)
710
919
  return { from: tried, to: declared };
711
- return yield* Effect8.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.`));
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.`));
712
921
  });
713
- var refuseMovedUpdate = (family, from, to) => Effect8.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."));
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."));
714
923
 
715
924
  // src/openbao/rename-identity.ts
716
925
  var nameIdentity = (family, pathOf, key) => ({
@@ -730,10 +939,10 @@ var foldName = (path) => path.replace(/[^/]+$/, (name) => name.toLowerCase());
730
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);
731
940
  var guardRename = (identity, news, output) => {
732
941
  if (output === undefined)
733
- return Effect9.void;
942
+ return Effect14.void;
734
943
  const from = identity.recorded(output);
735
944
  const to = identity.declared(news);
736
- return to !== undefined && isMoved(from, to, identity.key) === true ? refuseMovedUpdate(identity.family, from, to) : Effect9.void;
945
+ return to !== undefined && isMoved(from, to, identity.key) === true ? refuseMovedUpdate(identity.family, from, to) : Effect14.void;
737
946
  };
738
947
 
739
948
  // src/openbao/auth-role.ts
@@ -741,18 +950,20 @@ var BaoAuthRole = Resource2("Bao.AuthRole", {
741
950
  defaultRemovalPolicy: "retain"
742
951
  });
743
952
  var IDENTITY = nameIdentity("Bao.AuthRole", readPath3, foldName);
744
- var readRole = (props) => Effect10.gen(function* () {
953
+ var readRole = (props) => Effect15.gen(function* () {
745
954
  const live = yield* baoRead(readPath3(props.name));
746
955
  if (live === undefined)
747
956
  return;
748
957
  return attributesOf3(props, live);
749
958
  });
750
- var BaoAuthRoleProvider = () => Provider2.effect(BaoAuthRole, Effect10.succeed(BaoAuthRole.Provider.of({
751
- list: () => Effect10.succeed([]),
752
- read: Effect10.fn(function* ({ olds }) {
753
- 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);
754
965
  }),
755
- diff: Effect10.fn(function* ({ news, olds, output }) {
966
+ diff: Effect15.fn(function* ({ news, olds, output }) {
756
967
  const move = yield* judgeRename(IDENTITY, olds, news, output);
757
968
  if (output === undefined)
758
969
  return;
@@ -767,25 +978,28 @@ var BaoAuthRoleProvider = () => Provider2.effect(BaoAuthRole, Effect10.succeed(B
767
978
  return { action: "noop" };
768
979
  return { action: "update" };
769
980
  }),
770
- reconcile: Effect10.fn(function* ({ news, output }) {
981
+ reconcile: Effect15.fn(function* ({ fqn, instanceId, news, output }) {
771
982
  yield* guardRename(IDENTITY, news, output);
772
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}`);
773
987
  if (live === undefined || !matches3(live, news)) {
774
988
  yield* baoWrite("PUT", readPath3(news.name), writeBody(news));
775
989
  }
776
990
  const after = yield* readRole(news);
777
991
  if (after === undefined) {
778
- return yield* Effect10.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.`));
779
993
  }
780
994
  return after;
781
995
  }),
782
- delete: Effect10.fn(function* ({ output }) {
996
+ delete: Effect15.fn(function* ({ output }) {
783
997
  yield* baoDelete(readPath3(output.name));
784
998
  return;
785
999
  })
786
- })));
1000
+ })).pipe(Effect15.map(provingResumes)));
787
1001
  // src/openbao/bao-identity.ts
788
- import * as Effect11 from "effect/Effect";
1002
+ import * as Effect16 from "effect/Effect";
789
1003
  import * as FetchHttpClient3 from "effect/unstable/http/FetchHttpClient";
790
1004
 
791
1005
  // src/openbao/bao-namespace.ts
@@ -833,42 +1047,42 @@ var checkHealth = (address, expectedCluster, body) => {
833
1047
  }
834
1048
  return { clusterId: text3(body["cluster_id"]), version: text3(body["version"]) };
835
1049
  };
836
- var assertBaoIdentityEffect = (expected) => Effect11.gen(function* () {
1050
+ var assertBaoIdentityEffect = (expected) => Effect16.gen(function* () {
837
1051
  const env = yield* BaoEnv;
838
1052
  const address = addressOf(env);
839
1053
  const clusterName = expected.clusterName.trim();
840
1054
  if (clusterName === "") {
841
- return yield* Effect11.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."));
842
1056
  }
843
1057
  const namespace = canonicalNamespace(expected.namespace);
844
1058
  const sent = envNamespace(env);
845
1059
  if (sent !== namespace) {
846
- return yield* Effect11.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)}\`.`));
847
1061
  }
848
1062
  const anonymous = { ...env, BAO_NAMESPACE: "", BAO_TOKEN: "" };
849
- const body = yield* baoCall("read", "GET", HEALTH_PATH).pipe(Effect11.provideService(BaoEnv, anonymous), Effect11.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")));
850
1064
  const health = checkHealth(address, clusterName, body);
851
1065
  if (health instanceof BaoIdentityError)
852
- return yield* Effect11.fail(health);
1066
+ return yield* Effect16.fail(health);
853
1067
  if (namespace !== "") {
854
1068
  const scoped = { ...env, BAO_NAMESPACE: namespace, BAO_TOKEN: "" };
855
- const probe = yield* baoCall("read", "GET", NAMESPACE_PROBE_PATH).pipe(Effect11.provideService(BaoEnv, scoped), Effect11.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}\``)));
856
1070
  if (probe === undefined) {
857
- return yield* Effect11.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}\`.`));
858
1072
  }
859
1073
  }
860
1074
  return { address, clusterName, namespace, ...health };
861
1075
  });
862
1076
  var assertBaoIdentity = (input) => {
863
1077
  const effect3 = assertBaoIdentityEffect(input);
864
- const scoped = input.env === undefined ? effect3 : Effect11.provideService(effect3, BaoEnv, input.env);
865
- return Effect11.runPromise(scoped.pipe(Effect11.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)));
866
1080
  };
867
1081
  // src/openbao/cloudflare-role.ts
868
1082
  import { Resource as Resource3 } from "alchemy";
869
1083
  import { isResolved as isResolved3 } from "alchemy/Diff";
870
1084
  import * as Provider3 from "alchemy/Provider";
871
- import * as Effect14 from "effect/Effect";
1085
+ import * as Effect19 from "effect/Effect";
872
1086
 
873
1087
  // src/openbao/cloudflare-group-scope.ts
874
1088
  var zoneResource = (zoneId) => `com.cloudflare.api.account.zone.${zoneId}`;
@@ -888,7 +1102,7 @@ var groupKey = (name, scope) => `${scope} ${name}`;
888
1102
 
889
1103
  // src/openbao/cloudflare-permission-groups.ts
890
1104
  import * as Context2 from "effect/Context";
891
- import * as Effect13 from "effect/Effect";
1105
+ import * as Effect18 from "effect/Effect";
892
1106
 
893
1107
  // src/openbao/cloudflare-permission-groups-solve.ts
894
1108
  var observationsOf = (declared, live) => declared.flatMap((role) => {
@@ -1058,7 +1272,7 @@ var samePolicies2 = (want, have) => want.length === have.length && want.every((w
1058
1272
  });
1059
1273
 
1060
1274
  // src/openbao/cloudflare-role-wire.ts
1061
- import * as Effect12 from "effect/Effect";
1275
+ import * as Effect17 from "effect/Effect";
1062
1276
 
1063
1277
  // src/openbao/cloudflare-role-form.ts
1064
1278
  var rolePath = (mount, name) => `${mountPath(mount)}/roles/${name}`;
@@ -1123,7 +1337,7 @@ var differences = (props, policies, live) => {
1123
1337
  };
1124
1338
 
1125
1339
  // src/openbao/cloudflare-role-wire.ts
1126
- var readCloudflareRole = (mount, name) => Effect12.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));
1127
1341
  var writeCloudflareRole = (mount, name, body) => baoWrite("PUT", rolePath(mount, name), body);
1128
1342
  var deleteCloudflareRole = (mount, name) => baoDelete(rolePath(mount, name));
1129
1343
 
@@ -1145,9 +1359,9 @@ var pick = (mount, groups, refs, hint) => {
1145
1359
  const unknown = refs.filter((ref) => !groups.has(groupKey(ref.name, ref.scope)));
1146
1360
  if (unknown.length > 0) {
1147
1361
  const problems = unknown.map((ref) => `unknown group ${JSON.stringify(ref.name)} at scope ${ref.scope} — ${hint}`);
1148
- return Effect13.fail(new CloudflareGroupsError(mount, problems));
1362
+ return Effect18.fail(new CloudflareGroupsError(mount, problems));
1149
1363
  }
1150
- return Effect13.succeed(new Map(refs.flatMap((ref) => {
1364
+ return Effect18.succeed(new Map(refs.flatMap((ref) => {
1151
1365
  const key = groupKey(ref.name, ref.scope);
1152
1366
  const id = groups.get(key);
1153
1367
  return id === undefined ? [] : [[key, id]];
@@ -1155,15 +1369,15 @@ var pick = (mount, groups, refs, hint) => {
1155
1369
  };
1156
1370
  var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1157
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";
1158
- var deniedNamed = (mount, read) => Effect13.catchIf(read, (error) => error.status === 403, () => Effect13.fail(new CloudflareGroupsError(mount, [deniedHint(mount)])));
1159
- var engineGroups = (mount) => Effect13.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) => {
1160
1374
  const list = data?.["permission_groups"];
1161
1375
  if (data === undefined) {
1162
1376
  const why = "GET <mount>/permission-groups answered 404 — the endpoint is not deployed here";
1163
- return Effect13.fail(new CloudflareGroupsError(mount, [why]));
1377
+ return Effect18.fail(new CloudflareGroupsError(mount, [why]));
1164
1378
  }
1165
1379
  if (!Array.isArray(list)) {
1166
- return Effect13.fail(new CloudflareGroupsError(mount, ["no permission_groups array in reply"]));
1380
+ return Effect18.fail(new CloudflareGroupsError(mount, ["no permission_groups array in reply"]));
1167
1381
  }
1168
1382
  const scoped = new Map;
1169
1383
  const idsOf = new Map;
@@ -1190,24 +1404,24 @@ var engineGroups = (mount) => Effect13.flatMap(deniedNamed(mount, baoRead(`${mou
1190
1404
  const only = [...ids];
1191
1405
  return ids.size === 1 && only[0] !== undefined ? [[name, only[0]]] : [];
1192
1406
  }));
1193
- return problems.length > 0 ? Effect13.fail(new CloudflareGroupsError(mount, problems)) : Effect13.succeed(new Map([...atEveryScope(sole), ...scoped]));
1407
+ return problems.length > 0 ? Effect18.fail(new CloudflareGroupsError(mount, problems)) : Effect18.succeed(new Map([...atEveryScope(sole), ...scoped]));
1194
1408
  });
1195
- var permissionGroupsFromEngine = () => Effect13.sync(() => {
1409
+ var permissionGroupsFromEngine = () => Effect18.sync(() => {
1196
1410
  const cache = new Map;
1197
- const cachedFor = (mount) => Effect13.suspend(() => {
1411
+ const cachedFor = (mount) => Effect18.suspend(() => {
1198
1412
  const hit = cache.get(mountPath(mount));
1199
1413
  if (hit !== undefined)
1200
1414
  return hit;
1201
- return Effect13.flatMap(Effect13.cached(engineGroups(mount)), (lookup) => {
1415
+ return Effect18.flatMap(Effect18.cached(engineGroups(mount)), (lookup) => {
1202
1416
  cache.set(mountPath(mount), lookup);
1203
1417
  return lookup;
1204
1418
  });
1205
1419
  });
1206
1420
  return CloudflarePermissionGroups.of({
1207
- resolve: (mount, refs) => Effect13.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"))
1208
1422
  });
1209
1423
  });
1210
- var permissionGroupsFromLiveRoles = (declared) => Effect13.gen(function* () {
1424
+ var permissionGroupsFromLiveRoles = (declared) => Effect18.gen(function* () {
1211
1425
  const accountOf = new Map;
1212
1426
  const byAccount = new Map;
1213
1427
  for (const [mount, roles] of declared) {
@@ -1218,9 +1432,9 @@ var permissionGroupsFromLiveRoles = (declared) => Effect13.gen(function* () {
1218
1432
  }
1219
1433
  const solved = new Map;
1220
1434
  for (const [account, roles] of byAccount) {
1221
- const solve = Effect13.gen(function* () {
1435
+ const solve = Effect18.gen(function* () {
1222
1436
  const live = new Map;
1223
- yield* Effect13.all(roles.map((role) => Effect13.map(readCloudflareRole(role.mount, role.name), (read) => {
1437
+ yield* Effect18.all(roles.map((role) => Effect18.map(readCloudflareRole(role.mount, role.name), (read) => {
1224
1438
  if (read?.policies !== undefined) {
1225
1439
  live.set(`${role.mount}/${role.name}`, read.policies);
1226
1440
  }
@@ -1228,11 +1442,11 @@ var permissionGroupsFromLiveRoles = (declared) => Effect13.gen(function* () {
1228
1442
  const result = solveGroups(observationsOf(roles, live));
1229
1443
  const problems = describeProblems(result);
1230
1444
  if (problems.length > 0) {
1231
- return yield* Effect13.fail(new CloudflareGroupsError(`account ${account}`, problems));
1445
+ return yield* Effect18.fail(new CloudflareGroupsError(`account ${account}`, problems));
1232
1446
  }
1233
1447
  return atEveryScope(result.groups);
1234
1448
  });
1235
- solved.set(account, yield* Effect13.cached(solve));
1449
+ solved.set(account, yield* Effect18.cached(solve));
1236
1450
  }
1237
1451
  return CloudflarePermissionGroups.of({
1238
1452
  resolve: (mount, refs) => {
@@ -1240,10 +1454,10 @@ var permissionGroupsFromLiveRoles = (declared) => Effect13.gen(function* () {
1240
1454
  const lookup = account === undefined ? undefined : solved.get(account);
1241
1455
  if (account === undefined || lookup === undefined) {
1242
1456
  const why = "no declared role on this mount, so no live role can vouch for its groups";
1243
- return Effect13.fail(new CloudflareGroupsError(mount, [why]));
1457
+ return Effect18.fail(new CloudflareGroupsError(mount, [why]));
1244
1458
  }
1245
1459
  const hint = `no live role on account ${account} carries it; a NEW group needs the engine endpoint`;
1246
- return Effect13.flatMap(lookup, (groups) => pick(mount, groups, refs, hint));
1460
+ return Effect18.flatMap(lookup, (groups) => pick(mount, groups, refs, hint));
1247
1461
  }
1248
1462
  });
1249
1463
  });
@@ -1253,9 +1467,9 @@ var BaoCloudflareRole = Resource3("Bao.CloudflareRole", {
1253
1467
  defaultRemovalPolicy: "retain"
1254
1468
  });
1255
1469
  var IDENTITY2 = roleIdentity("Bao.CloudflareRole", rolePath);
1256
- var BaoCloudflareRoleProvider = () => Provider3.effect(BaoCloudflareRole, Effect14.gen(function* () {
1470
+ var BaoCloudflareRoleProvider = () => Provider3.effect(BaoCloudflareRole, Effect19.gen(function* () {
1257
1471
  const groups = yield* CloudflarePermissionGroups;
1258
- const resolved = (props) => Effect14.gen(function* () {
1472
+ const resolved = (props) => Effect19.gen(function* () {
1259
1473
  const refs = new Map;
1260
1474
  for (const policy of props.policies) {
1261
1475
  const scope = policy.scope ?? scopeOfResource(policy.resource);
@@ -1265,17 +1479,23 @@ var BaoCloudflareRoleProvider = () => Provider3.effect(BaoCloudflareRole, Effect
1265
1479
  const ids = yield* groups.resolve(props.mount, [...refs.values()]);
1266
1480
  const { missing, policies } = resolvePolicies(props.policies, ids);
1267
1481
  if (missing.length > 0) {
1268
- return yield* Effect14.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.`));
1269
1483
  }
1270
1484
  return policies;
1271
1485
  });
1272
1486
  return BaoCloudflareRole.Provider.of({
1273
- list: () => Effect14.succeed([]),
1274
- read: Effect14.fn(function* ({ olds }) {
1487
+ list: () => Effect19.succeed([]),
1488
+ read: Effect19.fn(function* ({ fqn, instanceId, olds, output }) {
1275
1489
  const live = yield* readCloudflareRole(olds.mount, olds.name);
1276
- 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);
1277
1497
  }),
1278
- diff: Effect14.fn(function* ({ news, olds, output }) {
1498
+ diff: Effect19.fn(function* ({ news, olds, output }) {
1279
1499
  const move = yield* judgeRename(IDENTITY2, olds, news, output);
1280
1500
  if (output === undefined)
1281
1501
  return;
@@ -1291,34 +1511,37 @@ var BaoCloudflareRoleProvider = () => Provider3.effect(BaoCloudflareRole, Effect
1291
1511
  return { action: "update" };
1292
1512
  return differences(news, policies, live).length === 0 ? { action: "noop" } : { action: "update" };
1293
1513
  }),
1294
- reconcile: Effect14.fn(function* ({ news, output }) {
1514
+ reconcile: Effect19.fn(function* ({ fqn, instanceId, news, output }) {
1295
1515
  const path = rolePath(news.mount, news.name);
1296
1516
  yield* guardRename(IDENTITY2, news, output);
1297
1517
  const refusal2 = refusalOf(news);
1298
1518
  if (refusal2 !== undefined) {
1299
- return yield* Effect14.die(new Error(`Bao.CloudflareRole ${path}: ${refusal2}.`));
1519
+ return yield* Effect19.die(new Error(`Bao.CloudflareRole ${path}: ${refusal2}.`));
1300
1520
  }
1301
1521
  const policies = yield* resolved(news);
1302
1522
  const live = yield* readCloudflareRole(news.mount, news.name);
1523
+ if (live !== undefined) {
1524
+ yield* refuseTakeover({ fqn, instanceId, output }, `Bao.CloudflareRole ${path}`);
1525
+ }
1303
1526
  if (live === undefined || differences(news, policies, live).length > 0) {
1304
1527
  yield* writeCloudflareRole(news.mount, news.name, writeBody2(news, policies));
1305
1528
  }
1306
1529
  const after = yield* readCloudflareRole(news.mount, news.name);
1307
1530
  if (after === undefined) {
1308
- return yield* Effect14.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.`));
1309
1532
  }
1310
1533
  const left = differences(news, policies, after);
1311
1534
  if (left.length > 0) {
1312
- return yield* Effect14.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.`));
1313
1536
  }
1314
1537
  return attributesOf4(news, after);
1315
1538
  }),
1316
- delete: Effect14.fn(function* ({ output }) {
1539
+ delete: Effect19.fn(function* ({ output }) {
1317
1540
  yield* deleteCloudflareRole(output.mount, output.name);
1318
1541
  return;
1319
1542
  })
1320
1543
  });
1321
- }));
1544
+ }).pipe(Effect19.map(provingResumes)));
1322
1545
  // src/openbao/cloudflare-roles-expand.ts
1323
1546
  var slug = (name) => trimRuns(name.toLowerCase().replace(/[^a-z0-9]+/g, "-"), "-");
1324
1547
  var zoneSlug = (zone) => `${slug(zone.name)}${zone.internal ? "-internal" : ""}`;
@@ -1565,7 +1788,7 @@ var hostAppRoles = (input) => {
1565
1788
  import { Resource as Resource4 } from "alchemy";
1566
1789
  import { isResolved as isResolved4 } from "alchemy/Diff";
1567
1790
  import * as Provider4 from "alchemy/Provider";
1568
- import * as Effect16 from "effect/Effect";
1791
+ import * as Effect21 from "effect/Effect";
1569
1792
 
1570
1793
  // src/openbao/jwt-config-form.ts
1571
1794
  var text5 = (value) => typeof value === "string" ? value.trim() : "";
@@ -1655,19 +1878,25 @@ var problems = (props) => {
1655
1878
  };
1656
1879
 
1657
1880
  // src/openbao/role-reconcile.ts
1658
- import * as Effect15 from "effect/Effect";
1659
- var readRoleAt = (spec) => Effect15.map(baoRead(spec.path), (live) => live === undefined ? undefined : { attributes: spec.attributesOf(live), live });
1660
- var planRole = (spec) => Effect15.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* () {
1661
1888
  if (spec.problems.length > 0)
1662
1889
  return "update";
1663
1890
  const found = yield* readRoleAt(spec);
1664
1891
  return found !== undefined && spec.matches(found.attributes) ? "noop" : "update";
1665
1892
  });
1666
- var refuse2 = (spec, message) => Effect15.die(new Error(`${spec.family} ${spec.path}: ${message}`));
1667
- var reconcileRole = (spec) => Effect15.gen(function* () {
1893
+ var refuse2 = (spec, message) => Effect20.die(new Error(`${spec.family} ${spec.path}: ${message}`));
1894
+ var reconcileRole = (spec, claim) => Effect20.gen(function* () {
1668
1895
  if (spec.problems.length > 0)
1669
1896
  return yield* refuse2(spec, spec.problems.join("; "));
1670
1897
  const found = yield* readRoleAt(spec);
1898
+ if (found !== undefined && claim !== undefined)
1899
+ yield* claim(`${spec.family} ${spec.path}`);
1671
1900
  if (found === undefined || !spec.matches(found.attributes)) {
1672
1901
  const lost = found === undefined ? [] : spec.wouldErase?.(found.live) ?? [];
1673
1902
  if (lost.length > 0) {
@@ -1705,13 +1934,12 @@ var jwtConfigSpec = (props) => ({
1705
1934
  problems: problems(props),
1706
1935
  wouldErase
1707
1936
  });
1708
- var BaoJwtAuthConfigProvider = () => Provider4.effect(BaoJwtAuthConfig, Effect16.succeed(BaoJwtAuthConfig.Provider.of({
1709
- list: () => Effect16.succeed([]),
1710
- read: Effect16.fn(function* ({ olds }) {
1711
- const found = yield* readRoleAt(jwtConfigSpec(olds));
1712
- 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));
1713
1941
  }),
1714
- diff: Effect16.fn(function* ({ news, olds, output }) {
1942
+ diff: Effect21.fn(function* ({ news, olds, output }) {
1715
1943
  const move = yield* judgeRename(IDENTITY3, olds, news, output);
1716
1944
  if (output === undefined)
1717
1945
  return;
@@ -1721,17 +1949,17 @@ var BaoJwtAuthConfigProvider = () => Provider4.effect(BaoJwtAuthConfig, Effect16
1721
1949
  return;
1722
1950
  return { action: yield* planRole(jwtConfigSpec(news)) };
1723
1951
  }),
1724
- reconcile: Effect16.fn(function* ({ news, output }) {
1952
+ reconcile: Effect21.fn(function* ({ fqn, instanceId, news, output }) {
1725
1953
  yield* guardRename(IDENTITY3, news, output);
1726
- return yield* reconcileRole(jwtConfigSpec(news));
1954
+ return yield* reconcileRole(jwtConfigSpec(news), claimFor({ fqn, instanceId, output }));
1727
1955
  }),
1728
- delete: () => Effect16.void
1729
- })));
1956
+ delete: () => Effect21.void
1957
+ })).pipe(Effect21.map(provingResumes)));
1730
1958
  // src/openbao/jwt-role.ts
1731
1959
  import { Resource as Resource5 } from "alchemy";
1732
1960
  import { isResolved as isResolved5 } from "alchemy/Diff";
1733
1961
  import * as Provider5 from "alchemy/Provider";
1734
- import * as Effect17 from "effect/Effect";
1962
+ import * as Effect22 from "effect/Effect";
1735
1963
 
1736
1964
  // src/openbao/auth-token-form.ts
1737
1965
  var sortedSet2 = (values) => [...new Set(values)].sort();
@@ -1942,13 +2170,12 @@ var jwtRoleSpec = (props) => ({
1942
2170
  path: rolePath2(props),
1943
2171
  problems: problems2(props)
1944
2172
  });
1945
- var BaoJwtRoleProvider = () => Provider5.effect(BaoJwtRole, Effect17.succeed(BaoJwtRole.Provider.of({
1946
- list: () => Effect17.succeed([]),
1947
- read: Effect17.fn(function* ({ olds }) {
1948
- const found = yield* readRoleAt(jwtRoleSpec(olds));
1949
- 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));
1950
2177
  }),
1951
- diff: Effect17.fn(function* ({ news, olds, output }) {
2178
+ diff: Effect22.fn(function* ({ news, olds, output }) {
1952
2179
  const move = yield* judgeRename(IDENTITY4, olds, news, output);
1953
2180
  if (output === undefined)
1954
2181
  return;
@@ -1958,20 +2185,20 @@ var BaoJwtRoleProvider = () => Provider5.effect(BaoJwtRole, Effect17.succeed(Bao
1958
2185
  return;
1959
2186
  return { action: yield* planRole(jwtRoleSpec(news)) };
1960
2187
  }),
1961
- reconcile: Effect17.fn(function* ({ news, output }) {
2188
+ reconcile: Effect22.fn(function* ({ fqn, instanceId, news, output }) {
1962
2189
  yield* guardRename(IDENTITY4, news, output);
1963
- return yield* reconcileRole(jwtRoleSpec(news));
2190
+ return yield* reconcileRole(jwtRoleSpec(news), claimFor({ fqn, instanceId, output }));
1964
2191
  }),
1965
- delete: Effect17.fn(function* ({ output }) {
2192
+ delete: Effect22.fn(function* ({ output }) {
1966
2193
  yield* baoDelete(rolePath2(output));
1967
2194
  return;
1968
2195
  })
1969
- })));
2196
+ })).pipe(Effect22.map(provingResumes)));
1970
2197
  // src/openbao/kubernetes-role.ts
1971
2198
  import { Resource as Resource6 } from "alchemy";
1972
2199
  import { isResolved as isResolved6 } from "alchemy/Diff";
1973
2200
  import * as Provider6 from "alchemy/Provider";
1974
- import * as Effect18 from "effect/Effect";
2201
+ import * as Effect23 from "effect/Effect";
1975
2202
 
1976
2203
  // src/openbao/kubernetes-role-form.ts
1977
2204
  var sortedSet4 = (values) => [...new Set(values)].sort();
@@ -2049,13 +2276,12 @@ var kubernetesRoleSpec = (props) => ({
2049
2276
  path: rolePath3(props),
2050
2277
  problems: problems3(props)
2051
2278
  });
2052
- var BaoKubernetesRoleProvider = () => Provider6.effect(BaoKubernetesRole, Effect18.succeed(BaoKubernetesRole.Provider.of({
2053
- list: () => Effect18.succeed([]),
2054
- read: Effect18.fn(function* ({ olds }) {
2055
- const found = yield* readRoleAt(kubernetesRoleSpec(olds));
2056
- 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));
2057
2283
  }),
2058
- diff: Effect18.fn(function* ({ news, olds, output }) {
2284
+ diff: Effect23.fn(function* ({ news, olds, output }) {
2059
2285
  const move = yield* judgeRename(IDENTITY5, olds, news, output);
2060
2286
  if (output === undefined)
2061
2287
  return;
@@ -2065,20 +2291,20 @@ var BaoKubernetesRoleProvider = () => Provider6.effect(BaoKubernetesRole, Effect
2065
2291
  return;
2066
2292
  return { action: yield* planRole(kubernetesRoleSpec(news)) };
2067
2293
  }),
2068
- reconcile: Effect18.fn(function* ({ news, output }) {
2294
+ reconcile: Effect23.fn(function* ({ fqn, instanceId, news, output }) {
2069
2295
  yield* guardRename(IDENTITY5, news, output);
2070
- return yield* reconcileRole(kubernetesRoleSpec(news));
2296
+ return yield* reconcileRole(kubernetesRoleSpec(news), claimFor({ fqn, instanceId, output }));
2071
2297
  }),
2072
- delete: Effect18.fn(function* ({ output }) {
2298
+ delete: Effect23.fn(function* ({ output }) {
2073
2299
  yield* baoDelete(rolePath3(output));
2074
2300
  return;
2075
2301
  })
2076
- })));
2302
+ })).pipe(Effect23.map(provingResumes)));
2077
2303
  // src/openbao/mfa-enforcement.ts
2078
2304
  import { Resource as Resource7 } from "alchemy";
2079
2305
  import { isResolved as isResolved7 } from "alchemy/Diff";
2080
2306
  import * as Provider7 from "alchemy/Provider";
2081
- import * as Effect20 from "effect/Effect";
2307
+ import * as Effect25 from "effect/Effect";
2082
2308
 
2083
2309
  // src/openbao/mfa-enforcement-form.ts
2084
2310
  var sortedSet5 = (values) => [...new Set(values.map((value) => value.trim()))].filter((value) => value !== "").sort();
@@ -2146,16 +2372,16 @@ var problems4 = (props, missingPaths) => {
2146
2372
  };
2147
2373
 
2148
2374
  // src/openbao/mfa-wire.ts
2149
- import * as Effect19 from "effect/Effect";
2375
+ import * as Effect24 from "effect/Effect";
2150
2376
  var TOTP_PATH = "identity/mfa/method/totp";
2151
2377
  var METHODS_PATH = "identity/mfa/method";
2152
2378
  var enforcementPath = (name) => `identity/mfa/login-enforcement/${name}`;
2153
2379
  var record4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
2154
- var ownNamespacePath = Effect19.gen(function* () {
2380
+ var ownNamespacePath = Effect24.gen(function* () {
2155
2381
  const env = yield* BaoEnv;
2156
2382
  return namespacePath(envNamespace(env));
2157
2383
  });
2158
- var findTotp = (name) => Effect19.gen(function* () {
2384
+ var findTotp = (name) => Effect24.gen(function* () {
2159
2385
  const listing = yield* baoRead(`${METHODS_PATH}?list=true`);
2160
2386
  const info = record4(listing?.["key_info"]) ?? {};
2161
2387
  const own = yield* ownNamespacePath;
@@ -2164,17 +2390,17 @@ var findTotp = (name) => Effect19.gen(function* () {
2164
2390
  if (live === undefined || live["name"] !== name || live["namespace_path"] !== own)
2165
2391
  continue;
2166
2392
  if (live["type"] !== "totp") {
2167
- return yield* Effect19.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.`));
2168
2394
  }
2169
2395
  return { id, live };
2170
2396
  }
2171
2397
  return;
2172
2398
  });
2173
- var writeTotp = (body) => Effect19.asVoid(baoCall("write", "POST", TOTP_PATH, body));
2399
+ var writeTotp = (body) => Effect24.asVoid(baoCall("write", "POST", TOTP_PATH, body));
2174
2400
  var deleteTotp = (id) => baoDelete(`${TOTP_PATH}/${id}`);
2175
2401
  var readEnforcement = (name) => baoRead(enforcementPath(name));
2176
- var writeEnforcement = (name, body) => Effect19.asVoid(baoCall("write", "POST", enforcementPath(name), body));
2177
- var authAccessors = (paths) => Effect19.gen(function* () {
2402
+ var writeEnforcement = (name, body) => Effect24.asVoid(baoCall("write", "POST", enforcementPath(name), body));
2403
+ var authAccessors = (paths) => Effect24.gen(function* () {
2178
2404
  if (paths.length === 0)
2179
2405
  return { accessors: [], missing: [] };
2180
2406
  const table = (yield* baoRead("sys/auth")) ?? {};
@@ -2195,24 +2421,27 @@ var BaoMfaLoginEnforcement = Resource7("Bao.MfaLoginEnforcement", {
2195
2421
  defaultRemovalPolicy: "retain"
2196
2422
  });
2197
2423
  var IDENTITY6 = nameIdentity("Bao.MfaLoginEnforcement", enforcementPath);
2198
- var refuse3 = (name, message) => Effect20.die(new Error(`Bao.MfaLoginEnforcement ${name}: ${message}`));
2199
- var resolveTargets = (props) => Effect20.map(authAccessors(props.authMethodPaths ?? []), ({ accessors, missing }) => ({
2424
+ var refuse3 = (name, message) => Effect25.die(new Error(`Bao.MfaLoginEnforcement ${name}: ${message}`));
2425
+ var resolveTargets = (props) => Effect25.map(authAccessors(props.authMethodPaths ?? []), ({ accessors, missing }) => ({
2200
2426
  bad: problems4(props, missing),
2201
2427
  resolved: accessors
2202
2428
  }));
2203
- var readLive = (name) => Effect20.map(readEnforcement(name), (live) => live === undefined ? undefined : attributesOf8(name, live));
2204
- var planEnforcement = (props) => Effect20.gen(function* () {
2429
+ var readLive = (name) => Effect25.map(readEnforcement(name), (live) => live === undefined ? undefined : attributesOf8(name, live));
2430
+ var planEnforcement = (props) => Effect25.gen(function* () {
2205
2431
  const { bad, resolved } = yield* resolveTargets(props);
2206
2432
  if (bad.length > 0)
2207
2433
  return "update";
2208
2434
  const live = yield* readLive(props.name);
2209
2435
  return live !== undefined && matches7(live, props, resolved) ? "noop" : "update";
2210
2436
  });
2211
- var reconcileEnforcement = (props) => Effect20.gen(function* () {
2437
+ var reconcileEnforcement = (props, claim) => Effect25.gen(function* () {
2212
2438
  const { bad, resolved } = yield* resolveTargets(props);
2213
2439
  if (bad.length > 0)
2214
2440
  return yield* refuse3(props.name, bad.join("; "));
2215
2441
  const live = yield* readLive(props.name);
2442
+ if (live !== undefined && claim !== undefined) {
2443
+ yield* claim(`Bao.MfaLoginEnforcement ${props.name}`);
2444
+ }
2216
2445
  if (live === undefined || !matches7(live, props, resolved)) {
2217
2446
  yield* writeEnforcement(props.name, writeBody6(canonicalFromProps4(props, resolved)));
2218
2447
  }
@@ -2224,12 +2453,14 @@ var reconcileEnforcement = (props) => Effect20.gen(function* () {
2224
2453
  }
2225
2454
  return after;
2226
2455
  });
2227
- var BaoMfaLoginEnforcementProvider = () => Provider7.effect(BaoMfaLoginEnforcement, Effect20.succeed(BaoMfaLoginEnforcement.Provider.of({
2228
- list: () => Effect20.succeed([]),
2229
- read: Effect20.fn(function* ({ olds }) {
2230
- 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);
2231
2462
  }),
2232
- diff: Effect20.fn(function* ({ news, olds, output }) {
2463
+ diff: Effect25.fn(function* ({ news, olds, output }) {
2233
2464
  const move = yield* judgeRename(IDENTITY6, olds, news, output);
2234
2465
  if (output === undefined)
2235
2466
  return;
@@ -2239,19 +2470,19 @@ var BaoMfaLoginEnforcementProvider = () => Provider7.effect(BaoMfaLoginEnforceme
2239
2470
  return;
2240
2471
  return { action: yield* planEnforcement(news) };
2241
2472
  }),
2242
- reconcile: Effect20.fn(function* ({ news, output }) {
2473
+ reconcile: Effect25.fn(function* ({ fqn, instanceId, news, output }) {
2243
2474
  yield* guardRename(IDENTITY6, news, output);
2244
- return yield* reconcileEnforcement(news);
2475
+ return yield* reconcileEnforcement(news, claimFor({ fqn, instanceId, output }));
2245
2476
  }),
2246
- delete: Effect20.fn(function* ({ output }) {
2477
+ delete: Effect25.fn(function* ({ output }) {
2247
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.");
2248
2479
  })
2249
- })));
2480
+ })).pipe(Effect25.map(provingResumes)));
2250
2481
  // src/openbao/mfa-totp.ts
2251
2482
  import { Resource as Resource8 } from "alchemy";
2252
2483
  import { isResolved as isResolved8 } from "alchemy/Diff";
2253
2484
  import * as Provider8 from "alchemy/Provider";
2254
- import * as Effect21 from "effect/Effect";
2485
+ import * as Effect26 from "effect/Effect";
2255
2486
 
2256
2487
  // src/openbao/mfa-totp-form.ts
2257
2488
  var canonical6 = (input) => ({
@@ -2332,19 +2563,21 @@ var problems5 = (props) => {
2332
2563
  var BaoMfaTotpMethod = Resource8("Bao.MfaTotpMethod", {
2333
2564
  defaultRemovalPolicy: "retain"
2334
2565
  });
2335
- var refuse4 = (name, message) => Effect21.die(new Error(`Bao.MfaTotpMethod ${name}: ${message}`));
2336
- var readTotp = (props) => Effect21.map(findTotp(props.name), (found) => found === undefined ? undefined : attributesOf9(found.id, found.live));
2337
- var planTotp = (props) => Effect21.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* () {
2338
2569
  if (problems5(props).length > 0)
2339
2570
  return "update";
2340
2571
  const live = yield* readTotp(props);
2341
2572
  return live !== undefined && matches8(live, props) ? "noop" : "update";
2342
2573
  });
2343
- var reconcileTotp = (props) => Effect21.gen(function* () {
2574
+ var reconcileTotp = (props, claim) => Effect26.gen(function* () {
2344
2575
  const bad = problems5(props);
2345
2576
  if (bad.length > 0)
2346
2577
  return yield* refuse4(props.name, bad.join("; "));
2347
2578
  const live = yield* readTotp(props);
2579
+ if (live !== undefined && claim !== undefined)
2580
+ yield* claim(`Bao.MfaTotpMethod ${props.name}`);
2348
2581
  if (live === undefined || !matches8(live, props))
2349
2582
  yield* writeTotp(writeBody7(props));
2350
2583
  const after = yield* readTotp(props);
@@ -2354,55 +2587,59 @@ var reconcileTotp = (props) => Effect21.gen(function* () {
2354
2587
  return yield* refuse4(props.name, "the method read back different.");
2355
2588
  return after;
2356
2589
  });
2357
- var BaoMfaTotpMethodProvider = () => Provider8.effect(BaoMfaTotpMethod, Effect21.succeed(BaoMfaTotpMethod.Provider.of({
2358
- list: () => Effect21.succeed([]),
2359
- read: Effect21.fn(function* ({ olds }) {
2360
- 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);
2361
2596
  }),
2362
- diff: Effect21.fn(function* ({ news, output }) {
2597
+ diff: Effect26.fn(function* ({ news, output }) {
2363
2598
  const name = declaredString(news, "name");
2364
2599
  if (output !== undefined && name !== undefined) {
2365
2600
  const rename = renameProblem(output.name, name);
2366
2601
  if (rename !== undefined)
2367
2602
  return yield* refuse4(name, rename);
2368
2603
  }
2369
- if (output === undefined || !isResolved8(news))
2604
+ if (output === undefined)
2605
+ return;
2606
+ if (!isResolved8(news))
2370
2607
  return;
2371
2608
  return { action: yield* planTotp(news) };
2372
2609
  }),
2373
- reconcile: Effect21.fn(function* ({ news, output }) {
2610
+ reconcile: Effect26.fn(function* ({ fqn, instanceId, news, output }) {
2374
2611
  const rename = output === undefined ? undefined : renameProblem(output.name, news.name);
2375
2612
  if (rename !== undefined)
2376
2613
  return yield* refuse4(news.name, rename);
2377
- return yield* reconcileTotp(news);
2614
+ return yield* reconcileTotp(news, claimFor({ fqn, instanceId, output }));
2378
2615
  }),
2379
- delete: Effect21.fn(function* ({ output }) {
2616
+ delete: Effect26.fn(function* ({ output }) {
2380
2617
  yield* deleteTotp(output.methodId);
2381
2618
  return;
2382
2619
  })
2383
- })));
2620
+ })).pipe(Effect26.map(provingResumes)));
2384
2621
  // src/openbao/mount.ts
2385
2622
  import { Resource as Resource9 } from "alchemy";
2386
2623
  import { isResolved as isResolved9 } from "alchemy/Diff";
2387
2624
  import * as Provider9 from "alchemy/Provider";
2388
- import * as Effect24 from "effect/Effect";
2625
+ import * as Effect29 from "effect/Effect";
2389
2626
 
2390
2627
  // src/openbao/mount-reconcile.ts
2391
- import * as Effect23 from "effect/Effect";
2628
+ import * as Effect28 from "effect/Effect";
2392
2629
 
2393
2630
  // src/openbao/mount-wire.ts
2394
- import * as Effect22 from "effect/Effect";
2395
- var absentFromTable2 = (path, asked) => baoRead("sys/mounts").pipe(Effect22.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, [
2396
2633
  ...asked.errors,
2397
2634
  `sys/mounts could not confirm the mount is absent: ${listing.message}`
2398
- ])), Effect22.flatMap((table) => table !== undefined && !Object.hasOwn(table, `${mountPath(path)}/`) ? Effect22.succeed(undefined) : Effect22.fail(asked)));
2399
- var readMountData = (path) => baoRead(readPath(path)).pipe(Effect22.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)));
2400
2637
  var enableMount = (props) => baoWrite("POST", readPath(props.path), enableBody(props));
2401
2638
  var tuneMount = (props) => baoWrite("POST", tunePath(props.path), tuneBody(props));
2402
2639
  var disableMount = (path) => baoDelete(readPath(path));
2403
2640
 
2404
2641
  // src/openbao/mount-reconcile.ts
2405
- var readMount = (props) => Effect23.gen(function* () {
2642
+ var readMount = (props) => Effect28.gen(function* () {
2406
2643
  const live = yield* readMountData(props.path);
2407
2644
  if (live === undefined)
2408
2645
  return;
@@ -2415,10 +2652,16 @@ var moveTarget2 = (props) => ({
2415
2652
  type: props.type,
2416
2653
  wire: mountPath
2417
2654
  });
2418
- var reconcileMount = (news, stated, options) => Effect23.gen(function* () {
2655
+ var reconcileMount = (news, stated, options, claim) => Effect28.gen(function* () {
2419
2656
  const move = planMove("Bao.Mount", stated, news.path, news.remountFrom);
2420
2657
  if (move.kind === "refuse")
2421
- return yield* Effect23.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
+ }
2422
2665
  if (move.kind === "move")
2423
2666
  yield* performMove(moveTarget2(news), move.from, options);
2424
2667
  let live = yield* readMount(news);
@@ -2426,15 +2669,18 @@ var reconcileMount = (news, stated, options) => Effect23.gen(function* () {
2426
2669
  yield* enableMount(news);
2427
2670
  if (wantsTune(news))
2428
2671
  yield* tuneMount(news);
2429
- } else if (!matches(live, news)) {
2672
+ } else {
2430
2673
  if (live.type !== news.type) {
2431
- return yield* Effect23.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."));
2432
2675
  }
2433
- yield* tuneMount(news);
2676
+ if (first !== undefined)
2677
+ yield* first(`Bao.Mount ${mountPath(news.path)}`);
2678
+ if (!matches(live, news))
2679
+ yield* tuneMount(news);
2434
2680
  }
2435
2681
  live = yield* readMount(news);
2436
2682
  if (live === undefined) {
2437
- return yield* Effect23.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.`));
2438
2684
  }
2439
2685
  return live;
2440
2686
  });
@@ -2443,17 +2689,21 @@ var reconcileMount = (news, stated, options) => Effect23.gen(function* () {
2443
2689
  var BaoMount = Resource9("Bao.Mount", {
2444
2690
  defaultRemovalPolicy: "retain"
2445
2691
  });
2446
- var BaoMountProvider = () => Provider9.effect(BaoMount, Effect24.succeed(BaoMount.Provider.of({
2447
- list: () => Effect24.succeed([]),
2448
- read: Effect24.fn(function* ({ olds }) {
2449
- 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);
2450
2698
  }),
2451
- diff: Effect24.fn(function* ({ news, output }) {
2452
- if (output === undefined || !isResolved9(news))
2699
+ diff: Effect29.fn(function* ({ news, output }) {
2700
+ if (output === undefined)
2701
+ return;
2702
+ if (!isResolved9(news))
2453
2703
  return;
2454
2704
  const move = planMove("Bao.Mount", output.path, news.path, news.remountFrom);
2455
2705
  if (move.kind === "refuse")
2456
- return yield* Effect24.die(new Error(move.message));
2706
+ return yield* Effect29.die(new Error(move.message));
2457
2707
  if (move.kind === "move")
2458
2708
  return { action: "update" };
2459
2709
  const live = yield* readMount(news);
@@ -2465,19 +2715,20 @@ var BaoMountProvider = () => Provider9.effect(BaoMount, Effect24.succeed(BaoMoun
2465
2715
  return { action: "noop" };
2466
2716
  return { action: "update" };
2467
2717
  }),
2468
- reconcile: Effect24.fn(function* ({ news, output }) {
2469
- 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);
2470
2721
  }),
2471
- delete: Effect24.fn(function* ({ output }) {
2722
+ delete: Effect29.fn(function* ({ output }) {
2472
2723
  yield* disableMount(output.path);
2473
2724
  return;
2474
2725
  })
2475
- })));
2726
+ })).pipe(Effect29.map(provingResumes)));
2476
2727
  // src/openbao/pki-role.ts
2477
2728
  import { Resource as Resource10 } from "alchemy";
2478
2729
  import { isResolved as isResolved10 } from "alchemy/Diff";
2479
2730
  import * as Provider10 from "alchemy/Provider";
2480
- import * as Effect25 from "effect/Effect";
2731
+ import * as Effect30 from "effect/Effect";
2481
2732
 
2482
2733
  // src/openbao/pki-role-canonical.ts
2483
2734
  var DEFAULT_KEY_USAGE = [
@@ -2644,18 +2895,20 @@ var BaoPkiRole = Resource10("Bao.PkiRole", {
2644
2895
  defaultRemovalPolicy: "retain"
2645
2896
  });
2646
2897
  var IDENTITY7 = roleIdentity("Bao.PkiRole", (mount, name) => rolePath4({ mount, name }), "pki");
2647
- var readRole2 = (props) => Effect25.gen(function* () {
2898
+ var readRole2 = (props) => Effect30.gen(function* () {
2648
2899
  const live = yield* baoRead(rolePath4(props));
2649
2900
  if (live === undefined)
2650
2901
  return;
2651
2902
  return attributesOf10(props, live);
2652
2903
  });
2653
- var BaoPkiRoleProvider = () => Provider10.effect(BaoPkiRole, Effect25.succeed(BaoPkiRole.Provider.of({
2654
- list: () => Effect25.succeed([]),
2655
- read: Effect25.fn(function* ({ olds }) {
2656
- 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);
2657
2910
  }),
2658
- diff: Effect25.fn(function* ({ news, olds, output }) {
2911
+ diff: Effect30.fn(function* ({ news, olds, output }) {
2659
2912
  const move = yield* judgeRename(IDENTITY7, olds, news, output);
2660
2913
  if (output === undefined)
2661
2914
  return;
@@ -2670,36 +2923,38 @@ var BaoPkiRoleProvider = () => Provider10.effect(BaoPkiRole, Effect25.succeed(Ba
2670
2923
  return { action: "update" };
2671
2924
  return matches9(live, news) ? { action: "noop" } : { action: "update" };
2672
2925
  }),
2673
- reconcile: Effect25.fn(function* ({ news, output }) {
2926
+ reconcile: Effect30.fn(function* ({ fqn, instanceId, news, output }) {
2674
2927
  const path = rolePath4(news);
2675
2928
  yield* guardRename(IDENTITY7, news, output);
2676
2929
  const bad = problems6(news);
2677
2930
  if (bad.length > 0) {
2678
- return yield* Effect25.die(new Error(`Bao.PkiRole ${path}: ${bad.join(" ")}`));
2931
+ return yield* Effect30.die(new Error(`Bao.PkiRole ${path}: ${bad.join(" ")}`));
2679
2932
  }
2680
2933
  const live = yield* readRole2(news);
2934
+ if (live !== undefined)
2935
+ yield* refuseTakeover({ fqn, instanceId, output }, `Bao.PkiRole ${path}`);
2681
2936
  if (live === undefined || !matches9(live, news)) {
2682
2937
  yield* baoWrite("PUT", path, writeBody8(news));
2683
2938
  }
2684
2939
  const after = yield* readRole2(news);
2685
2940
  if (after === undefined) {
2686
- return yield* Effect25.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.`));
2687
2942
  }
2688
2943
  if (!matches9(after, news)) {
2689
- return yield* Effect25.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.`));
2690
2945
  }
2691
2946
  return after;
2692
2947
  }),
2693
- delete: Effect25.fn(function* ({ output }) {
2948
+ delete: Effect30.fn(function* ({ output }) {
2694
2949
  yield* baoDelete(rolePath4(output));
2695
2950
  return;
2696
2951
  })
2697
- })));
2952
+ })).pipe(Effect30.map(provingResumes)));
2698
2953
  // src/openbao/plugin.ts
2699
2954
  import { Resource as Resource11 } from "alchemy";
2700
2955
  import { isResolved as isResolved11 } from "alchemy/Diff";
2701
2956
  import * as Provider11 from "alchemy/Provider";
2702
- import * as Effect27 from "effect/Effect";
2957
+ import * as Effect32 from "effect/Effect";
2703
2958
 
2704
2959
  // src/openbao/plugin-form.ts
2705
2960
  var resolve = (props) => ({
@@ -2755,7 +3010,7 @@ var sameArgs = (want, have) => want.length === have.length && want.every((entry,
2755
3010
  var matches10 = (attributes, form) => !attributes.builtin && attributes.version === form.version && attributes.sha256 === form.sha256 && attributes.command === form.command && sameArgs(form.args, attributes.args);
2756
3011
 
2757
3012
  // src/openbao/plugin-reconcile.ts
2758
- import * as Effect26 from "effect/Effect";
3013
+ import * as Effect31 from "effect/Effect";
2759
3014
 
2760
3015
  // src/openbao/plugin-wire.ts
2761
3016
  var readPluginData = (form) => baoRead(versionedPath(form.type, form.name, form.version));
@@ -2763,15 +3018,18 @@ var registerPlugin = (form) => baoWrite("PUT", catalogPath(form.type, form.name)
2763
3018
  var deregisterPlugin = (type, name, version) => baoDelete(versionedPath(type, name, version));
2764
3019
 
2765
3020
  // src/openbao/plugin-reconcile.ts
2766
- var readEntry = (form) => Effect26.map(readPluginData(form), (live) => live === undefined ? undefined : { attributes: attributesOf11(form, live), live });
2767
- var refuse5 = (form, message) => Effect26.die(new Error(`Bao.Plugin ${versionedPath(form.type, form.name, form.version)}: ${message}`));
2768
- 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.";
2769
- var reconcilePlugin = (props) => Effect26.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* () {
2770
3025
  const form = resolve(props);
2771
3026
  const bad = problems7(form);
2772
3027
  if (bad.length > 0)
2773
3028
  return yield* refuse5(form, bad.join("; "));
2774
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
+ }
2775
3033
  if (found !== undefined && !matches10(found.attributes, form)) {
2776
3034
  if (found.live["declarative"] === true || found.live["oci"] === true) {
2777
3035
  return yield* refuse5(form, "the live entry is declarative/OCI; manage it in server config.");
@@ -2805,13 +3063,15 @@ var IDENTITY8 = {
2805
3063
  family: "Bao.Plugin",
2806
3064
  recorded: (output) => versionedPath(output.type, output.name, output.version)
2807
3065
  };
2808
- var BaoPluginProvider = () => Provider11.effect(BaoPlugin, Effect27.succeed(BaoPlugin.Provider.of({
2809
- list: () => Effect27.succeed([]),
2810
- read: Effect27.fn(function* ({ olds }) {
2811
- const found = yield* readEntry(resolve(olds));
2812
- return found?.attributes;
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);
2813
3073
  }),
2814
- diff: Effect27.fn(function* ({ news, olds, output }) {
3074
+ diff: Effect32.fn(function* ({ news, olds, output }) {
2815
3075
  const move = yield* judgeRename(IDENTITY8, olds, news, output);
2816
3076
  if (output === undefined)
2817
3077
  return;
@@ -2825,20 +3085,20 @@ var BaoPluginProvider = () => Provider11.effect(BaoPlugin, Effect27.succeed(BaoP
2825
3085
  return { action: "update" };
2826
3086
  return matches10(found.attributes, form) ? { action: "noop" } : { action: "update" };
2827
3087
  }),
2828
- reconcile: Effect27.fn(function* ({ news, output }) {
3088
+ reconcile: Effect32.fn(function* ({ fqn, instanceId, news, output }) {
2829
3089
  yield* guardRename(IDENTITY8, news, output);
2830
- return yield* reconcilePlugin(news);
3090
+ return yield* reconcilePlugin(news, claimFor({ fqn, instanceId, output }));
2831
3091
  }),
2832
- delete: Effect27.fn(function* ({ output }) {
3092
+ delete: Effect32.fn(function* ({ output }) {
2833
3093
  yield* deregisterPlugin(output.type, output.name, output.version);
2834
3094
  return;
2835
3095
  })
2836
- })));
3096
+ })).pipe(Effect32.map(provingResumes)));
2837
3097
  // src/openbao/policy.ts
2838
3098
  import { Resource as Resource12 } from "alchemy";
2839
3099
  import { isResolved as isResolved12 } from "alchemy/Diff";
2840
3100
  import * as Provider12 from "alchemy/Provider";
2841
- import * as Effect29 from "effect/Effect";
3101
+ import * as Effect34 from "effect/Effect";
2842
3102
  import * as FileSystem from "effect/FileSystem";
2843
3103
  import * as Path from "effect/Path";
2844
3104
 
@@ -2846,13 +3106,13 @@ import * as Path from "effect/Path";
2846
3106
  var isEmptyAssembly = (parts, joined) => parts === 0 || !/^\s*path\s+"/m.test(joined);
2847
3107
 
2848
3108
  // src/openbao/policy-wire.ts
2849
- import * as Effect28 from "effect/Effect";
3109
+ import * as Effect33 from "effect/Effect";
2850
3110
  var policyPath = (name) => `sys/policies/acl/${name}`;
2851
- var readPolicy = (name) => Effect28.flatMap(baoRead(policyPath(name)), (data) => {
3111
+ var readPolicy = (name) => Effect33.flatMap(baoRead(policyPath(name)), (data) => {
2852
3112
  if (data === undefined)
2853
- return Effect28.succeed("");
3113
+ return Effect33.succeed("");
2854
3114
  const text11 = data["policy"];
2855
- return typeof text11 === "string" ? Effect28.succeed(text11) : Effect28.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"]));
2856
3116
  });
2857
3117
  var writePolicy = (name, hcl) => baoWrite("PUT", policyPath(name), { policy: hcl });
2858
3118
  var deletePolicy = (name) => baoDelete(policyPath(name));
@@ -2869,10 +3129,10 @@ var attributesOf12 = (name, hcl, parts) => ({
2869
3129
  grants: grantsOf(hcl),
2870
3130
  parts
2871
3131
  });
2872
- var BaoPolicyProvider = () => Provider12.effect(BaoPolicy, Effect29.gen(function* () {
3132
+ var BaoPolicyProvider = () => Provider12.effect(BaoPolicy, Effect34.gen(function* () {
2873
3133
  const fs = yield* FileSystem.FileSystem;
2874
3134
  const path = yield* Path.Path;
2875
- const assemble = (dir) => Effect29.gen(function* () {
3135
+ const assemble = (dir) => Effect34.gen(function* () {
2876
3136
  const entries2 = (yield* fs.readDirectory(dir)).filter((f) => f.endsWith(".hcl")).sort();
2877
3137
  let joined = "";
2878
3138
  for (const f of entries2)
@@ -2881,12 +3141,14 @@ var BaoPolicyProvider = () => Provider12.effect(BaoPolicy, Effect29.gen(function
2881
3141
  return { joined, parts: entries2.length };
2882
3142
  });
2883
3143
  return BaoPolicy.Provider.of({
2884
- list: () => Effect29.succeed([]),
2885
- read: Effect29.fn(function* ({ olds }) {
3144
+ list: () => Effect34.succeed([]),
3145
+ read: Effect34.fn(function* ({ fqn, instanceId, olds, output }) {
2886
3146
  const live = yield* readPolicy(olds.name);
2887
- 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);
2888
3150
  }),
2889
- diff: Effect29.fn(function* ({ news, olds, output }) {
3151
+ diff: Effect34.fn(function* ({ news, olds, output }) {
2890
3152
  const move = yield* judgeRename(IDENTITY9, olds, news, output);
2891
3153
  if (output === undefined)
2892
3154
  return;
@@ -2898,30 +3160,32 @@ var BaoPolicyProvider = () => Provider12.effect(BaoPolicy, Effect29.gen(function
2898
3160
  const live = yield* readPolicy(news.name);
2899
3161
  return live.length > 0 && sha256(live) === sha256(joined) ? { action: "noop" } : { action: "update" };
2900
3162
  }),
2901
- reconcile: Effect29.fn(function* ({ news, output }) {
3163
+ reconcile: Effect34.fn(function* ({ fqn, instanceId, news, output }) {
2902
3164
  const name = news.name;
2903
3165
  yield* guardRename(IDENTITY9, news, output);
2904
3166
  const { joined, parts } = yield* assemble(news.fragments);
2905
3167
  if (isEmptyAssembly(parts, joined))
2906
- return yield* Effect29.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.`));
2907
3169
  const current = yield* readPolicy(name);
3170
+ if (current.length > 0)
3171
+ yield* refuseTakeover({ fqn, instanceId, output }, `Bao.Policy ${name}`);
2908
3172
  if (current.length > 0 && sha256(current) === sha256(joined)) {
2909
3173
  return attributesOf12(name, joined, parts);
2910
3174
  }
2911
3175
  yield* writePolicy(name, joined);
2912
3176
  return attributesOf12(name, joined, parts);
2913
3177
  }),
2914
- delete: Effect29.fn(function* ({ output }) {
3178
+ delete: Effect34.fn(function* ({ output }) {
2915
3179
  yield* deletePolicy(output.name);
2916
3180
  return;
2917
3181
  })
2918
3182
  });
2919
- }));
3183
+ }).pipe(Effect34.map(provingResumes)));
2920
3184
  // src/openbao/proxmox-role.ts
2921
3185
  import { Resource as Resource13 } from "alchemy";
2922
3186
  import { isResolved as isResolved13 } from "alchemy/Diff";
2923
3187
  import * as Provider13 from "alchemy/Provider";
2924
- import * as Effect31 from "effect/Effect";
3188
+ import * as Effect36 from "effect/Effect";
2925
3189
 
2926
3190
  // src/openbao/proxmox-role-form.ts
2927
3191
  var text11 = (value) => typeof value === "string" ? value : "";
@@ -2971,8 +3235,8 @@ var rescopeRefusal = (before, mintUser, allowMintUserChange) => before === mintU
2971
3235
  var matches11 = (attributes, props) => attributes.mintUser === props.mintUser && sameTtl5(props.ttl, attributes.ttl) && sameTtl5(props.maxTtl, attributes.maxTtl);
2972
3236
 
2973
3237
  // src/openbao/proxmox-role-wire.ts
2974
- import * as Effect30 from "effect/Effect";
2975
- var readRole3 = (props) => Effect30.gen(function* () {
3238
+ import * as Effect35 from "effect/Effect";
3239
+ var readRole3 = (props) => Effect35.gen(function* () {
2976
3240
  const live = yield* baoRead(rolePath5(props.mount, props.name));
2977
3241
  if (live === undefined)
2978
3242
  return;
@@ -2984,12 +3248,14 @@ var BaoProxmoxRole = Resource13("Bao.ProxmoxRole", {
2984
3248
  defaultRemovalPolicy: "retain"
2985
3249
  });
2986
3250
  var IDENTITY10 = roleIdentity("Bao.ProxmoxRole", rolePath5);
2987
- var BaoProxmoxRoleProvider = () => Provider13.effect(BaoProxmoxRole, Effect31.succeed(BaoProxmoxRole.Provider.of({
2988
- list: () => Effect31.succeed([]),
2989
- read: Effect31.fn(function* ({ olds }) {
2990
- return yield* readRole3(olds);
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);
2991
3257
  }),
2992
- diff: Effect31.fn(function* ({ news, olds, output }) {
3258
+ diff: Effect36.fn(function* ({ news, olds, output }) {
2993
3259
  const move = yield* judgeRename(IDENTITY10, olds, news, output);
2994
3260
  if (output === undefined)
2995
3261
  return;
@@ -3002,7 +3268,7 @@ var BaoProxmoxRoleProvider = () => Provider13.effect(BaoProxmoxRole, Effect31.su
3002
3268
  const refusal2 = rescopeRefusal(from, mintUser, allow);
3003
3269
  if (refusal2 === undefined)
3004
3270
  return { action: "replace" };
3005
- return yield* Effect31.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}`));
3006
3272
  }
3007
3273
  if (!isResolved13(news))
3008
3274
  return;
@@ -3013,37 +3279,39 @@ var BaoProxmoxRoleProvider = () => Provider13.effect(BaoProxmoxRole, Effect31.su
3013
3279
  return { action: "noop" };
3014
3280
  return { action: "update" };
3015
3281
  }),
3016
- reconcile: Effect31.fn(function* ({ news, output }) {
3282
+ reconcile: Effect36.fn(function* ({ fqn, instanceId, news, output }) {
3017
3283
  const path = rolePath5(news.mount, news.name);
3018
3284
  yield* guardRename(IDENTITY10, news, output);
3019
3285
  const live = yield* readRole3(news);
3286
+ if (live !== undefined)
3287
+ yield* refuseTakeover({ fqn, instanceId, output }, `Bao.ProxmoxRole ${path}`);
3020
3288
  const rescope = live === undefined ? undefined : rescopeRefusal(live.mintUser, news.mintUser, news.allowMintUserChange);
3021
3289
  if (rescope !== undefined) {
3022
- return yield* Effect31.die(new Error(`Bao.ProxmoxRole ${path}: ${rescope}`));
3290
+ return yield* Effect36.die(new Error(`Bao.ProxmoxRole ${path}: ${rescope}`));
3023
3291
  }
3024
3292
  if (live === undefined || !matches11(live, news)) {
3025
3293
  const write = writeBody9(news);
3026
3294
  if (!write.ok) {
3027
- return yield* Effect31.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.`));
3028
3296
  }
3029
3297
  yield* baoWrite("PUT", path, write.body);
3030
3298
  }
3031
3299
  const after = yield* readRole3(news);
3032
3300
  if (after === undefined) {
3033
- return yield* Effect31.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."));
3034
3302
  }
3035
3303
  return after;
3036
3304
  }),
3037
- delete: Effect31.fn(function* ({ output }) {
3305
+ delete: Effect36.fn(function* ({ output }) {
3038
3306
  yield* baoDelete(rolePath5(output.mount, output.name));
3039
3307
  return;
3040
3308
  })
3041
- })));
3309
+ })).pipe(Effect36.map(provingResumes)));
3042
3310
  // src/openbao/ssh-role.ts
3043
3311
  import { Resource as Resource14 } from "alchemy";
3044
3312
  import { isResolved as isResolved14 } from "alchemy/Diff";
3045
3313
  import * as Provider14 from "alchemy/Provider";
3046
- import * as Effect32 from "effect/Effect";
3314
+ import * as Effect37 from "effect/Effect";
3047
3315
 
3048
3316
  // src/openbao/ssh-role-form.ts
3049
3317
  var readPath4 = (mount, name) => `${sshMountOf(mount)}/roles/${name}`;
@@ -3104,7 +3372,7 @@ var attributesOf14 = (form, live) => {
3104
3372
  };
3105
3373
  return { ...attrs, digest: sha256(JSON.stringify(attrs)) };
3106
3374
  };
3107
- var mapValue = (map10) => extMap(map10);
3375
+ var mapValue = (map23) => extMap(map23);
3108
3376
  var writeBody10 = (form) => ({
3109
3377
  key_type: "ca",
3110
3378
  allowed_users: form.allowedUsers.join(","),
@@ -3158,20 +3426,21 @@ var BaoSshRole = Resource14("Bao.SshRole", {
3158
3426
  defaultRemovalPolicy: "retain"
3159
3427
  });
3160
3428
  var IDENTITY11 = roleIdentity("Bao.SshRole", readPath4, "ssh");
3161
- var readRole4 = (form) => Effect32.gen(function* () {
3429
+ var readRole4 = (form) => Effect37.gen(function* () {
3162
3430
  const live = yield* baoRead(readPath4(form.mount, form.name));
3163
3431
  if (live === undefined)
3164
3432
  return;
3165
3433
  return { attributes: attributesOf14(form, live), live };
3166
3434
  });
3167
- var refuse6 = (message) => Effect32.die(new Error(message));
3168
- var BaoSshRoleProvider = () => Provider14.effect(BaoSshRole, Effect32.succeed(BaoSshRole.Provider.of({
3169
- list: () => Effect32.succeed([]),
3170
- read: Effect32.fn(function* ({ olds }) {
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 }) {
3171
3439
  const found = yield* readRole4(resolve2(olds));
3172
- 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);
3173
3442
  }),
3174
- diff: Effect32.fn(function* ({ news, olds, output }) {
3443
+ diff: Effect37.fn(function* ({ news, olds, output }) {
3175
3444
  const move = yield* judgeRename(IDENTITY11, olds, news, output);
3176
3445
  if (output === undefined)
3177
3446
  return;
@@ -3185,7 +3454,7 @@ var BaoSshRoleProvider = () => Provider14.effect(BaoSshRole, Effect32.succeed(Ba
3185
3454
  return { action: "update" };
3186
3455
  return matches12(found.attributes, form) ? { action: "noop" } : { action: "update" };
3187
3456
  }),
3188
- reconcile: Effect32.fn(function* ({ news, output }) {
3457
+ reconcile: Effect37.fn(function* ({ fqn, instanceId, news, output }) {
3189
3458
  const form = resolve2(news);
3190
3459
  const path = readPath4(form.mount, form.name);
3191
3460
  yield* guardRename(IDENTITY11, news, output);
@@ -3197,6 +3466,8 @@ var BaoSshRoleProvider = () => Provider14.effect(BaoSshRole, Effect32.succeed(Ba
3197
3466
  return yield* refuse6(`Bao.SshRole ${path}: allowUserCertificates with an empty allowedUsers and ` + "allowEmptyPrincipals false. Every sign request against it would be rejected.");
3198
3467
  }
3199
3468
  const found = yield* readRole4(form);
3469
+ if (found !== undefined)
3470
+ yield* refuseTakeover({ fqn, instanceId, output }, `Bao.SshRole ${path}`);
3200
3471
  if (found === undefined || !matches12(found.attributes, form)) {
3201
3472
  if (found !== undefined) {
3202
3473
  const lost = wouldErase2(found.live);
@@ -3212,11 +3483,11 @@ var BaoSshRoleProvider = () => Provider14.effect(BaoSshRole, Effect32.succeed(Ba
3212
3483
  }
3213
3484
  return after.attributes;
3214
3485
  }),
3215
- delete: Effect32.fn(function* ({ output }) {
3486
+ delete: Effect37.fn(function* ({ output }) {
3216
3487
  yield* baoDelete(readPath4(output.mount, output.name));
3217
3488
  return;
3218
3489
  })
3219
- })));
3490
+ })).pipe(Effect37.map(provingResumes)));
3220
3491
  export {
3221
3492
  BaoAuthMethod,
3222
3493
  BaoAuthMethodProvider,
@@ -3266,5 +3537,5 @@ export {
3266
3537
  revokeSelfEffect
3267
3538
  };
3268
3539
 
3269
- //# debugId=B83B2582FB998CE264756E2164756E21
3540
+ //# debugId=9AA407497A9014D964756E2164756E21
3270
3541
  //# sourceMappingURL=index.js.map