@homeflare/alchemy 0.7.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 (137) hide show
  1. package/README.md +26 -37
  2. package/dist/caddy/config-lifecycle.d.ts +49 -3
  3. package/dist/caddy/config-lifecycle.d.ts.map +1 -1
  4. package/dist/caddy/config.d.ts +6 -2
  5. package/dist/caddy/config.d.ts.map +1 -1
  6. package/dist/caddy/fake-caddy.d.ts +11 -0
  7. package/dist/caddy/fake-caddy.d.ts.map +1 -1
  8. package/dist/caddy/index.d.ts +2 -1
  9. package/dist/caddy/index.d.ts.map +1 -1
  10. package/dist/caddy/index.js +71 -13
  11. package/dist/caddy/index.js.map +9 -9
  12. package/dist/caddy/with-file.d.ts +7 -3
  13. package/dist/caddy/with-file.d.ts.map +1 -1
  14. package/dist/launchd/fake-sudo.d.ts +50 -0
  15. package/dist/launchd/fake-sudo.d.ts.map +1 -0
  16. package/dist/launchd/host-effect.d.ts +18 -9
  17. package/dist/launchd/host-effect.d.ts.map +1 -1
  18. package/dist/launchd/host-file-form.d.ts +5 -0
  19. package/dist/launchd/host-file-form.d.ts.map +1 -1
  20. package/dist/launchd/host-file-lifecycle.d.ts.map +1 -1
  21. package/dist/launchd/index.d.ts +2 -0
  22. package/dist/launchd/index.d.ts.map +1 -1
  23. package/dist/launchd/index.js +342 -8
  24. package/dist/launchd/index.js.map +15 -11
  25. package/dist/launchd/job-preflight.d.ts.map +1 -1
  26. package/dist/launchd/job-validate.d.ts +2 -0
  27. package/dist/launchd/job-validate.d.ts.map +1 -1
  28. package/dist/launchd/local-runner.d.ts +2 -0
  29. package/dist/launchd/local-runner.d.ts.map +1 -1
  30. package/dist/launchd/providers.d.ts +2 -1
  31. package/dist/launchd/providers.d.ts.map +1 -1
  32. package/dist/launchd/runner.d.ts +7 -5
  33. package/dist/launchd/runner.d.ts.map +1 -1
  34. package/dist/launchd/sudo-allowlist.d.ts +47 -0
  35. package/dist/launchd/sudo-allowlist.d.ts.map +1 -0
  36. package/dist/launchd/sudo-guard.d.ts +56 -0
  37. package/dist/launchd/sudo-guard.d.ts.map +1 -0
  38. package/dist/launchd/sudo-runner.d.ts +60 -0
  39. package/dist/launchd/sudo-runner.d.ts.map +1 -0
  40. package/dist/launchd/sudo-stage.d.ts +7 -0
  41. package/dist/launchd/sudo-stage.d.ts.map +1 -0
  42. package/dist/openbao/auth-role-form.d.ts +0 -9
  43. package/dist/openbao/auth-role-form.d.ts.map +1 -1
  44. package/dist/openbao/auth-role.d.ts +3 -2
  45. package/dist/openbao/auth-role.d.ts.map +1 -1
  46. package/dist/openbao/cloudflare-role.d.ts.map +1 -1
  47. package/dist/openbao/fake-engines-roles.d.ts +32 -0
  48. package/dist/openbao/fake-engines-roles.d.ts.map +1 -0
  49. package/dist/openbao/fake-engines.d.ts +8 -2
  50. package/dist/openbao/fake-engines.d.ts.map +1 -1
  51. package/dist/openbao/fake-families.d.ts +49 -0
  52. package/dist/openbao/fake-families.d.ts.map +1 -0
  53. package/dist/openbao/host-approles.d.ts.map +1 -1
  54. package/dist/openbao/index.js +359 -280
  55. package/dist/openbao/index.js.map +21 -19
  56. package/dist/openbao/jwt-config.d.ts +5 -0
  57. package/dist/openbao/jwt-config.d.ts.map +1 -1
  58. package/dist/openbao/jwt-role.d.ts +2 -1
  59. package/dist/openbao/jwt-role.d.ts.map +1 -1
  60. package/dist/openbao/kubernetes-role.d.ts +2 -1
  61. package/dist/openbao/kubernetes-role.d.ts.map +1 -1
  62. package/dist/openbao/mfa-enforcement.d.ts +6 -3
  63. package/dist/openbao/mfa-enforcement.d.ts.map +1 -1
  64. package/dist/openbao/mfa-totp.d.ts.map +1 -1
  65. package/dist/openbao/pki-role.d.ts.map +1 -1
  66. package/dist/openbao/plugin.d.ts.map +1 -1
  67. package/dist/openbao/policy.d.ts.map +1 -1
  68. package/dist/openbao/proxmox-role.d.ts.map +1 -1
  69. package/dist/openbao/rename-identity.d.ts +69 -0
  70. package/dist/openbao/rename-identity.d.ts.map +1 -0
  71. package/dist/openbao/rename.d.ts +13 -12
  72. package/dist/openbao/rename.d.ts.map +1 -1
  73. package/dist/openbao/ssh-role-erase.d.ts +11 -0
  74. package/dist/openbao/ssh-role-erase.d.ts.map +1 -0
  75. package/dist/openbao/ssh-role.d.ts.map +1 -1
  76. package/docs/caddy-admin.md +24 -0
  77. package/docs/caddy.md +73 -54
  78. package/docs/launchd-sudo.md +122 -0
  79. package/docs/launchd.md +7 -4
  80. package/docs/peers.md +39 -0
  81. package/package.json +1 -1
  82. package/src/caddy/adopt-scope.test.ts +82 -0
  83. package/src/caddy/config-adopt.test.ts +235 -0
  84. package/src/caddy/config-lifecycle.test.ts +31 -53
  85. package/src/caddy/config-lifecycle.ts +98 -7
  86. package/src/caddy/config.ts +48 -11
  87. package/src/caddy/fake-caddy.ts +19 -0
  88. package/src/caddy/index.ts +2 -1
  89. package/src/caddy/providers.test.ts +8 -23
  90. package/src/caddy/with-file.ts +7 -3
  91. package/src/launchd/fake-sudo.ts +136 -0
  92. package/src/launchd/host-effect.ts +33 -1
  93. package/src/launchd/host-file-form.ts +10 -2
  94. package/src/launchd/host-file-lifecycle.ts +2 -1
  95. package/src/launchd/index.ts +2 -0
  96. package/src/launchd/job-preflight.ts +3 -2
  97. package/src/launchd/job-validate.ts +1 -1
  98. package/src/launchd/local-runner.ts +6 -3
  99. package/src/launchd/providers.test.ts +3 -0
  100. package/src/launchd/providers.ts +2 -1
  101. package/src/launchd/runner.ts +7 -5
  102. package/src/launchd/sudo-allowlist.test.ts +191 -0
  103. package/src/launchd/sudo-allowlist.ts +213 -0
  104. package/src/launchd/sudo-guard.test.ts +100 -0
  105. package/src/launchd/sudo-guard.ts +151 -0
  106. package/src/launchd/sudo-lifecycle.test.ts +172 -0
  107. package/src/launchd/sudo-runner-files.test.ts +240 -0
  108. package/src/launchd/sudo-runner.test.ts +126 -0
  109. package/src/launchd/sudo-runner.ts +235 -0
  110. package/src/launchd/sudo-stage.ts +28 -0
  111. package/src/openbao/README.md +8 -2
  112. package/src/openbao/REPLACE.md +65 -37
  113. package/src/openbao/auth-role-form.test.ts +2 -16
  114. package/src/openbao/auth-role-form.ts +0 -11
  115. package/src/openbao/auth-role.ts +35 -14
  116. package/src/openbao/cloudflare-role.ts +6 -14
  117. package/src/openbao/fake-engines-roles.ts +114 -0
  118. package/src/openbao/fake-engines.ts +18 -11
  119. package/src/openbao/fake-families.ts +182 -0
  120. package/src/openbao/host-approles.test.ts +15 -2
  121. package/src/openbao/host-approles.ts +19 -4
  122. package/src/openbao/jwt-config.ts +26 -5
  123. package/src/openbao/jwt-role.ts +24 -8
  124. package/src/openbao/kubernetes-role.ts +20 -8
  125. package/src/openbao/mfa-enforcement.ts +23 -8
  126. package/src/openbao/mfa-totp.ts +21 -8
  127. package/src/openbao/pki-role.ts +19 -7
  128. package/src/openbao/plugin.ts +41 -15
  129. package/src/openbao/policy.ts +7 -7
  130. package/src/openbao/proxmox-role.ts +7 -16
  131. package/src/openbao/rename-adoption.test.ts +82 -0
  132. package/src/openbao/rename-families.test.ts +122 -0
  133. package/src/openbao/rename-identity.test.ts +129 -0
  134. package/src/openbao/rename-identity.ts +117 -0
  135. package/src/openbao/rename.ts +19 -15
  136. package/src/openbao/ssh-role-erase.ts +50 -0
  137. 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 Effect13 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,54 +1240,22 @@ 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
  });
1186
1250
 
1187
- // src/openbao/rename.ts
1188
- import * as Effect12 from "effect/Effect";
1189
- var exact = (identity) => identity;
1190
- var declaredString = (news, key) => {
1191
- if (typeof news !== "object" || news === null)
1192
- return;
1193
- const value = news[key];
1194
- return typeof value === "string" ? value : undefined;
1195
- };
1196
- var isPendingProp = (news, key) => {
1197
- if (typeof news !== "object" || news === null)
1198
- return false;
1199
- const value = news[key];
1200
- return value !== undefined && typeof value !== "string";
1201
- };
1202
- var declaredRolePath = (news, rolePath2) => {
1203
- const mount = declaredString(news, "mount");
1204
- const name = declaredString(news, "name");
1205
- return mount === undefined || name === undefined ? undefined : rolePath2(mount, name);
1206
- };
1207
- var policyKey = (name) => name.trim().toLowerCase();
1208
- var isMoved = (stored, declared, key = exact) => declared === undefined ? undefined : key(stored) !== key(declared);
1209
- var triedRolePath = (output, olds, rolePath2) => output === undefined ? declaredRolePath(olds, rolePath2) : rolePath2(output.mount, output.name);
1210
- var judgeMove = (family, tried, declared, pathOf, key = exact) => Effect12.gen(function* () {
1211
- if (tried === undefined || declared === undefined || key(tried) === key(declared)) {
1212
- return;
1213
- }
1214
- if ((yield* baoRead(pathOf(declared))) === undefined)
1215
- return { from: tried, to: declared };
1216
- return yield* Effect12.die(new Error(`${family}: ${tried} → ${declared} would land on an object that already exists at ` + `${declared}. Under RemovalPolicy.destroy() a resource moving off that name (a swap, a ` + "shift, or a reverted move that did not finish) deletes it after this one writes it. " + "Nothing was written. Move through a name nothing holds, in two deploys, or remove " + `${declared} by hand first.`));
1217
- });
1218
- var refuseMovedUpdate = (family, from, to) => Effect12.die(new Error(`${family}: planned as an update, but its identity moved from ${from} to ${to}. Writing ` + `${to} now would leave ${from} live with no state record. Nothing was written. Run the ` + "deploy again: once the value resolves at plan time it plans as a replace."));
1219
-
1220
1251
  // src/openbao/cloudflare-role.ts
1221
1252
  var BaoCloudflareRole = Resource3("Bao.CloudflareRole", {
1222
1253
  defaultRemovalPolicy: "retain"
1223
1254
  });
1224
- var BaoCloudflareRoleProvider = () => Provider3.effect(BaoCloudflareRole, Effect13.gen(function* () {
1255
+ var IDENTITY2 = roleIdentity("Bao.CloudflareRole", rolePath);
1256
+ var BaoCloudflareRoleProvider = () => Provider3.effect(BaoCloudflareRole, Effect14.gen(function* () {
1225
1257
  const groups = yield* CloudflarePermissionGroups;
1226
- const resolved = (props) => Effect13.gen(function* () {
1258
+ const resolved = (props) => Effect14.gen(function* () {
1227
1259
  const refs = new Map;
1228
1260
  for (const policy of props.policies) {
1229
1261
  const scope = policy.scope ?? scopeOfResource(policy.resource);
@@ -1233,20 +1265,18 @@ var BaoCloudflareRoleProvider = () => Provider3.effect(BaoCloudflareRole, Effect
1233
1265
  const ids = yield* groups.resolve(props.mount, [...refs.values()]);
1234
1266
  const { missing, policies } = resolvePolicies(props.policies, ids);
1235
1267
  if (missing.length > 0) {
1236
- return yield* Effect13.die(new Error(`Bao.CloudflareRole ${rolePath(props.mount, props.name)}: groups ` + `${missing.join(", ")} came back unresolved from a lookup that reported success.`));
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.`));
1237
1269
  }
1238
1270
  return policies;
1239
1271
  });
1240
1272
  return BaoCloudflareRole.Provider.of({
1241
- list: () => Effect13.succeed([]),
1242
- read: Effect13.fn(function* ({ olds }) {
1273
+ list: () => Effect14.succeed([]),
1274
+ read: Effect14.fn(function* ({ olds }) {
1243
1275
  const live = yield* readCloudflareRole(olds.mount, olds.name);
1244
1276
  return live === undefined ? undefined : attributesOf4(olds, live);
1245
1277
  }),
1246
- diff: Effect13.fn(function* ({ news, olds, output }) {
1247
- const tried = triedRolePath(output, olds, rolePath);
1248
- const declared = declaredRolePath(news, rolePath);
1249
- const move = yield* judgeMove("Bao.CloudflareRole", tried, declared, (path) => path);
1278
+ diff: Effect14.fn(function* ({ news, olds, output }) {
1279
+ const move = yield* judgeRename(IDENTITY2, olds, news, output);
1250
1280
  if (output === undefined)
1251
1281
  return;
1252
1282
  if (move !== undefined)
@@ -1261,15 +1291,12 @@ var BaoCloudflareRoleProvider = () => Provider3.effect(BaoCloudflareRole, Effect
1261
1291
  return { action: "update" };
1262
1292
  return differences(news, policies, live).length === 0 ? { action: "noop" } : { action: "update" };
1263
1293
  }),
1264
- reconcile: Effect13.fn(function* ({ news, output }) {
1294
+ reconcile: Effect14.fn(function* ({ news, output }) {
1265
1295
  const path = rolePath(news.mount, news.name);
1266
- const before = output === undefined ? path : rolePath(output.mount, output.name);
1267
- if (isMoved(before, path) === true) {
1268
- return yield* refuseMovedUpdate("Bao.CloudflareRole", before, path);
1269
- }
1296
+ yield* guardRename(IDENTITY2, news, output);
1270
1297
  const refusal2 = refusalOf(news);
1271
1298
  if (refusal2 !== undefined) {
1272
- return yield* Effect13.die(new Error(`Bao.CloudflareRole ${path}: ${refusal2}.`));
1299
+ return yield* Effect14.die(new Error(`Bao.CloudflareRole ${path}: ${refusal2}.`));
1273
1300
  }
1274
1301
  const policies = yield* resolved(news);
1275
1302
  const live = yield* readCloudflareRole(news.mount, news.name);
@@ -1278,15 +1305,15 @@ var BaoCloudflareRoleProvider = () => Provider3.effect(BaoCloudflareRole, Effect
1278
1305
  }
1279
1306
  const after = yield* readCloudflareRole(news.mount, news.name);
1280
1307
  if (after === undefined) {
1281
- return yield* Effect13.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.`));
1282
1309
  }
1283
1310
  const left = differences(news, policies, after);
1284
1311
  if (left.length > 0) {
1285
- return yield* Effect13.die(new Error(`Bao.CloudflareRole ${path}: wrote cleanly but reads back different in ` + `${left.map((d) => d.field).join(", ")}. Inspect the role before retrying.`));
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.`));
1286
1313
  }
1287
1314
  return attributesOf4(news, after);
1288
1315
  }),
1289
- delete: Effect13.fn(function* ({ output }) {
1316
+ delete: Effect14.fn(function* ({ output }) {
1290
1317
  yield* deleteCloudflareRole(output.mount, output.name);
1291
1318
  return;
1292
1319
  })
@@ -1509,17 +1536,19 @@ var hostAppRoles = (input) => {
1509
1536
  if (!NAME.test(host.name)) {
1510
1537
  problems.push(`host \`${host.name}\` is not lowercase letters, digits, single hyphens`);
1511
1538
  }
1512
- if (seen.has(host.name))
1513
- problems.push(`host \`${host.name}\` is listed twice`);
1514
- seen.add(host.name);
1515
- const spec = input.classes[host.class];
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;
1516
1545
  if (spec === undefined) {
1517
1546
  problems.push(`host \`${host.name}\` names unknown class \`${host.class}\``);
1518
1547
  continue;
1519
1548
  }
1520
1549
  roles.push({
1521
1550
  bindSecretId: true,
1522
- name: hostRoleName(host.class, host.name),
1551
+ name,
1523
1552
  secretIdNumUses: spec.secretIdNumUses ?? 0,
1524
1553
  secretIdTtl: spec.secretIdTtl,
1525
1554
  tokenMaxTtl: spec.tokenMaxTtl,
@@ -1536,7 +1565,7 @@ var hostAppRoles = (input) => {
1536
1565
  import { Resource as Resource4 } from "alchemy";
1537
1566
  import { isResolved as isResolved4 } from "alchemy/Diff";
1538
1567
  import * as Provider4 from "alchemy/Provider";
1539
- import * as Effect15 from "effect/Effect";
1568
+ import * as Effect16 from "effect/Effect";
1540
1569
 
1541
1570
  // src/openbao/jwt-config-form.ts
1542
1571
  var text5 = (value) => typeof value === "string" ? value.trim() : "";
@@ -1626,16 +1655,16 @@ var problems = (props) => {
1626
1655
  };
1627
1656
 
1628
1657
  // src/openbao/role-reconcile.ts
1629
- import * as Effect14 from "effect/Effect";
1630
- var readRoleAt = (spec) => Effect14.map(baoRead(spec.path), (live) => live === undefined ? undefined : { attributes: spec.attributesOf(live), live });
1631
- var planRole = (spec) => Effect14.gen(function* () {
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* () {
1632
1661
  if (spec.problems.length > 0)
1633
1662
  return "update";
1634
1663
  const found = yield* readRoleAt(spec);
1635
1664
  return found !== undefined && spec.matches(found.attributes) ? "noop" : "update";
1636
1665
  });
1637
- var refuse2 = (spec, message) => Effect14.die(new Error(`${spec.family} ${spec.path}: ${message}`));
1638
- var reconcileRole = (spec) => Effect14.gen(function* () {
1666
+ var refuse2 = (spec, message) => Effect15.die(new Error(`${spec.family} ${spec.path}: ${message}`));
1667
+ var reconcileRole = (spec) => Effect15.gen(function* () {
1639
1668
  if (spec.problems.length > 0)
1640
1669
  return yield* refuse2(spec, spec.problems.join("; "));
1641
1670
  const found = yield* readRoleAt(spec);
@@ -1659,6 +1688,14 @@ var reconcileRole = (spec) => Effect14.gen(function* () {
1659
1688
  var BaoJwtAuthConfig = Resource4("Bao.JwtAuthConfig", {
1660
1689
  defaultRemovalPolicy: "retain"
1661
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
+ };
1662
1699
  var jwtConfigSpec = (props) => ({
1663
1700
  attributesOf: (live) => attributesOf5(props, live),
1664
1701
  body: writeBody3(props),
@@ -1668,29 +1705,33 @@ var jwtConfigSpec = (props) => ({
1668
1705
  problems: problems(props),
1669
1706
  wouldErase
1670
1707
  });
1671
- var BaoJwtAuthConfigProvider = () => Provider4.effect(BaoJwtAuthConfig, Effect15.succeed(BaoJwtAuthConfig.Provider.of({
1672
- list: () => Effect15.succeed([]),
1673
- read: Effect15.fn(function* ({ olds }) {
1708
+ var BaoJwtAuthConfigProvider = () => Provider4.effect(BaoJwtAuthConfig, Effect16.succeed(BaoJwtAuthConfig.Provider.of({
1709
+ list: () => Effect16.succeed([]),
1710
+ read: Effect16.fn(function* ({ olds }) {
1674
1711
  const found = yield* readRoleAt(jwtConfigSpec(olds));
1675
1712
  return found?.attributes;
1676
1713
  }),
1677
- diff: Effect15.fn(function* ({ news, output }) {
1678
- 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)
1679
1717
  return;
1680
- if (mountOf(news) !== output.mount)
1718
+ if (move !== undefined)
1681
1719
  return { action: "replace" };
1720
+ if (!isResolved4(news))
1721
+ return;
1682
1722
  return { action: yield* planRole(jwtConfigSpec(news)) };
1683
1723
  }),
1684
- reconcile: Effect15.fn(function* ({ news }) {
1724
+ reconcile: Effect16.fn(function* ({ news, output }) {
1725
+ yield* guardRename(IDENTITY3, news, output);
1685
1726
  return yield* reconcileRole(jwtConfigSpec(news));
1686
1727
  }),
1687
- delete: () => Effect15.void
1728
+ delete: () => Effect16.void
1688
1729
  })));
1689
1730
  // src/openbao/jwt-role.ts
1690
1731
  import { Resource as Resource5 } from "alchemy";
1691
1732
  import { isResolved as isResolved5 } from "alchemy/Diff";
1692
1733
  import * as Provider5 from "alchemy/Provider";
1693
- import * as Effect16 from "effect/Effect";
1734
+ import * as Effect17 from "effect/Effect";
1694
1735
 
1695
1736
  // src/openbao/auth-token-form.ts
1696
1737
  var sortedSet2 = (values) => [...new Set(values)].sort();
@@ -1892,6 +1933,7 @@ var problems2 = (props) => {
1892
1933
  var BaoJwtRole = Resource5("Bao.JwtRole", {
1893
1934
  defaultRemovalPolicy: "retain"
1894
1935
  });
1936
+ var IDENTITY4 = roleIdentity("Bao.JwtRole", (mount, name) => rolePath2({ mount, name }), "jwt", foldName);
1895
1937
  var jwtRoleSpec = (props) => ({
1896
1938
  attributesOf: (live) => attributesOf6(props, live),
1897
1939
  body: writeBody4(props),
@@ -1900,24 +1942,27 @@ var jwtRoleSpec = (props) => ({
1900
1942
  path: rolePath2(props),
1901
1943
  problems: problems2(props)
1902
1944
  });
1903
- var BaoJwtRoleProvider = () => Provider5.effect(BaoJwtRole, Effect16.succeed(BaoJwtRole.Provider.of({
1904
- list: () => Effect16.succeed([]),
1905
- read: Effect16.fn(function* ({ olds }) {
1945
+ var BaoJwtRoleProvider = () => Provider5.effect(BaoJwtRole, Effect17.succeed(BaoJwtRole.Provider.of({
1946
+ list: () => Effect17.succeed([]),
1947
+ read: Effect17.fn(function* ({ olds }) {
1906
1948
  const found = yield* readRoleAt(jwtRoleSpec(olds));
1907
1949
  return found?.attributes;
1908
1950
  }),
1909
- diff: Effect16.fn(function* ({ news, output }) {
1910
- 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)
1911
1954
  return;
1912
- if (mountOf2(news) !== output.mount || news.name !== output.name) {
1955
+ if (move !== undefined)
1913
1956
  return { action: "replace" };
1914
- }
1957
+ if (!isResolved5(news))
1958
+ return;
1915
1959
  return { action: yield* planRole(jwtRoleSpec(news)) };
1916
1960
  }),
1917
- reconcile: Effect16.fn(function* ({ news }) {
1961
+ reconcile: Effect17.fn(function* ({ news, output }) {
1962
+ yield* guardRename(IDENTITY4, news, output);
1918
1963
  return yield* reconcileRole(jwtRoleSpec(news));
1919
1964
  }),
1920
- delete: Effect16.fn(function* ({ output }) {
1965
+ delete: Effect17.fn(function* ({ output }) {
1921
1966
  yield* baoDelete(rolePath2(output));
1922
1967
  return;
1923
1968
  })
@@ -1926,7 +1971,7 @@ var BaoJwtRoleProvider = () => Provider5.effect(BaoJwtRole, Effect16.succeed(Bao
1926
1971
  import { Resource as Resource6 } from "alchemy";
1927
1972
  import { isResolved as isResolved6 } from "alchemy/Diff";
1928
1973
  import * as Provider6 from "alchemy/Provider";
1929
- import * as Effect17 from "effect/Effect";
1974
+ import * as Effect18 from "effect/Effect";
1930
1975
 
1931
1976
  // src/openbao/kubernetes-role-form.ts
1932
1977
  var sortedSet4 = (values) => [...new Set(values)].sort();
@@ -1995,6 +2040,7 @@ var problems3 = (props) => {
1995
2040
  var BaoKubernetesRole = Resource6("Bao.KubernetesRole", {
1996
2041
  defaultRemovalPolicy: "retain"
1997
2042
  });
2043
+ var IDENTITY5 = roleIdentity("Bao.KubernetesRole", (mount, name) => rolePath3({ mount, name }), "kubernetes", foldName);
1998
2044
  var kubernetesRoleSpec = (props) => ({
1999
2045
  attributesOf: (live) => attributesOf7(props, live),
2000
2046
  body: writeBody5(props),
@@ -2003,24 +2049,27 @@ var kubernetesRoleSpec = (props) => ({
2003
2049
  path: rolePath3(props),
2004
2050
  problems: problems3(props)
2005
2051
  });
2006
- var BaoKubernetesRoleProvider = () => Provider6.effect(BaoKubernetesRole, Effect17.succeed(BaoKubernetesRole.Provider.of({
2007
- list: () => Effect17.succeed([]),
2008
- read: Effect17.fn(function* ({ olds }) {
2052
+ var BaoKubernetesRoleProvider = () => Provider6.effect(BaoKubernetesRole, Effect18.succeed(BaoKubernetesRole.Provider.of({
2053
+ list: () => Effect18.succeed([]),
2054
+ read: Effect18.fn(function* ({ olds }) {
2009
2055
  const found = yield* readRoleAt(kubernetesRoleSpec(olds));
2010
2056
  return found?.attributes;
2011
2057
  }),
2012
- diff: Effect17.fn(function* ({ news, output }) {
2013
- 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)
2014
2061
  return;
2015
- if (mountOf3(news) !== output.mount || news.name !== output.name) {
2062
+ if (move !== undefined)
2016
2063
  return { action: "replace" };
2017
- }
2064
+ if (!isResolved6(news))
2065
+ return;
2018
2066
  return { action: yield* planRole(kubernetesRoleSpec(news)) };
2019
2067
  }),
2020
- reconcile: Effect17.fn(function* ({ news }) {
2068
+ reconcile: Effect18.fn(function* ({ news, output }) {
2069
+ yield* guardRename(IDENTITY5, news, output);
2021
2070
  return yield* reconcileRole(kubernetesRoleSpec(news));
2022
2071
  }),
2023
- delete: Effect17.fn(function* ({ output }) {
2072
+ delete: Effect18.fn(function* ({ output }) {
2024
2073
  yield* baoDelete(rolePath3(output));
2025
2074
  return;
2026
2075
  })
@@ -2029,7 +2078,7 @@ var BaoKubernetesRoleProvider = () => Provider6.effect(BaoKubernetesRole, Effect
2029
2078
  import { Resource as Resource7 } from "alchemy";
2030
2079
  import { isResolved as isResolved7 } from "alchemy/Diff";
2031
2080
  import * as Provider7 from "alchemy/Provider";
2032
- import * as Effect19 from "effect/Effect";
2081
+ import * as Effect20 from "effect/Effect";
2033
2082
 
2034
2083
  // src/openbao/mfa-enforcement-form.ts
2035
2084
  var sortedSet5 = (values) => [...new Set(values.map((value) => value.trim()))].filter((value) => value !== "").sort();
@@ -2097,16 +2146,16 @@ var problems4 = (props, missingPaths) => {
2097
2146
  };
2098
2147
 
2099
2148
  // src/openbao/mfa-wire.ts
2100
- import * as Effect18 from "effect/Effect";
2149
+ import * as Effect19 from "effect/Effect";
2101
2150
  var TOTP_PATH = "identity/mfa/method/totp";
2102
2151
  var METHODS_PATH = "identity/mfa/method";
2103
2152
  var enforcementPath = (name) => `identity/mfa/login-enforcement/${name}`;
2104
2153
  var record4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
2105
- var ownNamespacePath = Effect18.gen(function* () {
2154
+ var ownNamespacePath = Effect19.gen(function* () {
2106
2155
  const env = yield* BaoEnv;
2107
2156
  return namespacePath(envNamespace(env));
2108
2157
  });
2109
- var findTotp = (name) => Effect18.gen(function* () {
2158
+ var findTotp = (name) => Effect19.gen(function* () {
2110
2159
  const listing = yield* baoRead(`${METHODS_PATH}?list=true`);
2111
2160
  const info = record4(listing?.["key_info"]) ?? {};
2112
2161
  const own = yield* ownNamespacePath;
@@ -2115,17 +2164,17 @@ var findTotp = (name) => Effect18.gen(function* () {
2115
2164
  if (live === undefined || live["name"] !== name || live["namespace_path"] !== own)
2116
2165
  continue;
2117
2166
  if (live["type"] !== "totp") {
2118
- return yield* Effect18.die(new Error(`Bao.MfaTotpMethod ${name}: that name belongs to a ${String(live["type"])} method ` + `(${id}) in this namespace, and a TOTP write would convert it. Pick another name.`));
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.`));
2119
2168
  }
2120
2169
  return { id, live };
2121
2170
  }
2122
2171
  return;
2123
2172
  });
2124
- var writeTotp = (body) => Effect18.asVoid(baoCall("write", "POST", TOTP_PATH, body));
2173
+ var writeTotp = (body) => Effect19.asVoid(baoCall("write", "POST", TOTP_PATH, body));
2125
2174
  var deleteTotp = (id) => baoDelete(`${TOTP_PATH}/${id}`);
2126
2175
  var readEnforcement = (name) => baoRead(enforcementPath(name));
2127
- var writeEnforcement = (name, body) => Effect18.asVoid(baoCall("write", "POST", enforcementPath(name), body));
2128
- var authAccessors = (paths) => Effect18.gen(function* () {
2176
+ var writeEnforcement = (name, body) => Effect19.asVoid(baoCall("write", "POST", enforcementPath(name), body));
2177
+ var authAccessors = (paths) => Effect19.gen(function* () {
2129
2178
  if (paths.length === 0)
2130
2179
  return { accessors: [], missing: [] };
2131
2180
  const table = (yield* baoRead("sys/auth")) ?? {};
@@ -2145,20 +2194,21 @@ var authAccessors = (paths) => Effect18.gen(function* () {
2145
2194
  var BaoMfaLoginEnforcement = Resource7("Bao.MfaLoginEnforcement", {
2146
2195
  defaultRemovalPolicy: "retain"
2147
2196
  });
2148
- var refuse3 = (name, message) => Effect19.die(new Error(`Bao.MfaLoginEnforcement ${name}: ${message}`));
2149
- var resolveTargets = (props) => Effect19.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 }) => ({
2150
2200
  bad: problems4(props, missing),
2151
2201
  resolved: accessors
2152
2202
  }));
2153
- var readLive = (name) => Effect19.map(readEnforcement(name), (live) => live === undefined ? undefined : attributesOf8(name, live));
2154
- var planEnforcement = (props) => Effect19.gen(function* () {
2203
+ var readLive = (name) => Effect20.map(readEnforcement(name), (live) => live === undefined ? undefined : attributesOf8(name, live));
2204
+ var planEnforcement = (props) => Effect20.gen(function* () {
2155
2205
  const { bad, resolved } = yield* resolveTargets(props);
2156
2206
  if (bad.length > 0)
2157
2207
  return "update";
2158
2208
  const live = yield* readLive(props.name);
2159
2209
  return live !== undefined && matches7(live, props, resolved) ? "noop" : "update";
2160
2210
  });
2161
- var reconcileEnforcement = (props) => Effect19.gen(function* () {
2211
+ var reconcileEnforcement = (props) => Effect20.gen(function* () {
2162
2212
  const { bad, resolved } = yield* resolveTargets(props);
2163
2213
  if (bad.length > 0)
2164
2214
  return yield* refuse3(props.name, bad.join("; "));
@@ -2174,22 +2224,26 @@ var reconcileEnforcement = (props) => Effect19.gen(function* () {
2174
2224
  }
2175
2225
  return after;
2176
2226
  });
2177
- var BaoMfaLoginEnforcementProvider = () => Provider7.effect(BaoMfaLoginEnforcement, Effect19.succeed(BaoMfaLoginEnforcement.Provider.of({
2178
- list: () => Effect19.succeed([]),
2179
- read: Effect19.fn(function* ({ olds }) {
2227
+ var BaoMfaLoginEnforcementProvider = () => Provider7.effect(BaoMfaLoginEnforcement, Effect20.succeed(BaoMfaLoginEnforcement.Provider.of({
2228
+ list: () => Effect20.succeed([]),
2229
+ read: Effect20.fn(function* ({ olds }) {
2180
2230
  return yield* readLive(olds.name);
2181
2231
  }),
2182
- diff: Effect19.fn(function* ({ news, output }) {
2183
- 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)
2184
2235
  return;
2185
- if (news.name !== output.name)
2236
+ if (move !== undefined)
2186
2237
  return { action: "replace" };
2238
+ if (!isResolved7(news))
2239
+ return;
2187
2240
  return { action: yield* planEnforcement(news) };
2188
2241
  }),
2189
- reconcile: Effect19.fn(function* ({ news }) {
2242
+ reconcile: Effect20.fn(function* ({ news, output }) {
2243
+ yield* guardRename(IDENTITY6, news, output);
2190
2244
  return yield* reconcileEnforcement(news);
2191
2245
  }),
2192
- delete: Effect19.fn(function* ({ output }) {
2246
+ delete: Effect20.fn(function* ({ output }) {
2193
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.");
2194
2248
  })
2195
2249
  })));
@@ -2197,7 +2251,7 @@ var BaoMfaLoginEnforcementProvider = () => Provider7.effect(BaoMfaLoginEnforceme
2197
2251
  import { Resource as Resource8 } from "alchemy";
2198
2252
  import { isResolved as isResolved8 } from "alchemy/Diff";
2199
2253
  import * as Provider8 from "alchemy/Provider";
2200
- import * as Effect20 from "effect/Effect";
2254
+ import * as Effect21 from "effect/Effect";
2201
2255
 
2202
2256
  // src/openbao/mfa-totp-form.ts
2203
2257
  var canonical6 = (input) => ({
@@ -2278,45 +2332,51 @@ var problems5 = (props) => {
2278
2332
  var BaoMfaTotpMethod = Resource8("Bao.MfaTotpMethod", {
2279
2333
  defaultRemovalPolicy: "retain"
2280
2334
  });
2281
- var refuse4 = (props, message) => Effect20.die(new Error(`Bao.MfaTotpMethod ${props.name}: ${message}`));
2282
- var readTotp = (props) => Effect20.map(findTotp(props.name), (found) => found === undefined ? undefined : attributesOf9(found.id, found.live));
2283
- var planTotp = (props) => Effect20.gen(function* () {
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* () {
2284
2338
  if (problems5(props).length > 0)
2285
2339
  return "update";
2286
2340
  const live = yield* readTotp(props);
2287
2341
  return live !== undefined && matches8(live, props) ? "noop" : "update";
2288
2342
  });
2289
- var reconcileTotp = (props) => Effect20.gen(function* () {
2343
+ var reconcileTotp = (props) => Effect21.gen(function* () {
2290
2344
  const bad = problems5(props);
2291
2345
  if (bad.length > 0)
2292
- return yield* refuse4(props, bad.join("; "));
2346
+ return yield* refuse4(props.name, bad.join("; "));
2293
2347
  const live = yield* readTotp(props);
2294
2348
  if (live === undefined || !matches8(live, props))
2295
2349
  yield* writeTotp(writeBody7(props));
2296
2350
  const after = yield* readTotp(props);
2297
2351
  if (after === undefined)
2298
- 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.");
2299
2353
  if (!matches8(after, props))
2300
- return yield* refuse4(props, "the method read back different.");
2354
+ return yield* refuse4(props.name, "the method read back different.");
2301
2355
  return after;
2302
2356
  });
2303
- var BaoMfaTotpMethodProvider = () => Provider8.effect(BaoMfaTotpMethod, Effect20.succeed(BaoMfaTotpMethod.Provider.of({
2304
- list: () => Effect20.succeed([]),
2305
- read: Effect20.fn(function* ({ olds }) {
2357
+ var BaoMfaTotpMethodProvider = () => Provider8.effect(BaoMfaTotpMethod, Effect21.succeed(BaoMfaTotpMethod.Provider.of({
2358
+ list: () => Effect21.succeed([]),
2359
+ read: Effect21.fn(function* ({ olds }) {
2306
2360
  return yield* readTotp(olds);
2307
2361
  }),
2308
- diff: Effect20.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
+ }
2309
2369
  if (output === undefined || !isResolved8(news))
2310
2370
  return;
2311
- const rename = renameProblem(output.name, news.name);
2312
- if (rename !== undefined)
2313
- return yield* refuse4(news, rename);
2314
2371
  return { action: yield* planTotp(news) };
2315
2372
  }),
2316
- reconcile: Effect20.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);
2317
2377
  return yield* reconcileTotp(news);
2318
2378
  }),
2319
- delete: Effect20.fn(function* ({ output }) {
2379
+ delete: Effect21.fn(function* ({ output }) {
2320
2380
  yield* deleteTotp(output.methodId);
2321
2381
  return;
2322
2382
  })
@@ -2325,24 +2385,24 @@ var BaoMfaTotpMethodProvider = () => Provider8.effect(BaoMfaTotpMethod, Effect20
2325
2385
  import { Resource as Resource9 } from "alchemy";
2326
2386
  import { isResolved as isResolved9 } from "alchemy/Diff";
2327
2387
  import * as Provider9 from "alchemy/Provider";
2328
- import * as Effect23 from "effect/Effect";
2388
+ import * as Effect24 from "effect/Effect";
2329
2389
 
2330
2390
  // src/openbao/mount-reconcile.ts
2331
- import * as Effect22 from "effect/Effect";
2391
+ import * as Effect23 from "effect/Effect";
2332
2392
 
2333
2393
  // src/openbao/mount-wire.ts
2334
- import * as Effect21 from "effect/Effect";
2335
- var absentFromTable2 = (path, asked) => baoRead("sys/mounts").pipe(Effect21.mapError((listing) => new BaoError(asked.status, asked.method, asked.path, [
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, [
2336
2396
  ...asked.errors,
2337
2397
  `sys/mounts could not confirm the mount is absent: ${listing.message}`
2338
- ])), Effect21.flatMap((table) => table !== undefined && !Object.hasOwn(table, `${mountPath(path)}/`) ? Effect21.succeed(undefined) : Effect21.fail(asked)));
2339
- var readMountData = (path) => baoRead(readPath(path)).pipe(Effect21.catchIf((error) => error.status === 400, (error) => absentFromTable2(path, error)));
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)));
2340
2400
  var enableMount = (props) => baoWrite("POST", readPath(props.path), enableBody(props));
2341
2401
  var tuneMount = (props) => baoWrite("POST", tunePath(props.path), tuneBody(props));
2342
2402
  var disableMount = (path) => baoDelete(readPath(path));
2343
2403
 
2344
2404
  // src/openbao/mount-reconcile.ts
2345
- var readMount = (props) => Effect22.gen(function* () {
2405
+ var readMount = (props) => Effect23.gen(function* () {
2346
2406
  const live = yield* readMountData(props.path);
2347
2407
  if (live === undefined)
2348
2408
  return;
@@ -2355,10 +2415,10 @@ var moveTarget2 = (props) => ({
2355
2415
  type: props.type,
2356
2416
  wire: mountPath
2357
2417
  });
2358
- var reconcileMount = (news, stated, options) => Effect22.gen(function* () {
2418
+ var reconcileMount = (news, stated, options) => Effect23.gen(function* () {
2359
2419
  const move = planMove("Bao.Mount", stated, news.path, news.remountFrom);
2360
2420
  if (move.kind === "refuse")
2361
- return yield* Effect22.die(new Error(move.message));
2421
+ return yield* Effect23.die(new Error(move.message));
2362
2422
  if (move.kind === "move")
2363
2423
  yield* performMove(moveTarget2(news), move.from, options);
2364
2424
  let live = yield* readMount(news);
@@ -2368,13 +2428,13 @@ var reconcileMount = (news, stated, options) => Effect22.gen(function* () {
2368
2428
  yield* tuneMount(news);
2369
2429
  } else if (!matches(live, news)) {
2370
2430
  if (live.type !== news.type) {
2371
- return yield* Effect22.die(new Error(`Bao.Mount ${mountPath(news.path)}: live type ${live.type} != ${news.type}. ` + "Secrets engine type is immutable — replace manually."));
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."));
2372
2432
  }
2373
2433
  yield* tuneMount(news);
2374
2434
  }
2375
2435
  live = yield* readMount(news);
2376
2436
  if (live === undefined) {
2377
- return yield* Effect22.die(new Error(`Bao.Mount ${mountPath(news.path)}: write returned no error but the mount is still absent.`));
2437
+ return yield* Effect23.die(new Error(`Bao.Mount ${mountPath(news.path)}: write returned no error but the mount is still absent.`));
2378
2438
  }
2379
2439
  return live;
2380
2440
  });
@@ -2383,17 +2443,17 @@ var reconcileMount = (news, stated, options) => Effect22.gen(function* () {
2383
2443
  var BaoMount = Resource9("Bao.Mount", {
2384
2444
  defaultRemovalPolicy: "retain"
2385
2445
  });
2386
- var BaoMountProvider = () => Provider9.effect(BaoMount, Effect23.succeed(BaoMount.Provider.of({
2387
- list: () => Effect23.succeed([]),
2388
- read: Effect23.fn(function* ({ olds }) {
2446
+ var BaoMountProvider = () => Provider9.effect(BaoMount, Effect24.succeed(BaoMount.Provider.of({
2447
+ list: () => Effect24.succeed([]),
2448
+ read: Effect24.fn(function* ({ olds }) {
2389
2449
  return yield* readMount(olds);
2390
2450
  }),
2391
- diff: Effect23.fn(function* ({ news, output }) {
2451
+ diff: Effect24.fn(function* ({ news, output }) {
2392
2452
  if (output === undefined || !isResolved9(news))
2393
2453
  return;
2394
2454
  const move = planMove("Bao.Mount", output.path, news.path, news.remountFrom);
2395
2455
  if (move.kind === "refuse")
2396
- return yield* Effect23.die(new Error(move.message));
2456
+ return yield* Effect24.die(new Error(move.message));
2397
2457
  if (move.kind === "move")
2398
2458
  return { action: "update" };
2399
2459
  const live = yield* readMount(news);
@@ -2405,10 +2465,10 @@ var BaoMountProvider = () => Provider9.effect(BaoMount, Effect23.succeed(BaoMoun
2405
2465
  return { action: "noop" };
2406
2466
  return { action: "update" };
2407
2467
  }),
2408
- reconcile: Effect23.fn(function* ({ news, output }) {
2468
+ reconcile: Effect24.fn(function* ({ news, output }) {
2409
2469
  return yield* reconcileMount(news, output?.path);
2410
2470
  }),
2411
- delete: Effect23.fn(function* ({ output }) {
2471
+ delete: Effect24.fn(function* ({ output }) {
2412
2472
  yield* disableMount(output.path);
2413
2473
  return;
2414
2474
  })
@@ -2417,7 +2477,7 @@ var BaoMountProvider = () => Provider9.effect(BaoMount, Effect23.succeed(BaoMoun
2417
2477
  import { Resource as Resource10 } from "alchemy";
2418
2478
  import { isResolved as isResolved10 } from "alchemy/Diff";
2419
2479
  import * as Provider10 from "alchemy/Provider";
2420
- import * as Effect24 from "effect/Effect";
2480
+ import * as Effect25 from "effect/Effect";
2421
2481
 
2422
2482
  // src/openbao/pki-role-canonical.ts
2423
2483
  var DEFAULT_KEY_USAGE = [
@@ -2583,22 +2643,26 @@ var problems6 = (props) => {
2583
2643
  var BaoPkiRole = Resource10("Bao.PkiRole", {
2584
2644
  defaultRemovalPolicy: "retain"
2585
2645
  });
2586
- var readRole2 = (props) => Effect24.gen(function* () {
2646
+ var IDENTITY7 = roleIdentity("Bao.PkiRole", (mount, name) => rolePath4({ mount, name }), "pki");
2647
+ var readRole2 = (props) => Effect25.gen(function* () {
2587
2648
  const live = yield* baoRead(rolePath4(props));
2588
2649
  if (live === undefined)
2589
2650
  return;
2590
2651
  return attributesOf10(props, live);
2591
2652
  });
2592
- var BaoPkiRoleProvider = () => Provider10.effect(BaoPkiRole, Effect24.succeed(BaoPkiRole.Provider.of({
2593
- list: () => Effect24.succeed([]),
2594
- read: Effect24.fn(function* ({ olds }) {
2653
+ var BaoPkiRoleProvider = () => Provider10.effect(BaoPkiRole, Effect25.succeed(BaoPkiRole.Provider.of({
2654
+ list: () => Effect25.succeed([]),
2655
+ read: Effect25.fn(function* ({ olds }) {
2595
2656
  return yield* readRole2(olds);
2596
2657
  }),
2597
- diff: Effect24.fn(function* ({ news, output }) {
2598
- 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)
2599
2661
  return;
2600
- if (mountPath(news.mount ?? "pki") !== output.mount || news.name !== output.name)
2662
+ if (move !== undefined)
2601
2663
  return { action: "replace" };
2664
+ if (!isResolved10(news))
2665
+ return;
2602
2666
  if (problems6(news).length > 0)
2603
2667
  return { action: "update" };
2604
2668
  const live = yield* readRole2(news);
@@ -2606,11 +2670,12 @@ var BaoPkiRoleProvider = () => Provider10.effect(BaoPkiRole, Effect24.succeed(Ba
2606
2670
  return { action: "update" };
2607
2671
  return matches9(live, news) ? { action: "noop" } : { action: "update" };
2608
2672
  }),
2609
- reconcile: Effect24.fn(function* ({ news }) {
2673
+ reconcile: Effect25.fn(function* ({ news, output }) {
2610
2674
  const path = rolePath4(news);
2675
+ yield* guardRename(IDENTITY7, news, output);
2611
2676
  const bad = problems6(news);
2612
2677
  if (bad.length > 0) {
2613
- return yield* Effect24.die(new Error(`Bao.PkiRole ${path}: ${bad.join(" ")}`));
2678
+ return yield* Effect25.die(new Error(`Bao.PkiRole ${path}: ${bad.join(" ")}`));
2614
2679
  }
2615
2680
  const live = yield* readRole2(news);
2616
2681
  if (live === undefined || !matches9(live, news)) {
@@ -2618,14 +2683,14 @@ var BaoPkiRoleProvider = () => Provider10.effect(BaoPkiRole, Effect24.succeed(Ba
2618
2683
  }
2619
2684
  const after = yield* readRole2(news);
2620
2685
  if (after === undefined) {
2621
- return yield* Effect24.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.`));
2622
2687
  }
2623
2688
  if (!matches9(after, news)) {
2624
- return yield* Effect24.die(new Error(`Bao.PkiRole ${path}: wrote cleanly but the role read back different. ` + "OpenBao stored something other than what was declared — inspect with " + `\`bao read ${path}\` before retrying.`));
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.`));
2625
2690
  }
2626
2691
  return after;
2627
2692
  }),
2628
- delete: Effect24.fn(function* ({ output }) {
2693
+ delete: Effect25.fn(function* ({ output }) {
2629
2694
  yield* baoDelete(rolePath4(output));
2630
2695
  return;
2631
2696
  })
@@ -2634,7 +2699,7 @@ var BaoPkiRoleProvider = () => Provider10.effect(BaoPkiRole, Effect24.succeed(Ba
2634
2699
  import { Resource as Resource11 } from "alchemy";
2635
2700
  import { isResolved as isResolved11 } from "alchemy/Diff";
2636
2701
  import * as Provider11 from "alchemy/Provider";
2637
- import * as Effect26 from "effect/Effect";
2702
+ import * as Effect27 from "effect/Effect";
2638
2703
 
2639
2704
  // src/openbao/plugin-form.ts
2640
2705
  var resolve = (props) => ({
@@ -2690,7 +2755,7 @@ var sameArgs = (want, have) => want.length === have.length && want.every((entry,
2690
2755
  var matches10 = (attributes, form) => !attributes.builtin && attributes.version === form.version && attributes.sha256 === form.sha256 && attributes.command === form.command && sameArgs(form.args, attributes.args);
2691
2756
 
2692
2757
  // src/openbao/plugin-reconcile.ts
2693
- import * as Effect25 from "effect/Effect";
2758
+ import * as Effect26 from "effect/Effect";
2694
2759
 
2695
2760
  // src/openbao/plugin-wire.ts
2696
2761
  var readPluginData = (form) => baoRead(versionedPath(form.type, form.name, form.version));
@@ -2698,10 +2763,10 @@ var registerPlugin = (form) => baoWrite("PUT", catalogPath(form.type, form.name)
2698
2763
  var deregisterPlugin = (type, name, version) => baoDelete(versionedPath(type, name, version));
2699
2764
 
2700
2765
  // src/openbao/plugin-reconcile.ts
2701
- var readEntry = (form) => Effect25.map(readPluginData(form), (live) => live === undefined ? undefined : { attributes: attributesOf11(form, live), live });
2702
- var refuse5 = (form, message) => Effect25.die(new Error(`Bao.Plugin ${versionedPath(form.type, form.name, form.version)}: ${message}`));
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}`));
2703
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.";
2704
- var reconcilePlugin = (props) => Effect25.gen(function* () {
2769
+ var reconcilePlugin = (props) => Effect26.gen(function* () {
2705
2770
  const form = resolve(props);
2706
2771
  const bad = problems7(form);
2707
2772
  if (bad.length > 0)
@@ -2728,28 +2793,43 @@ var reconcilePlugin = (props) => Effect25.gen(function* () {
2728
2793
  var BaoPlugin = Resource11("Bao.Plugin", {
2729
2794
  defaultRemovalPolicy: "retain"
2730
2795
  });
2731
- var BaoPluginProvider = () => Provider11.effect(BaoPlugin, Effect26.succeed(BaoPlugin.Provider.of({
2732
- list: () => Effect26.succeed([]),
2733
- read: Effect26.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 }) {
2734
2811
  const found = yield* readEntry(resolve(olds));
2735
2812
  return found?.attributes;
2736
2813
  }),
2737
- diff: Effect26.fn(function* ({ news, output }) {
2738
- 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)
2739
2817
  return;
2740
- const form = resolve(news);
2741
- if (output.name !== form.name || output.type !== form.type || output.version !== form.version) {
2818
+ if (move !== undefined)
2742
2819
  return { action: "replace" };
2743
- }
2820
+ if (!isResolved11(news))
2821
+ return;
2822
+ const form = resolve(news);
2744
2823
  const found = yield* readEntry(form);
2745
2824
  if (found === undefined)
2746
2825
  return { action: "update" };
2747
2826
  return matches10(found.attributes, form) ? { action: "noop" } : { action: "update" };
2748
2827
  }),
2749
- reconcile: Effect26.fn(function* ({ news }) {
2828
+ reconcile: Effect27.fn(function* ({ news, output }) {
2829
+ yield* guardRename(IDENTITY8, news, output);
2750
2830
  return yield* reconcilePlugin(news);
2751
2831
  }),
2752
- delete: Effect26.fn(function* ({ output }) {
2832
+ delete: Effect27.fn(function* ({ output }) {
2753
2833
  yield* deregisterPlugin(output.type, output.name, output.version);
2754
2834
  return;
2755
2835
  })
@@ -2758,7 +2838,7 @@ var BaoPluginProvider = () => Provider11.effect(BaoPlugin, Effect26.succeed(BaoP
2758
2838
  import { Resource as Resource12 } from "alchemy";
2759
2839
  import { isResolved as isResolved12 } from "alchemy/Diff";
2760
2840
  import * as Provider12 from "alchemy/Provider";
2761
- import * as Effect28 from "effect/Effect";
2841
+ import * as Effect29 from "effect/Effect";
2762
2842
  import * as FileSystem from "effect/FileSystem";
2763
2843
  import * as Path from "effect/Path";
2764
2844
 
@@ -2766,13 +2846,13 @@ import * as Path from "effect/Path";
2766
2846
  var isEmptyAssembly = (parts, joined) => parts === 0 || !/^\s*path\s+"/m.test(joined);
2767
2847
 
2768
2848
  // src/openbao/policy-wire.ts
2769
- import * as Effect27 from "effect/Effect";
2849
+ import * as Effect28 from "effect/Effect";
2770
2850
  var policyPath = (name) => `sys/policies/acl/${name}`;
2771
- var readPolicy = (name) => Effect27.flatMap(baoRead(policyPath(name)), (data) => {
2851
+ var readPolicy = (name) => Effect28.flatMap(baoRead(policyPath(name)), (data) => {
2772
2852
  if (data === undefined)
2773
- return Effect27.succeed("");
2853
+ return Effect28.succeed("");
2774
2854
  const text11 = data["policy"];
2775
- return typeof text11 === "string" ? Effect27.succeed(text11) : Effect27.fail(new BaoError(200, "GET", policyPath(name), ["no data.policy in the response"]));
2855
+ return typeof text11 === "string" ? Effect28.succeed(text11) : Effect28.fail(new BaoError(200, "GET", policyPath(name), ["no data.policy in the response"]));
2776
2856
  });
2777
2857
  var writePolicy = (name, hcl) => baoWrite("PUT", policyPath(name), { policy: hcl });
2778
2858
  var deletePolicy = (name) => baoDelete(policyPath(name));
@@ -2781,6 +2861,7 @@ var deletePolicy = (name) => baoDelete(policyPath(name));
2781
2861
  var BaoPolicy = Resource12("Bao.Policy", {
2782
2862
  defaultRemovalPolicy: "retain"
2783
2863
  });
2864
+ var IDENTITY9 = nameIdentity("Bao.Policy", policyPath, policyKey);
2784
2865
  var grantsOf = (hcl) => (hcl.match(/^path /gm) ?? []).length;
2785
2866
  var attributesOf12 = (name, hcl, parts) => ({
2786
2867
  name,
@@ -2788,10 +2869,10 @@ var attributesOf12 = (name, hcl, parts) => ({
2788
2869
  grants: grantsOf(hcl),
2789
2870
  parts
2790
2871
  });
2791
- var BaoPolicyProvider = () => Provider12.effect(BaoPolicy, Effect28.gen(function* () {
2872
+ var BaoPolicyProvider = () => Provider12.effect(BaoPolicy, Effect29.gen(function* () {
2792
2873
  const fs = yield* FileSystem.FileSystem;
2793
2874
  const path = yield* Path.Path;
2794
- const assemble = (dir) => Effect28.gen(function* () {
2875
+ const assemble = (dir) => Effect29.gen(function* () {
2795
2876
  const entries2 = (yield* fs.readDirectory(dir)).filter((f) => f.endsWith(".hcl")).sort();
2796
2877
  let joined = "";
2797
2878
  for (const f of entries2)
@@ -2800,15 +2881,13 @@ var BaoPolicyProvider = () => Provider12.effect(BaoPolicy, Effect28.gen(function
2800
2881
  return { joined, parts: entries2.length };
2801
2882
  });
2802
2883
  return BaoPolicy.Provider.of({
2803
- list: () => Effect28.succeed([]),
2804
- read: Effect28.fn(function* ({ olds }) {
2884
+ list: () => Effect29.succeed([]),
2885
+ read: Effect29.fn(function* ({ olds }) {
2805
2886
  const live = yield* readPolicy(olds.name);
2806
2887
  return live.length === 0 ? undefined : attributesOf12(olds.name, live, 0);
2807
2888
  }),
2808
- diff: Effect28.fn(function* ({ news, olds, output }) {
2809
- const tried = output?.name ?? declaredString(olds, "name");
2810
- const declared = declaredString(news, "name");
2811
- const move = yield* judgeMove("Bao.Policy", tried, declared, policyPath, policyKey);
2889
+ diff: Effect29.fn(function* ({ news, olds, output }) {
2890
+ const move = yield* judgeRename(IDENTITY9, olds, news, output);
2812
2891
  if (output === undefined)
2813
2892
  return;
2814
2893
  if (move !== undefined)
@@ -2819,14 +2898,12 @@ var BaoPolicyProvider = () => Provider12.effect(BaoPolicy, Effect28.gen(function
2819
2898
  const live = yield* readPolicy(news.name);
2820
2899
  return live.length > 0 && sha256(live) === sha256(joined) ? { action: "noop" } : { action: "update" };
2821
2900
  }),
2822
- reconcile: Effect28.fn(function* ({ news, output }) {
2901
+ reconcile: Effect29.fn(function* ({ news, output }) {
2823
2902
  const name = news.name;
2824
- if (output !== undefined && isMoved(output.name, name, policyKey) === true) {
2825
- return yield* refuseMovedUpdate(`Bao.Policy ${name}`, output.name, name);
2826
- }
2903
+ yield* guardRename(IDENTITY9, news, output);
2827
2904
  const { joined, parts } = yield* assemble(news.fragments);
2828
2905
  if (isEmptyAssembly(parts, joined))
2829
- return yield* Effect28.die(new Error(`Bao.Policy ${name}: the fragments under ${news.fragments} carry no path grant ` + `(${String(parts)} file(s)). Writing it would revoke every grant the policy carries.`));
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.`));
2830
2907
  const current = yield* readPolicy(name);
2831
2908
  if (current.length > 0 && sha256(current) === sha256(joined)) {
2832
2909
  return attributesOf12(name, joined, parts);
@@ -2834,7 +2911,7 @@ var BaoPolicyProvider = () => Provider12.effect(BaoPolicy, Effect28.gen(function
2834
2911
  yield* writePolicy(name, joined);
2835
2912
  return attributesOf12(name, joined, parts);
2836
2913
  }),
2837
- delete: Effect28.fn(function* ({ output }) {
2914
+ delete: Effect29.fn(function* ({ output }) {
2838
2915
  yield* deletePolicy(output.name);
2839
2916
  return;
2840
2917
  })
@@ -2844,7 +2921,7 @@ var BaoPolicyProvider = () => Provider12.effect(BaoPolicy, Effect28.gen(function
2844
2921
  import { Resource as Resource13 } from "alchemy";
2845
2922
  import { isResolved as isResolved13 } from "alchemy/Diff";
2846
2923
  import * as Provider13 from "alchemy/Provider";
2847
- import * as Effect30 from "effect/Effect";
2924
+ import * as Effect31 from "effect/Effect";
2848
2925
 
2849
2926
  // src/openbao/proxmox-role-form.ts
2850
2927
  var text11 = (value) => typeof value === "string" ? value : "";
@@ -2894,8 +2971,8 @@ var rescopeRefusal = (before, mintUser, allowMintUserChange) => before === mintU
2894
2971
  var matches11 = (attributes, props) => attributes.mintUser === props.mintUser && sameTtl5(props.ttl, attributes.ttl) && sameTtl5(props.maxTtl, attributes.maxTtl);
2895
2972
 
2896
2973
  // src/openbao/proxmox-role-wire.ts
2897
- import * as Effect29 from "effect/Effect";
2898
- var readRole3 = (props) => Effect29.gen(function* () {
2974
+ import * as Effect30 from "effect/Effect";
2975
+ var readRole3 = (props) => Effect30.gen(function* () {
2899
2976
  const live = yield* baoRead(rolePath5(props.mount, props.name));
2900
2977
  if (live === undefined)
2901
2978
  return;
@@ -2906,15 +2983,14 @@ var readRole3 = (props) => Effect29.gen(function* () {
2906
2983
  var BaoProxmoxRole = Resource13("Bao.ProxmoxRole", {
2907
2984
  defaultRemovalPolicy: "retain"
2908
2985
  });
2909
- var BaoProxmoxRoleProvider = () => Provider13.effect(BaoProxmoxRole, Effect30.succeed(BaoProxmoxRole.Provider.of({
2910
- list: () => Effect30.succeed([]),
2911
- read: Effect30.fn(function* ({ olds }) {
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 }) {
2912
2990
  return yield* readRole3(olds);
2913
2991
  }),
2914
- diff: Effect30.fn(function* ({ news, olds, output }) {
2915
- const tried = triedRolePath(output, olds, rolePath5);
2916
- const declared = declaredRolePath(news, rolePath5);
2917
- const move = yield* judgeMove("Bao.ProxmoxRole", tried, declared, (path) => path);
2992
+ diff: Effect31.fn(function* ({ news, olds, output }) {
2993
+ const move = yield* judgeRename(IDENTITY10, olds, news, output);
2918
2994
  if (output === undefined)
2919
2995
  return;
2920
2996
  if (move !== undefined) {
@@ -2926,7 +3002,7 @@ var BaoProxmoxRoleProvider = () => Provider13.effect(BaoProxmoxRole, Effect30.su
2926
3002
  const refusal2 = rescopeRefusal(from, mintUser, allow);
2927
3003
  if (refusal2 === undefined)
2928
3004
  return { action: "replace" };
2929
- return yield* Effect30.die(new Error(`Bao.ProxmoxRole ${move.from} → ${move.to}: the rename ${refusal2}`));
3005
+ return yield* Effect31.die(new Error(`Bao.ProxmoxRole ${move.from} → ${move.to}: the rename ${refusal2}`));
2930
3006
  }
2931
3007
  if (!isResolved13(news))
2932
3008
  return;
@@ -2937,31 +3013,28 @@ var BaoProxmoxRoleProvider = () => Provider13.effect(BaoProxmoxRole, Effect30.su
2937
3013
  return { action: "noop" };
2938
3014
  return { action: "update" };
2939
3015
  }),
2940
- reconcile: Effect30.fn(function* ({ news, output }) {
3016
+ reconcile: Effect31.fn(function* ({ news, output }) {
2941
3017
  const path = rolePath5(news.mount, news.name);
2942
- const before = output === undefined ? path : rolePath5(output.mount, output.name);
2943
- if (isMoved(before, path) === true) {
2944
- return yield* refuseMovedUpdate("Bao.ProxmoxRole", before, path);
2945
- }
3018
+ yield* guardRename(IDENTITY10, news, output);
2946
3019
  const live = yield* readRole3(news);
2947
3020
  const rescope = live === undefined ? undefined : rescopeRefusal(live.mintUser, news.mintUser, news.allowMintUserChange);
2948
3021
  if (rescope !== undefined) {
2949
- return yield* Effect30.die(new Error(`Bao.ProxmoxRole ${path}: ${rescope}`));
3022
+ return yield* Effect31.die(new Error(`Bao.ProxmoxRole ${path}: ${rescope}`));
2950
3023
  }
2951
3024
  if (live === undefined || !matches11(live, news)) {
2952
3025
  const write = writeBody9(news);
2953
3026
  if (!write.ok) {
2954
- return yield* Effect30.die(new Error(`Bao.ProxmoxRole ${path}: unparseable duration ` + `${write.bad.join(", ")}. Use 30s / 5m / 1h / 1d, or 0.`));
3027
+ return yield* Effect31.die(new Error(`Bao.ProxmoxRole ${path}: unparseable duration ` + `${write.bad.join(", ")}. Use 30s / 5m / 1h / 1d, or 0.`));
2955
3028
  }
2956
3029
  yield* baoWrite("PUT", path, write.body);
2957
3030
  }
2958
3031
  const after = yield* readRole3(news);
2959
3032
  if (after === undefined) {
2960
- return yield* Effect30.die(new Error(`Bao.ProxmoxRole ${path}: 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."));
2961
3034
  }
2962
3035
  return after;
2963
3036
  }),
2964
- delete: Effect30.fn(function* ({ output }) {
3037
+ delete: Effect31.fn(function* ({ output }) {
2965
3038
  yield* baoDelete(rolePath5(output.mount, output.name));
2966
3039
  return;
2967
3040
  })
@@ -2970,7 +3043,7 @@ var BaoProxmoxRoleProvider = () => Provider13.effect(BaoProxmoxRole, Effect30.su
2970
3043
  import { Resource as Resource14 } from "alchemy";
2971
3044
  import { isResolved as isResolved14 } from "alchemy/Diff";
2972
3045
  import * as Provider14 from "alchemy/Provider";
2973
- import * as Effect31 from "effect/Effect";
3046
+ import * as Effect32 from "effect/Effect";
2974
3047
 
2975
3048
  // src/openbao/ssh-role-form.ts
2976
3049
  var readPath4 = (mount, name) => `${sshMountOf(mount)}/roles/${name}`;
@@ -3058,16 +3131,7 @@ var sameTtl6 = (want, have) => parseDuration(want) === have;
3058
3131
  var badDurations = (form) => ["ttl", "maxTtl"].filter((field) => parseDuration(form[field]) === undefined);
3059
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);
3060
3133
 
3061
- // src/openbao/ssh-role.ts
3062
- var BaoSshRole = Resource14("Bao.SshRole", {
3063
- defaultRemovalPolicy: "retain"
3064
- });
3065
- var readRole4 = (form) => Effect31.gen(function* () {
3066
- const live = yield* baoRead(readPath4(form.mount, form.name));
3067
- if (live === undefined)
3068
- return;
3069
- return { attributes: attributesOf14(form, live), live };
3070
- });
3134
+ // src/openbao/ssh-role-erase.ts
3071
3135
  var UNMANAGED_DEFAULTS = {
3072
3136
  algorithm_signer: "default",
3073
3137
  allow_commas_in_identity_templates: false,
@@ -3088,28 +3152,43 @@ var wouldErase2 = (live) => {
3088
3152
  }
3089
3153
  return lost.sort();
3090
3154
  };
3091
- var refuse6 = (message) => Effect31.die(new Error(message));
3092
- var BaoSshRoleProvider = () => Provider14.effect(BaoSshRole, Effect31.succeed(BaoSshRole.Provider.of({
3093
- list: () => Effect31.succeed([]),
3094
- read: Effect31.fn(function* ({ olds }) {
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 }) {
3095
3171
  const found = yield* readRole4(resolve2(olds));
3096
3172
  return found?.attributes;
3097
3173
  }),
3098
- diff: Effect31.fn(function* ({ news, output }) {
3099
- 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)
3100
3177
  return;
3101
- const form = resolve2(news);
3102
- if (output.mount !== form.mount || output.name !== form.name) {
3178
+ if (move !== undefined)
3103
3179
  return { action: "replace" };
3104
- }
3180
+ if (!isResolved14(news))
3181
+ return;
3182
+ const form = resolve2(news);
3105
3183
  const found = yield* readRole4(form);
3106
3184
  if (found === undefined)
3107
3185
  return { action: "update" };
3108
3186
  return matches12(found.attributes, form) ? { action: "noop" } : { action: "update" };
3109
3187
  }),
3110
- reconcile: Effect31.fn(function* ({ news }) {
3188
+ reconcile: Effect32.fn(function* ({ news, output }) {
3111
3189
  const form = resolve2(news);
3112
3190
  const path = readPath4(form.mount, form.name);
3191
+ yield* guardRename(IDENTITY11, news, output);
3113
3192
  const bad = badDurations(form);
3114
3193
  if (bad.length > 0) {
3115
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.");
@@ -3133,7 +3212,7 @@ var BaoSshRoleProvider = () => Provider14.effect(BaoSshRole, Effect31.succeed(Ba
3133
3212
  }
3134
3213
  return after.attributes;
3135
3214
  }),
3136
- delete: Effect31.fn(function* ({ output }) {
3215
+ delete: Effect32.fn(function* ({ output }) {
3137
3216
  yield* baoDelete(readPath4(output.mount, output.name));
3138
3217
  return;
3139
3218
  })
@@ -3187,5 +3266,5 @@ export {
3187
3266
  revokeSelfEffect
3188
3267
  };
3189
3268
 
3190
- //# debugId=81D869587FC0B7AF64756E2164756E21
3269
+ //# debugId=B83B2582FB998CE264756E2164756E21
3191
3270
  //# sourceMappingURL=index.js.map