@homeflare/alchemy 0.30.0 → 0.31.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/README.md +22 -0
  2. package/dist/discord/application-command.d.ts +52 -0
  3. package/dist/discord/application-command.d.ts.map +1 -0
  4. package/dist/discord/command-form.d.ts +62 -0
  5. package/dist/discord/command-form.d.ts.map +1 -0
  6. package/dist/discord/credentials.d.ts +35 -0
  7. package/dist/discord/credentials.d.ts.map +1 -0
  8. package/dist/discord/fake-discord.d.ts +44 -0
  9. package/dist/discord/fake-discord.d.ts.map +1 -0
  10. package/dist/discord/guild-application-command.d.ts +53 -0
  11. package/dist/discord/guild-application-command.d.ts.map +1 -0
  12. package/dist/discord/index.d.ts +12 -0
  13. package/dist/discord/index.d.ts.map +1 -0
  14. package/dist/discord/index.js +193 -0
  15. package/dist/discord/index.js.map +15 -0
  16. package/dist/discord/providers.d.ts +15 -0
  17. package/dist/discord/providers.d.ts.map +1 -0
  18. package/dist/discord/resource.d.ts +75 -0
  19. package/dist/discord/resource.d.ts.map +1 -0
  20. package/dist/proxmox/acl-wire.d.ts +26 -0
  21. package/dist/proxmox/acl-wire.d.ts.map +1 -0
  22. package/dist/proxmox/acl.d.ts.map +1 -1
  23. package/dist/proxmox/api-token-form.d.ts +28 -26
  24. package/dist/proxmox/api-token-form.d.ts.map +1 -1
  25. package/dist/proxmox/api-token.d.ts +48 -89
  26. package/dist/proxmox/api-token.d.ts.map +1 -1
  27. package/dist/proxmox/ceph-fs-wire.d.ts +2 -2
  28. package/dist/proxmox/ceph-fs-wire.d.ts.map +1 -1
  29. package/dist/proxmox/ceph-osd-tree.d.ts +1 -1
  30. package/dist/proxmox/ceph-osd-tree.d.ts.map +1 -1
  31. package/dist/proxmox/ceph-osd-write.d.ts +2 -2
  32. package/dist/proxmox/ceph-osd-write.d.ts.map +1 -1
  33. package/dist/proxmox/ceph-pool-settle.d.ts +1 -1
  34. package/dist/proxmox/ceph-pool-settle.d.ts.map +1 -1
  35. package/dist/proxmox/client.d.ts +2 -2
  36. package/dist/proxmox/client.d.ts.map +1 -1
  37. package/dist/proxmox/credential-errors.d.ts +27 -0
  38. package/dist/proxmox/credential-errors.d.ts.map +1 -0
  39. package/dist/proxmox/credentials.d.ts +0 -38
  40. package/dist/proxmox/credentials.d.ts.map +1 -1
  41. package/dist/proxmox/distilled-guard.d.ts +31 -0
  42. package/dist/proxmox/distilled-guard.d.ts.map +1 -0
  43. package/dist/proxmox/distilled-pve.d.ts +11 -4
  44. package/dist/proxmox/distilled-pve.d.ts.map +1 -1
  45. package/dist/proxmox/generated/constraints/index.d.ts +1 -1
  46. package/dist/proxmox/generated/constraints/pve-access.d.ts +1 -1
  47. package/dist/proxmox/generated/constraints/pve-access.d.ts.map +1 -1
  48. package/dist/proxmox/generated/constraints/pve-cluster-backup.d.ts +1 -1
  49. package/dist/proxmox/generated/constraints/pve-cluster-ceph.d.ts +1 -1
  50. package/dist/proxmox/generated/constraints/pve-cluster-firewall.d.ts +1 -1
  51. package/dist/proxmox/generated/constraints/pve-cluster-ha.d.ts +1 -1
  52. package/dist/proxmox/generated/constraints/pve-cluster-metrics.d.ts +1 -1
  53. package/dist/proxmox/generated/constraints/pve-cluster-notifications.d.ts +1 -1
  54. package/dist/proxmox/generated/constraints/pve-cluster-replication.d.ts +1 -1
  55. package/dist/proxmox/generated/constraints/pve-cluster-sdn.d.ts +1 -1
  56. package/dist/proxmox/generated/constraints/pve-nodes-ceph.d.ts +1 -1
  57. package/dist/proxmox/generated/constraints/pve-nodes-disks.d.ts +1 -1
  58. package/dist/proxmox/generated/constraints/pve-nodes-lxc.d.ts +1 -1
  59. package/dist/proxmox/generated/constraints/pve-nodes-network.d.ts +1 -1
  60. package/dist/proxmox/generated/constraints/pve-nodes-qemu.d.ts +1 -1
  61. package/dist/proxmox/generated/constraints/pve-pools.d.ts +1 -1
  62. package/dist/proxmox/generated/constraints/pve-storage.d.ts +1 -1
  63. package/dist/proxmox/group-wire.d.ts +29 -0
  64. package/dist/proxmox/group-wire.d.ts.map +1 -0
  65. package/dist/proxmox/group.d.ts +20 -24
  66. package/dist/proxmox/group.d.ts.map +1 -1
  67. package/dist/proxmox/index.js +725 -509
  68. package/dist/proxmox/index.js.map +40 -33
  69. package/dist/proxmox/lease-cache.d.ts +1 -1
  70. package/dist/proxmox/lease-cache.d.ts.map +1 -1
  71. package/dist/proxmox/lxc-lifecycle.d.ts +5 -5
  72. package/dist/proxmox/lxc-lifecycle.d.ts.map +1 -1
  73. package/dist/proxmox/mint.d.ts +50 -0
  74. package/dist/proxmox/mint.d.ts.map +1 -0
  75. package/dist/proxmox/network-apply-read.d.ts +2 -2
  76. package/dist/proxmox/network-apply-read.d.ts.map +1 -1
  77. package/dist/proxmox/pbs-notification-target-lifecycle.d.ts +2 -2
  78. package/dist/proxmox/pbs-sync-job-form.d.ts +2 -2
  79. package/dist/proxmox/provision-cli-fake.d.ts.map +1 -1
  80. package/dist/proxmox/resource.d.ts +4 -4
  81. package/dist/proxmox/role-wire.d.ts +28 -0
  82. package/dist/proxmox/role-wire.d.ts.map +1 -0
  83. package/dist/proxmox/role.d.ts +36 -52
  84. package/dist/proxmox/role.d.ts.map +1 -1
  85. package/dist/proxmox/sdn-apply-read.d.ts +1 -1
  86. package/dist/proxmox/sdn-apply-read.d.ts.map +1 -1
  87. package/dist/proxmox/unreadable-read.d.ts +50 -0
  88. package/dist/proxmox/unreadable-read.d.ts.map +1 -0
  89. package/dist/proxmox/user-wire.d.ts +18 -0
  90. package/dist/proxmox/user-wire.d.ts.map +1 -0
  91. package/dist/proxmox/user.d.ts +25 -32
  92. package/dist/proxmox/user.d.ts.map +1 -1
  93. package/dist/proxmox/zfs-pool-write.d.ts +2 -2
  94. package/dist/proxmox/zfs-pool-write.d.ts.map +1 -1
  95. package/docs/credentials.md +9 -0
  96. package/docs/discord.md +129 -0
  97. package/docs/upstream-conformance.md +17 -1
  98. package/package.json +5 -1
  99. package/src/discord/application-command.test.ts +103 -0
  100. package/src/discord/application-command.ts +109 -0
  101. package/src/discord/command-form.test.ts +144 -0
  102. package/src/discord/command-form.ts +133 -0
  103. package/src/discord/credentials.ts +36 -0
  104. package/src/discord/fake-discord.ts +70 -0
  105. package/src/discord/guild-application-command.test.ts +215 -0
  106. package/src/discord/guild-application-command.ts +129 -0
  107. package/src/discord/index.ts +31 -0
  108. package/src/discord/providers.ts +18 -0
  109. package/src/discord/resource.ts +164 -0
  110. package/src/discord/retry.test.ts +74 -0
  111. package/src/proxmox/acl-wire.ts +83 -0
  112. package/src/proxmox/acl.ts +29 -82
  113. package/src/proxmox/adopt-noop.test.ts +18 -0
  114. package/src/proxmox/api-token-adopt.test.ts +28 -12
  115. package/src/proxmox/api-token-form.ts +50 -93
  116. package/src/proxmox/api-token.ts +149 -135
  117. package/src/proxmox/client.ts +2 -7
  118. package/src/proxmox/constraints-forms.test.ts +7 -27
  119. package/src/proxmox/credential-errors.ts +33 -0
  120. package/src/proxmox/credentials.test.ts +29 -3
  121. package/src/proxmox/credentials.ts +4 -134
  122. package/src/proxmox/distilled-guard.ts +42 -0
  123. package/src/proxmox/distilled-pve.ts +15 -4
  124. package/src/proxmox/generated/constraints/index.ts +1 -1
  125. package/src/proxmox/generated/constraints/pve-access.ts +1 -4
  126. package/src/proxmox/generated/constraints/pve-cluster-backup.ts +1 -1
  127. package/src/proxmox/generated/constraints/pve-cluster-ceph.ts +1 -1
  128. package/src/proxmox/generated/constraints/pve-cluster-firewall.ts +1 -1
  129. package/src/proxmox/generated/constraints/pve-cluster-ha.ts +1 -1
  130. package/src/proxmox/generated/constraints/pve-cluster-metrics.ts +1 -1
  131. package/src/proxmox/generated/constraints/pve-cluster-notifications.ts +1 -1
  132. package/src/proxmox/generated/constraints/pve-cluster-replication.ts +1 -1
  133. package/src/proxmox/generated/constraints/pve-cluster-sdn.ts +1 -1
  134. package/src/proxmox/generated/constraints/pve-nodes-ceph.ts +1 -1
  135. package/src/proxmox/generated/constraints/pve-nodes-disks.ts +1 -1
  136. package/src/proxmox/generated/constraints/pve-nodes-lxc.ts +1 -1
  137. package/src/proxmox/generated/constraints/pve-nodes-network.ts +1 -1
  138. package/src/proxmox/generated/constraints/pve-nodes-qemu.ts +1 -1
  139. package/src/proxmox/generated/constraints/pve-pools.ts +1 -1
  140. package/src/proxmox/generated/constraints/pve-storage.ts +1 -1
  141. package/src/proxmox/group-wire.ts +63 -0
  142. package/src/proxmox/group.test.ts +110 -0
  143. package/src/proxmox/group.ts +135 -105
  144. package/src/proxmox/lease-cache.ts +2 -1
  145. package/src/proxmox/lxc-lifecycle.ts +2 -1
  146. package/src/proxmox/mint.ts +158 -0
  147. package/src/proxmox/network-apply.ts +1 -1
  148. package/src/proxmox/provision-cli-fake.ts +11 -0
  149. package/src/proxmox/provision-declare.test.ts +6 -1
  150. package/src/proxmox/role-wire.ts +49 -0
  151. package/src/proxmox/role.test.ts +89 -0
  152. package/src/proxmox/role.ts +147 -106
  153. package/src/proxmox/unreadable-read.test.ts +83 -0
  154. package/src/proxmox/unreadable-read.ts +60 -0
  155. package/src/proxmox/user-wire.ts +112 -0
  156. package/src/proxmox/user.test.ts +142 -0
  157. package/src/proxmox/user.ts +132 -129
@@ -94,5 +94,5 @@ export declare const makeLeases: <E, R>(mintFor: (key: LeaseKey) => Effect.Effec
94
94
  * ★ client.ts is the one place that decides to use the cache. `mint` stays exported and unchanged
95
95
  * for the caller that reasons about identity rather than cost — network-apply.ts.
96
96
  */
97
- export declare const leased: (target: ApiTarget, role: PveRole) => Effect.Effect<PveCredential, Error, import("effect/unstable/http/HttpClient").HttpClient>;
97
+ export declare const leased: (target: ApiTarget, role: PveRole) => Effect.Effect<PveCredential, Error | import("./credential-errors.ts").PveCredentialDenied, import("effect/unstable/http/HttpClient").HttpClient>;
98
98
  //# sourceMappingURL=lease-cache.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"lease-cache.d.ts","sourceRoot":"","sources":["../../src/proxmox/lease-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,IAAI,MAAM,aAAa,CAAC;AACpC,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,aAAa,EAAE,KAAK,OAAO,EAAQ,MAAM,kBAAkB,CAAC;AAe1F;;;;;;;;;;GAUG;AACH,MAAM,MAAM,QAAQ,GAAG;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC,QAAQ,CAAC,CAAC;CACtC,CAAC;AAQF;;;;;;;;GAQG;AACH,eAAO,MAAM,UAAU,SAAU,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,OAAO,CAAC,KAAG,QAAQ,CAAC,QAG3D,CAAC;AAEpB;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,UAAU,GAAI,CAAC,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,QAAQ,KAAK,MAAM,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC,EAAE,CAAC,CAAC,4EACX,CAAC;AASrF;;;;;;GAMG;AACH,eAAO,MAAM,MAAM,WAAY,SAAS,QAAQ,OAAO,8FAA2C,CAAC"}
1
+ {"version":3,"file":"lease-cache.d.ts","sourceRoot":"","sources":["../../src/proxmox/lease-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,IAAI,MAAM,aAAa,CAAC;AACpC,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,aAAa,EAAE,KAAK,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAgBpF;;;;;;;;;;GAUG;AACH,MAAM,MAAM,QAAQ,GAAG;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC,QAAQ,CAAC,CAAC;CACtC,CAAC;AAQF;;;;;;;;GAQG;AACH,eAAO,MAAM,UAAU,SAAU,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,OAAO,CAAC,KAAG,QAAQ,CAAC,QAG3D,CAAC;AAEpB;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,UAAU,GAAI,CAAC,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,QAAQ,KAAK,MAAM,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC,EAAE,CAAC,CAAC,4EACX,CAAC;AASrF;;;;;;GAMG;AACH,eAAO,MAAM,MAAM,WAAY,SAAS,QAAQ,OAAO,qJAA2C,CAAC"}
@@ -24,7 +24,7 @@
24
24
  * a cached lease can be handed out with 60s left (lease-cache.ts).
25
25
  */
26
26
  import * as Effect from 'effect/Effect';
27
- import { type PveTarget } from './credentials.ts';
27
+ import type { PveTarget } from './credentials.ts';
28
28
  import { type LxcChange } from './lxc-judge.ts';
29
29
  import type { LxcAttributes, LxcProps } from './lxc-props.ts';
30
30
  /** A plan-time or apply-time refusal. Nothing was written when one of these is raised. */
@@ -39,10 +39,10 @@ export type LxcWhere = {
39
39
  };
40
40
  export declare const guestPath: (where: LxcWhere) => string;
41
41
  /** The live config, or undefined when no guest holds this vmid anywhere in the cluster. */
42
- export declare const readLive: (where: LxcWhere) => Effect.Effect<Record<string, unknown> | undefined, Error | LxcRefusedError, import("effect/unstable/http/HttpClient").HttpClient>;
42
+ export declare const readLive: (where: LxcWhere) => Effect.Effect<Record<string, unknown> | undefined, Error | LxcRefusedError | import("./credential-errors.ts").PveCredentialDenied, import("effect/unstable/http/HttpClient").HttpClient>;
43
43
  export declare const attributesOf: (where: LxcWhere, live: Record<string, unknown>) => LxcAttributes;
44
44
  /** POST the create and wait for the template to unpack. ⛔ Refusals are the caller's to check. */
45
- export declare const createGuest: (props: LxcProps) => Effect.Effect<undefined, Error | LxcRefusedError, import("effect/unstable/http/HttpClient").HttpClient>;
45
+ export declare const createGuest: (props: LxcProps) => Effect.Effect<undefined, Error | LxcRefusedError | import("./credential-errors.ts").PveCredentialDenied, import("effect/unstable/http/HttpClient").HttpClient>;
46
46
  /**
47
47
  * Apply a judged change: the config PUT first, then each resize.
48
48
  * ⚠️ IN THAT ORDER BECAUSE A MOUNT POINT'S PUT CARRIES ITS LIVE `size=`, and the resize rewrites it.
@@ -53,7 +53,7 @@ export declare const createGuest: (props: LxcProps) => Effect.Effect<undefined,
53
53
  * seen by reconcile's fresh judge instead: a declared key edited meanwhile is written back to the
54
54
  * declaration (even after a plan that warned of nothing), an undeclared one is left alone.
55
55
  */
56
- export declare const updateGuest: (props: LxcProps, change: LxcChange, digest: string) => Effect.Effect<void, Error | LxcRefusedError, import("effect/unstable/http/HttpClient").HttpClient>;
56
+ export declare const updateGuest: (props: LxcProps, change: LxcChange, digest: string) => Effect.Effect<void, Error | LxcRefusedError | import("./credential-errors.ts").PveCredentialDenied, import("effect/unstable/http/HttpClient").HttpClient>;
57
57
  /**
58
58
  * ⛔ NO `force`, NO `purge`, NO STOP FIRST. PVE refuses to destroy a running or protected guest,
59
59
  * and that refusal is kept: stopping a guest so a delete can proceed is an operator's decision.
@@ -64,5 +64,5 @@ export declare const updateGuest: (props: LxcProps, change: LxcChange, digest: s
64
64
  * first; one that no longer matches is refused with the keys that differ, and deleting it (or
65
65
  * declaring it again) is the operator's call.
66
66
  */
67
- export declare const destroyGuest: (where: LxcWhere, declared: LxcProps) => Effect.Effect<undefined, Error | LxcRefusedError, import("effect/unstable/http/HttpClient").HttpClient>;
67
+ export declare const destroyGuest: (where: LxcWhere, declared: LxcProps) => Effect.Effect<undefined, Error | LxcRefusedError | import("./credential-errors.ts").PveCredentialDenied, import("effect/unstable/http/HttpClient").HttpClient>;
68
68
  //# sourceMappingURL=lxc-lifecycle.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"lxc-lifecycle.d.ts","sourceRoot":"","sources":["../../src/proxmox/lxc-lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAIxC,OAAO,EAAE,KAAK,SAAS,EAAQ,MAAM,kBAAkB,CAAC;AAExD,OAAO,EAAE,KAAK,SAAS,EAAS,MAAM,gBAAgB,CAAC;AACvD,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAK9D,0FAA0F;AAC1F,qBAAa,eAAgB,SAAQ,KAAK;IACxC,SAAkB,IAAI,qBAAqB;CAC5C;AAED,6DAA6D;AAC7D,MAAM,MAAM,QAAQ,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpG,eAAO,MAAM,SAAS,UAAW,QAAQ,WAAoD,CAAC;AAgC9F,2FAA2F;AAC3F,eAAO,MAAM,QAAQ,UAAW,QAAQ,sIAqCpC,CAAC;AAEL,eAAO,MAAM,YAAY,UAAW,QAAQ,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,aAK5E,CAAC;AAgCH,iGAAiG;AACjG,eAAO,MAAM,WAAW,UAAW,QAAQ,4GAmBvC,CAAC;AAEL;;;;;;;;;GASG;AACH,eAAO,MAAM,WAAW,UAAW,QAAQ,UAAU,SAAS,UAAU,MAAM,uGAyB1E,CAAC;AAEL;;;;;;;;;GASG;AACH,eAAO,MAAM,YAAY,UAAW,QAAQ,YAAY,QAAQ,4GAe5D,CAAC"}
1
+ {"version":3,"file":"lxc-lifecycle.d.ts","sourceRoot":"","sources":["../../src/proxmox/lxc-lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAIxC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAGlD,OAAO,EAAE,KAAK,SAAS,EAAS,MAAM,gBAAgB,CAAC;AACvD,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAK9D,0FAA0F;AAC1F,qBAAa,eAAgB,SAAQ,KAAK;IACxC,SAAkB,IAAI,qBAAqB;CAC5C;AAED,6DAA6D;AAC7D,MAAM,MAAM,QAAQ,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpG,eAAO,MAAM,SAAS,UAAW,QAAQ,WAAoD,CAAC;AAgC9F,2FAA2F;AAC3F,eAAO,MAAM,QAAQ,UAAW,QAAQ,6LAqCpC,CAAC;AAEL,eAAO,MAAM,YAAY,UAAW,QAAQ,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,aAK5E,CAAC;AAgCH,iGAAiG;AACjG,eAAO,MAAM,WAAW,UAAW,QAAQ,mKAmBvC,CAAC;AAEL;;;;;;;;;GASG;AACH,eAAO,MAAM,WAAW,UAAW,QAAQ,UAAU,SAAS,UAAU,MAAM,8JAyB1E,CAAC;AAEL;;;;;;;;;GASG;AACH,eAAO,MAAM,YAAY,UAAW,QAAQ,YAAY,QAAQ,mKAe5D,CAAC"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Minting one PVE/PBS credential from OpenBao: `GET /v1/<mount>/creds/<role>`, alive for five
3
+ * minutes. Split out of `credentials.ts` (2026-09-24) to keep that file's TYPES under the
4
+ * 250-line cap once `PveCredentialDenied`'s catchers needed a real header there; the reasoning
5
+ * for WHY the estate mints rather than stores a token is still credentials.ts's own header.
6
+ *
7
+ * ⛔ THE SECRET NEVER TOUCHES DISK, A LOG, OR ALCHEMY STATE — see credentials.ts's ⛔. Nothing
8
+ * here returns it to a resource; it is used to build a header and then dropped.
9
+ */
10
+ import * as Effect from 'effect/Effect';
11
+ import * as HttpClient from 'effect/unstable/http/HttpClient';
12
+ import { PveCredentialDenied } from './credential-errors.ts';
13
+ import type { ApiTarget, BaoEnvironment, PveCredential, PveRole } from './credentials.ts';
14
+ /**
15
+ * Mint one credential for `role`: `GET /v1/<mount>/creds/<role>` on OpenBao's HTTP API.
16
+ *
17
+ * 🔴 IT SHELLED OUT TO `bao read -format=json` UNTIL 2026-09-14. The case for the CLI was that one
18
+ * client could not disagree with `bao kv get` about what this machine may do. <estate>/openbao then
19
+ * learned twice what deciding by stderr costs — every failure read as absent, and two commands
20
+ * said "absent" in different words — so this reads STATUS CODES: 2xx is a credential, and 403
21
+ * (denied), 404 (no such role or mount — a mint has no "absent"), 503 (sealed) all fail.
22
+ * ★ THE ENVIRONMENT CONTRACT IS STILL THE CLI's, READ THE CLI's WAY, which keeps the case above
23
+ * true (openbao v2.6.2 api/client.go:34-66, 370, 503-507, 761-762, 776-785): BAO_AGENT_ADDR over
24
+ * BAO_ADDR over https://127.0.0.1:8200; BAO_NAMESPACE as X-Vault-Namespace; BAO_TOKEN as
25
+ * X-Vault-Token; X-Vault-Request always. <estate>/openbao/src/bao-address.ts has the long form.
26
+ * ★ AGENT MODE IS NO BAO_TOKEN AND NO TOKEN HEADER. An Agent or Proxy listener with
27
+ * `api_proxy { use_auto_auth_token = true }` (or the older `cache { … }`) substitutes its
28
+ * auto-auth token exactly when a request carries none — command/agentproxyshared/cache/handler.go
29
+ * :29-38, command/agent.go:517-541, command/agent/config/config.go:288-292, and
30
+ * website/content/docs/agent-and-proxy/agent/apiproxy.mdx:57-62. The CLI's token-file fallback
31
+ * is deliberately not read. ⚠️ So an unset BAO_TOKEN aimed at a SERVER rather than an agent is a
32
+ * 403, which reads like a missing grant rather than a missing variable.
33
+ * ★ UNIX SOCKETS: `unix:///path` is plain HTTP to `localhost` over that socket, as the CLI does it
34
+ * (api/client.go:654-667). Bun's fetch takes a `unix` option; `FetchHttpClient.RequestInit` is
35
+ * Effect's documented way to pass fetch options, read from the calling fiber (effect
36
+ * FetchHttpClient.ts:59-72). Off Bun it REFUSES, rather than reach http://localhost:80.
37
+ */
38
+ export declare const mint: (target: ApiTarget, role: PveRole, env?: BaoEnvironment) => Effect.Effect<{
39
+ leaseSeconds: number;
40
+ secret: string;
41
+ tokenId: string;
42
+ }, Error | PveCredentialDenied, HttpClient.HttpClient>;
43
+ /**
44
+ * The header PVE wants. `PVEAPIToken=<tokenid>=<secret>`.
45
+ *
46
+ * ⚠️ THE SEPARATOR IS `=`, NOT `:`. Proxmox rejects the colon form with a 401 that says only
47
+ * "authentication failure", which reads as a bad credential rather than as a malformed header.
48
+ */
49
+ export declare const authorization: (credential: PveCredential, target: ApiTarget) => string;
50
+ //# sourceMappingURL=mint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mint.d.ts","sourceRoot":"","sources":["../../src/proxmox/mint.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAIxC,OAAO,KAAK,UAAU,MAAM,iCAAiC,CAAC;AAE9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAM1F;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,IAAI,WAAY,SAAS,QAAQ,OAAO,QAAO,cAAc;;;;sDAoGtE,CAAC;AAEL;;;;;GAKG;AACH,eAAO,MAAM,aAAa,eAAgB,aAAa,UAAU,SAAS,KAAG,MAGf,CAAC"}
@@ -54,7 +54,7 @@ export type ClusterHealth = {
54
54
  */
55
55
  export declare const pendingLines: (diff: string) => number;
56
56
  /** How many staged lines this node is carrying. Zero is the only settled value. */
57
- export declare const pendingCount: (target: PveTarget, node: string) => Effect.Effect<number, Error, import("effect/unstable/http/HttpClient").HttpClient>;
57
+ export declare const pendingCount: (target: PveTarget, node: string) => Effect.Effect<number, Error | import("./credential-errors.ts").PveCredentialDenied, import("effect/unstable/http/HttpClient").HttpClient>;
58
58
  /**
59
59
  * Why the cluster is not healthy, or `undefined` when it is.
60
60
  *
@@ -69,7 +69,7 @@ export declare const pendingCount: (target: PveTarget, node: string) => Effect.E
69
69
  * pools losing min_size; reloading another node's networking while it is out is the move that
70
70
  * completes the outage. Bring it back, or apply by hand with the cluster in front of you.
71
71
  */
72
- export declare const degradedReason: (target: PveTarget) => Effect.Effect<string | undefined, Error, import("effect/unstable/http/HttpClient").HttpClient>;
72
+ export declare const degradedReason: (target: PveTarget) => Effect.Effect<string | undefined, Error | import("./credential-errors.ts").PveCredentialDenied, import("effect/unstable/http/HttpClient").HttpClient>;
73
73
  /**
74
74
  * ⛔ NOT AN EXITSTATUS PVE CAN RETURN, AND THAT IS THE POINT. The caller compares against `'OK'`;
75
75
  * these two say WHY the answer is not OK without needing a second branch for "we do not know".
@@ -1 +1 @@
1
- {"version":3,"file":"network-apply-read.d.ts","sourceRoot":"","sources":["../../src/proxmox/network-apply-read.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAExC,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAatE,oGAAoG;AACpG,MAAM,MAAM,aAAa,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAC;AA0B7E;;;;;;;;;GASG;AACH,eAAO,MAAM,YAAY,SAAU,MAAM,WAWxC,CAAC;AAEF,mFAAmF;AACnF,eAAO,MAAM,YAAY,WAAY,SAAS,QAAQ,MAAM,uFACH,CAAC;AAiC1D;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,cAAc,WAAY,SAAS,mGAO7C,CAAC;AAEJ;;;GAGG;AACH,eAAO,MAAM,WAAW,0FACiE,CAAC;AAY1F;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,SAAS,WACZ,SAAS,cACL,aAAa,QACnB,MAAM,QACN,MAAM,aAEF,MAAM,uFAgCd,CAAC"}
1
+ {"version":3,"file":"network-apply-read.d.ts","sourceRoot":"","sources":["../../src/proxmox/network-apply-read.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAExC,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAatE,oGAAoG;AACpG,MAAM,MAAM,aAAa,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAC;AA0B7E;;;;;;;;;GASG;AACH,eAAO,MAAM,YAAY,SAAU,MAAM,WAWxC,CAAC;AAEF,mFAAmF;AACnF,eAAO,MAAM,YAAY,WAAY,SAAS,QAAQ,MAAM,8IACH,CAAC;AAiC1D;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,cAAc,WAAY,SAAS,0JAO7C,CAAC;AAEJ;;;GAGG;AACH,eAAO,MAAM,WAAW,0FACiE,CAAC;AAY1F;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,SAAS,WACZ,SAAS,cACL,aAAa,QACnB,MAAM,QACN,MAAM,aAEF,MAAM,uFAgCd,CAAC"}
@@ -91,10 +91,10 @@ export declare const handlers: {
91
91
  header: string;
92
92
  headerDigest: string;
93
93
  secret: string;
94
- }, Error, import("effect/unstable/http/HttpClient").HttpClient>;
94
+ }, Error | import("./credential-errors.ts").PveCredentialDenied, import("effect/unstable/http/HttpClient").HttpClient>;
95
95
  delete: ({ olds }: {
96
96
  olds: Props;
97
- }) => Effect.Effect<unknown, Error, import("effect/unstable/http/HttpClient").HttpClient>;
97
+ }) => Effect.Effect<unknown, Error | import("./credential-errors.ts").PveCredentialDenied, import("effect/unstable/http/HttpClient").HttpClient>;
98
98
  };
99
99
  export {};
100
100
  //# sourceMappingURL=pbs-notification-target-lifecycle.d.ts.map
@@ -75,9 +75,9 @@ export declare const handlers: {
75
75
  } | undefined, never, import("effect/unstable/http/HttpClient").HttpClient>;
76
76
  reconcile: ({ news }: {
77
77
  news: SyncJobProps;
78
- }) => import("effect/Effect").Effect<SyncJobAttributes, Error, import("effect/unstable/http/HttpClient").HttpClient>;
78
+ }) => import("effect/Effect").Effect<SyncJobAttributes, Error | import("./credential-errors.ts").PveCredentialDenied, import("effect/unstable/http/HttpClient").HttpClient>;
79
79
  delete: ({ olds }: {
80
80
  olds: SyncJobProps;
81
- }) => import("effect/Effect").Effect<unknown, Error, import("effect/unstable/http/HttpClient").HttpClient>;
81
+ }) => import("effect/Effect").Effect<unknown, Error | import("./credential-errors.ts").PveCredentialDenied, import("effect/unstable/http/HttpClient").HttpClient>;
82
82
  };
83
83
  //# sourceMappingURL=pbs-sync-job-form.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"provision-cli-fake.d.ts","sourceRoot":"","sources":["../../src/proxmox/provision-cli-fake.ts"],"names":[],"mappings":"AAkBA,MAAM,MAAM,OAAO,GAAG;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC;AACF,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AACF,MAAM,MAAM,QAAQ,GAAG;IACrB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5C,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,GAAG,EAAE,QAAQ,EAAE,CAAC;CACjB,CAAC;AAEF,4FAA4F;AAC5F,eAAO,MAAM,YAAY,QAAO,QAc9B,CAAC;AAIH,mGAAmG;AACnG,eAAO,MAAM,OAAO,UAAW,QAAQ,KAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAsB/D,CAAC;AA0FF,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,EAAE,QAAQ,CAAC;CACjB,CAAC;AAEF,6EAA6E;AAC7E,eAAO,MAAM,SAAS,WAAkB,MAAM,SAAS,QAAQ,KAAG,OAAO,CAAC,SAAS,CAkClF,CAAC"}
1
+ {"version":3,"file":"provision-cli-fake.d.ts","sourceRoot":"","sources":["../../src/proxmox/provision-cli-fake.ts"],"names":[],"mappings":"AAkBA,MAAM,MAAM,OAAO,GAAG;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC;AACF,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AACF,MAAM,MAAM,QAAQ,GAAG;IACrB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5C,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,GAAG,EAAE,QAAQ,EAAE,CAAC;CACjB,CAAC;AAEF,4FAA4F;AAC5F,eAAO,MAAM,YAAY,QAAO,QAc9B,CAAC;AAIH,mGAAmG;AACnG,eAAO,MAAM,OAAO,UAAW,QAAQ,KAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAiC/D,CAAC;AA0FF,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,EAAE,QAAQ,CAAC;CACjB,CAAC;AAEF,6EAA6E;AAC7E,eAAO,MAAM,SAAS,WAAkB,MAAM,SAAS,QAAQ,KAAG,OAAO,CAAC,SAAS,CAkClF,CAAC"}
@@ -19,13 +19,13 @@ export declare const pveOperations: <Props extends WithApiTarget, Attributes>(sp
19
19
  } | {
20
20
  readonly action: 'replace';
21
21
  } | undefined, never, import("effect/unstable/http/HttpClient").HttpClient>;
22
- reconcile: (news: Props) => Effect.Effect<Attributes & ({} | null), Error, import("effect/unstable/http/HttpClient").HttpClient>;
22
+ reconcile: (news: Props) => Effect.Effect<Attributes & ({} | null), Error | import("./credential-errors.ts").PveCredentialDenied, import("effect/unstable/http/HttpClient").HttpClient>;
23
23
  /**
24
24
  * ⚠️ PVE REFUSES TO DELETE THINGS THAT ARE STILL IN USE — a pool holding guests, a storage with
25
25
  * volumes. That refusal is the cluster declining to orphan something because a line left a
26
26
  * file, so it is surfaced as-is rather than retried with a force flag.
27
27
  */
28
- destroy: (olds: Props) => Effect.Effect<unknown, Error, import("effect/unstable/http/HttpClient").HttpClient>;
28
+ destroy: (olds: Props) => Effect.Effect<unknown, Error | import("./credential-errors.ts").PveCredentialDenied, import("effect/unstable/http/HttpClient").HttpClient>;
29
29
  };
30
30
  /**
31
31
  * The five provider handlers for a spec'd PVE object, wired once.
@@ -77,9 +77,9 @@ export declare const pveHandlers: <Props extends WithApiTarget, Attributes exten
77
77
  } | undefined, never, import("effect/unstable/http/HttpClient").HttpClient>;
78
78
  reconcile: ({ news }: {
79
79
  news: Props;
80
- }) => Effect.Effect<Attributes, Error, import("effect/unstable/http/HttpClient").HttpClient>;
80
+ }) => Effect.Effect<Attributes, Error | import("./credential-errors.ts").PveCredentialDenied, import("effect/unstable/http/HttpClient").HttpClient>;
81
81
  delete: ({ olds }: {
82
82
  olds: Props;
83
- }) => Effect.Effect<unknown, Error, import("effect/unstable/http/HttpClient").HttpClient>;
83
+ }) => Effect.Effect<unknown, Error | import("./credential-errors.ts").PveCredentialDenied, import("effect/unstable/http/HttpClient").HttpClient>;
84
84
  };
85
85
  //# sourceMappingURL=resource.d.ts.map
@@ -0,0 +1,28 @@
1
+ /**
2
+ * `Proxmox.Role`'s wire shape: PVE's create/update forms and how a live list row becomes
3
+ * attributes. Split out of role.ts (2026-09-24, the distilled migration) to keep that file
4
+ * under the 250-line cap — the api-token.ts/api-token-form.ts seam.
5
+ */
6
+ import type * as access from '@distilled.cloud/proxmox/access';
7
+ import type { RoleAttributes, RoleProps } from './role.ts';
8
+ export declare const ROLE_CREATE = "pve:POST /access/roles";
9
+ export declare const ROLE_UPDATE = "pve:PUT /access/roles/{roleid}";
10
+ /**
11
+ * The only shape two privilege sets may be compared in: sorted, de-duplicated, blanks dropped.
12
+ *
13
+ * ⚠️ THE TRIM AND THE EMPTY FILTER ARE NOT DECORATION. `'A,B,'.split(',')` yields a trailing `''`,
14
+ * and a hand-written list is quite likely to carry a stray space after a comma. Either one turns
15
+ * into a phantom member that no live answer can contain, so `matches` would be false on every
16
+ * plan and the deploy would rewrite the role to exactly what it already was.
17
+ */
18
+ export declare const canonical: (privs: readonly string[]) => string[];
19
+ export declare const createForm: (props: RoleProps) => access.CreateAccessRoleRequest;
20
+ /**
21
+ * ⚠️ NO `append` FIELD, DELIBERATELY. Sending `append=1` would make every update additive, so a
22
+ * privilege could be granted from here but never taken away — see role.ts's own header.
23
+ */
24
+ export declare const updateForm: (props: RoleProps) => access.PutAccessRoleRequest;
25
+ export declare const find: (rows: readonly access.ListAccessRolesResponseBodyItem[], roleid: string) => access.ListAccessRolesResponseBodyItem | undefined;
26
+ export declare const attributesOf: (row: access.ListAccessRolesResponseBodyItem, props: RoleProps) => RoleAttributes;
27
+ export declare const matches: (attributes: RoleAttributes, props: RoleProps) => boolean;
28
+ //# sourceMappingURL=role-wire.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"role-wire.d.ts","sourceRoot":"","sources":["../../src/proxmox/role-wire.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,KAAK,MAAM,MAAM,iCAAiC,CAAC;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAE3D,eAAO,MAAM,WAAW,2BAA2B,CAAC;AACpD,eAAO,MAAM,WAAW,mCAAmC,CAAC;AAE5D;;;;;;;GAOG;AACH,eAAO,MAAM,SAAS,UAAW,SAAS,MAAM,EAAE,KAAG,MAAM,EAC8B,CAAC;AAE1F,eAAO,MAAM,UAAU,UAAW,SAAS,KAAG,MAAM,CAAC,uBAGnD,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,UAAU,UAAW,SAAS,KAAG,MAAM,CAAC,oBAGnD,CAAC;AAEH,eAAO,MAAM,IAAI,SAAU,SAAS,MAAM,CAAC,+BAA+B,EAAE,UAAU,MAAM,uDACjD,CAAC;AAE5C,eAAO,MAAM,YAAY,QAClB,MAAM,CAAC,+BAA+B,SACpC,SAAS,KACf,cAGD,CAAC;AAEH,eAAO,MAAM,OAAO,eAAgB,cAAc,SAAS,SAAS,YACQ,CAAC"}
@@ -2,53 +2,54 @@
2
2
  * `Proxmox.Role` — a PVE role: one name, one set of privileges. The object every other resource
3
3
  * in this package stands on, because a role is what the provision credential actually holds.
4
4
  *
5
- * ★ THIS IS WHERE pool.ts's ⚠️ STOPS BEING A MEMORY. That file records, in prose, that the
6
- * provision role lacked `Pool.Allocate`, that reconcile answered "Permission check failed", and
7
- * that a human widened the role over SSH with `pveum role modify`. Prose cannot be planned
8
- * against: it goes stale the first time somebody edits the role in the UI and tells nobody.
9
- * Declared here, the same fact is a diff — the privileges the role is SUPPOSED to hold are read
10
- * off the cluster on every plan, and a hand edit shows up as `1 to update` rather than as a 403
11
- * six weeks later in the middle of something else.
12
- *
13
- * ⛔ THE INDEX AND THE ITEM DISAGREE ABOUT WHAT `privs` IS, AND MISSING THAT COSTS A DIFF THAT
14
- * NEVER CONVERGES. `GET /access/roles` (the index) reports each role's privileges as a COMMA
15
- * STRING. `GET /access/roles/{roleid}` — the path this resource reads — returns a privilege MAP
16
- * instead, `{"VM.Allocate":1,"Sys.Audit":1,…}`, and its key order is a Perl hash's order, so it
17
- * is not stable between two calls to the same endpoint. Compare the raw string, or the raw
18
- * object, or the keys in the order they arrived, and every plan reports an update forever.
19
- * `canonical` below is the whole answer: both sides become a sorted, de-duplicated set before
20
- * anything is compared, and `attributes` STORES the sorted form so Alchemy's state does not
21
- * churn either.
5
+ * ★ THIS IS WHERE pool.ts's ⚠️ STOPS BEING A MEMORY. Declared here, a hand edit shows up as
6
+ * `1 to update` rather than as a 403 six weeks later in the middle of something else.
22
7
  *
23
8
  * ⚠️ A DECLARATION REPLACES THE PRIVILEGE SET; IT DOES NOT ADD TO IT. `PUT /access/roles/{roleid}`
24
- * accepts an `append` flag and this resource deliberately never sends it — with append a role
25
- * could only ever grow, so `matches` would report an update forever whenever props were a subset
26
- * of live, which is the same perpetual diff by a different road. The consequence is the one that
27
- * bites: anything a human added by hand is REVOKED on the next deploy unless it is in `privs`.
28
- * Copy the live set out of `pveum role list` before declaring a role that already exists; do not
29
- * type it from memory.
9
+ * accepts an `append` flag and this resource deliberately never sends it (role-wire.ts) — with
10
+ * append a role could only ever grow, the same perpetual diff by a different road. The
11
+ * consequence: anything a human added by hand is REVOKED on the next deploy unless it is in
12
+ * `privs`. Copy the live set out of `pveum role list` before declaring a role that already
13
+ * exists; do not type it from memory.
30
14
  *
31
15
  * ⛔ A ROLE CAN LOCK ITS OWN PROVIDER OUT, AND NOTHING IN THIS PACKAGE CAN UNDO IT. Reconcile runs
32
16
  * as the provision credential, and that credential holds a role. Declare THAT role without the
33
17
  * privileges the provider needs — `Sys.Modify` on `/access` above all — and the write succeeds,
34
18
  * after which every later plan reads 403 and the repair has to happen out of band as `root@pam`
35
- * over SSH. It is a one-way door, which is why the privileges a reconcile needs are written
36
- * down at the bottom of this comment rather than left to be rediscovered from an error message.
19
+ * over SSH.
37
20
  *
38
21
  * ⚠️ PVE REFUSES TO EDIT ITS OWN BUILT-IN ROLES (`Administrator`, `NoAccess`, the `PVE*` set), and
39
- * the item endpoint gives no sign of which those are — only the index carries the `special`
40
- * flag. A declaration aimed at a built-in role therefore READS BACK CLEANLY and fails at write
41
- * time, which reads like a broken provider rather than like a refusal. Declare roles you own.
22
+ * the LIST read (below) DOES carry the `special` flag that marks them, unlike the item endpoint
23
+ * this file used before the distilled migration — `role-wire.ts`'s `attributesOf` does not use
24
+ * it, but a caller reading `list`'s own answer could. A declaration aimed at a built-in role
25
+ * still reads back cleanly and fails at write time. Declare roles you own.
42
26
  *
43
27
  * ★ PRIVILEGES A RECONCILE NEEDS: `Sys.Audit` on `/access` to read and diff, `Sys.Modify` on
44
- * `/access` to create, update or delete. Not `Permissions.Modify` — binding a role to a user is
45
- * `/access/acl`, a different object, and nothing here writes it. An auditor-shaped credential
46
- * can already PLAN this resource and cannot DEPLOY it, which is the honest failure: the plan is
47
- * true and the deploy says exactly which privilege is missing.
28
+ * `/access` to create, update or delete. Not `Permissions.Modify` — that is a different object.
29
+ *
30
+ * ★ READ VIA THE LIST, NOT THE ITEM — A DELIBERATE CHANGE FROM THE PRE-MIGRATION CODE, AND THE
31
+ * REASON IS THE ITEM'S OWN GENERATED SCHEMA. `access.getAccessRole`'s response
32
+ * (`GetAccessRoleResponse`) enumerates a FIXED list of ~47 known privilege field names
33
+ * (`Sys_Modify`, `VM_Allocate`, …), each its own optional property — MEASURED in
34
+ * `@distilled.cloud/proxmox`'s generated `access.ts`. A privilege this package's generator did
35
+ * not know about would silently not appear as a field at all, and this file's old `privs: []`
36
+ * reconstruction would drop it from state without a diff ever seeing the loss. The LIST read
37
+ * (`access.listAccessRoles`) answers `privs` as the SAME plain comma string PVE's item read
38
+ * also disagreed with the index about pre-migration — a shape with no fixed enumeration to fall
39
+ * behind. `find` below (role-wire.ts) does the item-read's old job client-side.
40
+ * ⚠️ REASONED, NOT MEASURED, THAT THE LIST READ NEEDS NO MORE THAN `read`'s AUDITOR SHAPE. The
41
+ * item read's own privilege check (`Sys.Audit` on `/access`) was measured; the list's was not
42
+ * separately checked, but PVE's own convention is that an index read is never MORE restrictive
43
+ * than its item. `readRole` stays at the default `read` on that basis, same as before.
44
+ *
45
+ * ★ MIGRATED OFF `client.ts`'s generic `pve()`/`pveHandlers` ONTO `@distilled.cloud/proxmox`'s
46
+ * typed `access.listAccessRoles`/`createAccessRole`/`putAccessRole`/`deleteAccessRole`
47
+ * (2026-09-24, decision 43's proxmox walk-down). `distilled-pve.ts`'s `runPve` replaces
48
+ * `pve()`; the cries-wolf fix is wired the same way as acl.ts, group.ts and user.ts.
48
49
  */
49
50
  import { Resource } from 'alchemy';
50
51
  import * as Provider from 'alchemy/Provider';
51
- import { type PveRequirements, type WithTarget } from './resource.ts';
52
+ import type { PveRequirements, WithTarget } from './resource-spec.ts';
52
53
  export interface RoleProps extends WithTarget {
53
54
  /**
54
55
  * PVE's primary key for a role.
@@ -60,13 +61,11 @@ export interface RoleProps extends WithTarget {
60
61
  roleid: string;
61
62
  /**
62
63
  * The COMPLETE privilege set, e.g. `['VM.Allocate', 'VM.Audit', 'Sys.Audit']`. Order and
63
- * duplicates do not matter — see the ⛔ in the header — but completeness does: this list is the
64
+ * duplicates do not matter — see the header — but completeness does: this list is the
64
65
  * role, not an addition to it.
65
66
  *
66
67
  * ⚠️ DECLARE AT LEAST ONE. A role with no privileges grants nothing and reads back as an empty
67
- * document, which is the one shape the factory cannot tell apart from an object that is not
68
- * there — reconcile would then refuse with its "the write returned no error but the object is
69
- * still absent" message, which is true of the read and misleading about the cause.
68
+ * document, which reconcile cannot tell apart from an object that is not there.
70
69
  */
71
70
  privs: string[];
72
71
  }
@@ -78,20 +77,5 @@ export interface RoleAttributes {
78
77
  export interface ProxmoxRole extends Resource<'Proxmox.Role', RoleProps, RoleAttributes, never, PveRequirements> {
79
78
  }
80
79
  export declare const ProxmoxRole: import("alchemy").ResourceClass<ProxmoxRole>;
81
- /**
82
- * ⛔ AN EMPTY LIST, AND NOWHERE DOES IT MATTER MORE. `GET /access/roles` returns every
83
- * role on the cluster, PVE's own built-ins included. Returning them would invite Alchemy
84
- * to adopt `Administrator` — and therefore one day to narrow or delete it. Adoption is
85
- * an explicit act, here as everywhere else in this package.
86
-
87
- *
88
- * ⛔ DO NOT CARRY THE FACTORY'S REASSURANCE OVER TO THIS ONE. `destroy` there notes that
89
- * PVE refuses to delete things still in use — a pool holding guests, a storage with
90
- * volumes — and a role is NOT protected that way. Whoever is bound to the role loses
91
- * those privileges the moment it goes, and the credential this provider runs with is
92
- * bound through exactly such a binding. Checking who holds a role before removing it is
93
- * an operator's job, and it is not done here on their behalf while they read a diff.
94
-
95
- */
96
80
  export declare const ProxmoxRoleProvider: () => import("effect/Layer").Layer<Provider.Provider<ProxmoxRole>, never, import("effect/unstable/http/HttpClient").HttpClient>;
97
81
  //# sourceMappingURL=role.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"role.d.ts","sourceRoot":"","sources":["../../src/proxmox/role.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAE7C,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,UAAU,EAAe,MAAM,eAAe,CAAC;AAEnF,MAAM,WAAW,SAAU,SAAQ,UAAU;IAC3C;;;;;;OAMG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;;;OASG;IACH,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,4FAA4F;IAC5F,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,WAAY,SAAQ,QAAQ,CAC3C,cAAc,EACd,SAAS,EACT,cAAc,EACd,KAAK,EACL,eAAe,CAChB;CAAG;AAEJ,eAAO,MAAM,WAAW,8CAAwC,CAAC;AAyDjE;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,mBAAmB,iIACiD,CAAC"}
1
+ {"version":3,"file":"role.d.ts","sourceRoot":"","sources":["../../src/proxmox/role.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAa7C,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAStE,MAAM,WAAW,SAAU,SAAQ,UAAU;IAC3C;;;;;;OAMG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;OAOG;IACH,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,4FAA4F;IAC5F,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,WAAY,SAAQ,QAAQ,CAC3C,cAAc,EACd,SAAS,EACT,cAAc,EACd,KAAK,EACL,eAAe,CAChB;CAAG;AAEJ,eAAO,MAAM,WAAW,8CAAwC,CAAC;AAiBjE,eAAO,MAAM,mBAAmB,iIA+E7B,CAAC"}
@@ -76,5 +76,5 @@ export declare const canonical: (value: unknown) => string;
76
76
  * Alchemy always calls it the first time a resource appears — and `PUT /cluster/sdn` regenerates
77
77
  * `/etc/network/interfaces.d/sdn` on every node. On C1 that file carries Ceph's cluster network.
78
78
  */
79
- export declare const sdnPendingCount: (target: PveTarget) => Effect.Effect<number, Error, import("effect/unstable/http/HttpClient").HttpClient>;
79
+ export declare const sdnPendingCount: (target: PveTarget) => Effect.Effect<number, Error | import("./credential-errors.ts").PveCredentialDenied, import("effect/unstable/http/HttpClient").HttpClient>;
80
80
  //# sourceMappingURL=sdn-apply-read.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sdn-apply-read.d.ts","sourceRoot":"","sources":["../../src/proxmox/sdn-apply-read.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAExC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAwBlD;;;;;;;;GAQG;AACH,eAAO,MAAM,UAAU,YAAI,mBAAmB,EAAE,iBAAiB,CAAU,CAAC;AAE5E;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,eAAe,UAAW,OAAO,KAAG,OACE,CAAC;AAqBpD;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,WACf,MAAM,GAAG,SAAS,WACjB,MAAM,GAAG,SAAS,KAC1B,MAAM,GAAG,cAIX,CAAC;AAuCF,+FAA+F;AAC/F,eAAO,MAAM,SAAS,UAAW,OAAO,KAAG,MAAyC,CAAC;AAiBrF;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,WAAY,SAAS,uFAG9C,CAAC"}
1
+ {"version":3,"file":"sdn-apply-read.d.ts","sourceRoot":"","sources":["../../src/proxmox/sdn-apply-read.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAExC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAwBlD;;;;;;;;GAQG;AACH,eAAO,MAAM,UAAU,YAAI,mBAAmB,EAAE,iBAAiB,CAAU,CAAC;AAE5E;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,eAAe,UAAW,OAAO,KAAG,OACE,CAAC;AAqBpD;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,WACf,MAAM,GAAG,SAAS,WACjB,MAAM,GAAG,SAAS,KAC1B,MAAM,GAAG,cAIX,CAAC;AAuCF,+FAA+F;AAC/F,eAAO,MAAM,SAAS,UAAW,OAAO,KAAG,MAAyC,CAAC;AAiBrF;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,WAAY,SAAS,8IAG9C,CAAC"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * The "unreadable" outcome a refused read produces — distinct from `undefined` (genuinely
3
+ * absent) and from a real attributes object (present) — so `diff` stops treating a REFUSED read
4
+ * the same as a confirmed-gone one. Fixes the "cries wolf" bug (measured 2026-09-24): on a lane
5
+ * whose credential cannot mint `provision`, every already-correct ACL and storage row read back
6
+ * as absent, and `diff` forced `update` for every one of them without comparing a single field,
7
+ * because `.pipe(Effect.orElseSucceed(() => undefined))` folds every failure — a genuine 404, a
8
+ * network blip, AND a permission refusal — into the one "absent" value `diff` cannot tell apart.
9
+ *
10
+ * ★ WHY A SENTINEL, NOT A FAILED `diff`. Upstream's `Diff` type is exactly
11
+ * `NoopDiff | UpdateDiff | ReplaceDiff` (`packages/alchemy/src/Diff.ts@v2.0.0-beta.79`) — there
12
+ * is no fourth "unreadable" action to return. And `Plan.ts`'s resource pass runs every
13
+ * resource's `diff` under `Effect.exit`, then FAILS THE WHOLE PLAN if even one failed
14
+ * (`diffFailures.length > 0` -> `Effect.failCause`, `Plan.ts@v2.0.0-beta.79` ~lines 1711-1731).
15
+ * So making `diff` FAIL on a refused read would not report one honest row — it would abort
16
+ * `bun run plan` for every OTHER resource in the stack too, worse than today's false "update".
17
+ * `{ action: 'noop' }` plus a logged warning is the only outcome that (a) never forces a write
18
+ * on a lane that cannot prove one is needed and (b) never takes the rest of the plan down with
19
+ * it — the "noop-with-warning" reading of the fix, over the alternative of a typed plan error.
20
+ *
21
+ * ⛔ ONLY THE CREDENTIAL DENIAL IS CAUGHT HERE. Every other read failure (a genuine
22
+ * `Cause.TimeoutError`, `PveClusterExhausted`, a network error) keeps folding into `undefined`
23
+ * exactly as it always has — this narrows ONE typed tag out of that fold, via `catchTag`, and
24
+ * changes nothing else about it. See credential-errors.ts's own ⛔ for why 403 alone qualifies.
25
+ */
26
+ import * as Effect from 'effect/Effect';
27
+ import type { PveCredentialDenied } from './credential-errors.ts';
28
+ /** Distinct from `undefined` (absent) and from a real attributes object (present). */
29
+ export declare const UNREADABLE: unique symbol;
30
+ export type Unreadable = typeof UNREADABLE;
31
+ /**
32
+ * Catches ONLY `PveCredentialDenied` and turns it into the sentinel, so a caller that also
33
+ * `.pipe(Effect.orElseSucceed(() => undefined))` for every OTHER failure never conflates a
34
+ * refusal with a genuine absence. Compose this INSIDE that fold, never after it.
35
+ *
36
+ * ⚠️ `E | PveCredentialDenied` IN, `E` OUT — not a generic `E` narrowed by `Exclude`. `catchTag`
37
+ * needs the tag to be STATICALLY present in the error union to typecheck at all; a bare
38
+ * unconstrained `E` gives it nothing to match against. Every caller's `runPve` already carries
39
+ * `PveCredentialDenied` explicitly (distilled-pve.ts's own ★ on why), so this signature costs
40
+ * nothing at the call site.
41
+ */
42
+ export declare const readOrUnreadable: <A, E, R>(effect: Effect.Effect<A, E | PveCredentialDenied, R>) => Effect.Effect<A | Unreadable, E, R>;
43
+ /**
44
+ * The warning `diff` logs when it reports `noop` for a row it could not actually read — visible
45
+ * in `bun run plan`'s own output (Effect's default logger), even though the Diff type has
46
+ * nowhere to carry the distinction through to the printed action. See the header for why a log
47
+ * line, not a failed diff or a fourth Diff action, is what "the plan reports" means here.
48
+ */
49
+ export declare const unreadableWarning: (type: string, identity: string) => Effect.Effect<void>;
50
+ //# sourceMappingURL=unreadable-read.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unreadable-read.d.ts","sourceRoot":"","sources":["../../src/proxmox/unreadable-read.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAElE,sFAAsF;AACtF,eAAO,MAAM,UAAU,eAA2B,CAAC;AACnD,MAAM,MAAM,UAAU,GAAG,OAAO,UAAU,CAAC;AAE3C;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gBAAgB,GAAI,CAAC,EAAE,CAAC,EAAE,CAAC,UAC9B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,GAAG,mBAAmB,EAAE,CAAC,CAAC,KACnD,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,UAAU,EAAE,CAAC,EAAE,CAAC,CACkD,CAAC;AAExF;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,SAAU,MAAM,YAAY,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAKlF,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * `Proxmox.User`'s wire shape: PVE's create/update forms and how a live item becomes
3
+ * attributes. Split out of user.ts (2026-09-24, the distilled migration) to keep that file
4
+ * under the 250-line cap — the api-token.ts/api-token-form.ts seam.
5
+ */
6
+ import type * as access from '@distilled.cloud/proxmox/access';
7
+ import type { UserAttributes, UserProps } from './user.ts';
8
+ export declare const USER_CREATE = "pve:POST /access/users";
9
+ export declare const USER_UPDATE = "pve:PUT /access/users/{userid}";
10
+ export declare const createForm: (props: UserProps) => access.CreateAccessUserRequest;
11
+ export declare const updateForm: (props: UserProps) => access.PutAccessUserRequest;
12
+ export declare const attributesOf: (live: access.GetAccessUserResponse, props: UserProps) => UserAttributes;
13
+ /**
14
+ * ⚠️ `tokens` IS NOT COMPARED, and neither is anything create-only. What is compared is exactly
15
+ * what a PUT can put back; a field PVE reports but will not accept is drift nobody can fix.
16
+ */
17
+ export declare const matches: (attributes: UserAttributes, props: UserProps) => boolean;
18
+ //# sourceMappingURL=user-wire.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user-wire.d.ts","sourceRoot":"","sources":["../../src/proxmox/user-wire.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,KAAK,MAAM,MAAM,iCAAiC,CAAC;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAG3D,eAAO,MAAM,WAAW,2BAA2B,CAAC;AACpD,eAAO,MAAM,WAAW,mCAAmC,CAAC;AA2D5D,eAAO,MAAM,UAAU,UAAW,SAAS,KAAG,MAAM,CAAC,uBAGnD,CAAC;AAEH,eAAO,MAAM,UAAU,UAAW,SAAS,KAAG,MAAM,CAAC,oBAGnD,CAAC;AAEH,eAAO,MAAM,YAAY,SACjB,MAAM,CAAC,qBAAqB,SAC3B,SAAS,KACf,cAgBD,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,OAAO,eAAgB,cAAc,SAAS,SAAS,YAOF,CAAC"}
@@ -2,7 +2,7 @@
2
2
  * `Proxmox.User` — a PVE account. The identity every other resource in this package borrows.
3
3
  *
4
4
  * ★ THIS IS THE ONE OBJECT THAT CAN CUT THE BRANCH IT SITS ON. The user the credential mount vends
5
- * tokens for, and the group whose members are allowed to mint, are what `src/credentials.ts`
5
+ * tokens for, and the group whose members are allowed to mint, are what `credentials.ts`
6
6
  * ultimately asks OpenBao for. A cluster rebuilt without them cannot plan ANY resource here —
7
7
  * including the plan that would recreate them, which needs a credential to run. So this resource
8
8
  * is drift repair for an account that already exists (comment, email, enable, expire, groups),
@@ -18,37 +18,43 @@
18
18
  *
19
19
  * ⚠️ `groups` COMES BACK AS AN ARRAY AND GOES OUT AS A COMMA STRING, and the asymmetry is the whole
20
20
  * trap. GET answers `["a","b"]`, POST/PUT want `a,b`, and PVE returns the list in ITS order, not
21
- * the declared one. Compared naively, `matches` reads ['b','a'] against 'a,b' and every plan for
22
- * the rest of time says "1 to update". Both sides are normalised to a sorted, deduplicated set
23
- * below. Joining is a safe comparison precisely because a group id cannot contain a comma — the
24
- * wire format is comma-separated, so PVE refuses one.
21
+ * the declared one. Both sides are normalised to a sorted, deduplicated set — see user-wire.ts.
25
22
  *
26
23
  * ⚠️ `tokens` IS NESTED AND IS NOT SETTABLE THROUGH THIS ENDPOINT. The read hands back a map of the
27
24
  * account's API tokens; `PUT /access/users/{userid}` has no parameter for them, because they are
28
- * their own objects under `.../token/{tokenid}`. So they are reported as an attribute — a plan
29
- * can then say what a delete would take with it — and kept OUT of `matches`, exactly like
30
- * `pool.members`. Reported, never declared.
25
+ * their own objects under `.../token/{tokenid}` (api-token.ts). So they are reported as an
26
+ * attribute and kept OUT of `matches`, exactly like `pool.members`. Reported, never declared.
31
27
  *
32
28
  * ⚠️ RECONCILE NEEDS PRIVILEGES A GUEST-ONLY ROLE DOES NOT HOLD, so they are stated rather than met
33
- * as a 403 in the middle of a deploy (pool.ts records how that went the last time).
34
- * `PROVISION_PRIVILEGES` (provision-baseline.ts) carries every one of them:
29
+ * as a 403 in the middle of a deploy. `PROVISION_PRIVILEGES` (provision-baseline.ts) carries:
35
30
  * · `Realm.AllocateUser` on `/access/realm/<realm>` — create and delete.
36
31
  * · `User.Modify` on `/access/groups`, and on `/access/groups/<group>` for EVERY group named in
37
32
  * `groups` — create, update and delete. PVE checks the groups you are granting, not just the
38
- * user, so a role wide enough to edit the account can still be refused for one group in the
39
- * list.
33
+ * user, so a role wide enough to edit the account can still be refused for one group.
40
34
  * · `Sys.Audit` (or `User.Modify`) for the read, which the `read` role's auditor already has.
41
- * `Permissions.Modify` is NOT on this list: that governs ACL entries, which are a different PVE
42
- * object and would be a different resource here.
43
35
  *
44
- * ⚠️ A MISSING USER IS A 500, NOT A 404 — "no such user ('x@pve')" — and the factory turns any
45
- * failed read into "absent". A 403 therefore also reads as absent, and the honest error arrives
46
- * one step later, from the create: "Permission check failed (/access/groups, User.Modify)". That
47
- * string is the one to grep for when a plan insists on creating an account that plainly exists.
36
+ * ⚠️ A MISSING USER IS A 500, NOT A 404 — "no such user ('x@pve')". distilled turns that into an
37
+ * `UnknownProxmoxError` (protocol.ts), and `readOrUnreadable`'s own `orElseSucceed` folds it
38
+ * into "absent" same as the old hand-rolled client did. A 403 therefore also reads as absent
39
+ * UNLESS it is the one refusal `readOrUnreadable` catches (unreadable-read.ts): a REFUSED
40
+ * `provision` mint, the cries-wolf case. Any other refusal still reads as absent, and the
41
+ * honest error arrives one step later, from the create.
42
+ *
43
+ * ⚠️ THE WIRE URL FOR THIS FAMILY CHANGED, THOUGH NOTHING A CALLER SEES DID. MEASURED
44
+ * (user.test.ts): distilled's `{userid}` label substitution percent-encodes it —
45
+ * `iac@pve` -> `.../access/users/iac%40pve` — where `client.ts`'s plain string concatenation
46
+ * sent the `@` literally. A real PVE (any HTTP server) decodes both the same way, so this is
47
+ * not a behaviour change a stack observes; a fake in a test has to decode it, `client.ts`'s
48
+ * own fixtures never had to.
49
+ *
50
+ * ★ MIGRATED OFF `client.ts`'s generic `pve()`/`pveHandlers` ONTO `@distilled.cloud/proxmox`'s
51
+ * typed `access.getAccessUser`/`createAccessUser`/`putAccessUser`/`deleteAccessUser`
52
+ * (2026-09-24, decision 43's proxmox walk-down, access sub-area). `distilled-pve.ts`'s `runPve`
53
+ * replaces `pve()` as in acl.ts and group.ts; the cries-wolf fix is wired the same way.
48
54
  */
49
55
  import { Resource } from 'alchemy';
50
56
  import * as Provider from 'alchemy/Provider';
51
- import { type PveRequirements, type WithTarget } from './resource.ts';
57
+ import type { PveRequirements, WithTarget } from './resource-spec.ts';
52
58
  export interface UserProps extends WithTarget {
53
59
  /**
54
60
  * ⛔ REALM-QUALIFIED, ALWAYS: `someone@pve`, `someone@pam`. The realm is part of PVE's primary
@@ -91,18 +97,5 @@ export interface UserAttributes {
91
97
  export interface ProxmoxUser extends Resource<'Proxmox.User', UserProps, UserAttributes, never, PveRequirements> {
92
98
  }
93
99
  export declare const ProxmoxUser: import("alchemy").ResourceClass<ProxmoxUser>;
94
- /**
95
- * ⛔ EMPTY, AND MORE POINTEDLY SO HERE THAN ANYWHERE ELSE. `GET /access/users` answers with
96
- * every account on the cluster: `root@pam`, every human who logs in, every service
97
- * identity someone made years ago. Handing that list to Alchemy would invite it to adopt
98
- * — and therefore one day DELETE — people. Adoption is an explicit act.
99
-
100
- *
101
- * ⛔ DELETING A USER TAKES ITS API TOKENS AND ITS ACL ENTRIES WITH IT, in one call and
102
- * without a confirmation. For the identity a credential mount vends from, that revokes
103
- * every outstanding lease at once and every plan in this package stops working — this one
104
- * included. PVE refuses only for `root@pam`; for everyone else it simply obeys.
105
-
106
- */
107
100
  export declare const ProxmoxUserProvider: () => import("effect/Layer").Layer<Provider.Provider<ProxmoxUser>, never, import("effect/unstable/http/HttpClient").HttpClient>;
108
101
  //# sourceMappingURL=user.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"user.d.ts","sourceRoot":"","sources":["../../src/proxmox/user.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAE7C,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,UAAU,EAAe,MAAM,eAAe,CAAC;AAGnF,MAAM,WAAW,SAAU,SAAQ,UAAU;IAC3C;;;;;;;OAOG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,iCAAiC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mGAAmG;IACnG,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,kFAAkF;IAClF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2FAA2F;IAC3F,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,qFAAqF;IACrF,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,WAAY,SAAQ,QAAQ,CAC3C,cAAc,EACd,SAAS,EACT,cAAc,EACd,KAAK,EACL,eAAe,CAChB;CAAG;AAEJ,eAAO,MAAM,WAAW,8CAAwC,CAAC;AAmGjE;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,mBAAmB,iIACiD,CAAC"}
1
+ {"version":3,"file":"user.d.ts","sourceRoot":"","sources":["../../src/proxmox/user.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAI7C,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAiBtE,MAAM,WAAW,SAAU,SAAQ,UAAU;IAC3C;;;;;;;OAOG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,iCAAiC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mGAAmG;IACnG,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,kFAAkF;IAClF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2FAA2F;IAC3F,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,qFAAqF;IACrF,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,WAAY,SAAQ,QAAQ,CAC3C,cAAc,EACd,SAAS,EACT,cAAc,EACd,KAAK,EACL,eAAe,CAChB;CAAG;AAEJ,eAAO,MAAM,WAAW,8CAAwC,CAAC;AAajE,eAAO,MAAM,mBAAmB,iIAgF7B,CAAC"}
@@ -110,7 +110,7 @@ export declare const createForm: (props: ZfsPoolProps) => Record<string, string>
110
110
  * here is the same `Effect.die` idiom as every other refusal in this package, and it costs
111
111
  * nothing this pool did not already lack.
112
112
  */
113
- export declare const createPool: <E, R>(props: ZfsPoolProps, collection: string, path: string, read: (props: ZfsPoolProps) => Effect.Effect<ZfsPoolAttributes | undefined, E, R>) => Effect.Effect<ZfsPoolAttributes, E | Error, R | import("effect/unstable/http/HttpClient").HttpClient>;
113
+ export declare const createPool: <E, R>(props: ZfsPoolProps, collection: string, path: string, read: (props: ZfsPoolProps) => Effect.Effect<ZfsPoolAttributes | undefined, E, R>) => Effect.Effect<ZfsPoolAttributes, E | Error | import("./credential-errors.ts").PveCredentialDenied, R | import("effect/unstable/http/HttpClient").HttpClient>;
114
114
  /**
115
115
  * ⛔ `DELETE /nodes/{node}/disks/zfs/{name}` RUNS `zpool destroy` AND THE DATASETS GO WITH IT. Per
116
116
  * S14 (alchemy-provider-standard), it is implemented rather than stubbed — a `delete` that
@@ -124,5 +124,5 @@ export declare const createPool: <E, R>(props: ZfsPoolProps, collection: string,
124
124
  * resource reaching over to delete another's is how a stack ends up with state describing
125
125
  * something that is gone.
126
126
  */
127
- export declare const destroyPool: (olds: ZfsPoolProps, path: string) => Effect.Effect<unknown, Error, import("effect/unstable/http/HttpClient").HttpClient>;
127
+ export declare const destroyPool: (olds: ZfsPoolProps, path: string) => Effect.Effect<unknown, Error | import("./credential-errors.ts").PveCredentialDenied, import("effect/unstable/http/HttpClient").HttpClient>;
128
128
  //# sourceMappingURL=zfs-pool-write.d.ts.map