@homeflare/alchemy 0.10.1 → 0.12.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 (265) hide show
  1. package/README.md +3 -3
  2. package/dist/caddy/admin-guard.d.ts +1 -1
  3. package/dist/caddy/index.js.map +4 -4
  4. package/dist/cloudflare/client.d.ts +1 -1
  5. package/dist/cloudflare/index.js.map +2 -2
  6. package/dist/cloudflare/lock-rules.d.ts +1 -1
  7. package/dist/forgejo/branch-protection-form.d.ts +1 -1
  8. package/dist/forgejo/branch-protection.d.ts +1 -1
  9. package/dist/forgejo/index.js.map +10 -10
  10. package/dist/forgejo/org-actions-secrets.d.ts +1 -1
  11. package/dist/forgejo/org-label.d.ts +1 -1
  12. package/dist/forgejo/org-team.d.ts +1 -1
  13. package/dist/forgejo/repo-webhook.d.ts +1 -1
  14. package/dist/forgejo/repository.d.ts +2 -2
  15. package/dist/forgejo/repository.d.ts.map +1 -1
  16. package/dist/forgejo/resource.d.ts +2 -2
  17. package/dist/forgejo/team-member.d.ts +1 -1
  18. package/dist/launchd/index.js.map +2 -2
  19. package/dist/openbao/auth-role.d.ts +1 -1
  20. package/dist/openbao/bao-http.d.ts +4 -4
  21. package/dist/openbao/cloudflare-permission-groups.d.ts +1 -1
  22. package/dist/openbao/cloudflare-role-form.d.ts +2 -2
  23. package/dist/openbao/cloudflare-role-form.d.ts.map +1 -1
  24. package/dist/openbao/cloudflare-roles-expand.d.ts +1 -1
  25. package/dist/openbao/digest.d.ts +1 -1
  26. package/dist/openbao/forgejo-bootstrap-parse.d.ts +2 -2
  27. package/dist/openbao/forgejo-bootstrap-parse.d.ts.map +1 -1
  28. package/dist/openbao/forgejo-bootstrap.d.ts +1 -1
  29. package/dist/openbao/index.js +10 -8
  30. package/dist/openbao/index.js.map +18 -18
  31. package/dist/openbao/mount.d.ts +1 -1
  32. package/dist/openbao/pki-role.d.ts +1 -1
  33. package/dist/openbao/policy.d.ts +2 -2
  34. package/dist/openbao/proxmox-role-form.d.ts +2 -2
  35. package/dist/openbao/proxmox-role-form.d.ts.map +1 -1
  36. package/dist/openbao/proxmox-role.d.ts +10 -10
  37. package/dist/openbao/ssh-role.d.ts +3 -3
  38. package/dist/ownership/adopting.d.ts +34 -0
  39. package/dist/ownership/adopting.d.ts.map +1 -0
  40. package/dist/ownership/probe.d.ts +15 -5
  41. package/dist/ownership/probe.d.ts.map +1 -1
  42. package/dist/ownership/rows.d.ts +12 -0
  43. package/dist/ownership/rows.d.ts.map +1 -1
  44. package/dist/ownership/store-fixture.d.ts +10 -0
  45. package/dist/ownership/store-fixture.d.ts.map +1 -0
  46. package/dist/proxmox/acl.d.ts +2 -2
  47. package/dist/proxmox/acl.d.ts.map +1 -1
  48. package/dist/proxmox/api-token-form.d.ts +2 -2
  49. package/dist/proxmox/api-token.d.ts +11 -11
  50. package/dist/proxmox/ceph-daemon-form.d.ts +3 -3
  51. package/dist/proxmox/ceph-daemon.d.ts +19 -19
  52. package/dist/proxmox/ceph-daemon.d.ts.map +1 -1
  53. package/dist/proxmox/ceph-flag.d.ts +5 -5
  54. package/dist/proxmox/ceph-fs-wire.d.ts +5 -5
  55. package/dist/proxmox/ceph-fs.d.ts +16 -16
  56. package/dist/proxmox/ceph-osd-tree.d.ts +16 -16
  57. package/dist/proxmox/ceph-osd.d.ts +5 -5
  58. package/dist/proxmox/ceph-pool-form.d.ts +13 -13
  59. package/dist/proxmox/ceph-pool-form.d.ts.map +1 -1
  60. package/dist/proxmox/ceph-pool.d.ts +3 -3
  61. package/dist/proxmox/client.d.ts +2 -2
  62. package/dist/proxmox/credentials.d.ts +8 -8
  63. package/dist/proxmox/credentials.d.ts.map +1 -1
  64. package/dist/proxmox/fake-pve-lxc.d.ts +8 -0
  65. package/dist/proxmox/fake-pve-lxc.d.ts.map +1 -1
  66. package/dist/proxmox/firewall-alias-form.d.ts +1 -1
  67. package/dist/proxmox/firewall-alias.d.ts +1 -1
  68. package/dist/proxmox/generated/pbs.d.ts +1 -1
  69. package/dist/proxmox/generated/pve.d.ts +1 -1
  70. package/dist/proxmox/group.d.ts +8 -8
  71. package/dist/proxmox/ha-resource.d.ts +3 -2
  72. package/dist/proxmox/ha-resource.d.ts.map +1 -1
  73. package/dist/proxmox/ha-rule-form.d.ts +5 -5
  74. package/dist/proxmox/ha-rule.d.ts +3 -3
  75. package/dist/proxmox/index.d.ts +6 -0
  76. package/dist/proxmox/index.d.ts.map +1 -1
  77. package/dist/proxmox/index.js +465 -217
  78. package/dist/proxmox/index.js.map +63 -58
  79. package/dist/proxmox/lease-cache.d.ts +4 -4
  80. package/dist/proxmox/lxc-adoption.d.ts +32 -0
  81. package/dist/proxmox/lxc-adoption.d.ts.map +1 -0
  82. package/dist/proxmox/lxc-harness.d.ts +12 -0
  83. package/dist/proxmox/lxc-harness.d.ts.map +1 -1
  84. package/dist/proxmox/lxc-props.d.ts +2 -2
  85. package/dist/proxmox/lxc.d.ts +8 -8
  86. package/dist/proxmox/lxc.d.ts.map +1 -1
  87. package/dist/proxmox/members.d.ts +3 -3
  88. package/dist/proxmox/network-apply-read.d.ts +5 -5
  89. package/dist/proxmox/network-apply.d.ts +9 -9
  90. package/dist/proxmox/node-network-form.d.ts +6 -6
  91. package/dist/proxmox/node-network.d.ts +17 -17
  92. package/dist/proxmox/node-network.d.ts.map +1 -1
  93. package/dist/proxmox/notification-target.d.ts +3 -2
  94. package/dist/proxmox/notification-target.d.ts.map +1 -1
  95. package/dist/proxmox/pbs-datastore-guard.d.ts +1 -1
  96. package/dist/proxmox/pbs-datastore.d.ts +10 -10
  97. package/dist/proxmox/pbs-prune-job.d.ts +2 -2
  98. package/dist/proxmox/pbs-sync-job.d.ts +4 -4
  99. package/dist/proxmox/pbs-verify-job.d.ts +1 -1
  100. package/dist/proxmox/pool.d.ts +4 -4
  101. package/dist/proxmox/provision-baseline.d.ts +74 -0
  102. package/dist/proxmox/provision-baseline.d.ts.map +1 -0
  103. package/dist/proxmox/provision-bootstrap.d.ts +33 -0
  104. package/dist/proxmox/provision-bootstrap.d.ts.map +1 -0
  105. package/dist/proxmox/provision-cli-fake.d.ts +38 -0
  106. package/dist/proxmox/provision-cli-fake.d.ts.map +1 -0
  107. package/dist/proxmox/provision-declare.d.ts +25 -0
  108. package/dist/proxmox/provision-declare.d.ts.map +1 -0
  109. package/dist/proxmox/replication-job.d.ts +9 -8
  110. package/dist/proxmox/replication-job.d.ts.map +1 -1
  111. package/dist/proxmox/resource.d.ts +2 -2
  112. package/dist/proxmox/resource.d.ts.map +1 -1
  113. package/dist/proxmox/sdn-apply-read.d.ts +7 -7
  114. package/dist/proxmox/sdn-apply.d.ts +1 -1
  115. package/dist/proxmox/sdn-subnet-form.d.ts +1 -1
  116. package/dist/proxmox/sdn-subnet.d.ts +5 -5
  117. package/dist/proxmox/sdn-vnet.d.ts +5 -6
  118. package/dist/proxmox/sdn-vnet.d.ts.map +1 -1
  119. package/dist/proxmox/storage.d.ts +4 -3
  120. package/dist/proxmox/storage.d.ts.map +1 -1
  121. package/dist/proxmox/user.d.ts +3 -2
  122. package/dist/proxmox/user.d.ts.map +1 -1
  123. package/dist/proxmox/values.d.ts +1 -1
  124. package/dist/proxmox/zfs-pool-write.d.ts +1 -1
  125. package/dist/proxmox/zfs-pool.d.ts +5 -5
  126. package/dist/talos/credentials.d.ts +1 -1
  127. package/dist/talos/credentials.d.ts.map +1 -1
  128. package/dist/talos/index.js.map +1 -1
  129. package/docs/adopt-verify.md +21 -5
  130. package/docs/adopted-deploys.md +5 -3
  131. package/docs/ownership.md +27 -2
  132. package/docs/provision-baseline.md +106 -0
  133. package/docs/proxmox-lxc-adopt.md +83 -0
  134. package/docs/proxmox-lxc.md +12 -49
  135. package/docs/proxmox.md +8 -0
  136. package/package.json +1 -1
  137. package/src/caddy/admin-guard.ts +1 -1
  138. package/src/caddy/local-admin.ts +1 -1
  139. package/src/cloudflare/client.ts +1 -1
  140. package/src/cloudflare/lock-rules.ts +1 -1
  141. package/src/forgejo/branch-protection-form.ts +1 -1
  142. package/src/forgejo/branch-protection.ts +1 -1
  143. package/src/forgejo/client.ts +1 -1
  144. package/src/forgejo/org-actions-secrets.ts +1 -1
  145. package/src/forgejo/org-label.ts +1 -1
  146. package/src/forgejo/org-team.ts +1 -1
  147. package/src/forgejo/repo-webhook.ts +1 -1
  148. package/src/forgejo/repository.ts +2 -2
  149. package/src/forgejo/resource.ts +2 -2
  150. package/src/forgejo/team-member.ts +1 -1
  151. package/src/openbao/README.md +4 -4
  152. package/src/openbao/auth-role.ts +1 -1
  153. package/src/openbao/bao-http.test.ts +2 -2
  154. package/src/openbao/bao-http.ts +6 -6
  155. package/src/openbao/bao-status.test.ts +1 -1
  156. package/src/openbao/bao-status.ts +1 -1
  157. package/src/openbao/cloudflare-group-scope.test.ts +1 -1
  158. package/src/openbao/cloudflare-permission-groups.test.ts +1 -1
  159. package/src/openbao/cloudflare-permission-groups.ts +3 -3
  160. package/src/openbao/cloudflare-policy.ts +1 -1
  161. package/src/openbao/cloudflare-role-form.ts +2 -2
  162. package/src/openbao/cloudflare-roles-expand.ts +1 -1
  163. package/src/openbao/digest.ts +1 -1
  164. package/src/openbao/forgejo-bootstrap-parse.test.ts +3 -3
  165. package/src/openbao/forgejo-bootstrap-parse.ts +2 -2
  166. package/src/openbao/forgejo-bootstrap.ts +4 -4
  167. package/src/openbao/kubernetes-role-form.test.ts +5 -5
  168. package/src/openbao/mount.ts +1 -1
  169. package/src/openbao/pki-role.ts +1 -1
  170. package/src/openbao/policy-wire.test.ts +3 -3
  171. package/src/openbao/policy.ts +2 -2
  172. package/src/openbao/proxmox-role-form.ts +5 -5
  173. package/src/openbao/proxmox-role.ts +11 -11
  174. package/src/openbao/ssh-role.ts +3 -3
  175. package/src/ownership/adopting.test.ts +63 -0
  176. package/src/ownership/adopting.ts +46 -0
  177. package/src/ownership/ownership.test.ts +1 -34
  178. package/src/ownership/probe.ts +27 -14
  179. package/src/ownership/rows.ts +8 -2
  180. package/src/ownership/store-fixture.ts +50 -0
  181. package/src/proxmox/acl.ts +4 -4
  182. package/src/proxmox/adopt-noop.test.ts +6 -6
  183. package/src/proxmox/api-token-form.ts +4 -4
  184. package/src/proxmox/api-token.ts +13 -13
  185. package/src/proxmox/ceph-daemon-form.ts +3 -3
  186. package/src/proxmox/ceph-daemon.ts +20 -20
  187. package/src/proxmox/ceph-flag.ts +7 -7
  188. package/src/proxmox/ceph-fs-wire.ts +7 -7
  189. package/src/proxmox/ceph-fs.ts +18 -18
  190. package/src/proxmox/ceph-osd-tree.ts +16 -16
  191. package/src/proxmox/ceph-osd.ts +6 -6
  192. package/src/proxmox/ceph-pool-adopt.test.ts +14 -14
  193. package/src/proxmox/ceph-pool-form.ts +13 -13
  194. package/src/proxmox/ceph-pool.ts +6 -6
  195. package/src/proxmox/client.test.ts +3 -3
  196. package/src/proxmox/client.ts +3 -3
  197. package/src/proxmox/credentials.test.ts +6 -8
  198. package/src/proxmox/credentials.ts +8 -8
  199. package/src/proxmox/fake-pve-lxc.ts +13 -1
  200. package/src/proxmox/firewall-alias-form.ts +1 -1
  201. package/src/proxmox/firewall-alias.ts +1 -1
  202. package/src/proxmox/generated/pbs.ts +1 -1
  203. package/src/proxmox/generated/pve.ts +1 -1
  204. package/src/proxmox/group.ts +10 -10
  205. package/src/proxmox/ha-resource.ts +3 -2
  206. package/src/proxmox/ha-rule-form.ts +5 -5
  207. package/src/proxmox/ha-rule.ts +3 -3
  208. package/src/proxmox/index.test.ts +7 -0
  209. package/src/proxmox/index.ts +14 -0
  210. package/src/proxmox/lease-cache.test.ts +19 -19
  211. package/src/proxmox/lease-cache.ts +4 -4
  212. package/src/proxmox/lxc-adopt.test.ts +16 -23
  213. package/src/proxmox/lxc-adoption.ts +50 -0
  214. package/src/proxmox/lxc-create-form.ts +1 -1
  215. package/src/proxmox/lxc-guards.test.ts +10 -17
  216. package/src/proxmox/lxc-harness.ts +27 -4
  217. package/src/proxmox/lxc-judge.test.ts +5 -5
  218. package/src/proxmox/lxc-lifecycle.ts +1 -1
  219. package/src/proxmox/lxc-ownership.test.ts +19 -14
  220. package/src/proxmox/lxc-props.ts +2 -2
  221. package/src/proxmox/lxc-read.test.ts +3 -3
  222. package/src/proxmox/lxc-strict-adopt.test.ts +104 -0
  223. package/src/proxmox/lxc-volume.test.ts +9 -9
  224. package/src/proxmox/lxc-volume.ts +1 -1
  225. package/src/proxmox/lxc-wire.ts +1 -1
  226. package/src/proxmox/lxc.ts +19 -15
  227. package/src/proxmox/members.test.ts +1 -1
  228. package/src/proxmox/members.ts +3 -3
  229. package/src/proxmox/metric-server-otel.ts +2 -2
  230. package/src/proxmox/network-apply-read.ts +10 -10
  231. package/src/proxmox/network-apply.ts +10 -10
  232. package/src/proxmox/node-network-form.ts +7 -7
  233. package/src/proxmox/node-network.ts +18 -18
  234. package/src/proxmox/notification-target.ts +3 -2
  235. package/src/proxmox/pbs-datastore-guard.ts +1 -1
  236. package/src/proxmox/pbs-datastore.ts +10 -10
  237. package/src/proxmox/pbs-prune-job.ts +2 -2
  238. package/src/proxmox/pbs-sync-job.ts +4 -4
  239. package/src/proxmox/pbs-verify-job.ts +1 -1
  240. package/src/proxmox/pool.test.ts +2 -2
  241. package/src/proxmox/pool.ts +4 -4
  242. package/src/proxmox/provision-baseline.test.ts +87 -0
  243. package/src/proxmox/provision-baseline.ts +162 -0
  244. package/src/proxmox/provision-bootstrap.test.ts +132 -0
  245. package/src/proxmox/provision-bootstrap.ts +135 -0
  246. package/src/proxmox/provision-cli-fake.ts +217 -0
  247. package/src/proxmox/provision-declare.test.ts +111 -0
  248. package/src/proxmox/provision-declare.ts +103 -0
  249. package/src/proxmox/replication-job.ts +9 -8
  250. package/src/proxmox/resource.ts +3 -3
  251. package/src/proxmox/sdn-apply-read.test.ts +16 -16
  252. package/src/proxmox/sdn-apply-read.ts +7 -7
  253. package/src/proxmox/sdn-apply.ts +3 -3
  254. package/src/proxmox/sdn-subnet-form.ts +1 -1
  255. package/src/proxmox/sdn-subnet.ts +5 -5
  256. package/src/proxmox/sdn-vnet.ts +5 -6
  257. package/src/proxmox/storage.ts +4 -3
  258. package/src/proxmox/user.ts +3 -2
  259. package/src/proxmox/values.test.ts +2 -2
  260. package/src/proxmox/values.ts +1 -1
  261. package/src/proxmox/zfs-pool-write.ts +1 -1
  262. package/src/proxmox/zfs-pool.ts +6 -6
  263. package/src/talos/credentials.ts +3 -3
  264. package/src/talos/values.test.ts +9 -9
  265. package/src/verify/cli.test.ts +1 -1
@@ -28,29 +28,28 @@ import type { Owner } from './adopt.ts';
28
28
  import { recordedInstance } from './rows.ts';
29
29
 
30
30
  /**
31
- * A family's `read` answer. With attributes in state it is `found` as is. Without, it is the probe
32
- * or the recovery read (header): `found` is ours only when the state store records this instance
33
- * with its whole declaration AND `settled` — the family's own "live matches these props" — says so.
34
- * ⛔ `settled` NEVER FAILS THE READ. The recovery read runs with the INTERRUPTED deploy's props, so
31
+ * Whether the state store proves the live object this instance's own: a row records `ask.instanceId`
32
+ * with its whole declaration (whole.ts) AND `settled` — the family's own "live matches these props"
33
+ * — says so. The one proof ownership/ accepts for a live object state holds no attributes for; the
34
+ * recovery read (`ownedRead`) and an adoption check (adopting.ts) both ask it.
35
+ * ⛔ `settled` NEVER FAILS THE CALLER. The recovery read runs with the INTERRUPTED deploy's props, so
35
36
  * a declaration that cannot be evaluated any more (a fragments directory since moved, a group
36
37
  * name since renamed) would fail every later plan, the fix included. It reads as "not proven
37
- * ours" instead — `Unowned`, which `--adopt` resolves — with the reason logged.
38
+ * ours" instead, with the reason logged.
38
39
  */
39
- export const ownedRead = <A extends object, E, R>(
40
- ask: Owner,
41
- found: A | undefined,
40
+ export const provenOurs = <E, R>(
41
+ ask: { readonly fqn: string; readonly instanceId: string },
42
42
  settled: Effect.Effect<boolean, E, R>,
43
- ): Effect.Effect<A | undefined, never, R> =>
43
+ ): Effect.Effect<boolean, never, R> =>
44
44
  Effect.gen(function* () {
45
- if (found === undefined || ask.output !== undefined) return found;
46
45
  const recorded = yield* recordedInstance(ask.fqn, ask.instanceId);
47
- if (recorded === 'absent') return Unowned(found);
46
+ if (recorded === 'absent') return false;
48
47
  if (recorded === 'partial') {
49
48
  yield* Effect.logWarning(
50
49
  `${ask.fqn}: the interrupted create's state row lacks part of the declaration (a prop ` +
51
50
  'was still an Output when it was written), so the live object is not proven ours',
52
51
  );
53
- return Unowned(found);
52
+ return false;
54
53
  }
55
54
  const unproven = (reason: unknown) =>
56
55
  Effect.as(
@@ -60,6 +59,20 @@ export const ownedRead = <A extends object, E, R>(
60
59
  ),
61
60
  false,
62
61
  );
63
- const ours = yield* settled.pipe(Effect.catch(unproven), Effect.catchDefect(unproven));
64
- return ours ? found : Unowned(found);
62
+ return yield* settled.pipe(Effect.catch(unproven), Effect.catchDefect(unproven));
63
+ });
64
+
65
+ /**
66
+ * A family's `read` answer. With attributes in state it is `found` as is. Without, it is the probe
67
+ * or the recovery read (header): `found` is ours only when `provenOurs` says so, else `Unowned`,
68
+ * which `--adopt` resolves.
69
+ */
70
+ export const ownedRead = <A extends object, E, R>(
71
+ ask: Owner,
72
+ found: A | undefined,
73
+ settled: Effect.Effect<boolean, E, R>,
74
+ ): Effect.Effect<A | undefined, never, R> =>
75
+ Effect.gen(function* () {
76
+ if (found === undefined || ask.output !== undefined) return found;
77
+ return (yield* provenOurs(ask, settled)) ? found : Unowned(found);
65
78
  });
@@ -31,12 +31,15 @@ const storeOf = Effect.gen(function* () {
31
31
  const rowAt = (store: StateService, where: Where, fqn: string) =>
32
32
  store.get({ ...where, fqn }).pipe(Effect.orElseSucceed(() => undefined));
33
33
 
34
- type Generation = {
34
+ /** One generation of a persisted row, as far as ownership/ reads it. */
35
+ export type Generation = {
35
36
  readonly instanceId?: unknown;
36
37
  readonly status?: unknown;
37
38
  readonly props?: unknown;
38
39
  readonly attr?: unknown;
39
40
  readonly old?: unknown;
41
+ /** Set by Apply on the `updating` row of an adoption, until that update commits (Apply.ts). */
42
+ readonly adopting?: unknown;
40
43
  };
41
44
 
42
45
  /** The generation of a persisted row that carries `instanceId`: the row itself, or one in `old`. */
@@ -51,7 +54,10 @@ const generationOf = (row: unknown, instanceId: string): Generation | undefined
51
54
  };
52
55
 
53
56
  /** The generation `instanceId` names — at the FQN, or at a former FQN `renamedFrom` names. */
54
- const recordedGeneration = (fqn: string, instanceId: string) =>
57
+ export const recordedGeneration = (
58
+ fqn: string,
59
+ instanceId: string,
60
+ ): Effect.Effect<Generation | undefined> =>
55
61
  Effect.gen(function* () {
56
62
  const found = yield* storeOf;
57
63
  if (found === undefined) return undefined;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * A state store and a Stack for the ownership/ unit tests — the two services rows.ts reads.
3
+ *
4
+ * ⛔ TEST-ONLY. No provider imports this file and it is not on any barrel.
5
+ */
6
+ import { InMemoryService } from 'alchemy/State/InMemoryState';
7
+ import type { ResourceState } from 'alchemy/State/ResourceState';
8
+ import { State } from 'alchemy/State/State';
9
+ import { Stack } from 'alchemy/Stack';
10
+ import * as Effect from 'effect/Effect';
11
+
12
+ /** A row with no attributes: `creating`, or `replacing` when it carries an `old` generation. */
13
+ export const row = (
14
+ fqn: string,
15
+ instanceId: string,
16
+ old?: unknown,
17
+ props: unknown = { name: 'a' },
18
+ ): ResourceState =>
19
+ ({
20
+ fqn,
21
+ instanceId,
22
+ old,
23
+ props,
24
+ status: old === undefined ? 'creating' : 'replacing',
25
+ }) as unknown as ResourceState;
26
+
27
+ /**
28
+ * A stack `s` at stage `test` whose store holds `rows`: `A` and `B` declared with a `name`, and `Z`
29
+ * declared `renamedFrom('X')`.
30
+ */
31
+ export const withStore = <A>(
32
+ rows: Record<string, ResourceState>,
33
+ effect: Effect.Effect<A>,
34
+ ): Promise<A> =>
35
+ Effect.runPromise(
36
+ effect.pipe(
37
+ Effect.provideService(State, InMemoryService({ s: { test: rows } })),
38
+ Effect.provideService(Stack, {
39
+ actions: {},
40
+ bindings: {},
41
+ name: 's',
42
+ resources: {
43
+ A: { Props: { name: 'a' } },
44
+ B: { Props: { name: 'b' } },
45
+ Z: { FormerFqns: ['X'], Props: { name: 'a' } },
46
+ },
47
+ stage: 'test',
48
+ } as never),
49
+ ),
50
+ );
@@ -41,14 +41,14 @@ export type AclSubjectType = 'user' | 'group' | 'token';
41
41
 
42
42
  export interface AclProps extends WithTarget {
43
43
  /**
44
- * The PVE object path the grant is ON — `/`, `/pool/house`, `/vms/101`, `/storage/local-zfs`.
44
+ * The PVE object path the grant is ON — `/`, `/pool/lab`, `/vms/101`, `/storage/local-zfs`.
45
45
  * ⚠️ NOT THE API PATH: `spec.path` below is the endpoint (`access/acl`). PVE's API overloads the
46
46
  * word, so this file does too rather than renaming a field the cluster calls `path`.
47
47
  */
48
48
  path: string;
49
49
  /** Which kind of subject `ugid` names. Identity: changing it is a different grant. */
50
50
  type: AclSubjectType;
51
- /** `tim@pve`, `admins`, `hf-provision@pve!hf-provision-…`. Identity. */
51
+ /** `alice@pve`, `admins`, `hf-provision@pve!hf-provision-…`. Identity. */
52
52
  ugid: string;
53
53
  /** The role bound here, e.g. `PVEAuditor`. ⚠️ It must exist — PVE refuses an unknown roleid. */
54
54
  roleid: string;
@@ -85,9 +85,9 @@ export const ProxmoxAcl = Resource<ProxmoxAcl>('Proxmox.Acl');
85
85
  /**
86
86
  * ⚠️ PVE NORMALISES ACL PATHS AND RETURNS THE NORMALISED FORM, a forever-update trap when only one
87
87
  * side of the comparison is normalised. `PVE::AccessControl::normalize_path` collapses repeated
88
- * slashes and strips the trailing one, so a declared `/pool/house/` reads back as `/pool/house`
88
+ * slashes and strips the trailing one, so a declared `/pool/lab/` reads back as `/pool/lab`
89
89
  * and a naive match never fires again. It also refuses a path with no leading slash, so one is
90
- * added here rather than letting `pool/house` 400.
90
+ * added here rather than letting `pool/lab` 400.
91
91
  */
92
92
  const normalize = (raw: string) => `/${raw.split('/').filter(Boolean).join('/')}`;
93
93
 
@@ -32,8 +32,8 @@ type Case = {
32
32
 
33
33
  const cases: readonly Case[] = [
34
34
  {
35
- declare: () => ProxmoxPool('house', { comment: 'x', poolid: 'house', target: FAKE_TARGET }),
36
- live: { 'pools/house': { comment: 'x', members: [] } },
35
+ declare: () => ProxmoxPool('lab', { comment: 'x', poolid: 'lab', target: FAKE_TARGET }),
36
+ live: { 'pools/lab': { comment: 'x', members: [] } },
37
37
  name: 'Proxmox.Pool (pveHandlers: every factory family)',
38
38
  provider: ProxmoxPoolProvider as never,
39
39
  },
@@ -98,15 +98,15 @@ describe('adopting a matching object only reads', () => {
98
98
  });
99
99
 
100
100
  test('and the harness does see a write: a drifted Pool is PUT once', async () => {
101
- const fake = clusterWith({ 'pools/house': { comment: 'old', members: [] } });
101
+ const fake = clusterWith({ 'pools/lab': { comment: 'old', members: [] } });
102
102
  await withoutBao(async () => {
103
103
  const engine = engineOver(ProxmoxPoolProvider().pipe(Layer.provideMerge(fake.layer)));
104
104
  const declared = () =>
105
- ProxmoxPool('house', { comment: 'x', poolid: 'house', target: FAKE_TARGET });
105
+ ProxmoxPool('lab', { comment: 'x', poolid: 'lab', target: FAKE_TARGET });
106
106
  const report = await engine.verify(declared());
107
107
  expect(report.rows[0]).toMatchObject({ changed: ['comment'], diff: 'update', ok: false });
108
- expect(await engine.deploy(declared())).toEqual({ house: 'adopted' });
108
+ expect(await engine.deploy(declared())).toEqual({ lab: 'adopted' });
109
109
  });
110
- expect(fake.writes()).toEqual(['PUT pools/house']);
110
+ expect(fake.writes()).toEqual(['PUT pools/lab']);
111
111
  });
112
112
  });
@@ -12,7 +12,7 @@ import type { ApiTokenAttributes, ApiTokenProps } from './api-token.ts';
12
12
  *
13
13
  * ⛔ THE WRITE SEMANTICS BELOW ARE READ FROM THE CLUSTER'S OWN PERL RATHER THAN INFERRED FROM THE
14
14
  * API SCHEMA — `/usr/share/perl5/PVE/API2/User.pm`, subs `generate_token` and
15
- * `update_token_info`, read over SSH on n2 on 2026-09-13. The two disagree in exactly the place
15
+ * `update_token_info`, read over SSH on node-b on 2026-09-13. The two disagree in exactly the place
16
16
  * that costs a forever-diff: the schema gives `expire` the default "same as user", and the code
17
17
  * never implements it. api-token.ts records what that means.
18
18
  */
@@ -26,7 +26,7 @@ import { bool, int, text } from './values.ts';
26
26
  * `ugid`, so reporting it as an attribute is what lets a privilege-separated token be granted
27
27
  * anything at all without somebody retyping the join by hand.
28
28
  *
29
- * ⚠️ DERIVED, NOT READ. MEASURED: `GET /access/users/monitoring@pve/token/exporter` answers
29
+ * ⚠️ DERIVED, NOT READ. MEASURED: `GET /access/users/metrics@pve/token/exporter` answers
30
30
  * `{"expire":0,"privsep":0}` and echoes back neither half of its own identity. So this is built
31
31
  * from props, cannot disagree with the path the read used, and is therefore true by
32
32
  * construction — which is why it is reported and never compared.
@@ -106,8 +106,8 @@ export const apiTokenSpec: PveSpec<ApiTokenProps, ApiTokenAttributes> = {
106
106
  /**
107
107
  * ⚠️ EXACTLY THE THREE FIELDS A PUT CAN PUT BACK, WHICH IS ALSO EXACTLY WHAT THE GET REPORTS.
108
108
  * MEASURED, by replaying these two functions over the live GET bodies of all four token
109
- * shapes this cluster has: `monitoring@pve!exporter` (no comment), `tofu@pve!apply` and
110
- * `sablier@pve!sablier` (commented), and a live OpenBao lease under `hf-read@pve` (a real
109
+ * shapes this cluster has: `metrics@pve!exporter` (no comment), `iac@pve!apply` and
110
+ * `app@pve!app` (commented), and a live OpenBao lease under `hf-read@pve` (a real
111
111
  * non-zero `expire`, offered as both `1789327175` and `"1789327175"` since `int` must not care
112
112
  * which). All four answer noop; flipping `privsep` answers update; an empty body answers
113
113
  * absent. ⚠️ THAT IS THE COMPARISON, NOT THE ENGINE — no `alchemy plan` was run against this
@@ -2,11 +2,11 @@
2
2
  * `Proxmox.ApiToken` — a PVE API token: the thing a service actually authenticates as.
3
3
  *
4
4
  * ⛔ THE SECRET EXISTS FOR ONE HTTP RESPONSE AND THEN NOWHERE. MEASURED from the cluster's own
5
- * schema (`/usr/share/pve-docs/api-viewer/apidoc.js` on n2, 2026-09-13): POST returns
5
+ * schema (`/usr/share/pve-docs/api-viewer/apidoc.js` on node-b, 2026-09-13): POST returns
6
6
  * `["full-tokenid","info","value"]`; PUT returns `["comment","expire","full-tokenid","privsep",
7
7
  * "value"]` with `value` present ONLY when `regenerate` was set; GET returns
8
8
  * `["comment","expire","privsep"]` and never the secret. A live token agrees —
9
- * `GET /access/users/monitoring@pve/token/exporter` answers `{"expire":0,"privsep":0}`. PVE says
9
+ * `GET /access/users/metrics@pve/token/exporter` answers `{"expire":0,"privsep":0}`. PVE says
10
10
  * it plainly on the POST: the value "needs to be stored as it cannot be retrieved afterwards".
11
11
  *
12
12
  * ⛔ SO A TOKEN THIS RESOURCE CREATES IS UNUSABLE, AND SAYING SO IS THE POINT OF THIS BLOCK. The
@@ -19,7 +19,7 @@
19
19
  * ★ SO POINT THIS FAMILY AT TOKENS THAT ALREADY EXIST. `comment`, `expire` and `privsep` are the
20
20
  * whole of a token's policy, and declaring them is real work: it is how `expire` stops being
21
21
  * whatever somebody typed in 2024. Mint NEW tokens where the secret can be caught — OpenBao's
22
- * `proxmox-tb4` mount, or a human at `pveum user token add`.
22
+ * `proxmox-c1` mount, or a human at `pveum user token add`.
23
23
  * ⚠️ THE CREATE PATH IS LEFT REACHABLE RATHER THAN STUBBED. A create that silently did nothing
24
24
  * would be the same lie as the stubbed delete resource.ts's ★ refuses; the honest arrangement
25
25
  * is a create that works and a header that says what it produces.
@@ -31,8 +31,8 @@
31
31
  * that owns the lease, or to a human who is watching.
32
32
  *
33
33
  * ⚠️ THERE IS A SECOND WRITER TO THESE OBJECTS AND IT IS THE ONE THIS PROVIDER RUNS ON. OpenBao's
34
- * `proxmox-tb4` engine mints PVE tokens under `hf-read@pve` and `hf-provision@pve` — read from
35
- * `house/platform/secrets/vault/plugin-proxmox/proxmox/`: `privsep=0` (client.go), `expire` set
34
+ * `proxmox-c1` engine mints PVE tokens under `hf-read@pve` and `hf-provision@pve` — read from
35
+ * `<estate>/platform/secrets/vault/plugin-proxmox/proxmox/`: `privsep=0` (client.go), `expire` set
36
36
  * to the lease deadline, and an id the plugin CHOOSES,
37
37
  * `hf-<role>-<actor>-<entity6>-<stamp>-<nonce>` (tokenname.go). Revocation and WAL rollback look
38
38
  * up that one exact id (wal.go); neither sweeps a prefix. So the two systems cannot collide on
@@ -62,7 +62,7 @@ import { type PveRequirements, type WithTarget, pveHandlers, pveOperations } fro
62
62
 
63
63
  export interface ApiTokenProps extends WithTarget {
64
64
  /**
65
- * The account that owns the token, realm-qualified: `tofu@pve`, `root@pam`. Identity.
65
+ * The account that owns the token, realm-qualified: `iac@pve`, `root@pam`. Identity.
66
66
  *
67
67
  * ⚠️ THE ACCOUNT MUST ALREADY EXIST. Every method here runs `check_user_exist` first, so a
68
68
  * token named under a missing user fails the READ (folded to "absent") and then fails the
@@ -120,7 +120,7 @@ export interface ApiTokenProps extends WithTarget {
120
120
  * — `$privsep = $privsep ? 1 : 0` (AccessControl.pm:1622), so there is no unset state to
121
121
  * preserve. Were this optional, adopting a live token without mentioning `privsep` would plan
122
122
  * an update to 1 and STRIP a working credential of every privilege it has, silently: PVE
123
- * returns 401/403 to the service, nothing errors here, and `tofu@pve!apply` simply stops
123
+ * returns 401/403 to the service, nothing errors here, and `iac@pve!apply` simply stops
124
124
  * working. Required, so adopting a token is a sentence that states what it is.
125
125
  * ⚠️ AND `false` IS NOT A SHRUG. It is a token with its owner's whole privilege set; under a
126
126
  * provisioning account that is the account's full authority with a separate secret.
@@ -156,9 +156,9 @@ export interface ProxmoxApiToken extends Resource<
156
156
  * ★ `retain` BY DEFAULT, AND THIS IS THE PLAINEST CASE FOR IT IN THE PACKAGE. A token's contents
157
157
  * are one irreplaceable secret: delete it and the value is gone, a replacement is a DIFFERENT
158
158
  * value, and every holder loses access the instant `cfs_write_file` returns — with no error
159
- * raised anywhere near them. The live cluster's tokens are `monitoring@pve!exporter` (the PVE
160
- * exporter feeding VictoriaMetrics), `mcp@pve!executor`, `tofu@pve!apply`, `tofu@pve!ro`,
161
- * `sablier@pve!sablier` and `vaultmint@pve!engine` — the last being the parent credential the
159
+ * raised anywhere near them. The live cluster's tokens are `metrics@pve!exporter` (the PVE
160
+ * exporter feeding VictoriaMetrics), `agent@pve!executor`, `iac@pve!apply`, `iac@pve!ro`,
161
+ * `app@pve!app` and `mint@pve!engine` — the last being the parent credential the
162
162
  * OpenBao mount itself authenticates with, so orphaning that one would stop every plan in this
163
163
  * package, this resource included. `delete` is FULLY IMPLEMENTED (DELETE is a real method on
164
164
  * this path, measured) and runs the moment a caller opts in with `.pipe(RemovalPolicy.destroy())`.
@@ -184,7 +184,7 @@ const ops = pveOperations(apiTokenSpec);
184
184
  * `reconcile` refuses by name and says what to do instead.
185
185
  *
186
186
  * ★ EVERYTHING ELSE STILL WORKS, AND IT IS THE HALF WORTH HAVING. Adopting an existing token and
187
- * converging its `comment`, `expire` and `privsep` are real operations: TB4 carries SEVEN tokens
187
+ * converging its `comment`, `expire` and `privsep` are real operations: C1 carries SEVEN tokens
188
188
  * with `expire=0` and `privsep=0` made by clicks nobody recorded. Declaring those freezes the
189
189
  * set, and an eighth appearing shows up as drift.
190
190
  *
@@ -203,7 +203,7 @@ const handlers = {
203
203
  'the secret only in the create response and it cannot be stored, so a token made here ' +
204
204
  'would be a live credential nobody holds. Create it with `pveum user token add` and ' +
205
205
  'capture the value, or mint a short-lived one from the OpenBao proxmox mount ' +
206
- '(`bao read proxmox-tb4/creds/<role>`) -- then declare it here to manage it.',
206
+ '(`bao read proxmox-c1/creds/<role>`) -- then declare it here to manage it.',
207
207
  ),
208
208
  );
209
209
  }
@@ -213,7 +213,7 @@ const handlers = {
213
213
 
214
214
  /**
215
215
  * ⛔ `list` IS EMPTY, AND FOR THIS FAMILY THAT MATTERS MORE THAN THE GENERIC ARGUMENT IN
216
- * resource.ts. A token index handed to Alchemy would offer up `vaultmint@pve!engine` and every
216
+ * resource.ts. A token index handed to Alchemy would offer up `mint@pve!engine` and every
217
217
  * live OpenBao lease for adoption — and adoption is what makes a later plan willing to delete.
218
218
  * The mount's leases in particular appear and vanish on their own; anything that adopted one
219
219
  * would report drift against a credential that was never its to hold.
@@ -9,7 +9,7 @@
9
9
  * here decides a diff; `findRow` picks a row out of an answer its caller already fetched.
10
10
  *
11
11
  * ⛔ THE TWO PATHS ARE DIFFERENT AND THAT IS THE WHOLE REASON THIS FILE HAS TWO OF THEM. MEASURED
12
- * on n2, 2026-09-13: there is no GET on the id path — `pvesh get /nodes/n2/ceph/mon/n2` answers
12
+ * on node-b, 2026-09-13: there is no GET on the id path — `pvesh get /nodes/node-b/ceph/mon/node-b` answers
13
13
  * "No 'get' handler defined" — while POST and DELETE are registered ONLY there. So the read uses
14
14
  * `collectionPath` and the two writes use `daemonPath`, and swapping either one is a silent
15
15
  * 501: a create that never lands, or a destroy that reports failure while the daemon runs on.
@@ -41,8 +41,8 @@ export const daemonId = (props: CephDaemonProps) => props.name ?? props.node;
41
41
 
42
42
  /**
43
43
  * The read path. ⚠️ `{node}` HERE IS ONLY THE NODE BEING ASKED, not a filter. MEASURED:
44
- * `GET /nodes/n2/ceph/mon` returns n2, n3 AND n4, and `GET /nodes/n3/ceph/mon` returns the same
45
- * three IN A DIFFERENT ORDER — [n4,n2,n3] from n2 against [n3,n2,n4] from n3. A caller that took
44
+ * `GET /nodes/node-b/ceph/mon` returns node-b, node-c AND node-d, and `GET /nodes/node-c/ceph/mon` returns the same
45
+ * three IN A DIFFERENT ORDER — [node-d,node-b,node-c] from node-b against [node-c,node-b,node-d] from node-c. A caller that took
46
46
  * row zero, or assumed the list was this node's daemons, would be wrong on both counts.
47
47
  */
48
48
  export const collectionPath = (props: CephDaemonProps) => `nodes/${props.node}/ceph/${props.kind}`;
@@ -2,7 +2,7 @@
2
2
  * `Proxmox.CephDaemon` — a Ceph monitor, manager or metadata server on one node.
3
3
  *
4
4
  * ★ ONE RESOURCE FOR THREE KINDS, AND THE SCHEMAS WERE CHECKED BEFORE THAT WAS DECIDED. Parsed
5
- * from `/usr/share/pve-docs/api-viewer/apidoc.js` on n2, 2026-09-13: mon, mgr and mds each
5
+ * from `/usr/share/pve-docs/api-viewer/apidoc.js` on node-b, 2026-09-13: mon, mgr and mds each
6
6
  * expose GET on the collection and POST + DELETE on the id below it, each returns
7
7
  * name/host/state/addr/ceph_version*, and the only divergence is the create parameter
8
8
  * (`mon-address` / none / `hotstandby`) and the read-only extras (mon: quorum, rank; mds: rank,
@@ -10,11 +10,11 @@
10
10
  * times over and let them drift — the argument resource.ts already makes for the factory itself.
11
11
  *
12
12
  * ⛔ THE ONLY GET IS THE COLLECTION'S, AND IT ANSWERS FOR THE WHOLE CLUSTER. MEASURED:
13
- * `GET /nodes/n2/ceph/mon` returns n2, n3 AND n4, and `GET /nodes/n3/ceph/mon` returns the same
14
- * three IN A DIFFERENT ORDER — [n4,n2,n3] from n2, [n3,n2,n4] from n3 — so a row is found by
13
+ * `GET /nodes/node-b/ceph/mon` returns node-b, node-c AND node-d, and `GET /nodes/node-c/ceph/mon` returns the same
14
+ * three IN A DIFFERENT ORDER — [node-d,node-b,node-c] from node-b, [node-c,node-b,node-d] from node-c — so a row is found by
15
15
  * `name` and NEVER by position, and the `{node}` in the path is only the node being ASKED.
16
- * MEASURED too: there is no GET on the id path at all; `pvesh get /nodes/n2/ceph/mon/n2` answers
17
- * "No 'get' handler defined for '/nodes/n2/ceph/mon/n2'", and mgr and mds answer the same.
16
+ * MEASURED too: there is no GET on the id path at all; `pvesh get /nodes/node-b/ceph/mon/node-b` answers
17
+ * "No 'get' handler defined for '/nodes/node-b/ceph/mon/node-b'", and mgr and mds answer the same.
18
18
  * That is why `path()` below is the COLLECTION, and that in turn is why `delete` is the one
19
19
  * handler this file writes by hand: `pveOperations.destroy` sends its DELETE to `spec.path`,
20
20
  * which here would be the collection — a 501 reported as a failed destroy, with the daemon still
@@ -27,11 +27,11 @@
27
27
  * strips its section from ceph.conf. THREE MONS TOLERATE ONE LOSS; TWO TOLERATE NONE. Removing
28
28
  * two in one deploy — or removing the mon on the node whose API is serving the call — loses
29
29
  * quorum, and a cluster without mon quorum BLOCKS every RBD and CephFS I/O rather than erroring
30
- * it, so on this cluster every guest on `cephtb4` hangs.
30
+ * it, so on this cluster every guest on `rbd-c1` hangs.
31
31
  * `mds`: destroying a STANDBY is a non-event; destroying the ACTIVE one fails CephFS over,
32
- * and with no standby left `cephfs-tb4` goes unavailable. `mgr`: no guest I/O depends on it, but
32
+ * and with no standby left `cephfs-c1` goes unavailable. `mgr`: no guest I/O depends on it, but
33
33
  * the last one takes the PG autoscaler, the dashboard and PVE's own Ceph status with it.
34
- * Live on 2026-09-13: mon, mgr AND mds on each of n2, n3, n4.
34
+ * Live on 2026-09-13: mon, mgr AND mds on each of node-b, node-c, node-d.
35
35
  *
36
36
  * ★ AND THE ONLY WAY TO REACH THAT DESTROY IS TO DELETE THE DECLARATION. `matches` is deliberately
37
37
  * total (see its ⛔), so this resource CANNOT plan a `replace` — which is the one action that
@@ -41,7 +41,7 @@
41
41
  * `Sys.Audit`/`Datastore.Audit` on `/`, and PVEAuditor holds both (MEASURED:
42
42
  * `GET /access/roles/PVEAuditor`), so the mount's `read` role can see this family — unlike
43
43
  * `Proxmox.Storage`, which 403s under that lease. POST and DELETE both check `Sys.Modify` on
44
- * `/`, and MEASURED on this cluster `LXCProvisioner` already holds it, so the provision lane
44
+ * `/`, and MEASURED on this cluster the provision role already held it (it is in `PROVISION_PRIVILEGES`), so the provision lane
45
45
  * needs no widening for once. ⚠️ `Sys.Modify` on the ROOT path is broad — it also buys
46
46
  * datacenter options and every other cluster-wide config write — so it is worth knowing that
47
47
  * this credential already has it rather than discovering it the next time something is scoped.
@@ -86,9 +86,9 @@ export interface CephDaemonProps extends WithTarget {
86
86
  name?: string;
87
87
  /**
88
88
  * mon only. Overrides the autodetected monitor IP; must sit in Ceph's public network — on this
89
- * cluster that is 10.20.11.0/24, carried by `vmbr1.11`.
89
+ * cluster that is 198.51.100.0/24, carried by `vmbr1.42`.
90
90
  * ⛔ CREATE-ONLY AND NEVER COMPARED. PVE takes a bare ip-list on write and hands back `addr` as
91
- * `10.20.11.12:6789/0` — an address, a port and a nonce. The two are not the same string and
91
+ * `198.51.100.12:6789/0` — an address, a port and a nonce. The two are not the same string and
92
92
  * never will be, so diffing them would report a mismatch on every plan; with no PUT on this
93
93
  * family that mismatch becomes a REPLACE, and a replace of a mon is the quorum loss in the
94
94
  * header. Left out of `matches` on purpose.
@@ -98,7 +98,7 @@ export interface CephDaemonProps extends WithTarget {
98
98
  * mds only. Makes this standby replay the active MDS's journal for a faster failover.
99
99
  * ⛔ CREATE-ONLY AND NEVER COMPARED, AND THE FIELD THAT LOOKS LIKE ITS READBACK IS NOT ONE.
100
100
  * `standby_replay` in the list is the daemon's CURRENT state, not its configuration: MEASURED
101
- * 2026-09-13, all three mds report `standby_replay: false`, n3 included — and n3 is the ACTIVE
101
+ * 2026-09-13, all three mds report `standby_replay: false`, node-c included — and node-c is the ACTIVE
102
102
  * mds, which is not a standby at all and so can never report true however it was created.
103
103
  * Comparing `hotstandby` to it would plan a replace against whichever mds Ceph happens to have
104
104
  * elected, i.e. against a value no declaration controls.
@@ -128,22 +128,22 @@ export interface CephDaemonAttributes {
128
128
  host: string;
129
129
  /**
130
130
  * mon `running`/`stopped`/`unknown`, mgr `active`/`standby`, mds `up:active`/`up:standby`/…
131
- * ⛔ CEPH ELECTS THIS AND REWRITES IT WITHOUT ANYONE DECLARING ANYTHING. MEASURED: mgr n2 is
132
- * `active` while n3 and n4 are `standby`; mds n3 is `up:active` while n2 and n4 are
131
+ * ⛔ CEPH ELECTS THIS AND REWRITES IT WITHOUT ANYONE DECLARING ANYTHING. MEASURED: mgr node-b is
132
+ * `active` while node-c and node-d are `standby`; mds node-c is `up:active` while node-b and node-d are
133
133
  * `up:standby`. Restart a daemon and the roles move. It is reported so a plan can show what is
134
134
  * live and kept out of `matches` so that an election is never a diff.
135
135
  * ⚠️ IT ALSO MEANS THIS RESOURCE DOES NOT MANAGE WHETHER THE DAEMON IS RUNNING. A configured but
136
136
  * stopped daemon is still a row in the list, so it reads as present and plans `noop`.
137
137
  */
138
138
  state: string;
139
- /** Ceph-formatted, e.g. `10.20.11.12:6789/0`. Reported; see `mon-address` for why never compared. */
139
+ /** Ceph-formatted, e.g. `198.51.100.12:6789/0`. Reported; see `mon-address` for why never compared. */
140
140
  addr: string;
141
141
  /** `ceph_version_short`, e.g. `20.2.2`. Reported: a straggler after an upgrade is worth seeing. */
142
142
  version: string;
143
143
  /**
144
144
  * mon rank in the monmap, mds rank in the filesystem, `-1` when the daemon has neither.
145
- * ⛔ CEPH ASSIGNS IT AND NOTHING DECLARES IT. MEASURED: the mons are ranked n4=0, n3=1, n2=2 —
146
- * monmap order, not declaration order — and the mds ranks are 0 for the active n3 and -1 for
145
+ * ⛔ CEPH ASSIGNS IT AND NOTHING DECLARES IT. MEASURED: the mons are ranked node-d=0, node-c=1, node-b=2 —
146
+ * monmap order, not declaration order — and the mds ranks are 0 for the active node-c and -1 for
147
147
  * the two standbys. Rank moves when a daemon is added, removed or restarted.
148
148
  */
149
149
  rank: number;
@@ -152,8 +152,8 @@ export interface CephDaemonAttributes {
152
152
  /** mds only, and NOT the readback of `hotstandby` — see the ⛔ on that prop. */
153
153
  standbyReplay: boolean;
154
154
  /**
155
- * mds only: the CephFS this daemon currently serves — `cephfs-tb4` here.
156
- * ⛔ PRESENT ONLY ON THE ACTIVE MDS. MEASURED: n3 carries `fs_name`, n2 and n4 have no such key
155
+ * mds only: the CephFS this daemon currently serves — `cephfs-c1` here.
156
+ * ⛔ PRESENT ONLY ON THE ACTIVE MDS. MEASURED: node-c carries `fs_name`, node-b and node-d have no such key
157
157
  * at all. So it is empty for two of three identical declarations, and comparing it would
158
158
  * report work on exactly the daemons that are healthy standbys.
159
159
  */
@@ -234,7 +234,7 @@ export const ProxmoxCephDaemonProvider = () =>
234
234
  * problem. Everything else here is the factory's.
235
235
  * ⚠️ AND IT IS THE DANGEROUS ONE. Re-read the destroy ⛔ in the header before letting a
236
236
  * plan that removes a mon run: two gone at once is a cluster with no quorum and every
237
- * guest on `cephtb4` blocked on I/O.
237
+ * guest on `rbd-c1` blocked on I/O.
238
238
  */
239
239
  delete: ({ olds }: { olds: CephDaemonProps }) =>
240
240
  pve(olds.target, 'provision', 'DELETE', daemonPath(olds)),
@@ -2,7 +2,7 @@
2
2
  * `Proxmox.CephFlag` — one cluster-wide Ceph OSD flag, declared: `noout`, `pause`, `norebalance`.
3
3
  *
4
4
  * ★ ONE RESOURCE PER FLAG, NOT ONE CARRYING ALL ELEVEN, AND THE CLUSTER'S OWN API DECIDED IT.
5
- * MEASURED on TB4 2026-09-13 (pve-manager 9.2.11, ceph tentacle 20.2.2) by reading
5
+ * MEASURED on C1 2026-09-13 (pve-manager 9.2.11, ceph tentacle 20.2.2) by reading
6
6
  * `/usr/share/perl5/PVE/API2/Cluster/Ceph.pm` and the published schema in
7
7
  * `/usr/share/pve-docs/api-viewer/apidoc.js`:
8
8
  * · `PUT /cluster/ceph/flags` ends in `fork_worker('cephsetflags', ...)` and returns a STRING
@@ -30,7 +30,7 @@
30
30
  * defaulting to false would make `CephFlag('noout', { flag: 'noout', target })` mean "clear
31
31
  * it" — the dangerous direction, chosen by OMISSION. Here the dangerous sentence has to be
32
32
  * typed out by a person. Do not add a default, and do not declare a flag merely to document
33
- * that it is off: MEASURED 2026-09-13, all eleven flags on TB4 read 0, so eleven
33
+ * that it is off: MEASURED 2026-09-13, all eleven flags on C1 read 0, so eleven
34
34
  * `value: false` lines would plan green forever and do nothing except take the brake off
35
35
  * whenever somebody happens to apply them.
36
36
  *
@@ -58,8 +58,8 @@
58
58
  * and plans `noop` straight past it. It cannot express a half-pause, so never use it to prove
59
59
  * one is gone; `ceph osd dump | head -1` can.
60
60
  * ⛔ `pause` AND `noup` ARE ALSO THE TWO THAT TAKE THE CLUSTER DOWN FROM A TYPO. `pause: true`
61
- * stops all reads and writes, so every guest on `cephtb4` — the cluster's only rbd pool,
62
- * MEASURED 2026-09-13 — freezes on its root disk, and so does `cephfs-tb4`. `noup: true`
61
+ * stops all reads and writes, so every guest on `rbd-c1` — the cluster's only rbd pool,
62
+ * MEASURED 2026-09-13 — freezes on its root disk, and so does `cephfs-c1`. `noup: true`
63
63
  * stops a rebooted OSD from ever rejoining. Neither is undone by deleting the line.
64
64
  *
65
65
  * ⚠️ PRIVILEGES, AND THE WRITE ONE IS A BIG ASK. Both GETs check `Sys.Audit` on `/`; both PUTs
@@ -83,7 +83,7 @@ import { bool, flag } from './values.ts';
83
83
  *
84
84
  * ⚠️ THE ENUM IS CLOSED AND A TYPO IS A 400, WHICH IS WHY THIS IS A UNION AND NOT `string`. Both
85
85
  * the GET and the PUT declare `additionalProperties => 0` over exactly this list.
86
- * ⚠️ CEPH HAS FLAGS PVE DOES NOT MODEL, and four of them are always on. MEASURED on TB4:
86
+ * ⚠️ CEPH HAS FLAGS PVE DOES NOT MODEL, and four of them are always on. MEASURED on C1:
87
87
  * `ceph osd dump` reports `flags sortbitwise,recovery_deletes,purged_snapdirs,pglog_hardlimit`.
88
88
  * PVE reads that same string and answers only about its own eleven, so the others can neither
89
89
  * leak in here nor be set from here — `noautoscale` and `nosnaptrim` included.
@@ -167,7 +167,7 @@ const handlers = pveHandlers<CephFlagProps, CephFlagAttributes>({
167
167
  *
168
168
  * ⛔ IT NEVER ANSWERS undefined, WHICH MAKES THE CREATE BRANCH UNREACHABLE, AND ITS ERROR WILL
169
169
  * MISLEAD YOU. All eleven flags always exist; there is nothing for "absent" to mean. So a POST
170
- * to `collection` means the READ failed — an expired 300s lease, n2 down, or ceph simply not
170
+ * to `collection` means the READ failed — an expired 300s lease, node-b down, or ceph simply not
171
171
  * configured on the cluster (every one of these four handlers opens with
172
172
  * `check_ceph_configured()`, which dies). Read the resulting "Method 'POST /cluster/ceph/flags'
173
173
  * not implemented" as "the read failed" and go and look at the credential, not at ceph. Same
@@ -187,7 +187,7 @@ const handlers = pveHandlers<CephFlagProps, CephFlagAttributes>({
187
187
  * and `name` exist only on the bulk GET, which this file does not read, so neither can be
188
188
  * compared by accident. Nothing PVE returns here is rewritten, re-ordered or re-typed by the
189
189
  * cluster, because all PVE returns here is one boolean.
190
- * ★ MEASURED: on TB4, `GET /cluster/ceph/flags` answers value 0 for all eleven flags, so a
190
+ * ★ MEASURED: on C1, `GET /cluster/ceph/flags` answers value 0 for all eleven flags, so a
191
191
  * declaration of `value: false` reads back false and plans `noop`.
192
192
  */
193
193
  matches: (attributes, props) => attributes.value === props.value,
@@ -16,7 +16,7 @@
16
16
  *
17
17
  * ⛔ POST AND DELETE ANSWER WITH A UPID, NOT A RESULT — THE WRITE IS ASYNCHRONOUS AND A FAILED ONE
18
18
  * IS STILL HTTP 200. Read from the cluster's own source, /usr/share/perl5/PVE/API2/Ceph/FS.pm on
19
- * n2: `createfs` and `destroyfs` both end `return $rpcenv->fork_worker(...)`. Two different lies
19
+ * node-b: `createfs` and `destroyfs` both end `return $rpcenv->fork_worker(...)`. Two different lies
20
20
  * come out of trusting that status code. A read-back straight after the POST reports "still
21
21
  * absent" about a filesystem that is being built as it says so — which is precisely the message
22
22
  * `pveOperations.reconcile` would print, pointing at the wrong cause. And a worker that dies
@@ -26,7 +26,7 @@
26
26
  * has not been observed; only the `fork_worker` that guarantees it is there.
27
27
  *
28
28
  * ⛔ A DELETE BODY IS SILENTLY DISCARDED BY PVE, SO `remove-pools` GOES IN THE QUERY STRING.
29
- * MEASURED in /usr/share/perl5/PVE/APIServer/AnyEvent.pm on n2: line 928 reads the request
29
+ * MEASURED in /usr/share/perl5/PVE/APIServer/AnyEvent.pm on node-b: line 928 reads the request
30
30
  * content into params only `if ($method eq 'PUT' || $method eq 'POST')`, and line 1653 sends
31
31
  * every other method down a branch that parses `$request->url->query()` and nothing else. So
32
32
  * `pve(target, role, 'DELETE', path, form)` would send a body PVE never looks at: the flags would
@@ -41,7 +41,7 @@
41
41
  * ⛔ THE POLLER IS NEVER THE TASK'S OWNER, WHICH COSTS A PRIVILEGE, AND THIS ONE IS MEASURED
42
42
  * RATHER THAN FEARED. client.ts mints a fresh credential per call and the mount vends a NEW
43
43
  * token id each time, so the token reading the status is never the token recorded in the UPID —
44
- * and PVE compares them exactly. /usr/share/perl5/PVE/API2/Tasks.pm on n2, `$check_task_user`:
44
+ * and PVE compares them exactly. /usr/share/perl5/PVE/API2/Tasks.pm on node-b, `$check_task_user`:
45
45
  * `return $user eq $fulltoken || $user eq $task->{user};`, above it the comment "token only sees
46
46
  * token tasks, user sees user + token tasks". Token B of the same user matches neither branch.
47
47
  * So the fallback is the schema's other clause for `GET /nodes/{node}/tasks/{upid}/status`: "The
@@ -128,8 +128,8 @@ const destroyPath = (props: CephFsProps) => {
128
128
  /**
129
129
  * Wait for a forked PVE task, and fail loudly rather than quietly.
130
130
  *
131
- * ⚠️ THE UPID IS PERCENT-ENCODED BECAUSE IT IS FULL OF COLONS — `UPID:n2:00396D3A:…:root@pam:`
132
- * (MEASURED shape, from `GET /nodes/n2/tasks`) is ONE path segment, not seven. Encoding is what
131
+ * ⚠️ THE UPID IS PERCENT-ENCODED BECAUSE IT IS FULL OF COLONS — `UPID:node-b:00396D3A:…:root@pam:`
132
+ * (MEASURED shape, from `GET /nodes/node-b/tasks`) is ONE path segment, not seven. Encoding is what
133
133
  * guarantees it arrives as one; PVE's router decodes each segment before matching. REASONED from
134
134
  * the URI handling rather than measured — no task of this provider's has been polled yet.
135
135
  */
@@ -178,8 +178,8 @@ const settle = (target: PveTarget, node: string, upid: string, what: string) =>
178
178
  *
179
179
  * ⚠️ A CREATE NEEDS A RUNNING *AND* A STANDBY MDS, and refuses BEFORE it forks -- "no running
180
180
  * Metadata Server (MDS) found!" / "no standby Metadata Server (MDS) found!" are synchronous, so
181
- * they surface as a failed POST rather than as a silent worker. TB4 has three (MEASURED: n3
182
- * `up:active` for cephfs-tb4, n2 and n4 `up:standby`), so this is a note for a smaller cluster.
181
+ * they surface as a failed POST rather than as a silent worker. C1 has three (MEASURED: node-c
182
+ * `up:active` for cephfs-c1, node-b and node-d `up:standby`), so this is a note for a smaller cluster.
183
183
  * ⚠️ BOTH WRITES CHECK `Sys.Modify` ON `/` -- the root, with the breadth metric-server.ts warns
184
184
  * about: granting it buys datacenter options and every other cluster-wide config write too.
185
185
  */