@homeflare/alchemy 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (219) hide show
  1. package/README.md +74 -41
  2. package/dist/caddy/admin-calls.d.ts +48 -0
  3. package/dist/caddy/admin-calls.d.ts.map +1 -0
  4. package/dist/caddy/admin-guard.d.ts +40 -0
  5. package/dist/caddy/admin-guard.d.ts.map +1 -0
  6. package/dist/caddy/admin.d.ts +73 -0
  7. package/dist/caddy/admin.d.ts.map +1 -0
  8. package/dist/caddy/caddyfile-tripwire.d.ts +5 -0
  9. package/dist/caddy/caddyfile-tripwire.d.ts.map +1 -0
  10. package/dist/caddy/config-form.d.ts +27 -0
  11. package/dist/caddy/config-form.d.ts.map +1 -0
  12. package/dist/caddy/config-lifecycle.d.ts +91 -0
  13. package/dist/caddy/config-lifecycle.d.ts.map +1 -0
  14. package/dist/caddy/config.d.ts +34 -0
  15. package/dist/caddy/config.d.ts.map +1 -0
  16. package/dist/caddy/digest.d.ts +7 -0
  17. package/dist/caddy/digest.d.ts.map +1 -0
  18. package/dist/caddy/fake-caddy.d.ts +54 -0
  19. package/dist/caddy/fake-caddy.d.ts.map +1 -0
  20. package/dist/caddy/index.d.ts +22 -0
  21. package/dist/caddy/index.d.ts.map +1 -0
  22. package/dist/caddy/index.js +970 -0
  23. package/dist/caddy/index.js.map +28 -0
  24. package/dist/caddy/local-admin.d.ts +45 -0
  25. package/dist/caddy/local-admin.d.ts.map +1 -0
  26. package/dist/caddy/providers.d.ts +14 -0
  27. package/dist/caddy/providers.d.ts.map +1 -0
  28. package/dist/caddy/with-file.d.ts +51 -0
  29. package/dist/caddy/with-file.d.ts.map +1 -0
  30. package/dist/cloudflare/fake-mesh.d.ts +58 -0
  31. package/dist/cloudflare/fake-mesh.d.ts.map +1 -0
  32. package/dist/cloudflare/index.d.ts +9 -3
  33. package/dist/cloudflare/index.d.ts.map +1 -1
  34. package/dist/cloudflare/index.js +218 -7
  35. package/dist/cloudflare/index.js.map +9 -4
  36. package/dist/cloudflare/mesh-node-api.d.ts +75 -0
  37. package/dist/cloudflare/mesh-node-api.d.ts.map +1 -0
  38. package/dist/cloudflare/mesh-node-form.d.ts +82 -0
  39. package/dist/cloudflare/mesh-node-form.d.ts.map +1 -0
  40. package/dist/cloudflare/mesh-node-harness.d.ts +46 -0
  41. package/dist/cloudflare/mesh-node-harness.d.ts.map +1 -0
  42. package/dist/cloudflare/mesh-node-lifecycle.d.ts +22 -0
  43. package/dist/cloudflare/mesh-node-lifecycle.d.ts.map +1 -0
  44. package/dist/cloudflare/mesh-node-token.d.ts +23 -0
  45. package/dist/cloudflare/mesh-node-token.d.ts.map +1 -0
  46. package/dist/cloudflare/mesh-node.d.ts +48 -0
  47. package/dist/cloudflare/mesh-node.d.ts.map +1 -0
  48. package/dist/cloudflare/providers.d.ts +1 -12
  49. package/dist/cloudflare/providers.d.ts.map +1 -1
  50. package/dist/index.d.ts +1 -0
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/launchd/fake-sudo.d.ts +50 -0
  53. package/dist/launchd/fake-sudo.d.ts.map +1 -0
  54. package/dist/launchd/host-effect.d.ts +18 -9
  55. package/dist/launchd/host-effect.d.ts.map +1 -1
  56. package/dist/launchd/host-file-form.d.ts +5 -0
  57. package/dist/launchd/host-file-form.d.ts.map +1 -1
  58. package/dist/launchd/host-file-lifecycle.d.ts.map +1 -1
  59. package/dist/launchd/index.d.ts +2 -0
  60. package/dist/launchd/index.d.ts.map +1 -1
  61. package/dist/launchd/index.js +342 -8
  62. package/dist/launchd/index.js.map +16 -12
  63. package/dist/launchd/job-preflight.d.ts.map +1 -1
  64. package/dist/launchd/job-validate.d.ts +2 -0
  65. package/dist/launchd/job-validate.d.ts.map +1 -1
  66. package/dist/launchd/local-runner.d.ts +2 -0
  67. package/dist/launchd/local-runner.d.ts.map +1 -1
  68. package/dist/launchd/providers.d.ts +2 -1
  69. package/dist/launchd/providers.d.ts.map +1 -1
  70. package/dist/launchd/runner.d.ts +7 -5
  71. package/dist/launchd/runner.d.ts.map +1 -1
  72. package/dist/launchd/secret-tripwire.d.ts +1 -0
  73. package/dist/launchd/secret-tripwire.d.ts.map +1 -1
  74. package/dist/launchd/sudo-allowlist.d.ts +47 -0
  75. package/dist/launchd/sudo-allowlist.d.ts.map +1 -0
  76. package/dist/launchd/sudo-guard.d.ts +56 -0
  77. package/dist/launchd/sudo-guard.d.ts.map +1 -0
  78. package/dist/launchd/sudo-runner.d.ts +60 -0
  79. package/dist/launchd/sudo-runner.d.ts.map +1 -0
  80. package/dist/launchd/sudo-stage.d.ts +7 -0
  81. package/dist/launchd/sudo-stage.d.ts.map +1 -0
  82. package/dist/openbao/auth-role-form.d.ts +0 -9
  83. package/dist/openbao/auth-role-form.d.ts.map +1 -1
  84. package/dist/openbao/auth-role.d.ts +3 -2
  85. package/dist/openbao/auth-role.d.ts.map +1 -1
  86. package/dist/openbao/cloudflare-role.d.ts.map +1 -1
  87. package/dist/openbao/fake-engines-roles.d.ts +32 -0
  88. package/dist/openbao/fake-engines-roles.d.ts.map +1 -0
  89. package/dist/openbao/fake-engines.d.ts +36 -0
  90. package/dist/openbao/fake-engines.d.ts.map +1 -0
  91. package/dist/openbao/fake-families.d.ts +49 -0
  92. package/dist/openbao/fake-families.d.ts.map +1 -0
  93. package/dist/openbao/fake-stack.d.ts +29 -0
  94. package/dist/openbao/fake-stack.d.ts.map +1 -0
  95. package/dist/openbao/host-approles.d.ts.map +1 -1
  96. package/dist/openbao/index.js +393 -234
  97. package/dist/openbao/index.js.map +23 -19
  98. package/dist/openbao/jwt-config.d.ts +5 -0
  99. package/dist/openbao/jwt-config.d.ts.map +1 -1
  100. package/dist/openbao/jwt-role.d.ts +2 -1
  101. package/dist/openbao/jwt-role.d.ts.map +1 -1
  102. package/dist/openbao/kubernetes-role.d.ts +2 -1
  103. package/dist/openbao/kubernetes-role.d.ts.map +1 -1
  104. package/dist/openbao/mfa-enforcement.d.ts +6 -3
  105. package/dist/openbao/mfa-enforcement.d.ts.map +1 -1
  106. package/dist/openbao/mfa-totp.d.ts.map +1 -1
  107. package/dist/openbao/pki-role.d.ts.map +1 -1
  108. package/dist/openbao/plugin.d.ts.map +1 -1
  109. package/dist/openbao/policy.d.ts +2 -0
  110. package/dist/openbao/policy.d.ts.map +1 -1
  111. package/dist/openbao/proxmox-role-form.d.ts +18 -8
  112. package/dist/openbao/proxmox-role-form.d.ts.map +1 -1
  113. package/dist/openbao/proxmox-role-wire.d.ts +18 -0
  114. package/dist/openbao/proxmox-role-wire.d.ts.map +1 -0
  115. package/dist/openbao/proxmox-role.d.ts.map +1 -1
  116. package/dist/openbao/rename-identity.d.ts +69 -0
  117. package/dist/openbao/rename-identity.d.ts.map +1 -0
  118. package/dist/openbao/rename.d.ts +107 -0
  119. package/dist/openbao/rename.d.ts.map +1 -0
  120. package/dist/openbao/ssh-role-erase.d.ts +11 -0
  121. package/dist/openbao/ssh-role-erase.d.ts.map +1 -0
  122. package/dist/openbao/ssh-role.d.ts.map +1 -1
  123. package/docs/caddy-admin.md +24 -0
  124. package/docs/caddy.md +199 -0
  125. package/docs/launchd-sudo.md +122 -0
  126. package/docs/launchd.md +7 -4
  127. package/docs/mesh-node.md +188 -0
  128. package/docs/peers.md +39 -0
  129. package/package.json +7 -2
  130. package/src/caddy/admin-calls.ts +128 -0
  131. package/src/caddy/admin-guard.ts +170 -0
  132. package/src/caddy/admin.ts +74 -0
  133. package/src/caddy/adopt-scope.test.ts +82 -0
  134. package/src/caddy/caddyfile-tripwire.ts +133 -0
  135. package/src/caddy/config-adopt.test.ts +235 -0
  136. package/src/caddy/config-form.ts +64 -0
  137. package/src/caddy/config-lifecycle.test.ts +196 -0
  138. package/src/caddy/config-lifecycle.ts +239 -0
  139. package/src/caddy/config.ts +153 -0
  140. package/src/caddy/digest.ts +44 -0
  141. package/src/caddy/fake-caddy.ts +179 -0
  142. package/src/caddy/guards.test.ts +156 -0
  143. package/src/caddy/index.ts +26 -0
  144. package/src/caddy/local-admin.test.ts +160 -0
  145. package/src/caddy/local-admin.ts +189 -0
  146. package/src/caddy/providers.test.ts +229 -0
  147. package/src/caddy/providers.ts +17 -0
  148. package/src/caddy/with-file.ts +66 -0
  149. package/src/cloudflare/fake-mesh.ts +181 -0
  150. package/src/cloudflare/index.ts +9 -3
  151. package/src/cloudflare/mesh-node-api.ts +127 -0
  152. package/src/cloudflare/mesh-node-edges.test.ts +103 -0
  153. package/src/cloudflare/mesh-node-form.test.ts +80 -0
  154. package/src/cloudflare/mesh-node-form.ts +115 -0
  155. package/src/cloudflare/mesh-node-harness.ts +124 -0
  156. package/src/cloudflare/mesh-node-lifecycle.ts +187 -0
  157. package/src/cloudflare/mesh-node-policy.test.ts +151 -0
  158. package/src/cloudflare/mesh-node-token.test.ts +117 -0
  159. package/src/cloudflare/mesh-node-token.ts +125 -0
  160. package/src/cloudflare/mesh-node.test.ts +172 -0
  161. package/src/cloudflare/mesh-node.ts +120 -0
  162. package/src/cloudflare/providers.ts +20 -2
  163. package/src/index.ts +1 -0
  164. package/src/launchd/fake-sudo.ts +136 -0
  165. package/src/launchd/host-effect.ts +33 -1
  166. package/src/launchd/host-file-form.ts +10 -2
  167. package/src/launchd/host-file-lifecycle.ts +2 -1
  168. package/src/launchd/index.ts +2 -0
  169. package/src/launchd/job-preflight.ts +3 -2
  170. package/src/launchd/job-validate.ts +1 -1
  171. package/src/launchd/local-runner.ts +6 -3
  172. package/src/launchd/providers.test.ts +3 -0
  173. package/src/launchd/providers.ts +2 -1
  174. package/src/launchd/runner.ts +7 -5
  175. package/src/launchd/secret-tripwire.ts +1 -1
  176. package/src/launchd/sudo-allowlist.test.ts +191 -0
  177. package/src/launchd/sudo-allowlist.ts +213 -0
  178. package/src/launchd/sudo-guard.test.ts +100 -0
  179. package/src/launchd/sudo-guard.ts +151 -0
  180. package/src/launchd/sudo-lifecycle.test.ts +172 -0
  181. package/src/launchd/sudo-runner-files.test.ts +240 -0
  182. package/src/launchd/sudo-runner.test.ts +126 -0
  183. package/src/launchd/sudo-runner.ts +235 -0
  184. package/src/launchd/sudo-stage.ts +28 -0
  185. package/src/openbao/README.md +8 -2
  186. package/src/openbao/REPLACE.md +126 -22
  187. package/src/openbao/auth-role-form.test.ts +2 -16
  188. package/src/openbao/auth-role-form.ts +0 -11
  189. package/src/openbao/auth-role.ts +35 -14
  190. package/src/openbao/cloudflare-role-wire.test.ts +8 -34
  191. package/src/openbao/cloudflare-role.ts +22 -8
  192. package/src/openbao/fake-engines-roles.ts +114 -0
  193. package/src/openbao/fake-engines.ts +126 -0
  194. package/src/openbao/fake-families.ts +182 -0
  195. package/src/openbao/fake-stack.ts +112 -0
  196. package/src/openbao/host-approles.test.ts +15 -2
  197. package/src/openbao/host-approles.ts +19 -4
  198. package/src/openbao/jwt-config.ts +26 -5
  199. package/src/openbao/jwt-role.ts +24 -8
  200. package/src/openbao/kubernetes-role.ts +20 -8
  201. package/src/openbao/mfa-enforcement.ts +23 -8
  202. package/src/openbao/mfa-totp.ts +21 -8
  203. package/src/openbao/pki-role.ts +19 -7
  204. package/src/openbao/plugin.ts +41 -15
  205. package/src/openbao/policy-rename.test.ts +112 -0
  206. package/src/openbao/policy-wire.test.ts +7 -26
  207. package/src/openbao/policy.ts +27 -4
  208. package/src/openbao/proxmox-role-form.ts +27 -8
  209. package/src/openbao/proxmox-role-wire.ts +24 -0
  210. package/src/openbao/proxmox-role.ts +52 -36
  211. package/src/openbao/rename-adoption.test.ts +82 -0
  212. package/src/openbao/rename-families.test.ts +122 -0
  213. package/src/openbao/rename-identity.test.ts +129 -0
  214. package/src/openbao/rename-identity.ts +117 -0
  215. package/src/openbao/rename-occupied.test.ts +166 -0
  216. package/src/openbao/rename.ts +166 -0
  217. package/src/openbao/role-rename.test.ts +205 -0
  218. package/src/openbao/ssh-role-erase.ts +50 -0
  219. package/src/openbao/ssh-role.ts +16 -54
@@ -616,7 +616,7 @@ var BaoAuthMethodProvider = () => Provider.effect(BaoAuthMethod, Effect7.succeed
616
616
  import { Resource as Resource2 } from "alchemy";
617
617
  import { isResolved as isResolved2 } from "alchemy/Diff";
618
618
  import * as Provider2 from "alchemy/Provider";
619
- import * as Effect8 from "effect/Effect";
619
+ import * as Effect10 from "effect/Effect";
620
620
 
621
621
  // src/openbao/auth-role-form.ts
622
622
  var policiesOf = (live) => {
@@ -665,7 +665,6 @@ var writeBody = (props) => {
665
665
  }
666
666
  return body;
667
667
  };
668
- var isRename = (from, to) => from.toLowerCase() !== to.toLowerCase();
669
668
  var samePolicies = (want, have) => want.length === have.length && want.every((policy, index) => policy === have[index]);
670
669
  var sameTtl3 = (want, have) => {
671
670
  const wantSeconds = parseDuration(want);
@@ -677,26 +676,90 @@ var matches3 = (attributes, props) => {
677
676
  return samePolicies(wantPolicies, attributes.tokenPolicies) && sameTtl3(props.tokenTtl, attributes.tokenTtl) && sameTtl3(props.tokenMaxTtl, attributes.tokenMaxTtl) && sameTtl3(props.secretIdTtl, attributes.secretIdTtl) && (props.bindSecretId === undefined || attributes.bindSecretId === props.bindSecretId) && (props.secretIdNumUses === undefined || attributes.secretIdNumUses === props.secretIdNumUses);
678
677
  };
679
678
 
679
+ // src/openbao/rename-identity.ts
680
+ import * as Effect9 from "effect/Effect";
681
+
682
+ // src/openbao/rename.ts
683
+ import * as Effect8 from "effect/Effect";
684
+ var exact = (identity) => identity;
685
+ var declaredString = (news, key) => {
686
+ if (typeof news !== "object" || news === null)
687
+ return;
688
+ const value = news[key];
689
+ return typeof value === "string" ? value : undefined;
690
+ };
691
+ var isPendingProp = (news, key) => {
692
+ if (typeof news !== "object" || news === null)
693
+ return false;
694
+ const value = news[key];
695
+ return value !== undefined && typeof value !== "string";
696
+ };
697
+ var declaredOr = (news, key, fallback) => isPendingProp(news, key) ? undefined : declaredString(news, key) ?? fallback;
698
+ var declaredRolePath = (news, rolePath, defaultMount) => {
699
+ const mount = defaultMount === undefined ? declaredString(news, "mount") : declaredOr(news, "mount", defaultMount);
700
+ const name = declaredString(news, "name");
701
+ return mount === undefined || name === undefined ? undefined : rolePath(mount, name);
702
+ };
703
+ var policyKey = (name) => name.trim().toLowerCase();
704
+ 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* () {
706
+ if (tried === undefined || declared === undefined || key(tried) === key(declared)) {
707
+ return;
708
+ }
709
+ if ((yield* baoRead(pathOf(declared))) === undefined)
710
+ 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.`));
712
+ });
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."));
714
+
715
+ // src/openbao/rename-identity.ts
716
+ var nameIdentity = (family, pathOf, key) => ({
717
+ declared: (props) => declaredString(props, "name"),
718
+ family,
719
+ key,
720
+ pathOf,
721
+ recorded: (output) => output.name
722
+ });
723
+ var roleIdentity = (family, rolePath, defaultMount, key) => ({
724
+ declared: (props) => declaredRolePath(props, rolePath, defaultMount),
725
+ family,
726
+ key,
727
+ recorded: (output) => rolePath(output.mount, output.name)
728
+ });
729
+ var foldName = (path) => path.replace(/[^/]+$/, (name) => name.toLowerCase());
730
+ 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
+ var guardRename = (identity, news, output) => {
732
+ if (output === undefined)
733
+ return Effect9.void;
734
+ const from = identity.recorded(output);
735
+ const to = identity.declared(news);
736
+ return to !== undefined && isMoved(from, to, identity.key) === true ? refuseMovedUpdate(identity.family, from, to) : Effect9.void;
737
+ };
738
+
680
739
  // src/openbao/auth-role.ts
681
740
  var BaoAuthRole = Resource2("Bao.AuthRole", {
682
741
  defaultRemovalPolicy: "retain"
683
742
  });
684
- var readRole = (props) => Effect8.gen(function* () {
743
+ var IDENTITY = nameIdentity("Bao.AuthRole", readPath3, foldName);
744
+ var readRole = (props) => Effect10.gen(function* () {
685
745
  const live = yield* baoRead(readPath3(props.name));
686
746
  if (live === undefined)
687
747
  return;
688
748
  return attributesOf3(props, live);
689
749
  });
690
- var BaoAuthRoleProvider = () => Provider2.effect(BaoAuthRole, Effect8.succeed(BaoAuthRole.Provider.of({
691
- list: () => Effect8.succeed([]),
692
- read: Effect8.fn(function* ({ olds }) {
750
+ var BaoAuthRoleProvider = () => Provider2.effect(BaoAuthRole, Effect10.succeed(BaoAuthRole.Provider.of({
751
+ list: () => Effect10.succeed([]),
752
+ read: Effect10.fn(function* ({ olds }) {
693
753
  return yield* readRole(olds);
694
754
  }),
695
- diff: Effect8.fn(function* ({ news, output }) {
696
- if (output === undefined || !isResolved2(news))
755
+ diff: Effect10.fn(function* ({ news, olds, output }) {
756
+ const move = yield* judgeRename(IDENTITY, olds, news, output);
757
+ if (output === undefined)
697
758
  return;
698
- if (isRename(output.name, news.name))
759
+ if (move !== undefined)
699
760
  return { action: "replace" };
761
+ if (!isResolved2(news))
762
+ return;
700
763
  const live = yield* readRole(news);
701
764
  if (live === undefined)
702
765
  return { action: "update" };
@@ -704,24 +767,25 @@ var BaoAuthRoleProvider = () => Provider2.effect(BaoAuthRole, Effect8.succeed(Ba
704
767
  return { action: "noop" };
705
768
  return { action: "update" };
706
769
  }),
707
- reconcile: Effect8.fn(function* ({ news }) {
770
+ reconcile: Effect10.fn(function* ({ news, output }) {
771
+ yield* guardRename(IDENTITY, news, output);
708
772
  const live = yield* readRole(news);
709
773
  if (live === undefined || !matches3(live, news)) {
710
774
  yield* baoWrite("PUT", readPath3(news.name), writeBody(news));
711
775
  }
712
776
  const after = yield* readRole(news);
713
777
  if (after === undefined) {
714
- return yield* Effect8.die(new Error(`Bao.AuthRole ${news.name}: write returned no error but the role is still absent.`));
778
+ return yield* Effect10.die(new Error(`Bao.AuthRole ${news.name}: write returned no error but the role is still absent.`));
715
779
  }
716
780
  return after;
717
781
  }),
718
- delete: Effect8.fn(function* ({ output }) {
782
+ delete: Effect10.fn(function* ({ output }) {
719
783
  yield* baoDelete(readPath3(output.name));
720
784
  return;
721
785
  })
722
786
  })));
723
787
  // src/openbao/bao-identity.ts
724
- import * as Effect9 from "effect/Effect";
788
+ import * as Effect11 from "effect/Effect";
725
789
  import * as FetchHttpClient3 from "effect/unstable/http/FetchHttpClient";
726
790
 
727
791
  // src/openbao/bao-namespace.ts
@@ -769,42 +833,42 @@ var checkHealth = (address, expectedCluster, body) => {
769
833
  }
770
834
  return { clusterId: text3(body["cluster_id"]), version: text3(body["version"]) };
771
835
  };
772
- var assertBaoIdentityEffect = (expected) => Effect9.gen(function* () {
836
+ var assertBaoIdentityEffect = (expected) => Effect11.gen(function* () {
773
837
  const env = yield* BaoEnv;
774
838
  const address = addressOf(env);
775
839
  const clusterName = expected.clusterName.trim();
776
840
  if (clusterName === "") {
777
- return yield* Effect9.fail(new BaoIdentityError("input", "clusterName is empty, so there is nothing to compare."));
841
+ return yield* Effect11.fail(new BaoIdentityError("input", "clusterName is empty, so there is nothing to compare."));
778
842
  }
779
843
  const namespace = canonicalNamespace(expected.namespace);
780
844
  const sent = envNamespace(env);
781
845
  if (sent !== namespace) {
782
- return yield* Effect9.fail(new BaoIdentityError("mismatch", `BAO_NAMESPACE resolves to \`${namespaceLabel(sent)}\`, but this stack expects ` + `\`${namespaceLabel(namespace)}\`.`));
846
+ return yield* Effect11.fail(new BaoIdentityError("mismatch", `BAO_NAMESPACE resolves to \`${namespaceLabel(sent)}\`, but this stack expects ` + `\`${namespaceLabel(namespace)}\`.`));
783
847
  }
784
848
  const anonymous = { ...env, BAO_NAMESPACE: "", BAO_TOKEN: "" };
785
- const body = yield* baoCall("read", "GET", HEALTH_PATH).pipe(Effect9.provideService(BaoEnv, anonymous), Effect9.mapError(unreachable(address, "sys/health")));
849
+ const body = yield* baoCall("read", "GET", HEALTH_PATH).pipe(Effect11.provideService(BaoEnv, anonymous), Effect11.mapError(unreachable(address, "sys/health")));
786
850
  const health = checkHealth(address, clusterName, body);
787
851
  if (health instanceof BaoIdentityError)
788
- return yield* Effect9.fail(health);
852
+ return yield* Effect11.fail(health);
789
853
  if (namespace !== "") {
790
854
  const scoped = { ...env, BAO_NAMESPACE: namespace, BAO_TOKEN: "" };
791
- const probe = yield* baoCall("read", "GET", NAMESPACE_PROBE_PATH).pipe(Effect9.provideService(BaoEnv, scoped), Effect9.mapError(unreachable(address, `the namespace probe for \`${namespace}\``)));
855
+ const probe = yield* baoCall("read", "GET", NAMESPACE_PROBE_PATH).pipe(Effect11.provideService(BaoEnv, scoped), Effect11.mapError(unreachable(address, `the namespace probe for \`${namespace}\``)));
792
856
  if (probe === undefined) {
793
- return yield* Effect9.fail(new BaoIdentityError("mismatch", `cluster \`${clusterName}\` at ${address} has no namespace \`${namespace}\`.`));
857
+ return yield* Effect11.fail(new BaoIdentityError("mismatch", `cluster \`${clusterName}\` at ${address} has no namespace \`${namespace}\`.`));
794
858
  }
795
859
  }
796
860
  return { address, clusterName, namespace, ...health };
797
861
  });
798
862
  var assertBaoIdentity = (input) => {
799
863
  const effect3 = assertBaoIdentityEffect(input);
800
- const scoped = input.env === undefined ? effect3 : Effect9.provideService(effect3, BaoEnv, input.env);
801
- return Effect9.runPromise(scoped.pipe(Effect9.provide(FetchHttpClient3.layer)));
864
+ const scoped = input.env === undefined ? effect3 : Effect11.provideService(effect3, BaoEnv, input.env);
865
+ return Effect11.runPromise(scoped.pipe(Effect11.provide(FetchHttpClient3.layer)));
802
866
  };
803
867
  // src/openbao/cloudflare-role.ts
804
868
  import { Resource as Resource3 } from "alchemy";
805
869
  import { isResolved as isResolved3 } from "alchemy/Diff";
806
870
  import * as Provider3 from "alchemy/Provider";
807
- import * as Effect12 from "effect/Effect";
871
+ import * as Effect14 from "effect/Effect";
808
872
 
809
873
  // src/openbao/cloudflare-group-scope.ts
810
874
  var zoneResource = (zoneId) => `com.cloudflare.api.account.zone.${zoneId}`;
@@ -824,7 +888,7 @@ var groupKey = (name, scope) => `${scope} ${name}`;
824
888
 
825
889
  // src/openbao/cloudflare-permission-groups.ts
826
890
  import * as Context2 from "effect/Context";
827
- import * as Effect11 from "effect/Effect";
891
+ import * as Effect13 from "effect/Effect";
828
892
 
829
893
  // src/openbao/cloudflare-permission-groups-solve.ts
830
894
  var observationsOf = (declared, live) => declared.flatMap((role) => {
@@ -994,7 +1058,7 @@ var samePolicies2 = (want, have) => want.length === have.length && want.every((w
994
1058
  });
995
1059
 
996
1060
  // src/openbao/cloudflare-role-wire.ts
997
- import * as Effect10 from "effect/Effect";
1061
+ import * as Effect12 from "effect/Effect";
998
1062
 
999
1063
  // src/openbao/cloudflare-role-form.ts
1000
1064
  var rolePath = (mount, name) => `${mountPath(mount)}/roles/${name}`;
@@ -1059,7 +1123,7 @@ var differences = (props, policies, live) => {
1059
1123
  };
1060
1124
 
1061
1125
  // src/openbao/cloudflare-role-wire.ts
1062
- var readCloudflareRole = (mount, name) => Effect10.map(baoRead(rolePath(mount, name)), (data) => data === undefined ? undefined : liveRoleOf(data));
1126
+ var readCloudflareRole = (mount, name) => Effect12.map(baoRead(rolePath(mount, name)), (data) => data === undefined ? undefined : liveRoleOf(data));
1063
1127
  var writeCloudflareRole = (mount, name, body) => baoWrite("PUT", rolePath(mount, name), body);
1064
1128
  var deleteCloudflareRole = (mount, name) => baoDelete(rolePath(mount, name));
1065
1129
 
@@ -1081,9 +1145,9 @@ var pick = (mount, groups, refs, hint) => {
1081
1145
  const unknown = refs.filter((ref) => !groups.has(groupKey(ref.name, ref.scope)));
1082
1146
  if (unknown.length > 0) {
1083
1147
  const problems = unknown.map((ref) => `unknown group ${JSON.stringify(ref.name)} at scope ${ref.scope} — ${hint}`);
1084
- return Effect11.fail(new CloudflareGroupsError(mount, problems));
1148
+ return Effect13.fail(new CloudflareGroupsError(mount, problems));
1085
1149
  }
1086
- return Effect11.succeed(new Map(refs.flatMap((ref) => {
1150
+ return Effect13.succeed(new Map(refs.flatMap((ref) => {
1087
1151
  const key = groupKey(ref.name, ref.scope);
1088
1152
  const id = groups.get(key);
1089
1153
  return id === undefined ? [] : [[key, id]];
@@ -1091,15 +1155,15 @@ var pick = (mount, groups, refs, hint) => {
1091
1155
  };
1092
1156
  var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1093
1157
  var deniedHint = (mount) => `GET ${mountPath(mount)}/permission-groups answered 403 — this token's policy carries no ` + `\`path "${mountPath(mount)}/permission-groups" { capabilities = ["read"] }\` grant. ` + "It is declared in ../platform/secrets/vault/policies/homeflare-admin/" + "20-cloudflare-permission-groups.hcl and applies with `alchemy deploy` of this stack";
1094
- var deniedNamed = (mount, read) => Effect11.catchIf(read, (error) => error.status === 403, () => Effect11.fail(new CloudflareGroupsError(mount, [deniedHint(mount)])));
1095
- var engineGroups = (mount) => Effect11.flatMap(deniedNamed(mount, baoRead(`${mountPath(mount)}/permission-groups`)), (data) => {
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) => {
1096
1160
  const list = data?.["permission_groups"];
1097
1161
  if (data === undefined) {
1098
1162
  const why = "GET <mount>/permission-groups answered 404 — the endpoint is not deployed here";
1099
- return Effect11.fail(new CloudflareGroupsError(mount, [why]));
1163
+ return Effect13.fail(new CloudflareGroupsError(mount, [why]));
1100
1164
  }
1101
1165
  if (!Array.isArray(list)) {
1102
- return Effect11.fail(new CloudflareGroupsError(mount, ["no permission_groups array in reply"]));
1166
+ return Effect13.fail(new CloudflareGroupsError(mount, ["no permission_groups array in reply"]));
1103
1167
  }
1104
1168
  const scoped = new Map;
1105
1169
  const idsOf = new Map;
@@ -1126,24 +1190,24 @@ var engineGroups = (mount) => Effect11.flatMap(deniedNamed(mount, baoRead(`${mou
1126
1190
  const only = [...ids];
1127
1191
  return ids.size === 1 && only[0] !== undefined ? [[name, only[0]]] : [];
1128
1192
  }));
1129
- return problems.length > 0 ? Effect11.fail(new CloudflareGroupsError(mount, problems)) : Effect11.succeed(new Map([...atEveryScope(sole), ...scoped]));
1193
+ return problems.length > 0 ? Effect13.fail(new CloudflareGroupsError(mount, problems)) : Effect13.succeed(new Map([...atEveryScope(sole), ...scoped]));
1130
1194
  });
1131
- var permissionGroupsFromEngine = () => Effect11.sync(() => {
1195
+ var permissionGroupsFromEngine = () => Effect13.sync(() => {
1132
1196
  const cache = new Map;
1133
- const cachedFor = (mount) => Effect11.suspend(() => {
1197
+ const cachedFor = (mount) => Effect13.suspend(() => {
1134
1198
  const hit = cache.get(mountPath(mount));
1135
1199
  if (hit !== undefined)
1136
1200
  return hit;
1137
- return Effect11.flatMap(Effect11.cached(engineGroups(mount)), (lookup) => {
1201
+ return Effect13.flatMap(Effect13.cached(engineGroups(mount)), (lookup) => {
1138
1202
  cache.set(mountPath(mount), lookup);
1139
1203
  return lookup;
1140
1204
  });
1141
1205
  });
1142
1206
  return CloudflarePermissionGroups.of({
1143
- resolve: (mount, refs) => Effect11.flatMap(cachedFor(mount), (groups) => pick(mount, groups, refs, "the engine does not list it for this account at that scope"))
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"))
1144
1208
  });
1145
1209
  });
1146
- var permissionGroupsFromLiveRoles = (declared) => Effect11.gen(function* () {
1210
+ var permissionGroupsFromLiveRoles = (declared) => Effect13.gen(function* () {
1147
1211
  const accountOf = new Map;
1148
1212
  const byAccount = new Map;
1149
1213
  for (const [mount, roles] of declared) {
@@ -1154,9 +1218,9 @@ var permissionGroupsFromLiveRoles = (declared) => Effect11.gen(function* () {
1154
1218
  }
1155
1219
  const solved = new Map;
1156
1220
  for (const [account, roles] of byAccount) {
1157
- const solve = Effect11.gen(function* () {
1221
+ const solve = Effect13.gen(function* () {
1158
1222
  const live = new Map;
1159
- yield* Effect11.all(roles.map((role) => Effect11.map(readCloudflareRole(role.mount, role.name), (read) => {
1223
+ yield* Effect13.all(roles.map((role) => Effect13.map(readCloudflareRole(role.mount, role.name), (read) => {
1160
1224
  if (read?.policies !== undefined) {
1161
1225
  live.set(`${role.mount}/${role.name}`, read.policies);
1162
1226
  }
@@ -1164,11 +1228,11 @@ var permissionGroupsFromLiveRoles = (declared) => Effect11.gen(function* () {
1164
1228
  const result = solveGroups(observationsOf(roles, live));
1165
1229
  const problems = describeProblems(result);
1166
1230
  if (problems.length > 0) {
1167
- return yield* Effect11.fail(new CloudflareGroupsError(`account ${account}`, problems));
1231
+ return yield* Effect13.fail(new CloudflareGroupsError(`account ${account}`, problems));
1168
1232
  }
1169
1233
  return atEveryScope(result.groups);
1170
1234
  });
1171
- solved.set(account, yield* Effect11.cached(solve));
1235
+ solved.set(account, yield* Effect13.cached(solve));
1172
1236
  }
1173
1237
  return CloudflarePermissionGroups.of({
1174
1238
  resolve: (mount, refs) => {
@@ -1176,10 +1240,10 @@ var permissionGroupsFromLiveRoles = (declared) => Effect11.gen(function* () {
1176
1240
  const lookup = account === undefined ? undefined : solved.get(account);
1177
1241
  if (account === undefined || lookup === undefined) {
1178
1242
  const why = "no declared role on this mount, so no live role can vouch for its groups";
1179
- return Effect11.fail(new CloudflareGroupsError(mount, [why]));
1243
+ return Effect13.fail(new CloudflareGroupsError(mount, [why]));
1180
1244
  }
1181
1245
  const hint = `no live role on account ${account} carries it; a NEW group needs the engine endpoint`;
1182
- return Effect11.flatMap(lookup, (groups) => pick(mount, groups, refs, hint));
1246
+ return Effect13.flatMap(lookup, (groups) => pick(mount, groups, refs, hint));
1183
1247
  }
1184
1248
  });
1185
1249
  });
@@ -1188,9 +1252,10 @@ var permissionGroupsFromLiveRoles = (declared) => Effect11.gen(function* () {
1188
1252
  var BaoCloudflareRole = Resource3("Bao.CloudflareRole", {
1189
1253
  defaultRemovalPolicy: "retain"
1190
1254
  });
1191
- var BaoCloudflareRoleProvider = () => Provider3.effect(BaoCloudflareRole, Effect12.gen(function* () {
1255
+ var IDENTITY2 = roleIdentity("Bao.CloudflareRole", rolePath);
1256
+ var BaoCloudflareRoleProvider = () => Provider3.effect(BaoCloudflareRole, Effect14.gen(function* () {
1192
1257
  const groups = yield* CloudflarePermissionGroups;
1193
- const resolved = (props) => Effect12.gen(function* () {
1258
+ const resolved = (props) => Effect14.gen(function* () {
1194
1259
  const refs = new Map;
1195
1260
  for (const policy of props.policies) {
1196
1261
  const scope = policy.scope ?? scopeOfResource(policy.resource);
@@ -1200,18 +1265,23 @@ var BaoCloudflareRoleProvider = () => Provider3.effect(BaoCloudflareRole, Effect
1200
1265
  const ids = yield* groups.resolve(props.mount, [...refs.values()]);
1201
1266
  const { missing, policies } = resolvePolicies(props.policies, ids);
1202
1267
  if (missing.length > 0) {
1203
- return yield* Effect12.die(new Error(`Bao.CloudflareRole ${rolePath(props.mount, props.name)}: groups ` + `${missing.join(", ")} came back unresolved from a lookup that reported success.`));
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.`));
1204
1269
  }
1205
1270
  return policies;
1206
1271
  });
1207
1272
  return BaoCloudflareRole.Provider.of({
1208
- list: () => Effect12.succeed([]),
1209
- read: Effect12.fn(function* ({ olds }) {
1273
+ list: () => Effect14.succeed([]),
1274
+ read: Effect14.fn(function* ({ olds }) {
1210
1275
  const live = yield* readCloudflareRole(olds.mount, olds.name);
1211
1276
  return live === undefined ? undefined : attributesOf4(olds, live);
1212
1277
  }),
1213
- diff: Effect12.fn(function* ({ news, output }) {
1214
- if (output === undefined || !isResolved3(news))
1278
+ diff: Effect14.fn(function* ({ news, olds, output }) {
1279
+ const move = yield* judgeRename(IDENTITY2, olds, news, output);
1280
+ if (output === undefined)
1281
+ return;
1282
+ if (move !== undefined)
1283
+ return { action: "replace" };
1284
+ if (!isResolved3(news))
1215
1285
  return;
1216
1286
  if (refusalOf(news) !== undefined)
1217
1287
  return { action: "update" };
@@ -1221,11 +1291,12 @@ var BaoCloudflareRoleProvider = () => Provider3.effect(BaoCloudflareRole, Effect
1221
1291
  return { action: "update" };
1222
1292
  return differences(news, policies, live).length === 0 ? { action: "noop" } : { action: "update" };
1223
1293
  }),
1224
- reconcile: Effect12.fn(function* ({ news }) {
1294
+ reconcile: Effect14.fn(function* ({ news, output }) {
1225
1295
  const path = rolePath(news.mount, news.name);
1296
+ yield* guardRename(IDENTITY2, news, output);
1226
1297
  const refusal2 = refusalOf(news);
1227
1298
  if (refusal2 !== undefined) {
1228
- return yield* Effect12.die(new Error(`Bao.CloudflareRole ${path}: ${refusal2}.`));
1299
+ return yield* Effect14.die(new Error(`Bao.CloudflareRole ${path}: ${refusal2}.`));
1229
1300
  }
1230
1301
  const policies = yield* resolved(news);
1231
1302
  const live = yield* readCloudflareRole(news.mount, news.name);
@@ -1234,15 +1305,15 @@ var BaoCloudflareRoleProvider = () => Provider3.effect(BaoCloudflareRole, Effect
1234
1305
  }
1235
1306
  const after = yield* readCloudflareRole(news.mount, news.name);
1236
1307
  if (after === undefined) {
1237
- return yield* Effect12.die(new Error(`Bao.CloudflareRole ${path}: the write succeeded but the role is absent.`));
1308
+ return yield* Effect14.die(new Error(`Bao.CloudflareRole ${path}: the write succeeded but the role is absent.`));
1238
1309
  }
1239
1310
  const left = differences(news, policies, after);
1240
1311
  if (left.length > 0) {
1241
- return yield* Effect12.die(new Error(`Bao.CloudflareRole ${path}: wrote cleanly but reads back different in ` + `${left.map((d) => d.field).join(", ")}. Inspect the role before retrying.`));
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.`));
1242
1313
  }
1243
1314
  return attributesOf4(news, after);
1244
1315
  }),
1245
- delete: Effect12.fn(function* ({ output }) {
1316
+ delete: Effect14.fn(function* ({ output }) {
1246
1317
  yield* deleteCloudflareRole(output.mount, output.name);
1247
1318
  return;
1248
1319
  })
@@ -1465,17 +1536,19 @@ var hostAppRoles = (input) => {
1465
1536
  if (!NAME.test(host.name)) {
1466
1537
  problems.push(`host \`${host.name}\` is not lowercase letters, digits, single hyphens`);
1467
1538
  }
1468
- if (seen.has(host.name))
1469
- problems.push(`host \`${host.name}\` is listed twice`);
1470
- seen.add(host.name);
1471
- const spec = input.classes[host.class];
1539
+ const name = hostRoleName(host.class, host.name);
1540
+ if (seen.has(name)) {
1541
+ problems.push(`host \`${host.name}\` is listed twice in class \`${host.class}\``);
1542
+ }
1543
+ seen.add(name);
1544
+ const spec = Object.hasOwn(input.classes, host.class) ? input.classes[host.class] : undefined;
1472
1545
  if (spec === undefined) {
1473
1546
  problems.push(`host \`${host.name}\` names unknown class \`${host.class}\``);
1474
1547
  continue;
1475
1548
  }
1476
1549
  roles.push({
1477
1550
  bindSecretId: true,
1478
- name: hostRoleName(host.class, host.name),
1551
+ name,
1479
1552
  secretIdNumUses: spec.secretIdNumUses ?? 0,
1480
1553
  secretIdTtl: spec.secretIdTtl,
1481
1554
  tokenMaxTtl: spec.tokenMaxTtl,
@@ -1492,7 +1565,7 @@ var hostAppRoles = (input) => {
1492
1565
  import { Resource as Resource4 } from "alchemy";
1493
1566
  import { isResolved as isResolved4 } from "alchemy/Diff";
1494
1567
  import * as Provider4 from "alchemy/Provider";
1495
- import * as Effect14 from "effect/Effect";
1568
+ import * as Effect16 from "effect/Effect";
1496
1569
 
1497
1570
  // src/openbao/jwt-config-form.ts
1498
1571
  var text5 = (value) => typeof value === "string" ? value.trim() : "";
@@ -1582,16 +1655,16 @@ var problems = (props) => {
1582
1655
  };
1583
1656
 
1584
1657
  // src/openbao/role-reconcile.ts
1585
- import * as Effect13 from "effect/Effect";
1586
- var readRoleAt = (spec) => Effect13.map(baoRead(spec.path), (live) => live === undefined ? undefined : { attributes: spec.attributesOf(live), live });
1587
- var planRole = (spec) => Effect13.gen(function* () {
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* () {
1588
1661
  if (spec.problems.length > 0)
1589
1662
  return "update";
1590
1663
  const found = yield* readRoleAt(spec);
1591
1664
  return found !== undefined && spec.matches(found.attributes) ? "noop" : "update";
1592
1665
  });
1593
- var refuse2 = (spec, message) => Effect13.die(new Error(`${spec.family} ${spec.path}: ${message}`));
1594
- var reconcileRole = (spec) => Effect13.gen(function* () {
1666
+ var refuse2 = (spec, message) => Effect15.die(new Error(`${spec.family} ${spec.path}: ${message}`));
1667
+ var reconcileRole = (spec) => Effect15.gen(function* () {
1595
1668
  if (spec.problems.length > 0)
1596
1669
  return yield* refuse2(spec, spec.problems.join("; "));
1597
1670
  const found = yield* readRoleAt(spec);
@@ -1615,6 +1688,14 @@ var reconcileRole = (spec) => Effect13.gen(function* () {
1615
1688
  var BaoJwtAuthConfig = Resource4("Bao.JwtAuthConfig", {
1616
1689
  defaultRemovalPolicy: "retain"
1617
1690
  });
1691
+ var IDENTITY3 = {
1692
+ declared: (props) => {
1693
+ const mount = declaredOr(props, "mount", "jwt");
1694
+ return mount === undefined ? undefined : configPath({ mount });
1695
+ },
1696
+ family: "Bao.JwtAuthConfig",
1697
+ recorded: (output) => configPath(output)
1698
+ };
1618
1699
  var jwtConfigSpec = (props) => ({
1619
1700
  attributesOf: (live) => attributesOf5(props, live),
1620
1701
  body: writeBody3(props),
@@ -1624,29 +1705,33 @@ var jwtConfigSpec = (props) => ({
1624
1705
  problems: problems(props),
1625
1706
  wouldErase
1626
1707
  });
1627
- var BaoJwtAuthConfigProvider = () => Provider4.effect(BaoJwtAuthConfig, Effect14.succeed(BaoJwtAuthConfig.Provider.of({
1628
- list: () => Effect14.succeed([]),
1629
- read: Effect14.fn(function* ({ olds }) {
1708
+ var BaoJwtAuthConfigProvider = () => Provider4.effect(BaoJwtAuthConfig, Effect16.succeed(BaoJwtAuthConfig.Provider.of({
1709
+ list: () => Effect16.succeed([]),
1710
+ read: Effect16.fn(function* ({ olds }) {
1630
1711
  const found = yield* readRoleAt(jwtConfigSpec(olds));
1631
1712
  return found?.attributes;
1632
1713
  }),
1633
- diff: Effect14.fn(function* ({ news, output }) {
1634
- if (output === undefined || !isResolved4(news))
1714
+ diff: Effect16.fn(function* ({ news, olds, output }) {
1715
+ const move = yield* judgeRename(IDENTITY3, olds, news, output);
1716
+ if (output === undefined)
1635
1717
  return;
1636
- if (mountOf(news) !== output.mount)
1718
+ if (move !== undefined)
1637
1719
  return { action: "replace" };
1720
+ if (!isResolved4(news))
1721
+ return;
1638
1722
  return { action: yield* planRole(jwtConfigSpec(news)) };
1639
1723
  }),
1640
- reconcile: Effect14.fn(function* ({ news }) {
1724
+ reconcile: Effect16.fn(function* ({ news, output }) {
1725
+ yield* guardRename(IDENTITY3, news, output);
1641
1726
  return yield* reconcileRole(jwtConfigSpec(news));
1642
1727
  }),
1643
- delete: () => Effect14.void
1728
+ delete: () => Effect16.void
1644
1729
  })));
1645
1730
  // src/openbao/jwt-role.ts
1646
1731
  import { Resource as Resource5 } from "alchemy";
1647
1732
  import { isResolved as isResolved5 } from "alchemy/Diff";
1648
1733
  import * as Provider5 from "alchemy/Provider";
1649
- import * as Effect15 from "effect/Effect";
1734
+ import * as Effect17 from "effect/Effect";
1650
1735
 
1651
1736
  // src/openbao/auth-token-form.ts
1652
1737
  var sortedSet2 = (values) => [...new Set(values)].sort();
@@ -1848,6 +1933,7 @@ var problems2 = (props) => {
1848
1933
  var BaoJwtRole = Resource5("Bao.JwtRole", {
1849
1934
  defaultRemovalPolicy: "retain"
1850
1935
  });
1936
+ var IDENTITY4 = roleIdentity("Bao.JwtRole", (mount, name) => rolePath2({ mount, name }), "jwt", foldName);
1851
1937
  var jwtRoleSpec = (props) => ({
1852
1938
  attributesOf: (live) => attributesOf6(props, live),
1853
1939
  body: writeBody4(props),
@@ -1856,24 +1942,27 @@ var jwtRoleSpec = (props) => ({
1856
1942
  path: rolePath2(props),
1857
1943
  problems: problems2(props)
1858
1944
  });
1859
- var BaoJwtRoleProvider = () => Provider5.effect(BaoJwtRole, Effect15.succeed(BaoJwtRole.Provider.of({
1860
- list: () => Effect15.succeed([]),
1861
- read: Effect15.fn(function* ({ olds }) {
1945
+ var BaoJwtRoleProvider = () => Provider5.effect(BaoJwtRole, Effect17.succeed(BaoJwtRole.Provider.of({
1946
+ list: () => Effect17.succeed([]),
1947
+ read: Effect17.fn(function* ({ olds }) {
1862
1948
  const found = yield* readRoleAt(jwtRoleSpec(olds));
1863
1949
  return found?.attributes;
1864
1950
  }),
1865
- diff: Effect15.fn(function* ({ news, output }) {
1866
- if (output === undefined || !isResolved5(news))
1951
+ diff: Effect17.fn(function* ({ news, olds, output }) {
1952
+ const move = yield* judgeRename(IDENTITY4, olds, news, output);
1953
+ if (output === undefined)
1867
1954
  return;
1868
- if (mountOf2(news) !== output.mount || news.name !== output.name) {
1955
+ if (move !== undefined)
1869
1956
  return { action: "replace" };
1870
- }
1957
+ if (!isResolved5(news))
1958
+ return;
1871
1959
  return { action: yield* planRole(jwtRoleSpec(news)) };
1872
1960
  }),
1873
- reconcile: Effect15.fn(function* ({ news }) {
1961
+ reconcile: Effect17.fn(function* ({ news, output }) {
1962
+ yield* guardRename(IDENTITY4, news, output);
1874
1963
  return yield* reconcileRole(jwtRoleSpec(news));
1875
1964
  }),
1876
- delete: Effect15.fn(function* ({ output }) {
1965
+ delete: Effect17.fn(function* ({ output }) {
1877
1966
  yield* baoDelete(rolePath2(output));
1878
1967
  return;
1879
1968
  })
@@ -1882,7 +1971,7 @@ var BaoJwtRoleProvider = () => Provider5.effect(BaoJwtRole, Effect15.succeed(Bao
1882
1971
  import { Resource as Resource6 } from "alchemy";
1883
1972
  import { isResolved as isResolved6 } from "alchemy/Diff";
1884
1973
  import * as Provider6 from "alchemy/Provider";
1885
- import * as Effect16 from "effect/Effect";
1974
+ import * as Effect18 from "effect/Effect";
1886
1975
 
1887
1976
  // src/openbao/kubernetes-role-form.ts
1888
1977
  var sortedSet4 = (values) => [...new Set(values)].sort();
@@ -1951,6 +2040,7 @@ var problems3 = (props) => {
1951
2040
  var BaoKubernetesRole = Resource6("Bao.KubernetesRole", {
1952
2041
  defaultRemovalPolicy: "retain"
1953
2042
  });
2043
+ var IDENTITY5 = roleIdentity("Bao.KubernetesRole", (mount, name) => rolePath3({ mount, name }), "kubernetes", foldName);
1954
2044
  var kubernetesRoleSpec = (props) => ({
1955
2045
  attributesOf: (live) => attributesOf7(props, live),
1956
2046
  body: writeBody5(props),
@@ -1959,24 +2049,27 @@ var kubernetesRoleSpec = (props) => ({
1959
2049
  path: rolePath3(props),
1960
2050
  problems: problems3(props)
1961
2051
  });
1962
- var BaoKubernetesRoleProvider = () => Provider6.effect(BaoKubernetesRole, Effect16.succeed(BaoKubernetesRole.Provider.of({
1963
- list: () => Effect16.succeed([]),
1964
- read: Effect16.fn(function* ({ olds }) {
2052
+ var BaoKubernetesRoleProvider = () => Provider6.effect(BaoKubernetesRole, Effect18.succeed(BaoKubernetesRole.Provider.of({
2053
+ list: () => Effect18.succeed([]),
2054
+ read: Effect18.fn(function* ({ olds }) {
1965
2055
  const found = yield* readRoleAt(kubernetesRoleSpec(olds));
1966
2056
  return found?.attributes;
1967
2057
  }),
1968
- diff: Effect16.fn(function* ({ news, output }) {
1969
- if (output === undefined || !isResolved6(news))
2058
+ diff: Effect18.fn(function* ({ news, olds, output }) {
2059
+ const move = yield* judgeRename(IDENTITY5, olds, news, output);
2060
+ if (output === undefined)
1970
2061
  return;
1971
- if (mountOf3(news) !== output.mount || news.name !== output.name) {
2062
+ if (move !== undefined)
1972
2063
  return { action: "replace" };
1973
- }
2064
+ if (!isResolved6(news))
2065
+ return;
1974
2066
  return { action: yield* planRole(kubernetesRoleSpec(news)) };
1975
2067
  }),
1976
- reconcile: Effect16.fn(function* ({ news }) {
2068
+ reconcile: Effect18.fn(function* ({ news, output }) {
2069
+ yield* guardRename(IDENTITY5, news, output);
1977
2070
  return yield* reconcileRole(kubernetesRoleSpec(news));
1978
2071
  }),
1979
- delete: Effect16.fn(function* ({ output }) {
2072
+ delete: Effect18.fn(function* ({ output }) {
1980
2073
  yield* baoDelete(rolePath3(output));
1981
2074
  return;
1982
2075
  })
@@ -1985,7 +2078,7 @@ var BaoKubernetesRoleProvider = () => Provider6.effect(BaoKubernetesRole, Effect
1985
2078
  import { Resource as Resource7 } from "alchemy";
1986
2079
  import { isResolved as isResolved7 } from "alchemy/Diff";
1987
2080
  import * as Provider7 from "alchemy/Provider";
1988
- import * as Effect18 from "effect/Effect";
2081
+ import * as Effect20 from "effect/Effect";
1989
2082
 
1990
2083
  // src/openbao/mfa-enforcement-form.ts
1991
2084
  var sortedSet5 = (values) => [...new Set(values.map((value) => value.trim()))].filter((value) => value !== "").sort();
@@ -2053,16 +2146,16 @@ var problems4 = (props, missingPaths) => {
2053
2146
  };
2054
2147
 
2055
2148
  // src/openbao/mfa-wire.ts
2056
- import * as Effect17 from "effect/Effect";
2149
+ import * as Effect19 from "effect/Effect";
2057
2150
  var TOTP_PATH = "identity/mfa/method/totp";
2058
2151
  var METHODS_PATH = "identity/mfa/method";
2059
2152
  var enforcementPath = (name) => `identity/mfa/login-enforcement/${name}`;
2060
2153
  var record4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
2061
- var ownNamespacePath = Effect17.gen(function* () {
2154
+ var ownNamespacePath = Effect19.gen(function* () {
2062
2155
  const env = yield* BaoEnv;
2063
2156
  return namespacePath(envNamespace(env));
2064
2157
  });
2065
- var findTotp = (name) => Effect17.gen(function* () {
2158
+ var findTotp = (name) => Effect19.gen(function* () {
2066
2159
  const listing = yield* baoRead(`${METHODS_PATH}?list=true`);
2067
2160
  const info = record4(listing?.["key_info"]) ?? {};
2068
2161
  const own = yield* ownNamespacePath;
@@ -2071,17 +2164,17 @@ var findTotp = (name) => Effect17.gen(function* () {
2071
2164
  if (live === undefined || live["name"] !== name || live["namespace_path"] !== own)
2072
2165
  continue;
2073
2166
  if (live["type"] !== "totp") {
2074
- return yield* Effect17.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.`));
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.`));
2075
2168
  }
2076
2169
  return { id, live };
2077
2170
  }
2078
2171
  return;
2079
2172
  });
2080
- var writeTotp = (body) => Effect17.asVoid(baoCall("write", "POST", TOTP_PATH, body));
2173
+ var writeTotp = (body) => Effect19.asVoid(baoCall("write", "POST", TOTP_PATH, body));
2081
2174
  var deleteTotp = (id) => baoDelete(`${TOTP_PATH}/${id}`);
2082
2175
  var readEnforcement = (name) => baoRead(enforcementPath(name));
2083
- var writeEnforcement = (name, body) => Effect17.asVoid(baoCall("write", "POST", enforcementPath(name), body));
2084
- var authAccessors = (paths) => Effect17.gen(function* () {
2176
+ var writeEnforcement = (name, body) => Effect19.asVoid(baoCall("write", "POST", enforcementPath(name), body));
2177
+ var authAccessors = (paths) => Effect19.gen(function* () {
2085
2178
  if (paths.length === 0)
2086
2179
  return { accessors: [], missing: [] };
2087
2180
  const table = (yield* baoRead("sys/auth")) ?? {};
@@ -2101,20 +2194,21 @@ var authAccessors = (paths) => Effect17.gen(function* () {
2101
2194
  var BaoMfaLoginEnforcement = Resource7("Bao.MfaLoginEnforcement", {
2102
2195
  defaultRemovalPolicy: "retain"
2103
2196
  });
2104
- var refuse3 = (name, message) => Effect18.die(new Error(`Bao.MfaLoginEnforcement ${name}: ${message}`));
2105
- var resolveTargets = (props) => Effect18.map(authAccessors(props.authMethodPaths ?? []), ({ accessors, missing }) => ({
2197
+ 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 }) => ({
2106
2200
  bad: problems4(props, missing),
2107
2201
  resolved: accessors
2108
2202
  }));
2109
- var readLive = (name) => Effect18.map(readEnforcement(name), (live) => live === undefined ? undefined : attributesOf8(name, live));
2110
- var planEnforcement = (props) => Effect18.gen(function* () {
2203
+ var readLive = (name) => Effect20.map(readEnforcement(name), (live) => live === undefined ? undefined : attributesOf8(name, live));
2204
+ var planEnforcement = (props) => Effect20.gen(function* () {
2111
2205
  const { bad, resolved } = yield* resolveTargets(props);
2112
2206
  if (bad.length > 0)
2113
2207
  return "update";
2114
2208
  const live = yield* readLive(props.name);
2115
2209
  return live !== undefined && matches7(live, props, resolved) ? "noop" : "update";
2116
2210
  });
2117
- var reconcileEnforcement = (props) => Effect18.gen(function* () {
2211
+ var reconcileEnforcement = (props) => Effect20.gen(function* () {
2118
2212
  const { bad, resolved } = yield* resolveTargets(props);
2119
2213
  if (bad.length > 0)
2120
2214
  return yield* refuse3(props.name, bad.join("; "));
@@ -2130,22 +2224,26 @@ var reconcileEnforcement = (props) => Effect18.gen(function* () {
2130
2224
  }
2131
2225
  return after;
2132
2226
  });
2133
- var BaoMfaLoginEnforcementProvider = () => Provider7.effect(BaoMfaLoginEnforcement, Effect18.succeed(BaoMfaLoginEnforcement.Provider.of({
2134
- list: () => Effect18.succeed([]),
2135
- read: Effect18.fn(function* ({ olds }) {
2227
+ var BaoMfaLoginEnforcementProvider = () => Provider7.effect(BaoMfaLoginEnforcement, Effect20.succeed(BaoMfaLoginEnforcement.Provider.of({
2228
+ list: () => Effect20.succeed([]),
2229
+ read: Effect20.fn(function* ({ olds }) {
2136
2230
  return yield* readLive(olds.name);
2137
2231
  }),
2138
- diff: Effect18.fn(function* ({ news, output }) {
2139
- if (output === undefined || !isResolved7(news))
2232
+ diff: Effect20.fn(function* ({ news, olds, output }) {
2233
+ const move = yield* judgeRename(IDENTITY6, olds, news, output);
2234
+ if (output === undefined)
2140
2235
  return;
2141
- if (news.name !== output.name)
2236
+ if (move !== undefined)
2142
2237
  return { action: "replace" };
2238
+ if (!isResolved7(news))
2239
+ return;
2143
2240
  return { action: yield* planEnforcement(news) };
2144
2241
  }),
2145
- reconcile: Effect18.fn(function* ({ news }) {
2242
+ reconcile: Effect20.fn(function* ({ news, output }) {
2243
+ yield* guardRename(IDENTITY6, news, output);
2146
2244
  return yield* reconcileEnforcement(news);
2147
2245
  }),
2148
- delete: Effect18.fn(function* ({ output }) {
2246
+ delete: Effect20.fn(function* ({ output }) {
2149
2247
  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.");
2150
2248
  })
2151
2249
  })));
@@ -2153,7 +2251,7 @@ var BaoMfaLoginEnforcementProvider = () => Provider7.effect(BaoMfaLoginEnforceme
2153
2251
  import { Resource as Resource8 } from "alchemy";
2154
2252
  import { isResolved as isResolved8 } from "alchemy/Diff";
2155
2253
  import * as Provider8 from "alchemy/Provider";
2156
- import * as Effect19 from "effect/Effect";
2254
+ import * as Effect21 from "effect/Effect";
2157
2255
 
2158
2256
  // src/openbao/mfa-totp-form.ts
2159
2257
  var canonical6 = (input) => ({
@@ -2234,45 +2332,51 @@ var problems5 = (props) => {
2234
2332
  var BaoMfaTotpMethod = Resource8("Bao.MfaTotpMethod", {
2235
2333
  defaultRemovalPolicy: "retain"
2236
2334
  });
2237
- var refuse4 = (props, message) => Effect19.die(new Error(`Bao.MfaTotpMethod ${props.name}: ${message}`));
2238
- var readTotp = (props) => Effect19.map(findTotp(props.name), (found) => found === undefined ? undefined : attributesOf9(found.id, found.live));
2239
- var planTotp = (props) => Effect19.gen(function* () {
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* () {
2240
2338
  if (problems5(props).length > 0)
2241
2339
  return "update";
2242
2340
  const live = yield* readTotp(props);
2243
2341
  return live !== undefined && matches8(live, props) ? "noop" : "update";
2244
2342
  });
2245
- var reconcileTotp = (props) => Effect19.gen(function* () {
2343
+ var reconcileTotp = (props) => Effect21.gen(function* () {
2246
2344
  const bad = problems5(props);
2247
2345
  if (bad.length > 0)
2248
- return yield* refuse4(props, bad.join("; "));
2346
+ return yield* refuse4(props.name, bad.join("; "));
2249
2347
  const live = yield* readTotp(props);
2250
2348
  if (live === undefined || !matches8(live, props))
2251
2349
  yield* writeTotp(writeBody7(props));
2252
2350
  const after = yield* readTotp(props);
2253
2351
  if (after === undefined)
2254
- return yield* refuse4(props, "the write succeeded but no method has that name.");
2352
+ return yield* refuse4(props.name, "the write succeeded but no method has that name.");
2255
2353
  if (!matches8(after, props))
2256
- return yield* refuse4(props, "the method read back different.");
2354
+ return yield* refuse4(props.name, "the method read back different.");
2257
2355
  return after;
2258
2356
  });
2259
- var BaoMfaTotpMethodProvider = () => Provider8.effect(BaoMfaTotpMethod, Effect19.succeed(BaoMfaTotpMethod.Provider.of({
2260
- list: () => Effect19.succeed([]),
2261
- read: Effect19.fn(function* ({ olds }) {
2357
+ var BaoMfaTotpMethodProvider = () => Provider8.effect(BaoMfaTotpMethod, Effect21.succeed(BaoMfaTotpMethod.Provider.of({
2358
+ list: () => Effect21.succeed([]),
2359
+ read: Effect21.fn(function* ({ olds }) {
2262
2360
  return yield* readTotp(olds);
2263
2361
  }),
2264
- diff: Effect19.fn(function* ({ news, output }) {
2362
+ diff: Effect21.fn(function* ({ news, output }) {
2363
+ const name = declaredString(news, "name");
2364
+ if (output !== undefined && name !== undefined) {
2365
+ const rename = renameProblem(output.name, name);
2366
+ if (rename !== undefined)
2367
+ return yield* refuse4(name, rename);
2368
+ }
2265
2369
  if (output === undefined || !isResolved8(news))
2266
2370
  return;
2267
- const rename = renameProblem(output.name, news.name);
2268
- if (rename !== undefined)
2269
- return yield* refuse4(news, rename);
2270
2371
  return { action: yield* planTotp(news) };
2271
2372
  }),
2272
- reconcile: Effect19.fn(function* ({ news }) {
2373
+ reconcile: Effect21.fn(function* ({ news, output }) {
2374
+ const rename = output === undefined ? undefined : renameProblem(output.name, news.name);
2375
+ if (rename !== undefined)
2376
+ return yield* refuse4(news.name, rename);
2273
2377
  return yield* reconcileTotp(news);
2274
2378
  }),
2275
- delete: Effect19.fn(function* ({ output }) {
2379
+ delete: Effect21.fn(function* ({ output }) {
2276
2380
  yield* deleteTotp(output.methodId);
2277
2381
  return;
2278
2382
  })
@@ -2281,24 +2385,24 @@ var BaoMfaTotpMethodProvider = () => Provider8.effect(BaoMfaTotpMethod, Effect19
2281
2385
  import { Resource as Resource9 } from "alchemy";
2282
2386
  import { isResolved as isResolved9 } from "alchemy/Diff";
2283
2387
  import * as Provider9 from "alchemy/Provider";
2284
- import * as Effect22 from "effect/Effect";
2388
+ import * as Effect24 from "effect/Effect";
2285
2389
 
2286
2390
  // src/openbao/mount-reconcile.ts
2287
- import * as Effect21 from "effect/Effect";
2391
+ import * as Effect23 from "effect/Effect";
2288
2392
 
2289
2393
  // src/openbao/mount-wire.ts
2290
- import * as Effect20 from "effect/Effect";
2291
- var absentFromTable2 = (path, asked) => baoRead("sys/mounts").pipe(Effect20.mapError((listing) => new BaoError(asked.status, asked.method, asked.path, [
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, [
2292
2396
  ...asked.errors,
2293
2397
  `sys/mounts could not confirm the mount is absent: ${listing.message}`
2294
- ])), Effect20.flatMap((table) => table !== undefined && !Object.hasOwn(table, `${mountPath(path)}/`) ? Effect20.succeed(undefined) : Effect20.fail(asked)));
2295
- var readMountData = (path) => baoRead(readPath(path)).pipe(Effect20.catchIf((error) => error.status === 400, (error) => absentFromTable2(path, error)));
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)));
2296
2400
  var enableMount = (props) => baoWrite("POST", readPath(props.path), enableBody(props));
2297
2401
  var tuneMount = (props) => baoWrite("POST", tunePath(props.path), tuneBody(props));
2298
2402
  var disableMount = (path) => baoDelete(readPath(path));
2299
2403
 
2300
2404
  // src/openbao/mount-reconcile.ts
2301
- var readMount = (props) => Effect21.gen(function* () {
2405
+ var readMount = (props) => Effect23.gen(function* () {
2302
2406
  const live = yield* readMountData(props.path);
2303
2407
  if (live === undefined)
2304
2408
  return;
@@ -2311,10 +2415,10 @@ var moveTarget2 = (props) => ({
2311
2415
  type: props.type,
2312
2416
  wire: mountPath
2313
2417
  });
2314
- var reconcileMount = (news, stated, options) => Effect21.gen(function* () {
2418
+ var reconcileMount = (news, stated, options) => Effect23.gen(function* () {
2315
2419
  const move = planMove("Bao.Mount", stated, news.path, news.remountFrom);
2316
2420
  if (move.kind === "refuse")
2317
- return yield* Effect21.die(new Error(move.message));
2421
+ return yield* Effect23.die(new Error(move.message));
2318
2422
  if (move.kind === "move")
2319
2423
  yield* performMove(moveTarget2(news), move.from, options);
2320
2424
  let live = yield* readMount(news);
@@ -2324,13 +2428,13 @@ var reconcileMount = (news, stated, options) => Effect21.gen(function* () {
2324
2428
  yield* tuneMount(news);
2325
2429
  } else if (!matches(live, news)) {
2326
2430
  if (live.type !== news.type) {
2327
- return yield* Effect21.die(new Error(`Bao.Mount ${mountPath(news.path)}: live type ${live.type} != ${news.type}. ` + "Secrets engine type is immutable — replace manually."));
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."));
2328
2432
  }
2329
2433
  yield* tuneMount(news);
2330
2434
  }
2331
2435
  live = yield* readMount(news);
2332
2436
  if (live === undefined) {
2333
- return yield* Effect21.die(new Error(`Bao.Mount ${mountPath(news.path)}: write returned no error but the mount is still absent.`));
2437
+ return yield* Effect23.die(new Error(`Bao.Mount ${mountPath(news.path)}: write returned no error but the mount is still absent.`));
2334
2438
  }
2335
2439
  return live;
2336
2440
  });
@@ -2339,17 +2443,17 @@ var reconcileMount = (news, stated, options) => Effect21.gen(function* () {
2339
2443
  var BaoMount = Resource9("Bao.Mount", {
2340
2444
  defaultRemovalPolicy: "retain"
2341
2445
  });
2342
- var BaoMountProvider = () => Provider9.effect(BaoMount, Effect22.succeed(BaoMount.Provider.of({
2343
- list: () => Effect22.succeed([]),
2344
- read: Effect22.fn(function* ({ olds }) {
2446
+ var BaoMountProvider = () => Provider9.effect(BaoMount, Effect24.succeed(BaoMount.Provider.of({
2447
+ list: () => Effect24.succeed([]),
2448
+ read: Effect24.fn(function* ({ olds }) {
2345
2449
  return yield* readMount(olds);
2346
2450
  }),
2347
- diff: Effect22.fn(function* ({ news, output }) {
2451
+ diff: Effect24.fn(function* ({ news, output }) {
2348
2452
  if (output === undefined || !isResolved9(news))
2349
2453
  return;
2350
2454
  const move = planMove("Bao.Mount", output.path, news.path, news.remountFrom);
2351
2455
  if (move.kind === "refuse")
2352
- return yield* Effect22.die(new Error(move.message));
2456
+ return yield* Effect24.die(new Error(move.message));
2353
2457
  if (move.kind === "move")
2354
2458
  return { action: "update" };
2355
2459
  const live = yield* readMount(news);
@@ -2361,10 +2465,10 @@ var BaoMountProvider = () => Provider9.effect(BaoMount, Effect22.succeed(BaoMoun
2361
2465
  return { action: "noop" };
2362
2466
  return { action: "update" };
2363
2467
  }),
2364
- reconcile: Effect22.fn(function* ({ news, output }) {
2468
+ reconcile: Effect24.fn(function* ({ news, output }) {
2365
2469
  return yield* reconcileMount(news, output?.path);
2366
2470
  }),
2367
- delete: Effect22.fn(function* ({ output }) {
2471
+ delete: Effect24.fn(function* ({ output }) {
2368
2472
  yield* disableMount(output.path);
2369
2473
  return;
2370
2474
  })
@@ -2373,7 +2477,7 @@ var BaoMountProvider = () => Provider9.effect(BaoMount, Effect22.succeed(BaoMoun
2373
2477
  import { Resource as Resource10 } from "alchemy";
2374
2478
  import { isResolved as isResolved10 } from "alchemy/Diff";
2375
2479
  import * as Provider10 from "alchemy/Provider";
2376
- import * as Effect23 from "effect/Effect";
2480
+ import * as Effect25 from "effect/Effect";
2377
2481
 
2378
2482
  // src/openbao/pki-role-canonical.ts
2379
2483
  var DEFAULT_KEY_USAGE = [
@@ -2539,22 +2643,26 @@ var problems6 = (props) => {
2539
2643
  var BaoPkiRole = Resource10("Bao.PkiRole", {
2540
2644
  defaultRemovalPolicy: "retain"
2541
2645
  });
2542
- var readRole2 = (props) => Effect23.gen(function* () {
2646
+ var IDENTITY7 = roleIdentity("Bao.PkiRole", (mount, name) => rolePath4({ mount, name }), "pki");
2647
+ var readRole2 = (props) => Effect25.gen(function* () {
2543
2648
  const live = yield* baoRead(rolePath4(props));
2544
2649
  if (live === undefined)
2545
2650
  return;
2546
2651
  return attributesOf10(props, live);
2547
2652
  });
2548
- var BaoPkiRoleProvider = () => Provider10.effect(BaoPkiRole, Effect23.succeed(BaoPkiRole.Provider.of({
2549
- list: () => Effect23.succeed([]),
2550
- read: Effect23.fn(function* ({ olds }) {
2653
+ var BaoPkiRoleProvider = () => Provider10.effect(BaoPkiRole, Effect25.succeed(BaoPkiRole.Provider.of({
2654
+ list: () => Effect25.succeed([]),
2655
+ read: Effect25.fn(function* ({ olds }) {
2551
2656
  return yield* readRole2(olds);
2552
2657
  }),
2553
- diff: Effect23.fn(function* ({ news, output }) {
2554
- if (output === undefined || !isResolved10(news))
2658
+ diff: Effect25.fn(function* ({ news, olds, output }) {
2659
+ const move = yield* judgeRename(IDENTITY7, olds, news, output);
2660
+ if (output === undefined)
2555
2661
  return;
2556
- if (mountPath(news.mount ?? "pki") !== output.mount || news.name !== output.name)
2662
+ if (move !== undefined)
2557
2663
  return { action: "replace" };
2664
+ if (!isResolved10(news))
2665
+ return;
2558
2666
  if (problems6(news).length > 0)
2559
2667
  return { action: "update" };
2560
2668
  const live = yield* readRole2(news);
@@ -2562,11 +2670,12 @@ var BaoPkiRoleProvider = () => Provider10.effect(BaoPkiRole, Effect23.succeed(Ba
2562
2670
  return { action: "update" };
2563
2671
  return matches9(live, news) ? { action: "noop" } : { action: "update" };
2564
2672
  }),
2565
- reconcile: Effect23.fn(function* ({ news }) {
2673
+ reconcile: Effect25.fn(function* ({ news, output }) {
2566
2674
  const path = rolePath4(news);
2675
+ yield* guardRename(IDENTITY7, news, output);
2567
2676
  const bad = problems6(news);
2568
2677
  if (bad.length > 0) {
2569
- return yield* Effect23.die(new Error(`Bao.PkiRole ${path}: ${bad.join(" ")}`));
2678
+ return yield* Effect25.die(new Error(`Bao.PkiRole ${path}: ${bad.join(" ")}`));
2570
2679
  }
2571
2680
  const live = yield* readRole2(news);
2572
2681
  if (live === undefined || !matches9(live, news)) {
@@ -2574,14 +2683,14 @@ var BaoPkiRoleProvider = () => Provider10.effect(BaoPkiRole, Effect23.succeed(Ba
2574
2683
  }
2575
2684
  const after = yield* readRole2(news);
2576
2685
  if (after === undefined) {
2577
- return yield* Effect23.die(new Error(`Bao.PkiRole ${path}: write reported success but the role is absent.`));
2686
+ return yield* Effect25.die(new Error(`Bao.PkiRole ${path}: write reported success but the role is absent.`));
2578
2687
  }
2579
2688
  if (!matches9(after, news)) {
2580
- return yield* Effect23.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.`));
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.`));
2581
2690
  }
2582
2691
  return after;
2583
2692
  }),
2584
- delete: Effect23.fn(function* ({ output }) {
2693
+ delete: Effect25.fn(function* ({ output }) {
2585
2694
  yield* baoDelete(rolePath4(output));
2586
2695
  return;
2587
2696
  })
@@ -2590,7 +2699,7 @@ var BaoPkiRoleProvider = () => Provider10.effect(BaoPkiRole, Effect23.succeed(Ba
2590
2699
  import { Resource as Resource11 } from "alchemy";
2591
2700
  import { isResolved as isResolved11 } from "alchemy/Diff";
2592
2701
  import * as Provider11 from "alchemy/Provider";
2593
- import * as Effect25 from "effect/Effect";
2702
+ import * as Effect27 from "effect/Effect";
2594
2703
 
2595
2704
  // src/openbao/plugin-form.ts
2596
2705
  var resolve = (props) => ({
@@ -2646,7 +2755,7 @@ var sameArgs = (want, have) => want.length === have.length && want.every((entry,
2646
2755
  var matches10 = (attributes, form) => !attributes.builtin && attributes.version === form.version && attributes.sha256 === form.sha256 && attributes.command === form.command && sameArgs(form.args, attributes.args);
2647
2756
 
2648
2757
  // src/openbao/plugin-reconcile.ts
2649
- import * as Effect24 from "effect/Effect";
2758
+ import * as Effect26 from "effect/Effect";
2650
2759
 
2651
2760
  // src/openbao/plugin-wire.ts
2652
2761
  var readPluginData = (form) => baoRead(versionedPath(form.type, form.name, form.version));
@@ -2654,10 +2763,10 @@ var registerPlugin = (form) => baoWrite("PUT", catalogPath(form.type, form.name)
2654
2763
  var deregisterPlugin = (type, name, version) => baoDelete(versionedPath(type, name, version));
2655
2764
 
2656
2765
  // src/openbao/plugin-reconcile.ts
2657
- var readEntry = (form) => Effect24.map(readPluginData(form), (live) => live === undefined ? undefined : { attributes: attributesOf11(form, live), live });
2658
- var refuse5 = (form, message) => Effect24.die(new Error(`Bao.Plugin ${versionedPath(form.type, form.name, form.version)}: ${message}`));
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}`));
2659
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.";
2660
- var reconcilePlugin = (props) => Effect24.gen(function* () {
2769
+ var reconcilePlugin = (props) => Effect26.gen(function* () {
2661
2770
  const form = resolve(props);
2662
2771
  const bad = problems7(form);
2663
2772
  if (bad.length > 0)
@@ -2684,28 +2793,43 @@ var reconcilePlugin = (props) => Effect24.gen(function* () {
2684
2793
  var BaoPlugin = Resource11("Bao.Plugin", {
2685
2794
  defaultRemovalPolicy: "retain"
2686
2795
  });
2687
- var BaoPluginProvider = () => Provider11.effect(BaoPlugin, Effect25.succeed(BaoPlugin.Provider.of({
2688
- list: () => Effect25.succeed([]),
2689
- read: Effect25.fn(function* ({ olds }) {
2796
+ var IDENTITY8 = {
2797
+ declared: (props) => {
2798
+ const type = declaredString(props, "type");
2799
+ const name = declaredString(props, "name");
2800
+ const version = declaredOr(props, "version", "");
2801
+ if (type === undefined || name === undefined || version === undefined)
2802
+ return;
2803
+ return versionedPath(type, name, version);
2804
+ },
2805
+ family: "Bao.Plugin",
2806
+ recorded: (output) => versionedPath(output.type, output.name, output.version)
2807
+ };
2808
+ var BaoPluginProvider = () => Provider11.effect(BaoPlugin, Effect27.succeed(BaoPlugin.Provider.of({
2809
+ list: () => Effect27.succeed([]),
2810
+ read: Effect27.fn(function* ({ olds }) {
2690
2811
  const found = yield* readEntry(resolve(olds));
2691
2812
  return found?.attributes;
2692
2813
  }),
2693
- diff: Effect25.fn(function* ({ news, output }) {
2694
- if (output === undefined || !isResolved11(news))
2814
+ diff: Effect27.fn(function* ({ news, olds, output }) {
2815
+ const move = yield* judgeRename(IDENTITY8, olds, news, output);
2816
+ if (output === undefined)
2695
2817
  return;
2696
- const form = resolve(news);
2697
- if (output.name !== form.name || output.type !== form.type || output.version !== form.version) {
2818
+ if (move !== undefined)
2698
2819
  return { action: "replace" };
2699
- }
2820
+ if (!isResolved11(news))
2821
+ return;
2822
+ const form = resolve(news);
2700
2823
  const found = yield* readEntry(form);
2701
2824
  if (found === undefined)
2702
2825
  return { action: "update" };
2703
2826
  return matches10(found.attributes, form) ? { action: "noop" } : { action: "update" };
2704
2827
  }),
2705
- reconcile: Effect25.fn(function* ({ news }) {
2828
+ reconcile: Effect27.fn(function* ({ news, output }) {
2829
+ yield* guardRename(IDENTITY8, news, output);
2706
2830
  return yield* reconcilePlugin(news);
2707
2831
  }),
2708
- delete: Effect25.fn(function* ({ output }) {
2832
+ delete: Effect27.fn(function* ({ output }) {
2709
2833
  yield* deregisterPlugin(output.type, output.name, output.version);
2710
2834
  return;
2711
2835
  })
@@ -2714,7 +2838,7 @@ var BaoPluginProvider = () => Provider11.effect(BaoPlugin, Effect25.succeed(BaoP
2714
2838
  import { Resource as Resource12 } from "alchemy";
2715
2839
  import { isResolved as isResolved12 } from "alchemy/Diff";
2716
2840
  import * as Provider12 from "alchemy/Provider";
2717
- import * as Effect27 from "effect/Effect";
2841
+ import * as Effect29 from "effect/Effect";
2718
2842
  import * as FileSystem from "effect/FileSystem";
2719
2843
  import * as Path from "effect/Path";
2720
2844
 
@@ -2722,13 +2846,13 @@ import * as Path from "effect/Path";
2722
2846
  var isEmptyAssembly = (parts, joined) => parts === 0 || !/^\s*path\s+"/m.test(joined);
2723
2847
 
2724
2848
  // src/openbao/policy-wire.ts
2725
- import * as Effect26 from "effect/Effect";
2849
+ import * as Effect28 from "effect/Effect";
2726
2850
  var policyPath = (name) => `sys/policies/acl/${name}`;
2727
- var readPolicy = (name) => Effect26.flatMap(baoRead(policyPath(name)), (data) => {
2851
+ var readPolicy = (name) => Effect28.flatMap(baoRead(policyPath(name)), (data) => {
2728
2852
  if (data === undefined)
2729
- return Effect26.succeed("");
2853
+ return Effect28.succeed("");
2730
2854
  const text11 = data["policy"];
2731
- return typeof text11 === "string" ? Effect26.succeed(text11) : Effect26.fail(new BaoError(200, "GET", policyPath(name), ["no data.policy in the response"]));
2855
+ return typeof text11 === "string" ? Effect28.succeed(text11) : Effect28.fail(new BaoError(200, "GET", policyPath(name), ["no data.policy in the response"]));
2732
2856
  });
2733
2857
  var writePolicy = (name, hcl) => baoWrite("PUT", policyPath(name), { policy: hcl });
2734
2858
  var deletePolicy = (name) => baoDelete(policyPath(name));
@@ -2737,6 +2861,7 @@ var deletePolicy = (name) => baoDelete(policyPath(name));
2737
2861
  var BaoPolicy = Resource12("Bao.Policy", {
2738
2862
  defaultRemovalPolicy: "retain"
2739
2863
  });
2864
+ var IDENTITY9 = nameIdentity("Bao.Policy", policyPath, policyKey);
2740
2865
  var grantsOf = (hcl) => (hcl.match(/^path /gm) ?? []).length;
2741
2866
  var attributesOf12 = (name, hcl, parts) => ({
2742
2867
  name,
@@ -2744,10 +2869,10 @@ var attributesOf12 = (name, hcl, parts) => ({
2744
2869
  grants: grantsOf(hcl),
2745
2870
  parts
2746
2871
  });
2747
- var BaoPolicyProvider = () => Provider12.effect(BaoPolicy, Effect27.gen(function* () {
2872
+ var BaoPolicyProvider = () => Provider12.effect(BaoPolicy, Effect29.gen(function* () {
2748
2873
  const fs = yield* FileSystem.FileSystem;
2749
2874
  const path = yield* Path.Path;
2750
- const assemble = (dir) => Effect27.gen(function* () {
2875
+ const assemble = (dir) => Effect29.gen(function* () {
2751
2876
  const entries2 = (yield* fs.readDirectory(dir)).filter((f) => f.endsWith(".hcl")).sort();
2752
2877
  let joined = "";
2753
2878
  for (const f of entries2)
@@ -2756,23 +2881,29 @@ var BaoPolicyProvider = () => Provider12.effect(BaoPolicy, Effect27.gen(function
2756
2881
  return { joined, parts: entries2.length };
2757
2882
  });
2758
2883
  return BaoPolicy.Provider.of({
2759
- list: () => Effect27.succeed([]),
2760
- read: Effect27.fn(function* ({ olds }) {
2884
+ list: () => Effect29.succeed([]),
2885
+ read: Effect29.fn(function* ({ olds }) {
2761
2886
  const live = yield* readPolicy(olds.name);
2762
2887
  return live.length === 0 ? undefined : attributesOf12(olds.name, live, 0);
2763
2888
  }),
2764
- diff: Effect27.fn(function* ({ news, output }) {
2765
- if (output === undefined || !isResolved12(news))
2889
+ diff: Effect29.fn(function* ({ news, olds, output }) {
2890
+ const move = yield* judgeRename(IDENTITY9, olds, news, output);
2891
+ if (output === undefined)
2892
+ return;
2893
+ if (move !== undefined)
2894
+ return { action: "replace" };
2895
+ if (!isResolved12(news))
2766
2896
  return;
2767
2897
  const { joined } = yield* assemble(news.fragments);
2768
2898
  const live = yield* readPolicy(news.name);
2769
2899
  return live.length > 0 && sha256(live) === sha256(joined) ? { action: "noop" } : { action: "update" };
2770
2900
  }),
2771
- reconcile: Effect27.fn(function* ({ news }) {
2901
+ reconcile: Effect29.fn(function* ({ news, output }) {
2772
2902
  const name = news.name;
2903
+ yield* guardRename(IDENTITY9, news, output);
2773
2904
  const { joined, parts } = yield* assemble(news.fragments);
2774
2905
  if (isEmptyAssembly(parts, joined))
2775
- return yield* Effect27.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.`));
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.`));
2776
2907
  const current = yield* readPolicy(name);
2777
2908
  if (current.length > 0 && sha256(current) === sha256(joined)) {
2778
2909
  return attributesOf12(name, joined, parts);
@@ -2780,7 +2911,7 @@ var BaoPolicyProvider = () => Provider12.effect(BaoPolicy, Effect27.gen(function
2780
2911
  yield* writePolicy(name, joined);
2781
2912
  return attributesOf12(name, joined, parts);
2782
2913
  }),
2783
- delete: Effect27.fn(function* ({ output }) {
2914
+ delete: Effect29.fn(function* ({ output }) {
2784
2915
  yield* deletePolicy(output.name);
2785
2916
  return;
2786
2917
  })
@@ -2790,7 +2921,7 @@ var BaoPolicyProvider = () => Provider12.effect(BaoPolicy, Effect27.gen(function
2790
2921
  import { Resource as Resource13 } from "alchemy";
2791
2922
  import { isResolved as isResolved13 } from "alchemy/Diff";
2792
2923
  import * as Provider13 from "alchemy/Provider";
2793
- import * as Effect28 from "effect/Effect";
2924
+ import * as Effect31 from "effect/Effect";
2794
2925
 
2795
2926
  // src/openbao/proxmox-role-form.ts
2796
2927
  var text11 = (value) => typeof value === "string" ? value : "";
@@ -2836,25 +2967,44 @@ var sameTtl5 = (want, have) => {
2836
2967
  const haveSeconds = ttlSeconds(have);
2837
2968
  return wantSeconds !== undefined && wantSeconds === haveSeconds;
2838
2969
  };
2970
+ var rescopeRefusal = (before, mintUser, allowMintUserChange) => before === mintUser || allowMintUserChange === before ? undefined : `re-scopes mint_user ${before} → ${mintUser}. Every token this role mints inherits ` + `the ACL of that PVE user. To re-scope deliberately, declare allowMintUserChange: '${before}'.`;
2839
2971
  var matches11 = (attributes, props) => attributes.mintUser === props.mintUser && sameTtl5(props.ttl, attributes.ttl) && sameTtl5(props.maxTtl, attributes.maxTtl);
2840
2972
 
2841
- // src/openbao/proxmox-role.ts
2842
- var BaoProxmoxRole = Resource13("Bao.ProxmoxRole", {
2843
- defaultRemovalPolicy: "retain"
2844
- });
2845
- var readRole3 = (props) => Effect28.gen(function* () {
2973
+ // src/openbao/proxmox-role-wire.ts
2974
+ import * as Effect30 from "effect/Effect";
2975
+ var readRole3 = (props) => Effect30.gen(function* () {
2846
2976
  const live = yield* baoRead(rolePath5(props.mount, props.name));
2847
2977
  if (live === undefined)
2848
2978
  return;
2849
2979
  return attributesOf13(props, live);
2850
2980
  });
2851
- var BaoProxmoxRoleProvider = () => Provider13.effect(BaoProxmoxRole, Effect28.succeed(BaoProxmoxRole.Provider.of({
2852
- list: () => Effect28.succeed([]),
2853
- read: Effect28.fn(function* ({ olds }) {
2981
+
2982
+ // src/openbao/proxmox-role.ts
2983
+ var BaoProxmoxRole = Resource13("Bao.ProxmoxRole", {
2984
+ defaultRemovalPolicy: "retain"
2985
+ });
2986
+ 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 }) {
2854
2990
  return yield* readRole3(olds);
2855
2991
  }),
2856
- diff: Effect28.fn(function* ({ news, output }) {
2857
- if (output === undefined || !isResolved13(news))
2992
+ diff: Effect31.fn(function* ({ news, olds, output }) {
2993
+ const move = yield* judgeRename(IDENTITY10, olds, news, output);
2994
+ if (output === undefined)
2995
+ return;
2996
+ if (move !== undefined) {
2997
+ const mintUser = declaredString(news, "mintUser");
2998
+ if (mintUser === undefined || isPendingProp(news, "allowMintUserChange"))
2999
+ return;
3000
+ const from = (yield* readRole3(output))?.mintUser ?? output.mintUser;
3001
+ const allow = declaredString(news, "allowMintUserChange");
3002
+ const refusal2 = rescopeRefusal(from, mintUser, allow);
3003
+ if (refusal2 === undefined)
3004
+ return { action: "replace" };
3005
+ return yield* Effect31.die(new Error(`Bao.ProxmoxRole ${move.from} → ${move.to}: the rename ${refusal2}`));
3006
+ }
3007
+ if (!isResolved13(news))
2858
3008
  return;
2859
3009
  const live = yield* readRole3(news);
2860
3010
  if (live === undefined)
@@ -2863,25 +3013,28 @@ var BaoProxmoxRoleProvider = () => Provider13.effect(BaoProxmoxRole, Effect28.su
2863
3013
  return { action: "noop" };
2864
3014
  return { action: "update" };
2865
3015
  }),
2866
- reconcile: Effect28.fn(function* ({ news }) {
3016
+ reconcile: Effect31.fn(function* ({ news, output }) {
3017
+ const path = rolePath5(news.mount, news.name);
3018
+ yield* guardRename(IDENTITY10, news, output);
2867
3019
  const live = yield* readRole3(news);
2868
- if (live !== undefined && live.mintUser !== news.mintUser && news.allowMintUserChange !== live.mintUser) {
2869
- return yield* Effect28.die(new Error(`Bao.ProxmoxRole ${rolePath5(news.mount, news.name)}: live mint_user ` + `${live.mintUser} != declared ${news.mintUser}. Every token this role mints ` + "inherits the ACL of that PVE user. To re-scope deliberately, declare " + `allowMintUserChange: '${live.mintUser}'.`));
3020
+ const rescope = live === undefined ? undefined : rescopeRefusal(live.mintUser, news.mintUser, news.allowMintUserChange);
3021
+ if (rescope !== undefined) {
3022
+ return yield* Effect31.die(new Error(`Bao.ProxmoxRole ${path}: ${rescope}`));
2870
3023
  }
2871
3024
  if (live === undefined || !matches11(live, news)) {
2872
3025
  const write = writeBody9(news);
2873
3026
  if (!write.ok) {
2874
- return yield* Effect28.die(new Error(`Bao.ProxmoxRole ${rolePath5(news.mount, news.name)}: unparseable duration ` + `${write.bad.join(", ")}. Use 30s / 5m / 1h / 1d, or 0.`));
3027
+ return yield* Effect31.die(new Error(`Bao.ProxmoxRole ${path}: unparseable duration ` + `${write.bad.join(", ")}. Use 30s / 5m / 1h / 1d, or 0.`));
2875
3028
  }
2876
- yield* baoWrite("PUT", rolePath5(news.mount, news.name), write.body);
3029
+ yield* baoWrite("PUT", path, write.body);
2877
3030
  }
2878
3031
  const after = yield* readRole3(news);
2879
3032
  if (after === undefined) {
2880
- return yield* Effect28.die(new Error(`Bao.ProxmoxRole ${rolePath5(news.mount, news.name)}: write returned no error ` + "but the role is still absent."));
3033
+ return yield* Effect31.die(new Error(`Bao.ProxmoxRole ${path}: write returned no error ` + "but the role is still absent."));
2881
3034
  }
2882
3035
  return after;
2883
3036
  }),
2884
- delete: Effect28.fn(function* ({ output }) {
3037
+ delete: Effect31.fn(function* ({ output }) {
2885
3038
  yield* baoDelete(rolePath5(output.mount, output.name));
2886
3039
  return;
2887
3040
  })
@@ -2890,7 +3043,7 @@ var BaoProxmoxRoleProvider = () => Provider13.effect(BaoProxmoxRole, Effect28.su
2890
3043
  import { Resource as Resource14 } from "alchemy";
2891
3044
  import { isResolved as isResolved14 } from "alchemy/Diff";
2892
3045
  import * as Provider14 from "alchemy/Provider";
2893
- import * as Effect29 from "effect/Effect";
3046
+ import * as Effect32 from "effect/Effect";
2894
3047
 
2895
3048
  // src/openbao/ssh-role-form.ts
2896
3049
  var readPath4 = (mount, name) => `${sshMountOf(mount)}/roles/${name}`;
@@ -2978,16 +3131,7 @@ var sameTtl6 = (want, have) => parseDuration(want) === have;
2978
3131
  var badDurations = (form) => ["ttl", "maxTtl"].filter((field) => parseDuration(form[field]) === undefined);
2979
3132
  var matches12 = (attributes, form) => attributes.keyType === "ca" && sameList2(form.allowedUsers, attributes.allowedUsers) && attributes.defaultUser === form.defaultUser && attributes.allowUserCertificates === form.allowUserCertificates && attributes.allowHostCertificates === form.allowHostCertificates && sameList2(form.allowedDomains, attributes.allowedDomains) && attributes.allowSubdomains === form.allowSubdomains && attributes.allowBareDomains === form.allowBareDomains && sameList2(form.allowedExtensions, attributes.allowedExtensions) && sameMap(form.defaultExtensions, attributes.defaultExtensions) && sameList2(form.allowedCriticalOptions, attributes.allowedCriticalOptions) && sameMap(form.defaultCriticalOptions, attributes.defaultCriticalOptions) && attributes.allowEmptyPrincipals === form.allowEmptyPrincipals && sameTtl6(form.ttl, attributes.ttlSeconds) && sameTtl6(form.maxTtl, attributes.maxTtlSeconds);
2980
3133
 
2981
- // src/openbao/ssh-role.ts
2982
- var BaoSshRole = Resource14("Bao.SshRole", {
2983
- defaultRemovalPolicy: "retain"
2984
- });
2985
- var readRole4 = (form) => Effect29.gen(function* () {
2986
- const live = yield* baoRead(readPath4(form.mount, form.name));
2987
- if (live === undefined)
2988
- return;
2989
- return { attributes: attributesOf14(form, live), live };
2990
- });
3134
+ // src/openbao/ssh-role-erase.ts
2991
3135
  var UNMANAGED_DEFAULTS = {
2992
3136
  algorithm_signer: "default",
2993
3137
  allow_commas_in_identity_templates: false,
@@ -3008,28 +3152,43 @@ var wouldErase2 = (live) => {
3008
3152
  }
3009
3153
  return lost.sort();
3010
3154
  };
3011
- var refuse6 = (message) => Effect29.die(new Error(message));
3012
- var BaoSshRoleProvider = () => Provider14.effect(BaoSshRole, Effect29.succeed(BaoSshRole.Provider.of({
3013
- list: () => Effect29.succeed([]),
3014
- read: Effect29.fn(function* ({ olds }) {
3155
+
3156
+ // src/openbao/ssh-role.ts
3157
+ var BaoSshRole = Resource14("Bao.SshRole", {
3158
+ defaultRemovalPolicy: "retain"
3159
+ });
3160
+ var IDENTITY11 = roleIdentity("Bao.SshRole", readPath4, "ssh");
3161
+ var readRole4 = (form) => Effect32.gen(function* () {
3162
+ const live = yield* baoRead(readPath4(form.mount, form.name));
3163
+ if (live === undefined)
3164
+ return;
3165
+ return { attributes: attributesOf14(form, live), live };
3166
+ });
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 }) {
3015
3171
  const found = yield* readRole4(resolve2(olds));
3016
3172
  return found?.attributes;
3017
3173
  }),
3018
- diff: Effect29.fn(function* ({ news, output }) {
3019
- if (output === undefined || !isResolved14(news))
3174
+ diff: Effect32.fn(function* ({ news, olds, output }) {
3175
+ const move = yield* judgeRename(IDENTITY11, olds, news, output);
3176
+ if (output === undefined)
3020
3177
  return;
3021
- const form = resolve2(news);
3022
- if (output.mount !== form.mount || output.name !== form.name) {
3178
+ if (move !== undefined)
3023
3179
  return { action: "replace" };
3024
- }
3180
+ if (!isResolved14(news))
3181
+ return;
3182
+ const form = resolve2(news);
3025
3183
  const found = yield* readRole4(form);
3026
3184
  if (found === undefined)
3027
3185
  return { action: "update" };
3028
3186
  return matches12(found.attributes, form) ? { action: "noop" } : { action: "update" };
3029
3187
  }),
3030
- reconcile: Effect29.fn(function* ({ news }) {
3188
+ reconcile: Effect32.fn(function* ({ news, output }) {
3031
3189
  const form = resolve2(news);
3032
3190
  const path = readPath4(form.mount, form.name);
3191
+ yield* guardRename(IDENTITY11, news, output);
3033
3192
  const bad = badDurations(form);
3034
3193
  if (bad.length > 0) {
3035
3194
  return yield* refuse6(`Bao.SshRole ${path}: ${bad.join(", ")} is not a duration parseDuration understands ` + "(`0` or `<n>s|m|h|d`). It would write, then report update on every plan forever.");
@@ -3053,7 +3212,7 @@ var BaoSshRoleProvider = () => Provider14.effect(BaoSshRole, Effect29.succeed(Ba
3053
3212
  }
3054
3213
  return after.attributes;
3055
3214
  }),
3056
- delete: Effect29.fn(function* ({ output }) {
3215
+ delete: Effect32.fn(function* ({ output }) {
3057
3216
  yield* baoDelete(readPath4(output.mount, output.name));
3058
3217
  return;
3059
3218
  })
@@ -3107,5 +3266,5 @@ export {
3107
3266
  revokeSelfEffect
3108
3267
  };
3109
3268
 
3110
- //# debugId=7E1DFE85CC398C5464756E2164756E21
3269
+ //# debugId=B83B2582FB998CE264756E2164756E21
3111
3270
  //# sourceMappingURL=index.js.map