@homeflare/alchemy 0.14.0 → 0.15.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 (90) hide show
  1. package/dist/openbao/index.d.ts +10 -3
  2. package/dist/openbao/index.d.ts.map +1 -1
  3. package/dist/proxmox/acl.d.ts.map +1 -1
  4. package/dist/proxmox/backup-job.d.ts.map +1 -1
  5. package/dist/proxmox/constraint-guard.d.ts +28 -0
  6. package/dist/proxmox/constraint-guard.d.ts.map +1 -0
  7. package/dist/proxmox/constraints.d.ts +71 -0
  8. package/dist/proxmox/constraints.d.ts.map +1 -0
  9. package/dist/proxmox/firewall-alias.d.ts.map +1 -1
  10. package/dist/proxmox/generated/constraints/index.d.ts +19 -0
  11. package/dist/proxmox/generated/constraints/index.d.ts.map +1 -0
  12. package/dist/proxmox/generated/constraints/pbs-config.d.ts +18 -0
  13. package/dist/proxmox/generated/constraints/pbs-config.d.ts.map +1 -0
  14. package/dist/proxmox/generated/constraints/pve-access.d.ts +18 -0
  15. package/dist/proxmox/generated/constraints/pve-access.d.ts.map +1 -0
  16. package/dist/proxmox/generated/constraints/pve-cluster.d.ts +18 -0
  17. package/dist/proxmox/generated/constraints/pve-cluster.d.ts.map +1 -0
  18. package/dist/proxmox/generated/constraints/pve-pools.d.ts +18 -0
  19. package/dist/proxmox/generated/constraints/pve-pools.d.ts.map +1 -0
  20. package/dist/proxmox/generated/constraints/pve-storage.d.ts +18 -0
  21. package/dist/proxmox/generated/constraints/pve-storage.d.ts.map +1 -0
  22. package/dist/proxmox/group.d.ts.map +1 -1
  23. package/dist/proxmox/ha-resource.d.ts.map +1 -1
  24. package/dist/proxmox/ha-rule.d.ts.map +1 -1
  25. package/dist/proxmox/index.d.ts +6 -1
  26. package/dist/proxmox/index.d.ts.map +1 -1
  27. package/dist/proxmox/index.js +816 -257
  28. package/dist/proxmox/index.js.map +33 -24
  29. package/dist/proxmox/notification-matcher.d.ts.map +1 -1
  30. package/dist/proxmox/pbs-datastore-endpoint.d.ts +22 -0
  31. package/dist/proxmox/pbs-datastore-endpoint.d.ts.map +1 -0
  32. package/dist/proxmox/pbs-datastore.d.ts.map +1 -1
  33. package/dist/proxmox/pbs-notification-matcher.d.ts.map +1 -1
  34. package/dist/proxmox/pbs-prune-job.d.ts.map +1 -1
  35. package/dist/proxmox/pbs-sync-job-form.d.ts.map +1 -1
  36. package/dist/proxmox/pbs-verify-job.d.ts.map +1 -1
  37. package/dist/proxmox/pool.d.ts.map +1 -1
  38. package/dist/proxmox/provision-baseline.d.ts +30 -5
  39. package/dist/proxmox/provision-baseline.d.ts.map +1 -1
  40. package/dist/proxmox/replication-job.d.ts.map +1 -1
  41. package/dist/proxmox/resource-spec.d.ts +120 -0
  42. package/dist/proxmox/resource-spec.d.ts.map +1 -0
  43. package/dist/proxmox/resource.d.ts +10 -91
  44. package/dist/proxmox/resource.d.ts.map +1 -1
  45. package/dist/proxmox/role.d.ts.map +1 -1
  46. package/dist/proxmox/sdn-vnet.d.ts.map +1 -1
  47. package/dist/proxmox/sdn-zone.d.ts.map +1 -1
  48. package/dist/proxmox/storage.d.ts.map +1 -1
  49. package/dist/proxmox/user.d.ts.map +1 -1
  50. package/docs/provision-baseline.md +34 -7
  51. package/docs/proxmox-lxc.md +40 -0
  52. package/package.json +1 -1
  53. package/src/openbao/index-types.test.ts +87 -0
  54. package/src/openbao/index.ts +15 -3
  55. package/src/proxmox/acl.ts +2 -0
  56. package/src/proxmox/backup-job.ts +2 -0
  57. package/src/proxmox/constraint-guard.ts +53 -0
  58. package/src/proxmox/constraints.test.ts +260 -0
  59. package/src/proxmox/constraints.ts +135 -0
  60. package/src/proxmox/firewall-alias.ts +5 -0
  61. package/src/proxmox/generated/constraints/index.ts +31 -0
  62. package/src/proxmox/generated/constraints/pbs-config.ts +141 -0
  63. package/src/proxmox/generated/constraints/pve-access.ts +57 -0
  64. package/src/proxmox/generated/constraints/pve-cluster.ts +174 -0
  65. package/src/proxmox/generated/constraints/pve-pools.ts +26 -0
  66. package/src/proxmox/generated/constraints/pve-storage.ts +75 -0
  67. package/src/proxmox/group.ts +2 -0
  68. package/src/proxmox/ha-resource.ts +5 -0
  69. package/src/proxmox/ha-rule.ts +2 -0
  70. package/src/proxmox/index.ts +12 -0
  71. package/src/proxmox/lxc-interior.test.ts +61 -0
  72. package/src/proxmox/notification-matcher.ts +5 -0
  73. package/src/proxmox/pbs-datastore-endpoint.ts +29 -0
  74. package/src/proxmox/pbs-datastore.ts +8 -1
  75. package/src/proxmox/pbs-notification-matcher.ts +5 -0
  76. package/src/proxmox/pbs-prune-job.ts +2 -0
  77. package/src/proxmox/pbs-sync-job-form.ts +2 -0
  78. package/src/proxmox/pbs-verify-job.ts +2 -0
  79. package/src/proxmox/pool.ts +2 -0
  80. package/src/proxmox/provision-baseline.test.ts +38 -0
  81. package/src/proxmox/provision-baseline.ts +73 -14
  82. package/src/proxmox/provision-bootstrap.test.ts +51 -0
  83. package/src/proxmox/replication-job.ts +5 -0
  84. package/src/proxmox/resource-spec.ts +117 -0
  85. package/src/proxmox/resource.ts +50 -91
  86. package/src/proxmox/role.ts +2 -0
  87. package/src/proxmox/sdn-vnet.ts +2 -0
  88. package/src/proxmox/sdn-zone.ts +2 -0
  89. package/src/proxmox/storage.ts +2 -0
  90. package/src/proxmox/user.ts +2 -0
@@ -2,7 +2,7 @@
2
2
  import { Resource } from "alchemy";
3
3
  import { isResolved as isResolved2 } from "alchemy/Diff";
4
4
  import * as Provider from "alchemy/Provider";
5
- import * as Effect6 from "effect/Effect";
5
+ import * as Effect7 from "effect/Effect";
6
6
 
7
7
  // src/proxmox/client.ts
8
8
  import * as Effect4 from "effect/Effect";
@@ -223,20 +223,508 @@ var pve = (target, role, method, path, form) => Effect4.gen(function* () {
223
223
 
224
224
  // src/proxmox/resource.ts
225
225
  import { isResolved } from "alchemy/Diff";
226
+ import * as Effect6 from "effect/Effect";
227
+
228
+ // src/proxmox/constraint-guard.ts
226
229
  import * as Effect5 from "effect/Effect";
227
230
 
231
+ // src/proxmox/constraints.ts
232
+ var lengthOf = (value) => [...value].length;
233
+ var checkValue = (name, value, rule) => {
234
+ const out = [];
235
+ if (rule.maxLength !== undefined && lengthOf(value) > rule.maxLength) {
236
+ out.push(`${name}: at most ${rule.maxLength} characters`);
237
+ }
238
+ if (rule.minLength !== undefined && lengthOf(value) < rule.minLength) {
239
+ out.push(`${name}: at least ${rule.minLength} characters`);
240
+ }
241
+ if (rule.enum !== undefined && !rule.enum.includes(value)) {
242
+ out.push(`${name}: must be one of ${rule.enum.join(", ")}`);
243
+ }
244
+ if (rule.pattern !== undefined && !new RegExp(rule.pattern).test(value)) {
245
+ out.push(`${name}: must match ${rule.patternSource ?? rule.pattern}`);
246
+ }
247
+ const numeric = Number(value);
248
+ const bounded = rule.minimum !== undefined || rule.maximum !== undefined;
249
+ if (bounded && value !== "" && Number.isFinite(numeric)) {
250
+ if (rule.minimum !== undefined && numeric < rule.minimum) {
251
+ out.push(`${name}: at least ${rule.minimum}`);
252
+ }
253
+ if (rule.maximum !== undefined && numeric > rule.maximum) {
254
+ out.push(`${name}: at most ${rule.maximum}`);
255
+ }
256
+ }
257
+ return out;
258
+ };
259
+ var violations = (table, form, options = {}) => {
260
+ const out = [];
261
+ for (const [name, rule] of Object.entries(table)) {
262
+ const value = form[name];
263
+ if (value === undefined) {
264
+ if (options.presence === true && rule.required === true)
265
+ out.push(`${name}: required`);
266
+ continue;
267
+ }
268
+ for (const item of typeof value === "string" ? [value] : value) {
269
+ out.push(...checkValue(name, item, rule));
270
+ }
271
+ }
272
+ return out;
273
+ };
274
+ var refusal = (endpoint, found) => `${endpoint} would be rejected by the vendor:
275
+ - ${found.join(`
276
+ - `)}
277
+ ` + "These are the vendor's own limits, read from its published schema " + "(packages/alchemy/src/proxmox/generated/constraints). Fix the declaration.";
278
+
279
+ // src/proxmox/generated/constraints/pve-access.ts
280
+ var PVE_ACCESS_CONSTRAINTS = {
281
+ "pve:POST /access/groups": {
282
+ groupid: { format: "pve-groupid", required: true, type: "string" }
283
+ },
284
+ "pve:POST /access/roles": {
285
+ privs: { format: "pve-priv-list", type: "string" },
286
+ roleid: { format: "pve-roleid", required: true, type: "string" }
287
+ },
288
+ "pve:POST /access/users": {
289
+ comment: { maxLength: 2048, type: "string" },
290
+ email: { format: "email-opt", maxLength: 254, type: "string" },
291
+ expire: { minimum: 0, type: "integer" },
292
+ firstname: { maxLength: 1024, type: "string" },
293
+ groups: { format: "pve-groupid-list", type: "string" },
294
+ keys: { pattern: "[0-9a-zA-Z!=]{0,4096}", patternSource: "[0-9a-zA-Z!=]{0,4096}", type: "string" },
295
+ lastname: { maxLength: 1024, type: "string" },
296
+ password: { maxLength: 64, minLength: 8, type: "string" },
297
+ userid: { format: "pve-userid", maxLength: 64, required: true, type: "string" }
298
+ },
299
+ "pve:PUT /access/acl": {
300
+ groups: { format: "pve-groupid-list", type: "string" },
301
+ path: { required: true, type: "string" },
302
+ roles: { format: "pve-roleid-list", required: true, type: "string" },
303
+ tokens: { format: "pve-tokenid-list", type: "string" },
304
+ users: { format: "pve-userid-list", type: "string" }
305
+ },
306
+ "pve:PUT /access/groups/{groupid}": {},
307
+ "pve:PUT /access/roles/{roleid}": {
308
+ privs: { format: "pve-priv-list", type: "string" }
309
+ },
310
+ "pve:PUT /access/users/{userid}": {
311
+ comment: { maxLength: 2048, type: "string" },
312
+ email: { format: "email-opt", maxLength: 254, type: "string" },
313
+ expire: { minimum: 0, type: "integer" },
314
+ firstname: { maxLength: 1024, type: "string" },
315
+ groups: { format: "pve-groupid-list", type: "string" },
316
+ keys: { pattern: "[0-9a-zA-Z!=]{0,4096}", patternSource: "[0-9a-zA-Z!=]{0,4096}", type: "string" },
317
+ lastname: { maxLength: 1024, type: "string" }
318
+ }
319
+ };
320
+
321
+ // src/proxmox/generated/constraints/pve-cluster.ts
322
+ var PVE_CLUSTER_CONSTRAINTS = {
323
+ "pve:POST /cluster/backup": {
324
+ bwlimit: { default: "0", minimum: 0, type: "integer" },
325
+ comment: { maxLength: 512, type: "string" },
326
+ compress: { default: "0", enum: ["0", "1", "gzip", "lzo", "zstd"], type: "string" },
327
+ dow: { default: "mon,tue,wed,thu,fri,sat,sun", format: "pve-day-of-week-list", type: "string" },
328
+ exclude: { format: "pve-vmid-list", type: "string" },
329
+ fleecing: { format: "backup-fleecing", type: "string" },
330
+ id: { format: "pve-configid", type: "string" },
331
+ ionice: { default: "7", maximum: 8, minimum: 0, type: "integer" },
332
+ lockwait: { default: "180", minimum: 0, type: "integer" },
333
+ mailnotification: { default: "always", enum: ["always", "failure"], type: "string" },
334
+ mailto: { format: "email-or-username-list", type: "string" },
335
+ mode: { default: "snapshot", enum: ["snapshot", "suspend", "stop"], type: "string" },
336
+ node: { format: "pve-node", type: "string" },
337
+ "notes-template": { maxLength: 1024, type: "string" },
338
+ "notification-mode": { default: "auto", enum: ["auto", "legacy-sendmail", "notification-system"], type: "string" },
339
+ "pbs-change-detection-mode": { enum: ["legacy", "data", "metadata"], type: "string" },
340
+ performance: { format: "backup-performance", type: "string" },
341
+ "prune-backups": { default: "keep-all=1", format: "prune-backups", type: "string" },
342
+ schedule: { format: "pve-calendar-event", maxLength: 128, type: "string" },
343
+ starttime: { pattern: "\\d{1,2}:\\d{1,2}", patternSource: "\\d{1,2}:\\d{1,2}", type: "string" },
344
+ stopwait: { default: "10", minimum: 0, type: "integer" },
345
+ storage: { format: "pve-storage-id", type: "string" },
346
+ vmid: { format: "pve-vmid-list", type: "string" }
347
+ },
348
+ "pve:POST /cluster/firewall/aliases": {
349
+ cidr: { format: "IPorCIDR", required: true, type: "string" },
350
+ comment: { format: "pve-fw-comment-spec", type: "string" },
351
+ name: { maxLength: 64, minLength: 2, pattern: "[A-Za-z][A-Za-z0-9\\-\\_]+", patternSource: "[A-Za-z][A-Za-z0-9\\-\\_]+", required: true, type: "string" }
352
+ },
353
+ "pve:POST /cluster/ha/resources": {
354
+ comment: { maxLength: 4096, type: "string" },
355
+ group: { format: "pve-configid", type: "string" },
356
+ max_relocate: { default: "1", minimum: 0, type: "integer" },
357
+ max_restart: { default: "1", minimum: 0, type: "integer" },
358
+ sid: { format: "pve-ha-resource-or-vm-id", required: true, type: "string" },
359
+ state: { default: "started", enum: ["started", "stopped", "enabled", "disabled", "ignored"], type: "string" },
360
+ type: { enum: ["ct", "vm"], type: "string" }
361
+ },
362
+ "pve:POST /cluster/ha/rules": {},
363
+ "pve:POST /cluster/notifications/matchers": {
364
+ mode: { default: "all", enum: ["all", "any"], type: "string" },
365
+ name: { format: "pve-configid", required: true, type: "string" }
366
+ },
367
+ "pve:POST /cluster/replication": {
368
+ comment: { maxLength: 4096, type: "string" },
369
+ id: { format: "pve-replication-job-id", pattern: "[1-9][0-9]{2,8}-\\d{1,9}", patternSource: "[1-9][0-9]{2,8}-\\d{1,9}", required: true, type: "string" },
370
+ rate: { minimum: 1, type: "number" },
371
+ remove_job: { enum: ["local", "full"], type: "string" },
372
+ schedule: { default: "*/15", format: "pve-calendar-event", maxLength: 128, type: "string" },
373
+ source: { format: "pve-node", type: "string" },
374
+ target: { format: "pve-node", required: true, type: "string" },
375
+ type: { enum: ["local"], required: true, type: "string" }
376
+ },
377
+ "pve:POST /cluster/sdn/vnets": {
378
+ alias: { maxLength: 256, patternSource: "(?^i:[\\(\\)-_.\\w\\d\\s]{0,256})", type: "string" },
379
+ tag: { maximum: 16777215, minimum: 1, type: "integer" },
380
+ type: { enum: ["vnet"], type: "string" },
381
+ vnet: { maxLength: 8, minLength: 2, pattern: "[a-zA-Z][a-zA-Z0-9]*[a-zA-Z0-9]", patternSource: "[a-zA-Z][a-zA-Z0-9]*[a-zA-Z0-9]", required: true, type: "string" },
382
+ zone: { required: true, type: "string" }
383
+ },
384
+ "pve:POST /cluster/sdn/zones": {
385
+ dhcp: { enum: ["dnsmasq"], type: "string" },
386
+ dnszone: { format: "dns-name", type: "string" },
387
+ exitnodes: { format: "pve-node-list", type: "string" },
388
+ "exitnodes-primary": { format: "pve-node", type: "string" },
389
+ fabric: { format: "pve-sdn-fabric-id", type: "string" },
390
+ mac: { format: "mac-addr", type: "string" },
391
+ nodes: { format: "pve-node-list", type: "string" },
392
+ peers: { format: "ip-list", type: "string" },
393
+ "rt-import": { format: "pve-sdn-bgp-rt-list", type: "string" },
394
+ tag: { minimum: 0, type: "integer" },
395
+ type: { enum: ["evpn", "faucet", "qinq", "simple", "vlan", "vxlan"], format: "pve-configid", required: true, type: "string" },
396
+ "vlan-protocol": { default: "802.1q", enum: ["802.1q", "802.1ad"], type: "string" },
397
+ "vrf-vxlan": { maximum: 16777215, minimum: 1, type: "integer" },
398
+ "vxlan-port": { default: "4789", maximum: 65536, minimum: 1, type: "integer" },
399
+ zone: { maxLength: 8, minLength: 2, pattern: "[a-zA-Z][a-zA-Z0-9]*[a-zA-Z0-9]", patternSource: "[a-zA-Z][a-zA-Z0-9]*[a-zA-Z0-9]", required: true, type: "string" }
400
+ },
401
+ "pve:PUT /cluster/backup/{id}": {
402
+ bwlimit: { default: "0", minimum: 0, type: "integer" },
403
+ comment: { maxLength: 512, type: "string" },
404
+ compress: { default: "0", enum: ["0", "1", "gzip", "lzo", "zstd"], type: "string" },
405
+ delete: { format: "pve-configid-list", type: "string" },
406
+ dow: { format: "pve-day-of-week-list", type: "string" },
407
+ exclude: { format: "pve-vmid-list", type: "string" },
408
+ fleecing: { format: "backup-fleecing", type: "string" },
409
+ ionice: { default: "7", maximum: 8, minimum: 0, type: "integer" },
410
+ lockwait: { default: "180", minimum: 0, type: "integer" },
411
+ mailnotification: { default: "always", enum: ["always", "failure"], type: "string" },
412
+ mailto: { format: "email-or-username-list", type: "string" },
413
+ mode: { default: "snapshot", enum: ["snapshot", "suspend", "stop"], type: "string" },
414
+ node: { format: "pve-node", type: "string" },
415
+ "notes-template": { maxLength: 1024, type: "string" },
416
+ "notification-mode": { default: "auto", enum: ["auto", "legacy-sendmail", "notification-system"], type: "string" },
417
+ "pbs-change-detection-mode": { enum: ["legacy", "data", "metadata"], type: "string" },
418
+ performance: { format: "backup-performance", type: "string" },
419
+ "prune-backups": { default: "keep-all=1", format: "prune-backups", type: "string" },
420
+ schedule: { format: "pve-calendar-event", maxLength: 128, type: "string" },
421
+ starttime: { pattern: "\\d{1,2}:\\d{1,2}", patternSource: "\\d{1,2}:\\d{1,2}", type: "string" },
422
+ stopwait: { default: "10", minimum: 0, type: "integer" },
423
+ storage: { format: "pve-storage-id", type: "string" },
424
+ vmid: { format: "pve-vmid-list", type: "string" }
425
+ },
426
+ "pve:PUT /cluster/firewall/aliases/{name}": {
427
+ cidr: { format: "IPorCIDR", required: true, type: "string" },
428
+ comment: { format: "pve-fw-comment-spec", type: "string" },
429
+ digest: { maxLength: 64, type: "string" },
430
+ rename: { maxLength: 64, minLength: 2, pattern: "[A-Za-z][A-Za-z0-9\\-\\_]+", patternSource: "[A-Za-z][A-Za-z0-9\\-\\_]+", type: "string" }
431
+ },
432
+ "pve:PUT /cluster/ha/resources/{sid}": {
433
+ comment: { maxLength: 4096, type: "string" },
434
+ delete: { format: "pve-configid-list", maxLength: 4096, type: "string" },
435
+ digest: { maxLength: 64, type: "string" },
436
+ group: { format: "pve-configid", type: "string" },
437
+ max_relocate: { default: "1", minimum: 0, type: "integer" },
438
+ max_restart: { default: "1", minimum: 0, type: "integer" },
439
+ state: { default: "started", enum: ["started", "stopped", "enabled", "disabled", "ignored"], type: "string" }
440
+ },
441
+ "pve:PUT /cluster/ha/rules/{rule}": {},
442
+ "pve:PUT /cluster/notifications/matchers/{name}": {
443
+ digest: { maxLength: 64, type: "string" },
444
+ mode: { default: "all", enum: ["all", "any"], type: "string" }
445
+ },
446
+ "pve:PUT /cluster/replication/{id}": {
447
+ comment: { maxLength: 4096, type: "string" },
448
+ delete: { format: "pve-configid-list", maxLength: 4096, type: "string" },
449
+ digest: { maxLength: 64, type: "string" },
450
+ rate: { minimum: 1, type: "number" },
451
+ remove_job: { enum: ["local", "full"], type: "string" },
452
+ schedule: { default: "*/15", format: "pve-calendar-event", maxLength: 128, type: "string" },
453
+ source: { format: "pve-node", type: "string" }
454
+ },
455
+ "pve:PUT /cluster/sdn/vnets/{vnet}": {
456
+ alias: { maxLength: 256, patternSource: "(?^i:[\\(\\)-_.\\w\\d\\s]{0,256})", type: "string" },
457
+ delete: { format: "pve-configid-list", maxLength: 4096, type: "string" },
458
+ digest: { maxLength: 64, type: "string" },
459
+ tag: { maximum: 16777215, minimum: 1, type: "integer" }
460
+ },
461
+ "pve:PUT /cluster/sdn/zones/{zone}": {
462
+ delete: { format: "pve-configid-list", maxLength: 4096, type: "string" },
463
+ dhcp: { enum: ["dnsmasq"], type: "string" },
464
+ digest: { maxLength: 64, type: "string" },
465
+ dnszone: { format: "dns-name", type: "string" },
466
+ exitnodes: { format: "pve-node-list", type: "string" },
467
+ "exitnodes-primary": { format: "pve-node", type: "string" },
468
+ fabric: { format: "pve-sdn-fabric-id", type: "string" },
469
+ mac: { format: "mac-addr", type: "string" },
470
+ nodes: { format: "pve-node-list", type: "string" },
471
+ peers: { format: "ip-list", type: "string" },
472
+ "rt-import": { format: "pve-sdn-bgp-rt-list", type: "string" },
473
+ tag: { minimum: 0, type: "integer" },
474
+ "vlan-protocol": { default: "802.1q", enum: ["802.1q", "802.1ad"], type: "string" },
475
+ "vrf-vxlan": { maximum: 16777215, minimum: 1, type: "integer" },
476
+ "vxlan-port": { default: "4789", maximum: 65536, minimum: 1, type: "integer" }
477
+ }
478
+ };
479
+
480
+ // src/proxmox/generated/constraints/pve-pools.ts
481
+ var PVE_POOLS_CONSTRAINTS = {
482
+ "pve:POST /pools": {
483
+ poolid: { format: "pve-poolid", required: true, type: "string" }
484
+ },
485
+ "pve:PUT /pools/{poolid}": {
486
+ storage: { format: "pve-storage-id-list", type: "string" },
487
+ vms: { format: "pve-vmid-list", type: "string" }
488
+ }
489
+ };
490
+
491
+ // src/proxmox/generated/constraints/pve-storage.ts
492
+ var PVE_STORAGE_CONSTRAINTS = {
493
+ "pve:POST /storage": {
494
+ base: { format: "pve-volume-id", type: "string" },
495
+ blocksize: { format: "pve-storage-zfs-blocksize", type: "string" },
496
+ content: { format: "pve-storage-content-list", type: "string" },
497
+ "content-dirs": { format: "pve-dir-override-list", type: "string" },
498
+ domain: { maxLength: 256, type: "string" },
499
+ export: { format: "pve-storage-path", type: "string" },
500
+ fingerprint: { pattern: "([A-Fa-f0-9]{2}:){31}[A-Fa-f0-9]{2}", patternSource: "([A-Fa-f0-9]{2}:){31}[A-Fa-f0-9]{2}", type: "string" },
501
+ format: { enum: ["raw", "qcow2", "subvol", "vmdk"], type: "string" },
502
+ "fs-name": { format: "pve-configid", type: "string" },
503
+ "max-protected-backups": { default: "Unlimited for users with Datastore.Allocate privilege, 5 for other users", minimum: -1, type: "integer" },
504
+ monhost: { format: "pve-storage-portal-dns-list", type: "string" },
505
+ mountpoint: { format: "pve-storage-path", type: "string" },
506
+ nodes: { format: "pve-node-list", type: "string" },
507
+ options: { format: "pve-storage-options", type: "string" },
508
+ password: { maxLength: 256, type: "string" },
509
+ path: { format: "pve-storage-path", type: "string" },
510
+ port: { maximum: 65535, minimum: 1, type: "integer" },
511
+ portal: { format: "pve-storage-portal-dns", type: "string" },
512
+ preallocation: { default: "metadata", enum: ["off", "metadata", "falloc", "full"], type: "string" },
513
+ "prune-backups": { format: "prune-backups", type: "string" },
514
+ "saferemove-stepsize": { default: "32", enum: ["1", "2", "4", "8", "16", "32"], type: "integer" },
515
+ server: { format: "pve-storage-server", type: "string" },
516
+ smbversion: { default: "default", enum: ["default", "2.0", "2.1", "3", "3.0", "3.11"], type: "string" },
517
+ storage: { format: "pve-storage-id", required: true, type: "string" },
518
+ subdir: { format: "pve-storage-path", type: "string" },
519
+ thinpool: { format: "pve-storage-vgname", type: "string" },
520
+ type: { enum: ["btrfs", "cephfs", "cifs", "dir", "esxi", "iscsi", "iscsidirect", "lvm", "lvmthin", "nfs", "pbs", "rbd", "zfs", "zfspool"], required: true, type: "string" },
521
+ vgname: { format: "pve-storage-vgname", type: "string" },
522
+ "zfs-base-path": { format: "pve-storage-path", type: "string" }
523
+ },
524
+ "pve:PUT /storage/{storage}": {
525
+ blocksize: { format: "pve-storage-zfs-blocksize", type: "string" },
526
+ content: { format: "pve-storage-content-list", type: "string" },
527
+ "content-dirs": { format: "pve-dir-override-list", type: "string" },
528
+ delete: { format: "pve-configid-list", maxLength: 4096, type: "string" },
529
+ digest: { maxLength: 64, type: "string" },
530
+ domain: { maxLength: 256, type: "string" },
531
+ fingerprint: { pattern: "([A-Fa-f0-9]{2}:){31}[A-Fa-f0-9]{2}", patternSource: "([A-Fa-f0-9]{2}:){31}[A-Fa-f0-9]{2}", type: "string" },
532
+ format: { enum: ["raw", "qcow2", "subvol", "vmdk"], type: "string" },
533
+ "fs-name": { format: "pve-configid", type: "string" },
534
+ "max-protected-backups": { default: "Unlimited for users with Datastore.Allocate privilege, 5 for other users", minimum: -1, type: "integer" },
535
+ monhost: { format: "pve-storage-portal-dns-list", type: "string" },
536
+ mountpoint: { format: "pve-storage-path", type: "string" },
537
+ nodes: { format: "pve-node-list", type: "string" },
538
+ options: { format: "pve-storage-options", type: "string" },
539
+ password: { maxLength: 256, type: "string" },
540
+ port: { maximum: 65535, minimum: 1, type: "integer" },
541
+ preallocation: { default: "metadata", enum: ["off", "metadata", "falloc", "full"], type: "string" },
542
+ "prune-backups": { format: "prune-backups", type: "string" },
543
+ "saferemove-stepsize": { default: "32", enum: ["1", "2", "4", "8", "16", "32"], type: "integer" },
544
+ server: { format: "pve-storage-server", type: "string" },
545
+ smbversion: { default: "default", enum: ["default", "2.0", "2.1", "3", "3.0", "3.11"], type: "string" },
546
+ subdir: { format: "pve-storage-path", type: "string" },
547
+ "zfs-base-path": { format: "pve-storage-path", type: "string" }
548
+ }
549
+ };
550
+
551
+ // src/proxmox/generated/constraints/pbs-config.ts
552
+ var PBS_CONFIG_CONSTRAINTS = {
553
+ "pbs:POST /config/datastore": {
554
+ "backing-device": { pattern: "^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$", patternSource: "/^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/", type: "string" },
555
+ comment: { maxLength: 128, pattern: "^[^\\x00-\\x1f\\x7f]*$", patternSource: "/^[[:^cntrl:]]*$/", type: "string" },
556
+ "keep-daily": { minimum: 1, type: "integer" },
557
+ "keep-hourly": { minimum: 1, type: "integer" },
558
+ "keep-last": { minimum: 1, type: "integer" },
559
+ "keep-monthly": { minimum: 1, type: "integer" },
560
+ "keep-weekly": { minimum: 1, type: "integer" },
561
+ "keep-yearly": { minimum: 1, type: "integer" },
562
+ name: { maxLength: 32, minLength: 3, pattern: "^(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)$", patternSource: "/^(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)$/", required: true, type: "string" },
563
+ "notification-mode": { default: "notification-system", enum: ["legacy-sendmail", "notification-system"], type: "string" },
564
+ "notify-user": { maxLength: 64, minLength: 3, pattern: "^(?:[^\\s:/\\x00-\\x1f\\x7f]+)@(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)$", patternSource: "/^(?:[^\\s:/[:cntrl:]]+)@(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)$/", type: "string" },
565
+ path: { maxLength: 4096, minLength: 1, required: true, type: "string" }
566
+ },
567
+ "pbs:POST /config/notifications/matchers": {
568
+ comment: { maxLength: 128, pattern: "^[^\\x00-\\x1f\\x7f]*$", patternSource: "/^[[:^cntrl:]]*$/", type: "string" },
569
+ mode: { default: "all", enum: ["all", "any"], type: "string" },
570
+ name: { maxLength: 32, minLength: 2, pattern: "^(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)$", patternSource: "/^(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)$/", required: true, type: "string" },
571
+ origin: { enum: ["user-created", "builtin", "modified-builtin"], type: "string" }
572
+ },
573
+ "pbs:POST /config/prune": {
574
+ comment: { maxLength: 128, pattern: "^[^\\x00-\\x1f\\x7f]*$", patternSource: "/^[[:^cntrl:]]*$/", type: "string" },
575
+ id: { maxLength: 32, minLength: 3, pattern: "^(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)$", patternSource: "/^(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)$/", required: true, type: "string" },
576
+ "keep-daily": { minimum: 1, type: "integer" },
577
+ "keep-hourly": { minimum: 1, type: "integer" },
578
+ "keep-last": { minimum: 1, type: "integer" },
579
+ "keep-monthly": { minimum: 1, type: "integer" },
580
+ "keep-weekly": { minimum: 1, type: "integer" },
581
+ "keep-yearly": { minimum: 1, type: "integer" },
582
+ "max-depth": { maximum: 7, minimum: 0, type: "integer" },
583
+ ns: { maxLength: 256, pattern: "^(?:(?:(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)/){0,7}(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*))?$", patternSource: "/^(?:(?:(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)/){0,7}(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*))?$/", type: "string" },
584
+ schedule: { required: true, type: "string" },
585
+ store: { maxLength: 32, minLength: 3, pattern: "^(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)$", patternSource: "/^(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)$/", required: true, type: "string" }
586
+ },
587
+ "pbs:POST /config/sync": {
588
+ "active-encryption-key": { maxLength: 32, minLength: 3, pattern: "^(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)$", patternSource: "/^(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)$/", type: "string" },
589
+ "burst-in": { maxLength: 64, minLength: 1, type: "string" },
590
+ "burst-out": { maxLength: 64, minLength: 1, type: "string" },
591
+ comment: { maxLength: 128, pattern: "^[^\\x00-\\x1f\\x7f]*$", patternSource: "/^[[:^cntrl:]]*$/", type: "string" },
592
+ id: { maxLength: 32, minLength: 3, pattern: "^(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)$", patternSource: "/^(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)$/", required: true, type: "string" },
593
+ "max-depth": { maximum: 7, minimum: 0, type: "integer" },
594
+ ns: { maxLength: 256, pattern: "^(?:(?:(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)/){0,7}(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*))?$", patternSource: "/^(?:(?:(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)/){0,7}(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*))?$/", type: "string" },
595
+ owner: { maxLength: 64, minLength: 3, pattern: "^(?:(?:[^\\s:/\\x00-\\x1f\\x7f]+)@(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)|(?:[^\\s:/\\x00-\\x1f\\x7f]+)@(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)!(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*))$", patternSource: "/^(?:(?:[^\\s:/[:cntrl:]]+)@(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)|(?:[^\\s:/[:cntrl:]]+)@(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)!(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*))$/", type: "string" },
596
+ "rate-in": { maxLength: 64, minLength: 1, type: "string" },
597
+ "rate-out": { maxLength: 64, minLength: 1, type: "string" },
598
+ remote: { maxLength: 32, minLength: 3, pattern: "^(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)$", patternSource: "/^(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)$/", type: "string" },
599
+ "remote-ns": { maxLength: 256, pattern: "^(?:(?:(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)/){0,7}(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*))?$", patternSource: "/^(?:(?:(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)/){0,7}(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*))?$/", type: "string" },
600
+ "remote-store": { maxLength: 32, minLength: 3, pattern: "^(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)$", patternSource: "/^(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)$/", required: true, type: "string" },
601
+ store: { maxLength: 32, minLength: 3, pattern: "^(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)$", patternSource: "/^(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)$/", required: true, type: "string" },
602
+ "sync-direction": { default: "pull", enum: ["pull", "push"], type: "string" },
603
+ "transfer-last": { minimum: 1, type: "integer" },
604
+ "worker-threads": { default: "1", maximum: 32, minimum: 1, type: "integer" }
605
+ },
606
+ "pbs:POST /config/verify": {
607
+ comment: { maxLength: 128, pattern: "^[^\\x00-\\x1f\\x7f]*$", patternSource: "/^[[:^cntrl:]]*$/", type: "string" },
608
+ id: { maxLength: 32, minLength: 3, pattern: "^(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)$", patternSource: "/^(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)$/", required: true, type: "string" },
609
+ "max-depth": { default: "7", maximum: 7, minimum: 0, type: "integer" },
610
+ ns: { maxLength: 256, pattern: "^(?:(?:(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)/){0,7}(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*))?$", patternSource: "/^(?:(?:(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)/){0,7}(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*))?$/", type: "string" },
611
+ "outdated-after": { minimum: 0, type: "integer" },
612
+ "read-threads": { default: "1", maximum: 32, minimum: 1, type: "integer" },
613
+ store: { maxLength: 32, minLength: 3, pattern: "^(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)$", patternSource: "/^(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)$/", required: true, type: "string" },
614
+ "verify-threads": { default: "4", maximum: 32, minimum: 1, type: "integer" }
615
+ },
616
+ "pbs:PUT /config/datastore/{name}": {
617
+ comment: { maxLength: 128, pattern: "^[^\\x00-\\x1f\\x7f]*$", patternSource: "/^[[:^cntrl:]]*$/", type: "string" },
618
+ digest: { pattern: "^[a-f0-9]{64}$", patternSource: "/^[a-f0-9]{64}$/", type: "string" },
619
+ "keep-daily": { minimum: 1, type: "integer" },
620
+ "keep-hourly": { minimum: 1, type: "integer" },
621
+ "keep-last": { minimum: 1, type: "integer" },
622
+ "keep-monthly": { minimum: 1, type: "integer" },
623
+ "keep-weekly": { minimum: 1, type: "integer" },
624
+ "keep-yearly": { minimum: 1, type: "integer" },
625
+ "notification-mode": { default: "notification-system", enum: ["legacy-sendmail", "notification-system"], type: "string" },
626
+ "notify-user": { maxLength: 64, minLength: 3, pattern: "^(?:[^\\s:/\\x00-\\x1f\\x7f]+)@(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)$", patternSource: "/^(?:[^\\s:/[:cntrl:]]+)@(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)$/", type: "string" }
627
+ },
628
+ "pbs:PUT /config/notifications/matchers/{name}": {
629
+ comment: { maxLength: 128, pattern: "^[^\\x00-\\x1f\\x7f]*$", patternSource: "/^[[:^cntrl:]]*$/", type: "string" },
630
+ digest: { pattern: "^[a-f0-9]{64}$", patternSource: "/^[a-f0-9]{64}$/", type: "string" },
631
+ mode: { default: "all", enum: ["all", "any"], type: "string" }
632
+ },
633
+ "pbs:PUT /config/prune/{id}": {
634
+ comment: { maxLength: 128, pattern: "^[^\\x00-\\x1f\\x7f]*$", patternSource: "/^[[:^cntrl:]]*$/", type: "string" },
635
+ digest: { pattern: "^[a-f0-9]{64}$", patternSource: "/^[a-f0-9]{64}$/", type: "string" },
636
+ "keep-daily": { minimum: 1, type: "integer" },
637
+ "keep-hourly": { minimum: 1, type: "integer" },
638
+ "keep-last": { minimum: 1, type: "integer" },
639
+ "keep-monthly": { minimum: 1, type: "integer" },
640
+ "keep-weekly": { minimum: 1, type: "integer" },
641
+ "keep-yearly": { minimum: 1, type: "integer" },
642
+ "max-depth": { maximum: 7, minimum: 0, type: "integer" },
643
+ ns: { maxLength: 256, pattern: "^(?:(?:(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)/){0,7}(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*))?$", patternSource: "/^(?:(?:(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)/){0,7}(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*))?$/", type: "string" },
644
+ store: { maxLength: 32, minLength: 3, pattern: "^(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)$", patternSource: "/^(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)$/", type: "string" }
645
+ },
646
+ "pbs:PUT /config/sync/{id}": {
647
+ "active-encryption-key": { maxLength: 32, minLength: 3, pattern: "^(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)$", patternSource: "/^(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)$/", type: "string" },
648
+ "burst-in": { maxLength: 64, minLength: 1, type: "string" },
649
+ "burst-out": { maxLength: 64, minLength: 1, type: "string" },
650
+ comment: { maxLength: 128, pattern: "^[^\\x00-\\x1f\\x7f]*$", patternSource: "/^[[:^cntrl:]]*$/", type: "string" },
651
+ digest: { pattern: "^[a-f0-9]{64}$", patternSource: "/^[a-f0-9]{64}$/", type: "string" },
652
+ "max-depth": { maximum: 7, minimum: 0, type: "integer" },
653
+ ns: { maxLength: 256, pattern: "^(?:(?:(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)/){0,7}(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*))?$", patternSource: "/^(?:(?:(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)/){0,7}(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*))?$/", type: "string" },
654
+ owner: { maxLength: 64, minLength: 3, pattern: "^(?:(?:[^\\s:/\\x00-\\x1f\\x7f]+)@(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)|(?:[^\\s:/\\x00-\\x1f\\x7f]+)@(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)!(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*))$", patternSource: "/^(?:(?:[^\\s:/[:cntrl:]]+)@(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)|(?:[^\\s:/[:cntrl:]]+)@(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)!(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*))$/", type: "string" },
655
+ "rate-in": { maxLength: 64, minLength: 1, type: "string" },
656
+ "rate-out": { maxLength: 64, minLength: 1, type: "string" },
657
+ remote: { maxLength: 32, minLength: 3, pattern: "^(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)$", patternSource: "/^(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)$/", type: "string" },
658
+ "remote-ns": { maxLength: 256, pattern: "^(?:(?:(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)/){0,7}(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*))?$", patternSource: "/^(?:(?:(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)/){0,7}(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*))?$/", type: "string" },
659
+ "remote-store": { maxLength: 32, minLength: 3, pattern: "^(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)$", patternSource: "/^(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)$/", type: "string" },
660
+ store: { maxLength: 32, minLength: 3, pattern: "^(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)$", patternSource: "/^(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)$/", type: "string" },
661
+ "sync-direction": { default: "pull", enum: ["pull", "push"], type: "string" },
662
+ "transfer-last": { minimum: 1, type: "integer" },
663
+ "worker-threads": { default: "1", maximum: 32, minimum: 1, type: "integer" }
664
+ },
665
+ "pbs:PUT /config/verify/{id}": {
666
+ comment: { maxLength: 128, pattern: "^[^\\x00-\\x1f\\x7f]*$", patternSource: "/^[[:^cntrl:]]*$/", type: "string" },
667
+ digest: { pattern: "^[a-f0-9]{64}$", patternSource: "/^[a-f0-9]{64}$/", type: "string" },
668
+ "max-depth": { default: "7", maximum: 7, minimum: 0, type: "integer" },
669
+ ns: { maxLength: 256, pattern: "^(?:(?:(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)/){0,7}(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*))?$", patternSource: "/^(?:(?:(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)/){0,7}(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*))?$/", type: "string" },
670
+ "outdated-after": { minimum: 0, type: "integer" },
671
+ "read-threads": { default: "1", maximum: 32, minimum: 1, type: "integer" },
672
+ store: { maxLength: 32, minLength: 3, pattern: "^(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)$", patternSource: "/^(?:[A-Za-z0-9_][A-Za-z0-9._\\-]*)$/", type: "string" },
673
+ "verify-threads": { default: "4", maximum: 32, minimum: 1, type: "integer" }
674
+ }
675
+ };
676
+
677
+ // src/proxmox/generated/constraints/index.ts
678
+ var PROXMOX_CONSTRAINTS = {
679
+ ...PVE_ACCESS_CONSTRAINTS,
680
+ ...PVE_CLUSTER_CONSTRAINTS,
681
+ ...PVE_POOLS_CONSTRAINTS,
682
+ ...PVE_STORAGE_CONSTRAINTS,
683
+ ...PBS_CONFIG_CONSTRAINTS
684
+ };
685
+
686
+ // src/proxmox/constraint-guard.ts
687
+ var constraintsFor = (key) => {
688
+ const table = PROXMOX_CONSTRAINTS[key];
689
+ if (table === undefined) {
690
+ throw new Error(`no vendor constraint table for '${key}'. It is named in this package but not generated: ` + "run `bun codegen/constraints.ts` (see codegen/README.md).");
691
+ }
692
+ return table;
693
+ };
694
+ var formViolations = (key, form, presence) => violations(constraintsFor(key), form, { presence });
695
+ var guardForm = (key, form, presence) => Effect5.suspend(() => {
696
+ if (key === undefined)
697
+ return Effect5.void;
698
+ const found = formViolations(key, form, presence);
699
+ return found.length === 0 ? Effect5.void : Effect5.die(new Error(refusal(key, found)));
700
+ });
701
+
228
702
  // src/proxmox/update-guard.ts
229
703
  var formToSend = (matches, live, props, form) => matches(live, props) || Object.keys(form).length === 0 ? undefined : form;
230
704
 
231
705
  // src/proxmox/resource.ts
232
706
  var pveOperations = (spec) => {
233
- const read = (props) => pve(props.target, spec.readRole ?? "read", "GET", spec.path(props)).pipe(Effect5.map((data) => data == null ? undefined : spec.attributes(data, props)), Effect5.orElseSucceed(() => {
707
+ const guardForms = (props) => Effect6.gen(function* () {
708
+ const create = spec.endpoint?.create;
709
+ if (create !== undefined)
710
+ yield* guardForm(create, spec.createForm(props), true);
711
+ const update = spec.endpoint?.update;
712
+ if (update === undefined)
713
+ return;
714
+ const form = spec.updateForm?.(props);
715
+ if (form !== undefined)
716
+ yield* guardForm(update, form, false);
717
+ });
718
+ const read = (props) => pve(props.target, spec.readRole ?? "read", "GET", spec.path(props)).pipe(Effect6.map((data) => data == null ? undefined : spec.attributes(data, props)), Effect6.orElseSucceed(() => {
234
719
  return;
235
720
  }));
236
721
  return {
237
722
  read,
238
- diff: (news, output) => Effect5.gen(function* () {
239
- if (output === undefined || !isResolved(news))
723
+ diff: (news, output) => Effect6.gen(function* () {
724
+ if (!isResolved(news))
725
+ return;
726
+ yield* guardForms(news);
727
+ if (output === undefined)
240
728
  return;
241
729
  const live = yield* read(news);
242
730
  if (live === undefined)
@@ -245,7 +733,8 @@ var pveOperations = (spec) => {
245
733
  return { action: "noop" };
246
734
  return spec.updateForm === undefined ? { action: "replace" } : { action: "update" };
247
735
  }),
248
- reconcile: (news) => Effect5.gen(function* () {
736
+ reconcile: (news) => Effect6.gen(function* () {
737
+ yield* guardForms(news);
249
738
  const live = yield* read(news);
250
739
  if (live === undefined) {
251
740
  yield* pve(news.target, "provision", "POST", spec.collection(news), spec.createForm(news));
@@ -257,7 +746,7 @@ var pveOperations = (spec) => {
257
746
  }
258
747
  const after = yield* read(news);
259
748
  if (after === undefined) {
260
- return yield* Effect5.die(new Error(`${spec.path(news)}: the write returned no error but the object is still absent. ` + 'PVE wraps every answer in {"data":...} and can report success on a call that did ' + "nothing -- read back rather than trusting the status code."));
749
+ return yield* Effect6.die(new Error(`${spec.path(news)}: the write returned no error but the object is still absent. ` + 'PVE wraps every answer in {"data":...} and can report success on a call that did ' + "nothing -- read back rather than trusting the status code."));
261
750
  }
262
751
  return after;
263
752
  }),
@@ -267,7 +756,7 @@ var pveOperations = (spec) => {
267
756
  var pveHandlers = (spec) => {
268
757
  const ops = pveOperations(spec);
269
758
  return {
270
- list: () => Effect5.succeed([]),
759
+ list: () => Effect6.succeed([]),
271
760
  read: ({ olds }) => ops.read(olds),
272
761
  diff: ({ news, output }) => ops.diff(news, output),
273
762
  reconcile: ({ news }) => ops.reconcile(news),
@@ -309,29 +798,30 @@ var ops = pveOperations({
309
798
  readRole: "provision",
310
799
  collection: () => "access/acl",
311
800
  createForm: bind,
801
+ endpoint: { create: "pve:PUT /access/acl", update: "pve:PUT /access/acl" },
312
802
  matches: (attributes, props) => attributes.bound && attributes.propagate === (props.propagate !== false),
313
803
  path: () => "access/acl",
314
804
  updateForm: bind
315
805
  });
316
- var ProxmoxAclProvider = () => Provider.effect(ProxmoxAcl, Effect6.succeed(ProxmoxAcl.Provider.of({
317
- list: () => Effect6.succeed([]),
318
- read: Effect6.fn(function* ({ olds }) {
806
+ var ProxmoxAclProvider = () => Provider.effect(ProxmoxAcl, Effect7.succeed(ProxmoxAcl.Provider.of({
807
+ list: () => Effect7.succeed([]),
808
+ read: Effect7.fn(function* ({ olds }) {
319
809
  return yield* ops.read(olds);
320
810
  }),
321
- diff: Effect6.fn(function* ({ news, output }) {
811
+ diff: Effect7.fn(function* ({ news, output }) {
322
812
  if (output !== undefined && isResolved2(news) && identity(news) !== identity(output)) {
323
813
  return { action: "replace" };
324
814
  }
325
815
  return yield* ops.diff(news, output);
326
816
  }),
327
- reconcile: Effect6.fn(function* ({ news }) {
817
+ reconcile: Effect7.fn(function* ({ news }) {
328
818
  const after = yield* ops.reconcile(news);
329
819
  if (!after.bound) {
330
- return yield* Effect6.die(new Error(`access/acl: the write returned no error but ${identity(news)} is still not ` + "bound. Either the PUT did nothing, or the read credential cannot see the " + "grant -- GET /access/acl is filtered to paths you may MODIFY permissions on, " + "so a read-only role is answered [] rather than 403."));
820
+ return yield* Effect7.die(new Error(`access/acl: the write returned no error but ${identity(news)} is still not ` + "bound. Either the PUT did nothing, or the read credential cannot see the " + "grant -- GET /access/acl is filtered to paths you may MODIFY permissions on, " + "so a read-only role is answered [] rather than 403."));
331
821
  }
332
822
  return after;
333
823
  }),
334
- delete: Effect6.fn(function* ({ olds }) {
824
+ delete: Effect7.fn(function* ({ olds }) {
335
825
  const removal = { delete: "1", ...tuple(olds) };
336
826
  yield* pve(olds.target, "provision", "PUT", "access/acl", removal);
337
827
  })
@@ -389,7 +879,7 @@ var alertmanagerAlertBody = (options = {}) => {
389
879
  // src/proxmox/api-token.ts
390
880
  import { Resource as Resource2 } from "alchemy";
391
881
  import * as Provider2 from "alchemy/Provider";
392
- import * as Effect7 from "effect/Effect";
882
+ import * as Effect8 from "effect/Effect";
393
883
 
394
884
  // src/proxmox/values.ts
395
885
  var num = (value, fallback) => typeof value === "number" ? value : fallback;
@@ -469,19 +959,19 @@ var ProxmoxApiToken = Resource2("Proxmox.ApiToken", {
469
959
  var ops2 = pveOperations(apiTokenSpec);
470
960
  var handlers = {
471
961
  ...pveHandlers(apiTokenSpec),
472
- reconcile: Effect7.fn(function* ({ news }) {
962
+ reconcile: Effect8.fn(function* ({ news }) {
473
963
  const live = yield* ops2.read(news);
474
964
  if (live === undefined) {
475
- return yield* Effect7.die(new Error(`${news.userid}!${news.tokenid}: this resource does not create API tokens. PVE returns ` + "the secret only in the create response and it cannot be stored, so a token made here " + "would be a live credential nobody holds. Create it with `pveum user token add` and " + "capture the value, or mint a short-lived one from the OpenBao proxmox mount " + "(`bao read proxmox-c1/creds/<role>`) -- then declare it here to manage it."));
965
+ return yield* Effect8.die(new Error(`${news.userid}!${news.tokenid}: this resource does not create API tokens. PVE returns ` + "the secret only in the create response and it cannot be stored, so a token made here " + "would be a live credential nobody holds. Create it with `pveum user token add` and " + "capture the value, or mint a short-lived one from the OpenBao proxmox mount " + "(`bao read proxmox-c1/creds/<role>`) -- then declare it here to manage it."));
476
966
  }
477
967
  return yield* ops2.reconcile(news);
478
968
  })
479
969
  };
480
- var ProxmoxApiTokenProvider = () => Provider2.effect(ProxmoxApiToken, Effect7.succeed(ProxmoxApiToken.Provider.of(handlers)));
970
+ var ProxmoxApiTokenProvider = () => Provider2.effect(ProxmoxApiToken, Effect8.succeed(ProxmoxApiToken.Provider.of(handlers)));
481
971
  // src/proxmox/backup-job.ts
482
972
  import { Resource as Resource3 } from "alchemy";
483
973
  import * as Provider3 from "alchemy/Provider";
484
- import * as Effect8 from "effect/Effect";
974
+ import * as Effect9 from "effect/Effect";
485
975
  var ProxmoxBackupJob = Resource3("Proxmox.BackupJob");
486
976
  var shape2 = (props) => ({
487
977
  all: props.all === true ? "1" : "0",
@@ -520,15 +1010,16 @@ var handlers2 = pveHandlers({
520
1010
  }),
521
1011
  collection: () => "cluster/backup",
522
1012
  createForm: (props) => ({ ...shape2(props), id: props.id }),
1013
+ endpoint: { create: "pve:POST /cluster/backup", update: "pve:PUT /cluster/backup/{id}" },
523
1014
  matches: (attributes, props) => attributes.schedule === props.schedule && attributes.enabled === (props.enabled !== false) && attributes.all === (props.all === true) && attributes.mode === (props.mode ?? "snapshot") && attributes["notification-mode"] === (props["notification-mode"] ?? "auto") && attributes["repeat-missed"] === (props["repeat-missed"] === true) && (props.storage === undefined || attributes.storage === props.storage) && (props.pool === undefined || attributes.pool === props.pool) && (props.comment === undefined || attributes.comment === props.comment) && (props.vmid === undefined || attributes.vmid === guestList(props.vmid)) && (props.exclude === undefined || attributes.exclude === guestList(props.exclude)) && (props["notes-template"] === undefined || attributes["notes-template"] === props["notes-template"]) && (props["prune-backups"] === undefined || attributes["prune-backups"] === propertyString(props["prune-backups"])) && (props.fleecing === undefined || attributes.fleecing === propertyString(props.fleecing, "enabled")),
524
1015
  path: (props) => `cluster/backup/${props.id}`,
525
1016
  updateForm: shape2
526
1017
  });
527
- var ProxmoxBackupJobProvider = () => Provider3.effect(ProxmoxBackupJob, Effect8.succeed(ProxmoxBackupJob.Provider.of(handlers2)));
1018
+ var ProxmoxBackupJobProvider = () => Provider3.effect(ProxmoxBackupJob, Effect9.succeed(ProxmoxBackupJob.Provider.of(handlers2)));
528
1019
  // src/proxmox/ceph-daemon.ts
529
1020
  import { Resource as Resource4 } from "alchemy";
530
1021
  import * as Provider4 from "alchemy/Provider";
531
- import * as Effect9 from "effect/Effect";
1022
+ import * as Effect10 from "effect/Effect";
532
1023
 
533
1024
  // src/proxmox/ceph-daemon-form.ts
534
1025
  var daemonId = (props) => props.name ?? props.node;
@@ -572,14 +1063,14 @@ var handlers3 = pveHandlers({
572
1063
  matches: () => true,
573
1064
  path: collectionPath
574
1065
  });
575
- var ProxmoxCephDaemonProvider = () => Provider4.effect(ProxmoxCephDaemon, Effect9.succeed(ProxmoxCephDaemon.Provider.of({
1066
+ var ProxmoxCephDaemonProvider = () => Provider4.effect(ProxmoxCephDaemon, Effect10.succeed(ProxmoxCephDaemon.Provider.of({
576
1067
  ...handlers3,
577
1068
  delete: ({ olds }) => pve(olds.target, "provision", "DELETE", daemonPath(olds))
578
1069
  })));
579
1070
  // src/proxmox/ceph-flag.ts
580
1071
  import { Resource as Resource5 } from "alchemy";
581
1072
  import * as Provider5 from "alchemy/Provider";
582
- import * as Effect10 from "effect/Effect";
1073
+ import * as Effect11 from "effect/Effect";
583
1074
  var ProxmoxCephFlag = Resource5("Proxmox.CephFlag");
584
1075
  var handlers4 = pveHandlers({
585
1076
  attributes: (live, props) => ({ flag: props.flag, value: bool(live) }),
@@ -589,13 +1080,13 @@ var handlers4 = pveHandlers({
589
1080
  path: (props) => `cluster/ceph/flags/${props.flag}`,
590
1081
  updateForm: (props) => ({ value: flag(props.value) ?? "0" })
591
1082
  });
592
- var ProxmoxCephFlagProvider = () => Provider5.effect(ProxmoxCephFlag, Effect10.succeed(ProxmoxCephFlag.Provider.of({
1083
+ var ProxmoxCephFlagProvider = () => Provider5.effect(ProxmoxCephFlag, Effect11.succeed(ProxmoxCephFlag.Provider.of({
593
1084
  ...handlers4,
594
- delete: () => Effect10.void,
595
- reconcile: Effect10.fn(function* ({ news }) {
1085
+ delete: () => Effect11.void,
1086
+ reconcile: Effect11.fn(function* ({ news }) {
596
1087
  const after = yield* handlers4.reconcile({ news });
597
1088
  if (after.value !== news.value) {
598
- return yield* Effect10.die(new Error(`cluster/ceph/flags/${news.flag}: the PUT returned no error but ceph still ` + `reports the flag ${after.value ? "set" : "clear"} rather than ` + `${news.value ? "set" : "clear"}. This endpoint's success is documented as a ` + "null body, so the status code proves nothing -- check `ceph osd dump | " + "head -1` and the cluster log. `pause` in particular is two ceph flags read " + "as one; see the header."));
1089
+ return yield* Effect11.die(new Error(`cluster/ceph/flags/${news.flag}: the PUT returned no error but ceph still ` + `reports the flag ${after.value ? "set" : "clear"} rather than ` + `${news.value ? "set" : "clear"}. This endpoint's success is documented as a ` + "null body, so the status code proves nothing -- check `ceph osd dump | " + "head -1` and the cluster log. `pause` in particular is two ceph flags read " + "as one; see the header."));
599
1090
  }
600
1091
  return after;
601
1092
  })
@@ -604,10 +1095,10 @@ var ProxmoxCephFlagProvider = () => Provider5.effect(ProxmoxCephFlag, Effect10.s
604
1095
  import { Resource as Resource6 } from "alchemy";
605
1096
  import { isResolved as isResolved3 } from "alchemy/Diff";
606
1097
  import * as Provider6 from "alchemy/Provider";
607
- import * as Effect12 from "effect/Effect";
1098
+ import * as Effect13 from "effect/Effect";
608
1099
 
609
1100
  // src/proxmox/ceph-fs-wire.ts
610
- import * as Effect11 from "effect/Effect";
1101
+ import * as Effect12 from "effect/Effect";
611
1102
  var readRow = (live, props) => {
612
1103
  const row = (Array.isArray(live) ? live : []).filter((entry) => typeof entry === "object" && entry !== null).find((entry) => entry["name"] === props.name);
613
1104
  if (row === undefined)
@@ -635,29 +1126,29 @@ var destroyPath = (props) => {
635
1126
  ].filter((part) => part !== undefined);
636
1127
  return query.length === 0 ? objectPath(props) : `${objectPath(props)}?${query.join("&")}`;
637
1128
  };
638
- var settle = (target, node, upid, what) => Effect11.gen(function* () {
1129
+ var settle = (target, node, upid, what) => Effect12.gen(function* () {
639
1130
  for (let attempt = 0;attempt < POLL_ATTEMPTS; attempt += 1) {
640
- const status = yield* pve(target, "read", "GET", `nodes/${node}/tasks/${encodeURIComponent(upid)}/status`).pipe(Effect11.orElseSucceed(() => {
1131
+ const status = yield* pve(target, "read", "GET", `nodes/${node}/tasks/${encodeURIComponent(upid)}/status`).pipe(Effect12.orElseSucceed(() => {
641
1132
  return;
642
1133
  }));
643
1134
  if (status?.status === "stopped") {
644
1135
  const exit = text(status.exitstatus);
645
1136
  if (exit === "OK")
646
1137
  return;
647
- return yield* Effect11.die(new Error(`${what}: PVE task ${upid} finished with "${exit === "" ? "no exit status" : exit}". ` + "The write returned HTTP 200 because it only forked the worker -- the real error is " + `in \`pvesh get /nodes/${node}/tasks/${upid}/log\`.`));
1138
+ return yield* Effect12.die(new Error(`${what}: PVE task ${upid} finished with "${exit === "" ? "no exit status" : exit}". ` + "The write returned HTTP 200 because it only forked the worker -- the real error is " + `in \`pvesh get /nodes/${node}/tasks/${upid}/log\`.`));
648
1139
  }
649
- yield* Effect11.sleep(POLL_SECONDS * 1000);
1140
+ yield* Effect12.sleep(POLL_SECONDS * 1000);
650
1141
  }
651
- return yield* Effect11.die(new Error(`${what}: PVE task ${upid} was still running after ` + `${String(POLL_ATTEMPTS * POLL_SECONDS)}s, or its ` + "status could not be read. Check the task log, and check that the `read` role holds " + `Sys.Audit on /nodes/${node} -- each call mints a new token, so the poller is never the ` + "task owner and is answered 403 rather than told to wait."));
1142
+ return yield* Effect12.die(new Error(`${what}: PVE task ${upid} was still running after ` + `${String(POLL_ATTEMPTS * POLL_SECONDS)}s, or its ` + "status could not be read. Check the task log, and check that the `read` role holds " + `Sys.Audit on /nodes/${node} -- each call mints a new token, so the poller is never the ` + "task owner and is answered 403 rather than told to wait."));
652
1143
  });
653
- var createFs = (props) => Effect11.gen(function* () {
1144
+ var createFs = (props) => Effect12.gen(function* () {
654
1145
  const upid = text(yield* pve(props.target, "provision", "POST", objectPath(props), createForm2(props)));
655
1146
  if (upid === "") {
656
- return yield* Effect11.die(new Error(`${objectPath(props)}: POST returned no task id. PVE wraps every answer in ` + '{"data":...} and can report success on a call that did nothing.'));
1147
+ return yield* Effect12.die(new Error(`${objectPath(props)}: POST returned no task id. PVE wraps every answer in ` + '{"data":...} and can report success on a call that did nothing.'));
657
1148
  }
658
1149
  yield* settle(props.target, props.node, upid, objectPath(props));
659
1150
  });
660
- var destroyFs = (props) => Effect11.gen(function* () {
1151
+ var destroyFs = (props) => Effect12.gen(function* () {
661
1152
  const upid = text(yield* pve(props.target, "provision", "DELETE", destroyPath(props)));
662
1153
  if (upid !== "")
663
1154
  yield* settle(props.target, props.node, upid, destroyPath(props));
@@ -676,44 +1167,44 @@ var ops3 = pveOperations({
676
1167
  matches: () => true,
677
1168
  path: (props) => `nodes/${props.node}/ceph/fs`
678
1169
  });
679
- var ProxmoxCephFsProvider = () => Provider6.effect(ProxmoxCephFs, Effect12.succeed(ProxmoxCephFs.Provider.of({
680
- list: () => Effect12.succeed([]),
681
- read: Effect12.fn(function* ({ olds }) {
1170
+ var ProxmoxCephFsProvider = () => Provider6.effect(ProxmoxCephFs, Effect13.succeed(ProxmoxCephFs.Provider.of({
1171
+ list: () => Effect13.succeed([]),
1172
+ read: Effect13.fn(function* ({ olds }) {
682
1173
  return yield* ops3.read(olds);
683
1174
  }),
684
- diff: Effect12.fn(function* ({ news, output }) {
1175
+ diff: Effect13.fn(function* ({ news, output }) {
685
1176
  if (output !== undefined && isResolved3(news) && news.name !== output.name) {
686
1177
  return { action: "replace" };
687
1178
  }
688
1179
  return yield* ops3.diff(news, output);
689
1180
  }),
690
- reconcile: Effect12.fn(function* ({ news }) {
1181
+ reconcile: Effect13.fn(function* ({ news }) {
691
1182
  const live = yield* ops3.read(news);
692
1183
  if (live !== undefined)
693
1184
  return live;
694
1185
  yield* createFs(news);
695
1186
  const after = yield* ops3.read(news);
696
1187
  if (after === undefined)
697
- return yield* Effect12.die(notCreated(news));
1188
+ return yield* Effect13.die(notCreated(news));
698
1189
  return after;
699
1190
  }),
700
- delete: Effect12.fn(function* ({ olds }) {
1191
+ delete: Effect13.fn(function* ({ olds }) {
701
1192
  if ((yield* ops3.read(olds)) === undefined)
702
1193
  return;
703
1194
  yield* destroyFs(olds);
704
1195
  const after = yield* ops3.read(olds);
705
1196
  if (after !== undefined)
706
- return yield* Effect12.die(notDestroyed(olds));
1197
+ return yield* Effect13.die(notDestroyed(olds));
707
1198
  })
708
1199
  })));
709
1200
  // src/proxmox/ceph-osd.ts
710
1201
  import { Resource as Resource7 } from "alchemy";
711
1202
  import { isResolved as isResolved4 } from "alchemy/Diff";
712
1203
  import * as Provider7 from "alchemy/Provider";
713
- import * as Effect15 from "effect/Effect";
1204
+ import * as Effect16 from "effect/Effect";
714
1205
 
715
1206
  // src/proxmox/ceph-osd-tree.ts
716
- import * as Effect13 from "effect/Effect";
1207
+ import * as Effect14 from "effect/Effect";
717
1208
  var branches = (node) => (Array.isArray(node["children"]) ? node["children"] : []).filter((child) => typeof child === "object" && child !== null);
718
1209
  var osdLeaves = (tree) => {
719
1210
  const root = tree["root"];
@@ -739,17 +1230,17 @@ var osdAttributes = (leaf, props) => ({
739
1230
  status: text(leaf["status"]),
740
1231
  total_space: num(leaf["total_space"], 0)
741
1232
  });
742
- var readOsd = (props) => pve(props.target, "read", "GET", `nodes/${props.node}/ceph/osd`).pipe(Effect13.map((t) => {
1233
+ var readOsd = (props) => pve(props.target, "read", "GET", `nodes/${props.node}/ceph/osd`).pipe(Effect14.map((t) => {
743
1234
  const leaf = t === undefined ? undefined : findOsd(t, props.osdid);
744
1235
  return leaf === undefined ? undefined : osdAttributes(leaf, props);
745
1236
  }));
746
1237
 
747
1238
  // src/proxmox/ceph-osd-write.ts
748
- import * as Effect14 from "effect/Effect";
1239
+ import * as Effect15 from "effect/Effect";
749
1240
  var describe = (props) => `osd.${String(props.osdid)} on ${props.node}`;
750
- var createOsd = Effect14.fn(function* (news) {
1241
+ var createOsd = Effect15.fn(function* (news) {
751
1242
  if (news.dev === undefined) {
752
- return yield* Effect14.die(new Error(`${describe(news)}: no such OSD in the CRUSH tree, and no \`dev\` was declared. ` + "Without a device this resource is an ASSERTION over an OSD that must already " + "exist. Declare `dev` to let it create one -- which WIPES that device -- or fix " + "the declaration; if the disk was there yesterday, a replica is missing and Ceph " + "is degraded."));
1243
+ return yield* Effect15.die(new Error(`${describe(news)}: no such OSD in the CRUSH tree, and no \`dev\` was declared. ` + "Without a device this resource is an ASSERTION over an OSD that must already " + "exist. Declare `dev` to let it create one -- which WIPES that device -- or fix " + "the declaration; if the disk was there yesterday, a replica is missing and Ceph " + "is degraded."));
753
1244
  }
754
1245
  yield* pve(news.target, "provision", "POST", `nodes/${news.node}/ceph/osd`, {
755
1246
  dev: news.dev,
@@ -757,7 +1248,7 @@ var createOsd = Effect14.fn(function* (news) {
757
1248
  });
758
1249
  const created = yield* readOsd(news);
759
1250
  if (created === undefined) {
760
- return yield* Effect14.die(new Error(`${describe(news)}: the create returned no error but osd.${String(news.osdid)} is ` + "still absent. Ceph allocates OSD ids and cannot be told which to use, so it most " + "likely assigned a different number -- check `ceph osd tree`, and declare the id it " + "actually gave the device rather than deploying again."));
1251
+ return yield* Effect15.die(new Error(`${describe(news)}: the create returned no error but osd.${String(news.osdid)} is ` + "still absent. Ceph allocates OSD ids and cannot be told which to use, so it most " + "likely assigned a different number -- check `ceph osd tree`, and declare the id it " + "actually gave the device rather than deploying again."));
761
1252
  }
762
1253
  return created;
763
1254
  });
@@ -770,11 +1261,11 @@ var ProxmoxCephOsd = Resource7("Proxmox.CephOsd", {
770
1261
  var settled = (live, props) => (props.host === undefined || props.host === live.host) && (props.device_class === undefined || props.device_class === live.device_class);
771
1262
  var describe2 = (props) => `osd.${String(props.osdid)} (read via nodes/${props.node}/ceph/osd)`;
772
1263
  var handlers5 = {
773
- list: () => Effect15.succeed([]),
774
- read: Effect15.fn(function* ({ olds }) {
1264
+ list: () => Effect16.succeed([]),
1265
+ read: Effect16.fn(function* ({ olds }) {
775
1266
  return yield* readOsd(olds);
776
1267
  }),
777
- diff: Effect15.fn(function* ({
1268
+ diff: Effect16.fn(function* ({
778
1269
  news,
779
1270
  output
780
1271
  }) {
@@ -787,25 +1278,25 @@ var handlers5 = {
787
1278
  return { action: "update" };
788
1279
  return settled(live, news) ? { action: "noop" } : { action: "update" };
789
1280
  }),
790
- reconcile: Effect15.fn(function* ({ news }) {
1281
+ reconcile: Effect16.fn(function* ({ news }) {
791
1282
  const live = yield* readOsd(news);
792
1283
  if (live === undefined) {
793
1284
  return yield* createOsd(news);
794
1285
  }
795
1286
  if (!settled(live, news)) {
796
- return yield* Effect15.die(new Error(`${describe2(news)}: exists but not as declared -- live host=${live.host} ` + `device_class=${live.device_class}. Nothing here moves an OSD between hosts or ` + "reclassifies it; fix the declaration, or move it deliberately with `ceph osd crush " + "move` / `ceph osd crush set-device-class` and deploy again."));
1287
+ return yield* Effect16.die(new Error(`${describe2(news)}: exists but not as declared -- live host=${live.host} ` + `device_class=${live.device_class}. Nothing here moves an OSD between hosts or ` + "reclassifies it; fix the declaration, or move it deliberately with `ceph osd crush " + "move` / `ceph osd crush set-device-class` and deploy again."));
797
1288
  }
798
1289
  return live;
799
1290
  }),
800
- delete: Effect15.fn(function* ({ olds }) {
1291
+ delete: Effect16.fn(function* ({ olds }) {
801
1292
  yield* destroyOsd(olds);
802
1293
  })
803
1294
  };
804
- var ProxmoxCephOsdProvider = () => Provider7.effect(ProxmoxCephOsd, Effect15.succeed(ProxmoxCephOsd.Provider.of(handlers5)));
1295
+ var ProxmoxCephOsdProvider = () => Provider7.effect(ProxmoxCephOsd, Effect16.succeed(ProxmoxCephOsd.Provider.of(handlers5)));
805
1296
  // src/proxmox/ceph-pool.ts
806
1297
  import { Resource as Resource8 } from "alchemy";
807
1298
  import * as Provider8 from "alchemy/Provider";
808
- import * as Effect17 from "effect/Effect";
1299
+ import * as Effect18 from "effect/Effect";
809
1300
 
810
1301
  // src/proxmox/ceph-pool-form.ts
811
1302
  var object = (props) => `nodes/${props.node}/ceph/pool/${props.name}`;
@@ -834,14 +1325,14 @@ var createBody = (props) => ({
834
1325
  });
835
1326
 
836
1327
  // src/proxmox/ceph-pool-settle.ts
837
- import * as Effect16 from "effect/Effect";
838
- var confirmAbsent = (props, collection2, describe3) => pve(props.target, "read", "GET", collection2).pipe(Effect16.flatMap((rows) => (rows ?? []).some((row) => row.pool_name === props.name) ? Effect16.die(new Error(`${describe3}: the cluster lists this pool but its status could not be read, ` + "so this deploy will NOT create over it. Check that the node is up and that the " + "read role holds Sys.Audit or Datastore.Audit on / -- a create here would apply " + "pg_num to a live pool and start a PG merge.")) : Effect16.void));
839
- var settle2 = (props, read, done) => Effect16.gen(function* () {
1328
+ import * as Effect17 from "effect/Effect";
1329
+ var confirmAbsent = (props, collection2, describe3) => pve(props.target, "read", "GET", collection2).pipe(Effect17.flatMap((rows) => (rows ?? []).some((row) => row.pool_name === props.name) ? Effect17.die(new Error(`${describe3}: the cluster lists this pool but its status could not be read, ` + "so this deploy will NOT create over it. Check that the node is up and that the " + "read role holds Sys.Audit or Datastore.Audit on / -- a create here would apply " + "pg_num to a live pool and start a PG merge.")) : Effect17.void));
1330
+ var settle2 = (props, read, done) => Effect17.gen(function* () {
840
1331
  for (let attempt = 0;attempt < 30; attempt += 1) {
841
1332
  const live = yield* read(props);
842
1333
  if (done(live))
843
1334
  return live;
844
- yield* Effect16.sleep("2 seconds");
1335
+ yield* Effect17.sleep("2 seconds");
845
1336
  }
846
1337
  return yield* read(props);
847
1338
  });
@@ -879,11 +1370,11 @@ var spec = {
879
1370
  updateForm: updateBody
880
1371
  };
881
1372
  var ops4 = pveOperations(spec);
882
- var ProxmoxCephPoolProvider = () => Provider8.effect(ProxmoxCephPool, Effect17.succeed(ProxmoxCephPool.Provider.of({
883
- list: () => Effect17.succeed([]),
1373
+ var ProxmoxCephPoolProvider = () => Provider8.effect(ProxmoxCephPool, Effect18.succeed(ProxmoxCephPool.Provider.of({
1374
+ list: () => Effect18.succeed([]),
884
1375
  read: ({ olds }) => ops4.read(olds),
885
1376
  diff: ({ news, output }) => ops4.diff(news, output),
886
- reconcile: Effect17.fn(function* ({ news }) {
1377
+ reconcile: Effect18.fn(function* ({ news }) {
887
1378
  const live = yield* ops4.read(news);
888
1379
  let upid;
889
1380
  if (live === undefined) {
@@ -897,15 +1388,15 @@ var ProxmoxCephPoolProvider = () => Provider8.effect(ProxmoxCephPool, Effect17.s
897
1388
  }
898
1389
  const after = yield* settle2(news, ops4.read, (row) => row !== undefined && spec.matches(row, news));
899
1390
  if (after === undefined || !spec.matches(after, news)) {
900
- return yield* Effect17.die(new Error(`${object(news)}: the write returned no error but the pool still does not match ` + `the declaration after 60s. PVE answered with task ${upid ?? "(none)"} -- read ` + "its log with `pvesh get /nodes/<node>/tasks/<upid>/log` to see what Ceph " + "refused."));
1391
+ return yield* Effect18.die(new Error(`${object(news)}: the write returned no error but the pool still does not match ` + `the declaration after 60s. PVE answered with task ${upid ?? "(none)"} -- read ` + "its log with `pvesh get /nodes/<node>/tasks/<upid>/log` to see what Ceph " + "refused."));
901
1392
  }
902
1393
  return after;
903
1394
  }),
904
- delete: Effect17.fn(function* ({ olds }) {
1395
+ delete: Effect18.fn(function* ({ olds }) {
905
1396
  yield* pve(olds.target, "provision", "DELETE", object(olds));
906
1397
  const left = yield* settle2(olds, ops4.read, (row) => row === undefined);
907
1398
  if (left !== undefined) {
908
- return yield* Effect17.die(new Error(`${object(olds)}: the DELETE returned no error but the pool is still there after ` + "60s. Either a storage on it still holds RBD images, or the mons are running " + "with mon_allow_pool_delete false."));
1399
+ return yield* Effect18.die(new Error(`${object(olds)}: the DELETE returned no error but the pool is still there after ` + "60s. Either a storage on it still holds RBD images, or the mons are running " + "with mon_allow_pool_delete false."));
909
1400
  }
910
1401
  })
911
1402
  })));
@@ -913,7 +1404,7 @@ var ProxmoxCephPoolProvider = () => Provider8.effect(ProxmoxCephPool, Effect17.s
913
1404
  import { Resource as Resource9 } from "alchemy";
914
1405
  import { isResolved as isResolved5 } from "alchemy/Diff";
915
1406
  import * as Provider9 from "alchemy/Provider";
916
- import * as Effect18 from "effect/Effect";
1407
+ import * as Effect19 from "effect/Effect";
917
1408
 
918
1409
  // src/proxmox/firewall-alias-form.ts
919
1410
  var fold = (name) => name.trim().toLowerCase();
@@ -945,13 +1436,17 @@ var handlers6 = pveHandlers({
945
1436
  },
946
1437
  collection: () => "cluster/firewall/aliases",
947
1438
  createForm: (props) => ({ ...body(props), name: props.name }),
1439
+ endpoint: {
1440
+ create: "pve:POST /cluster/firewall/aliases",
1441
+ update: "pve:PUT /cluster/firewall/aliases/{name}"
1442
+ },
948
1443
  matches: (attributes, props) => attributes.cidr === cidr(props.cidr) && attributes.comment === comment(props.comment),
949
1444
  path: (props) => `cluster/firewall/aliases/${props.name}`,
950
1445
  updateForm: body
951
1446
  });
952
- var ProxmoxFirewallAliasProvider = () => Provider9.effect(ProxmoxFirewallAlias, Effect18.succeed(ProxmoxFirewallAlias.Provider.of({
1447
+ var ProxmoxFirewallAliasProvider = () => Provider9.effect(ProxmoxFirewallAlias, Effect19.succeed(ProxmoxFirewallAlias.Provider.of({
953
1448
  ...handlers6,
954
- diff: Effect18.fn(function* ({ news, output }) {
1449
+ diff: Effect19.fn(function* ({ news, output }) {
955
1450
  if (output !== undefined && isResolved5(news) && fold(news.name) !== fold(output.name)) {
956
1451
  return { action: "replace" };
957
1452
  }
@@ -961,7 +1456,7 @@ var ProxmoxFirewallAliasProvider = () => Provider9.effect(ProxmoxFirewallAlias,
961
1456
  // src/proxmox/group.ts
962
1457
  import { Resource as Resource10 } from "alchemy";
963
1458
  import * as Provider10 from "alchemy/Provider";
964
- import * as Effect19 from "effect/Effect";
1459
+ import * as Effect20 from "effect/Effect";
965
1460
  var ProxmoxGroup = Resource10("Proxmox.Group", {
966
1461
  defaultRemovalPolicy: "retain"
967
1462
  });
@@ -981,15 +1476,16 @@ var handlers7 = pveHandlers({
981
1476
  }),
982
1477
  collection: () => "access/groups",
983
1478
  createForm: (props) => ({ comment: storedComment(props.comment), groupid: props.groupid }),
1479
+ endpoint: { create: "pve:POST /access/groups", update: "pve:PUT /access/groups/{groupid}" },
984
1480
  matches: (attributes, props) => attributes.comment === storedComment(props.comment),
985
1481
  path: (props) => `access/groups/${props.groupid}`,
986
1482
  updateForm: (props) => ({ comment: storedComment(props.comment) })
987
1483
  });
988
- var ProxmoxGroupProvider = () => Provider10.effect(ProxmoxGroup, Effect19.succeed(ProxmoxGroup.Provider.of(handlers7)));
1484
+ var ProxmoxGroupProvider = () => Provider10.effect(ProxmoxGroup, Effect20.succeed(ProxmoxGroup.Provider.of(handlers7)));
989
1485
  // src/proxmox/ha-resource.ts
990
1486
  import { Resource as Resource11 } from "alchemy";
991
1487
  import * as Provider11 from "alchemy/Provider";
992
- import * as Effect20 from "effect/Effect";
1488
+ import * as Effect21 from "effect/Effect";
993
1489
  var ProxmoxHaResource = Resource11("Proxmox.HaResource");
994
1490
  var kind = (sid) => sid.split(":")[0] ?? "";
995
1491
  var shape3 = (props) => ({
@@ -1009,15 +1505,19 @@ var handlers8 = pveHandlers({
1009
1505
  }),
1010
1506
  collection: () => "cluster/ha/resources",
1011
1507
  createForm: (props) => ({ ...shape3(props), sid: props.sid }),
1508
+ endpoint: {
1509
+ create: "pve:POST /cluster/ha/resources",
1510
+ update: "pve:PUT /cluster/ha/resources/{sid}"
1511
+ },
1012
1512
  matches: (attributes, props) => attributes.state === (props.state ?? "started") && attributes.comment === (props.comment ?? "") && attributes.max_restart === (props.max_restart ?? 1) && attributes.max_relocate === (props.max_relocate ?? 1),
1013
1513
  path: (props) => `cluster/ha/resources/${props.sid}`,
1014
1514
  updateForm: shape3
1015
1515
  });
1016
- var ProxmoxHaResourceProvider = () => Provider11.effect(ProxmoxHaResource, Effect20.succeed(ProxmoxHaResource.Provider.of(handlers8)));
1516
+ var ProxmoxHaResourceProvider = () => Provider11.effect(ProxmoxHaResource, Effect21.succeed(ProxmoxHaResource.Provider.of(handlers8)));
1017
1517
  // src/proxmox/ha-rule.ts
1018
1518
  import { Resource as Resource12 } from "alchemy";
1019
1519
  import * as Provider12 from "alchemy/Provider";
1020
- import * as Effect21 from "effect/Effect";
1520
+ import * as Effect22 from "effect/Effect";
1021
1521
 
1022
1522
  // src/proxmox/ha-rule-form.ts
1023
1523
  var nodeList = (value) => {
@@ -1085,6 +1585,7 @@ var handlers9 = pveHandlers({
1085
1585
  },
1086
1586
  collection: () => "cluster/ha/rules",
1087
1587
  createForm: (props) => ({ ...body2(props), rule: props.rule }),
1588
+ endpoint: { create: "pve:POST /cluster/ha/rules", update: "pve:PUT /cluster/ha/rules/{rule}" },
1088
1589
  matches: (attributes, props) => attributes.resources === csv(props.resources) && attributes.affinity === props.affinity && attributes.comment === commentText(props.comment ?? "") && attributes.disable === (props.disable === true) && (props.type !== "node-affinity" || attributes.nodes === nodeList(props.nodes) && attributes.strict === (props.strict === true)),
1089
1590
  path: (props) => `cluster/ha/rules/${props.rule}`,
1090
1591
  updateForm: (props) => {
@@ -1093,43 +1594,43 @@ var handlers9 = pveHandlers({
1093
1594
  return withClears(fields, clear);
1094
1595
  }
1095
1596
  });
1096
- var ProxmoxHaRuleProvider = () => Provider12.effect(ProxmoxHaRule, Effect21.succeed(ProxmoxHaRule.Provider.of(handlers9)));
1597
+ var ProxmoxHaRuleProvider = () => Provider12.effect(ProxmoxHaRule, Effect22.succeed(ProxmoxHaRule.Provider.of(handlers9)));
1097
1598
  // src/proxmox/lxc.ts
1098
1599
  import { Resource as Resource13 } from "alchemy";
1099
1600
  import { isResolved as isResolved6 } from "alchemy/Diff";
1100
1601
  import * as Provider13 from "alchemy/Provider";
1101
- import * as Effect31 from "effect/Effect";
1602
+ import * as Effect32 from "effect/Effect";
1102
1603
 
1103
1604
  // src/ownership/adopt.ts
1104
1605
  import { AdoptPolicy } from "alchemy/AdoptPolicy";
1105
1606
  import { AlchemyContext } from "alchemy/AlchemyContext";
1106
1607
  import { Stack as Stack3 } from "alchemy/Stack";
1107
- import * as Effect25 from "effect/Effect";
1608
+ import * as Effect26 from "effect/Effect";
1108
1609
  import * as Option5 from "effect/Option";
1109
1610
 
1110
1611
  // src/ownership/resume.ts
1111
1612
  import { Unowned } from "alchemy/AdoptPolicy";
1112
1613
  import { Artifacts } from "alchemy/Artifacts";
1113
- import * as Effect22 from "effect/Effect";
1614
+ import * as Effect23 from "effect/Effect";
1114
1615
  import * as Option2 from "effect/Option";
1115
1616
  var PROVEN = "homeflare/ownership/resumes";
1116
1617
  var UNFINISHED = "homeflare/ownership/unfinished";
1117
- var note = (key, instanceId) => Effect22.gen(function* () {
1118
- const bag = yield* Effect22.serviceOption(Artifacts);
1618
+ var note = (key, instanceId) => Effect23.gen(function* () {
1619
+ const bag = yield* Effect23.serviceOption(Artifacts);
1119
1620
  if (Option2.isSome(bag))
1120
1621
  yield* bag.value.set(key, instanceId);
1121
1622
  });
1122
- var noted = (key, instanceId) => Effect22.gen(function* () {
1123
- const bag = yield* Effect22.serviceOption(Artifacts);
1623
+ var noted = (key, instanceId) => Effect23.gen(function* () {
1624
+ const bag = yield* Effect23.serviceOption(Artifacts);
1124
1625
  if (Option2.isNone(bag))
1125
1626
  return false;
1126
1627
  return (yield* bag.value.get(key)) === instanceId;
1127
1628
  });
1128
- var noteUnfinished = (instanceId) => Effect22.as(note(UNFINISHED, instanceId), undefined);
1129
- var noteResume = (instanceId, recovered) => Effect22.gen(function* () {
1629
+ var noteUnfinished = (instanceId) => Effect23.as(note(UNFINISHED, instanceId), undefined);
1630
+ var noteResume = (instanceId, recovered) => Effect23.gen(function* () {
1130
1631
  yield* noteUnfinished(instanceId);
1131
- const unproven = () => Effect22.succeed(undefined);
1132
- const found = yield* recovered.pipe(Effect22.catch(unproven), Effect22.catchDefect(unproven));
1632
+ const unproven = () => Effect23.succeed(undefined);
1633
+ const found = yield* recovered.pipe(Effect23.catch(unproven), Effect23.catchDefect(unproven));
1133
1634
  if (found === undefined || found === null || Unowned.is(found))
1134
1635
  return;
1135
1636
  yield* note(PROVEN, instanceId);
@@ -1140,7 +1641,7 @@ var provingResumes = (service) => {
1140
1641
  const { diff, read } = service;
1141
1642
  if (diff === undefined || read === undefined)
1142
1643
  return service;
1143
- const proving = (input) => Effect22.gen(function* () {
1644
+ const proving = (input) => Effect23.gen(function* () {
1144
1645
  if (input.output === undefined) {
1145
1646
  yield* noteResume(input.instanceId, read({ ...input, output: undefined }));
1146
1647
  }
@@ -1152,12 +1653,12 @@ var provingResumes = (service) => {
1152
1653
  // src/ownership/rows.ts
1153
1654
  import { Stack as Stack2 } from "alchemy/Stack";
1154
1655
  import { State, isActionState } from "alchemy/State/State";
1155
- import * as Effect24 from "effect/Effect";
1656
+ import * as Effect25 from "effect/Effect";
1156
1657
  import * as Option4 from "effect/Option";
1157
1658
 
1158
1659
  // src/ownership/whole.ts
1159
1660
  import { Stack } from "alchemy/Stack";
1160
- import * as Effect23 from "effect/Effect";
1661
+ import * as Effect24 from "effect/Effect";
1161
1662
  import * as Option3 from "effect/Option";
1162
1663
  var absent = (value) => value === undefined || value === null;
1163
1664
  var isPlainObject = (value) => {
@@ -1183,22 +1684,22 @@ var carries = (declared, recorded) => {
1183
1684
  }
1184
1685
  return true;
1185
1686
  };
1186
- var wholeDeclaration = (fqn, props) => Effect23.map(Effect23.serviceOption(Stack), (stack) => {
1687
+ var wholeDeclaration = (fqn, props) => Effect24.map(Effect24.serviceOption(Stack), (stack) => {
1187
1688
  const declared = Option3.isSome(stack) ? stack.value.resources[fqn] : undefined;
1188
1689
  return declared !== undefined && carries(declared.Props, props);
1189
1690
  });
1190
1691
 
1191
1692
  // src/ownership/rows.ts
1192
- var storeOf = Effect24.gen(function* () {
1193
- const stack = yield* Effect24.serviceOption(Stack2);
1194
- const state = yield* Effect24.serviceOption(State);
1693
+ var storeOf = Effect25.gen(function* () {
1694
+ const stack = yield* Effect25.serviceOption(Stack2);
1695
+ const state = yield* Effect25.serviceOption(State);
1195
1696
  if (Option4.isNone(stack) || Option4.isNone(state))
1196
1697
  return;
1197
1698
  const store = yield* state.value;
1198
1699
  const where = { stack: stack.value.name, stage: stack.value.stage };
1199
1700
  return { resources: stack.value.resources, store, where };
1200
1701
  });
1201
- var rowAt = (store, where, fqn) => store.get({ ...where, fqn }).pipe(Effect24.orElseSucceed(() => {
1702
+ var rowAt = (store, where, fqn) => store.get({ ...where, fqn }).pipe(Effect25.orElseSucceed(() => {
1202
1703
  return;
1203
1704
  }));
1204
1705
  var generationOf = (row, instanceId) => {
@@ -1211,7 +1712,7 @@ var generationOf = (row, instanceId) => {
1211
1712
  }
1212
1713
  return;
1213
1714
  };
1214
- var recordedGeneration = (fqn, instanceId) => Effect24.gen(function* () {
1715
+ var recordedGeneration = (fqn, instanceId) => Effect25.gen(function* () {
1215
1716
  const found = yield* storeOf;
1216
1717
  if (found === undefined)
1217
1718
  return;
@@ -1222,14 +1723,14 @@ var recordedGeneration = (fqn, instanceId) => Effect24.gen(function* () {
1222
1723
  }
1223
1724
  return;
1224
1725
  });
1225
- var recordedInstance = (fqn, instanceId) => Effect24.gen(function* () {
1726
+ var recordedInstance = (fqn, instanceId) => Effect25.gen(function* () {
1226
1727
  const generation = yield* recordedGeneration(fqn, instanceId);
1227
1728
  if (generation === undefined)
1228
1729
  return "absent";
1229
1730
  return (yield* wholeDeclaration(fqn, generation.props)) ? "whole" : "partial";
1230
1731
  });
1231
- var isCreate = (fqn, instanceId) => Effect24.map(recordedGeneration(fqn, instanceId), (generation) => generation?.status === "creating");
1232
- var forgetRefusedCreate = (fqn, instanceId) => Effect24.gen(function* () {
1732
+ var isCreate = (fqn, instanceId) => Effect25.map(recordedGeneration(fqn, instanceId), (generation) => generation?.status === "creating");
1733
+ var forgetRefusedCreate = (fqn, instanceId) => Effect25.gen(function* () {
1233
1734
  const found = yield* storeOf;
1234
1735
  if (found === undefined)
1235
1736
  return;
@@ -1238,32 +1739,32 @@ var forgetRefusedCreate = (fqn, instanceId) => Effect24.gen(function* () {
1238
1739
  if (mine?.status !== "creating" || mine.instanceId !== instanceId || mine.attr !== undefined) {
1239
1740
  return;
1240
1741
  }
1241
- yield* found.store.delete({ ...found.where, fqn }).pipe(Effect24.ignore);
1742
+ yield* found.store.delete({ ...found.where, fqn }).pipe(Effect25.ignore);
1242
1743
  });
1243
1744
 
1244
1745
  // src/ownership/adopt.ts
1245
- var adoptEnabled = (fqn) => Effect25.gen(function* () {
1246
- const stack = yield* Effect25.serviceOption(Stack3);
1746
+ var adoptEnabled = (fqn) => Effect26.gen(function* () {
1747
+ const stack = yield* Effect26.serviceOption(Stack3);
1247
1748
  const scoped = Option5.isSome(stack) ? stack.value.resources[fqn]?.Adopt : undefined;
1248
1749
  if (scoped !== undefined)
1249
1750
  return scoped;
1250
- const policy = yield* Effect25.serviceOption(AdoptPolicy);
1751
+ const policy = yield* Effect26.serviceOption(AdoptPolicy);
1251
1752
  if (Option5.isSome(policy))
1252
1753
  return policy.value;
1253
- const context = yield* Effect25.serviceOption(AlchemyContext);
1754
+ const context = yield* Effect26.serviceOption(AlchemyContext);
1254
1755
  return Option5.isSome(context) ? context.value.adopt : false;
1255
1756
  });
1256
- var adoptable = (owner) => Effect25.gen(function* () {
1757
+ var adoptable = (owner) => Effect26.gen(function* () {
1257
1758
  if (yield* isCreate(owner.fqn, owner.instanceId))
1258
1759
  return true;
1259
1760
  return yield* unfinished(owner.instanceId);
1260
1761
  });
1261
- var adoptsAtApply = (owner) => Effect25.gen(function* () {
1762
+ var adoptsAtApply = (owner) => Effect26.gen(function* () {
1262
1763
  if (!(yield* adoptEnabled(owner.fqn)))
1263
1764
  return false;
1264
1765
  return yield* adoptable(owner);
1265
1766
  });
1266
- var refuseTakeover = (owner, what) => Effect25.gen(function* () {
1767
+ var refuseTakeover = (owner, what) => Effect26.gen(function* () {
1267
1768
  if (owner.output !== undefined)
1268
1769
  return;
1269
1770
  if (yield* resumes(owner.instanceId))
@@ -1273,25 +1774,25 @@ var refuseTakeover = (owner, what) => Effect25.gen(function* () {
1273
1774
  return;
1274
1775
  yield* forgetRefusedCreate(owner.fqn, owner.instanceId);
1275
1776
  const remedy = may ? "Deploy with --adopt (or wrap the resource in adopt(true)) to take it over, or remove it first." : "It is the new identity of a replace, which --adopt does not cover: remove it first.";
1276
- return yield* Effect25.die(new Error(`${what}: already exists, and this stack holds no state for it — the plan could not ask ` + "first (a prop was still an Output, a replace moved onto it, or it appeared after the " + `plan). Nothing was written. ${remedy}`));
1777
+ return yield* Effect26.die(new Error(`${what}: already exists, and this stack holds no state for it — the plan could not ask ` + "first (a prop was still an Output, a replace moved onto it, or it appeared after the " + `plan). Nothing was written. ${remedy}`));
1277
1778
  });
1278
1779
  var claimFor = (owner) => (what) => refuseTakeover(owner, what);
1279
1780
 
1280
1781
  // src/ownership/probe.ts
1281
1782
  import { Unowned as Unowned2 } from "alchemy/AdoptPolicy";
1282
- import * as Effect26 from "effect/Effect";
1283
- var provenOurs = (ask, settled2) => Effect26.gen(function* () {
1783
+ import * as Effect27 from "effect/Effect";
1784
+ var provenOurs = (ask, settled2) => Effect27.gen(function* () {
1284
1785
  const recorded = yield* recordedInstance(ask.fqn, ask.instanceId);
1285
1786
  if (recorded === "absent")
1286
1787
  return false;
1287
1788
  if (recorded === "partial") {
1288
- yield* Effect26.logWarning(`${ask.fqn}: the interrupted create's state row lacks part of the declaration (a prop ` + "was still an Output when it was written), so the live object is not proven ours");
1789
+ yield* Effect27.logWarning(`${ask.fqn}: the interrupted create's state row lacks part of the declaration (a prop ` + "was still an Output when it was written), so the live object is not proven ours");
1289
1790
  return false;
1290
1791
  }
1291
- const unproven = (reason) => Effect26.as(Effect26.logWarning(`${ask.fqn}: the interrupted create's object could not be compared with its props ` + `(${reason instanceof Error ? reason.message : String(reason)}), so it is not treated as ours`), false);
1292
- return yield* settled2.pipe(Effect26.catch(unproven), Effect26.catchDefect(unproven));
1792
+ const unproven = (reason) => Effect27.as(Effect27.logWarning(`${ask.fqn}: the interrupted create's object could not be compared with its props ` + `(${reason instanceof Error ? reason.message : String(reason)}), so it is not treated as ours`), false);
1793
+ return yield* settled2.pipe(Effect27.catch(unproven), Effect27.catchDefect(unproven));
1293
1794
  });
1294
- var ownedRead = (ask, found, settled2) => Effect26.gen(function* () {
1795
+ var ownedRead = (ask, found, settled2) => Effect27.gen(function* () {
1295
1796
  if (found === undefined || ask.output !== undefined)
1296
1797
  return found;
1297
1798
  return (yield* provenOurs(ask, settled2)) ? found : Unowned2(found);
@@ -1746,11 +2247,11 @@ var identityRefusals = (news, olds, output) => {
1746
2247
  };
1747
2248
 
1748
2249
  // src/proxmox/lxc-adoption.ts
1749
- import * as Effect30 from "effect/Effect";
2250
+ import * as Effect31 from "effect/Effect";
1750
2251
 
1751
2252
  // src/ownership/adopting.ts
1752
- import * as Effect27 from "effect/Effect";
1753
- var adopting = (owner, settled2) => Effect27.gen(function* () {
2253
+ import * as Effect28 from "effect/Effect";
2254
+ var adopting = (owner, settled2) => Effect28.gen(function* () {
1754
2255
  if (owner.output === undefined)
1755
2256
  return false;
1756
2257
  const generation = yield* recordedGeneration(owner.fqn, owner.instanceId);
@@ -1762,11 +2263,11 @@ var adopting = (owner, settled2) => Effect27.gen(function* () {
1762
2263
  });
1763
2264
 
1764
2265
  // src/proxmox/lxc-lifecycle.ts
1765
- import * as Effect29 from "effect/Effect";
2266
+ import * as Effect30 from "effect/Effect";
1766
2267
 
1767
2268
  // src/proxmox/network-apply-read.ts
1768
- import * as Effect28 from "effect/Effect";
1769
- var stagedDiff = (target, node) => Effect28.gen(function* () {
2269
+ import * as Effect29 from "effect/Effect";
2270
+ var stagedDiff = (target, node) => Effect29.gen(function* () {
1770
2271
  const path = `nodes/${node}/network`;
1771
2272
  const credential = yield* leased(target, "read");
1772
2273
  const envelope = yield* pveEnvelopeWith(target, credential, "GET", path);
@@ -1779,8 +2280,8 @@ var pendingLines = (diff) => {
1779
2280
  `).filter((line) => (line.startsWith("+") || line.startsWith("-")) && !line.startsWith("+++") && !line.startsWith("---")).length;
1780
2281
  return Math.max(changed, 1);
1781
2282
  };
1782
- var pendingCount = (target, node) => stagedDiff(target, node).pipe(Effect28.map(pendingLines));
1783
- var clusterHealth = (target) => pve(target, "read", "GET", "cluster/status").pipe(Effect28.map((rows) => {
2283
+ var pendingCount = (target, node) => stagedDiff(target, node).pipe(Effect29.map(pendingLines));
2284
+ var clusterHealth = (target) => pve(target, "read", "GET", "cluster/status").pipe(Effect29.map((rows) => {
1784
2285
  const all = rows ?? [];
1785
2286
  const cluster = all.find((row) => row.type === "cluster");
1786
2287
  return {
@@ -1788,21 +2289,21 @@ var clusterHealth = (target) => pve(target, "read", "GET", "cluster/status").pip
1788
2289
  quorate: cluster === undefined ? true : bool(cluster.quorate)
1789
2290
  };
1790
2291
  }));
1791
- var degradedReason = (target) => clusterHealth(target).pipe(Effect28.map((health) => health.quorate && health.offline.length === 0 ? undefined : `quorate=${String(health.quorate)} offline=[${health.offline.join(", ")}]`));
2292
+ var degradedReason = (target) => clusterHealth(target).pipe(Effect29.map((health) => health.quorate && health.offline.length === 0 ? undefined : `quorate=${String(health.quorate)} offline=[${health.offline.join(", ")}]`));
1792
2293
  var UNREACHABLE = "the task could not be read -- the node stopped answering, or this lease cannot see it";
1793
2294
  var STILL_RUNNING = "still running when this provider stopped waiting";
1794
2295
  var ATTEMPTS = 30;
1795
- var awaitTask = (target, credential, node, upid, attempts = ATTEMPTS) => Effect28.gen(function* () {
2296
+ var awaitTask = (target, credential, node, upid, attempts = ATTEMPTS) => Effect29.gen(function* () {
1796
2297
  const path = `nodes/${node}/tasks/${upid}/status`;
1797
2298
  for (let attempt = 0;attempt < attempts; attempt += 1) {
1798
- const status = yield* pveWith(target, credential, "GET", path).pipe(Effect28.orElseSucceed(() => {
2299
+ const status = yield* pveWith(target, credential, "GET", path).pipe(Effect29.orElseSucceed(() => {
1799
2300
  return;
1800
2301
  }));
1801
2302
  if (status === undefined)
1802
2303
  return UNREACHABLE;
1803
2304
  if (status.status === "stopped")
1804
2305
  return text(status.exitstatus, "stopped with no exitstatus");
1805
- yield* Effect28.sleep("1 second");
2306
+ yield* Effect29.sleep("1 second");
1806
2307
  }
1807
2308
  return STILL_RUNNING;
1808
2309
  });
@@ -1815,17 +2316,17 @@ var guestPath = (where) => `nodes/${where.node}/lxc/${String(where.vmid)}`;
1815
2316
  var CREATE_POLLS = 180;
1816
2317
  var TASK_POLLS = 60;
1817
2318
  var isServerError = (error) => error instanceof PveError && error.status === 500;
1818
- var readLive = (where) => Effect29.gen(function* () {
1819
- const answer = yield* pve(where.target, "read", "GET", `${guestPath(where)}/config`).pipe(Effect29.map((data) => ({ data, error: undefined })), Effect29.catchIf(isServerError, (error) => Effect29.succeed({ data: undefined, error })));
2319
+ var readLive = (where) => Effect30.gen(function* () {
2320
+ const answer = yield* pve(where.target, "read", "GET", `${guestPath(where)}/config`).pipe(Effect30.map((data) => ({ data, error: undefined })), Effect30.catchIf(isServerError, (error) => Effect30.succeed({ data: undefined, error })));
1820
2321
  if (answer.data !== undefined && answer.data !== null)
1821
2322
  return answer.data;
1822
2323
  const rows = yield* pve(where.target, "read", "GET", "cluster/resources?type=vm");
1823
2324
  const found = (rows ?? []).find((row) => Number(row.vmid) === where.vmid);
1824
2325
  if (found !== undefined && found.node === where.node && found.type === "lxc") {
1825
- return yield* Effect29.fail(answer.error ?? new LxcRefusedError(`CT ${String(where.vmid)} is listed but its config read empty.`));
2326
+ return yield* Effect30.fail(answer.error ?? new LxcRefusedError(`CT ${String(where.vmid)} is listed but its config read empty.`));
1826
2327
  }
1827
2328
  if (found !== undefined) {
1828
- return yield* Effect29.fail(new LxcRefusedError(`vmid ${String(where.vmid)} is a ${text(found.type, "guest")} on ` + `${text(found.node, "?")}, not a container on ${where.node}. A migration is not an ` + "update and vmids are cluster-wide: declare the node it is on, or pick a free vmid."));
2329
+ return yield* Effect30.fail(new LxcRefusedError(`vmid ${String(where.vmid)} is a ${text(found.type, "guest")} on ` + `${text(found.node, "?")}, not a container on ${where.node}. A migration is not an ` + "update and vmids are cluster-wide: declare the node it is on, or pick a free vmid."));
1829
2330
  }
1830
2331
  return;
1831
2332
  });
@@ -1835,19 +2336,19 @@ var attributesOf = (where, live) => ({
1835
2336
  rawKeys: rawKeysOf(live),
1836
2337
  vmid: where.vmid
1837
2338
  });
1838
- var task = (where, method, path, what, polls, form) => Effect29.gen(function* () {
2339
+ var task = (where, method, path, what, polls, form) => Effect30.gen(function* () {
1839
2340
  const credential = yield* mint(where.target, "provision");
1840
2341
  const upid = text(yield* pveWith(where.target, credential, method, path, form));
1841
2342
  if (upid === "") {
1842
- return yield* Effect29.fail(new LxcRefusedError(`${what}: PVE returned no task id, so the write cannot be confirmed and is not claimed.`));
2343
+ return yield* Effect30.fail(new LxcRefusedError(`${what}: PVE returned no task id, so the write cannot be confirmed and is not claimed.`));
1843
2344
  }
1844
2345
  const exit = yield* awaitTask(where.target, credential, where.node, upid, polls);
1845
2346
  if (exit !== "OK") {
1846
- return yield* Effect29.fail(new LxcRefusedError(`${what}: task ${upid} ended "${exit}". Read \`pvesh get /nodes/${where.node}/tasks/` + `${upid}/log\` before running the deploy again.`));
2347
+ return yield* Effect30.fail(new LxcRefusedError(`${what}: task ${upid} ended "${exit}". Read \`pvesh get /nodes/${where.node}/tasks/` + `${upid}/log\` before running the deploy again.`));
1847
2348
  }
1848
2349
  });
1849
2350
  var createGuest = (props) => task(props, "POST", `nodes/${props.node}/lxc`, `create CT ${String(props.vmid)}`, CREATE_POLLS, createForm3(props));
1850
- var updateGuest = (props, change, digest) => Effect29.gen(function* () {
2351
+ var updateGuest = (props, change, digest) => Effect30.gen(function* () {
1851
2352
  if (Object.keys(change.put).length > 0 || change.clear.length > 0) {
1852
2353
  const form = withClears({ ...change.put, ...digest === "" ? {} : { digest } }, change.clear);
1853
2354
  yield* pve(props.target, "provision", "PUT", `${guestPath(props)}/config`, form);
@@ -1856,33 +2357,33 @@ var updateGuest = (props, change, digest) => Effect29.gen(function* () {
1856
2357
  yield* task(props, "PUT", `${guestPath(props)}/resize`, `grow ${grow.disk} of CT ${String(props.vmid)} to ${grow.size}`, TASK_POLLS, { disk: grow.disk, size: grow.size });
1857
2358
  }
1858
2359
  });
1859
- var destroyGuest = (where, declared) => Effect29.gen(function* () {
2360
+ var destroyGuest = (where, declared) => Effect30.gen(function* () {
1860
2361
  const live = yield* readLive(where);
1861
2362
  if (live === undefined)
1862
2363
  return;
1863
2364
  const drift = judge(declared, live).drift;
1864
2365
  if (drift.length > 0) {
1865
- return yield* Effect29.fail(new LxcRefusedError(`destroy CT ${String(where.vmid)}: the guest on ${where.node} no longer matches its last ` + `declaration (${drift.join(", ")}), so it may not be the guest this stack made. ` + "Nothing was deleted. Delete it by hand if it should go, or declare it again."));
2366
+ return yield* Effect30.fail(new LxcRefusedError(`destroy CT ${String(where.vmid)}: the guest on ${where.node} no longer matches its last ` + `declaration (${drift.join(", ")}), so it may not be the guest this stack made. ` + "Nothing was deleted. Delete it by hand if it should go, or declare it again."));
1866
2367
  }
1867
2368
  yield* task(where, "DELETE", guestPath(where), `destroy CT ${String(where.vmid)}`, TASK_POLLS);
1868
2369
  });
1869
2370
 
1870
2371
  // src/proxmox/lxc-adoption.ts
1871
2372
  var adoptionRefusal = (props, drift) => `CT ${String(props.vmid)} on ${props.node}: adopting it would change ${drift.join(", ")}. An ` + "adoption never changes a guest, so nothing is written. Declare what is live for these keys " + "(the plan names keys, never values) and plan again; change them after the adoption, as an " + "update.";
1872
- var refuseAdoptedDrift = (owner, props, live, change) => Effect30.gen(function* () {
2373
+ var refuseAdoptedDrift = (owner, props, live, change) => Effect31.gen(function* () {
1873
2374
  if (change.drift.length === 0)
1874
2375
  return;
1875
- const settled2 = (recorded) => Effect30.sync(() => judge(recorded, live).drift.length === 0);
2376
+ const settled2 = (recorded) => Effect31.sync(() => judge(recorded, live).drift.length === 0);
1876
2377
  if (!(yield* adopting(owner, settled2)))
1877
2378
  return;
1878
- return yield* Effect30.fail(new LxcRefusedError(adoptionRefusal(props, change.drift)));
2379
+ return yield* Effect31.fail(new LxcRefusedError(adoptionRefusal(props, change.drift)));
1879
2380
  });
1880
2381
 
1881
2382
  // src/proxmox/lxc.ts
1882
2383
  var ProxmoxLxc = Resource13("Proxmox.Lxc", { defaultRemovalPolicy: "retain" });
1883
- var refused = (reasons) => Effect31.fail(new LxcRefusedError(reasons.join(`
2384
+ var refused = (reasons) => Effect32.fail(new LxcRefusedError(reasons.join(`
1884
2385
  `)));
1885
- var diff = (owner, news, olds) => Effect31.gen(function* () {
2386
+ var diff = (owner, news, olds) => Effect32.gen(function* () {
1886
2387
  const { output } = owner;
1887
2388
  const identity2 = identityRefusals(news, olds, output);
1888
2389
  if (identity2.length > 0)
@@ -1896,7 +2397,7 @@ var diff = (owner, news, olds) => Effect31.gen(function* () {
1896
2397
  const refusals = createRefusals(news);
1897
2398
  if (refusals.length > 0)
1898
2399
  return yield* refused(refusals);
1899
- yield* Effect31.logWarning(`Proxmox.Lxc ${news.node}/${String(news.vmid)}: the cluster lists no guest with this ` + "vmid -- a deploy CREATES it again from ostemplate, with new, empty volumes.");
2400
+ yield* Effect32.logWarning(`Proxmox.Lxc ${news.node}/${String(news.vmid)}: the cluster lists no guest with this ` + "vmid -- a deploy CREATES it again from ostemplate, with new, empty volumes.");
1900
2401
  return { action: "update" };
1901
2402
  }
1902
2403
  const change = judge(news, live);
@@ -1907,10 +2408,10 @@ var diff = (owner, news, olds) => Effect31.gen(function* () {
1907
2408
  if (change.drift.length === 0 && !moved)
1908
2409
  return { action: "noop" };
1909
2410
  const what = change.drift.length === 0 ? `the guest is on ${news.node} now, not ${output.node} -- a deploy re-records it, ` + "writing nothing." : `live config differs from the declaration in ${change.drift.join(", ")} -- a ` + "deploy writes these.";
1910
- yield* Effect31.logWarning(`Proxmox.Lxc ${news.node}/${String(news.vmid)}: ${what}`);
2411
+ yield* Effect32.logWarning(`Proxmox.Lxc ${news.node}/${String(news.vmid)}: ${what}`);
1911
2412
  return { action: "update" };
1912
2413
  });
1913
- var reconcile = (owner, news) => Effect31.gen(function* () {
2414
+ var reconcile = (owner, news) => Effect32.gen(function* () {
1914
2415
  const { output } = owner;
1915
2416
  const identity2 = identityRefusals(news, news, output);
1916
2417
  if (identity2.length > 0)
@@ -1956,12 +2457,12 @@ var whereOf = (olds, output) => ({
1956
2457
  target: olds.target,
1957
2458
  vmid: output?.vmid ?? olds.vmid
1958
2459
  });
1959
- var read = (owner, olds) => Effect31.gen(function* () {
2460
+ var read = (owner, olds) => Effect32.gen(function* () {
1960
2461
  const where = whereOf(olds, owner.output);
1961
2462
  const live = yield* readLive(where);
1962
2463
  if (live === undefined)
1963
2464
  return;
1964
- const settled2 = Effect31.sync(() => judge(olds, live).drift.length === 0);
2465
+ const settled2 = Effect32.sync(() => judge(olds, live).drift.length === 0);
1965
2466
  return yield* ownedRead(owner, attributesOf(where, live), settled2);
1966
2467
  });
1967
2468
  var handlers10 = {
@@ -1973,7 +2474,7 @@ var handlers10 = {
1973
2474
  olds,
1974
2475
  output
1975
2476
  }) => diff({ fqn, instanceId, output }, news, olds),
1976
- list: () => Effect31.succeed([]),
2477
+ list: () => Effect32.succeed([]),
1977
2478
  read: ({
1978
2479
  fqn,
1979
2480
  instanceId,
@@ -1987,11 +2488,11 @@ var handlers10 = {
1987
2488
  output
1988
2489
  }) => reconcile({ fqn, instanceId, output }, news)
1989
2490
  };
1990
- var ProxmoxLxcProvider = () => Provider13.effect(ProxmoxLxc, Effect31.succeed(ProxmoxLxc.Provider.of(handlers10)).pipe(Effect31.map(provingResumes)));
2491
+ var ProxmoxLxcProvider = () => Provider13.effect(ProxmoxLxc, Effect32.succeed(ProxmoxLxc.Provider.of(handlers10)).pipe(Effect32.map(provingResumes)));
1991
2492
  // src/proxmox/metric-server.ts
1992
2493
  import { Resource as Resource14 } from "alchemy";
1993
2494
  import * as Provider14 from "alchemy/Provider";
1994
- import * as Effect32 from "effect/Effect";
2495
+ import * as Effect33 from "effect/Effect";
1995
2496
 
1996
2497
  // src/proxmox/metric-server-form.ts
1997
2498
  var UNSET2 = -1;
@@ -2099,22 +2600,22 @@ var handlers11 = pveHandlers({
2099
2600
  path: (props) => `cluster/metrics/server/${props.id}`,
2100
2601
  updateForm: update
2101
2602
  });
2102
- var ProxmoxMetricServerProvider = () => Provider14.effect(ProxmoxMetricServer, Effect32.succeed(ProxmoxMetricServer.Provider.of(handlers11)));
2603
+ var ProxmoxMetricServerProvider = () => Provider14.effect(ProxmoxMetricServer, Effect33.succeed(ProxmoxMetricServer.Provider.of(handlers11)));
2103
2604
  // src/proxmox/network-apply.ts
2104
2605
  import { Resource as Resource15 } from "alchemy";
2105
2606
  import { isResolved as isResolved7 } from "alchemy/Diff";
2106
2607
  import * as Provider15 from "alchemy/Provider";
2107
- import * as Effect33 from "effect/Effect";
2608
+ import * as Effect34 from "effect/Effect";
2108
2609
  var ProxmoxNetworkApply = Resource15("Proxmox.NetworkApply");
2109
- var read2 = (props) => pendingCount(props.target, props.node).pipe(Effect33.map((pending) => ({ node: props.node, pending })));
2110
- var diff2 = (news) => Effect33.gen(function* () {
2610
+ var read2 = (props) => pendingCount(props.target, props.node).pipe(Effect34.map((pending) => ({ node: props.node, pending })));
2611
+ var diff2 = (news) => Effect34.gen(function* () {
2111
2612
  if (!isResolved7(news))
2112
2613
  return;
2113
2614
  const { pending } = yield* read2(news);
2114
2615
  return pending === 0 ? { action: "noop" } : { action: "update" };
2115
2616
  });
2116
- var refuse = (node, why) => Effect33.die(new Error(`Proxmox.NetworkApply ${node}: ${why}`));
2117
- var apply = (props) => Effect33.gen(function* () {
2617
+ var refuse = (node, why) => Effect34.die(new Error(`Proxmox.NetworkApply ${node}: ${why}`));
2618
+ var apply = (props) => Effect34.gen(function* () {
2118
2619
  const node = props.node;
2119
2620
  const staged = yield* pendingCount(props.target, node);
2120
2621
  if (staged === 0)
@@ -2143,17 +2644,17 @@ var apply = (props) => Effect33.gen(function* () {
2143
2644
  return { node, pending: left };
2144
2645
  });
2145
2646
  var handlers12 = {
2146
- delete: () => Effect33.void,
2647
+ delete: () => Effect34.void,
2147
2648
  diff: ({ news }) => diff2(news),
2148
- list: () => Effect33.succeed([]),
2649
+ list: () => Effect34.succeed([]),
2149
2650
  read: ({ olds }) => read2(olds),
2150
2651
  reconcile: ({ news }) => apply(news)
2151
2652
  };
2152
- var ProxmoxNetworkApplyProvider = () => Provider15.effect(ProxmoxNetworkApply, Effect33.succeed(ProxmoxNetworkApply.Provider.of(handlers12)));
2653
+ var ProxmoxNetworkApplyProvider = () => Provider15.effect(ProxmoxNetworkApply, Effect34.succeed(ProxmoxNetworkApply.Provider.of(handlers12)));
2153
2654
  // src/proxmox/node-network.ts
2154
2655
  import { Resource as Resource16 } from "alchemy";
2155
2656
  import * as Provider16 from "alchemy/Provider";
2156
- import * as Effect34 from "effect/Effect";
2657
+ import * as Effect35 from "effect/Effect";
2157
2658
 
2158
2659
  // src/proxmox/node-network-form.ts
2159
2660
  var UNSET3 = -1;
@@ -2233,11 +2734,11 @@ var handlers13 = pveHandlers({
2233
2734
  path: (props) => `nodes/${props.node}/network/${props.iface}`,
2234
2735
  updateForm: updateBody2
2235
2736
  });
2236
- var ProxmoxNodeNetworkProvider = () => Provider16.effect(ProxmoxNodeNetwork, Effect34.succeed(ProxmoxNodeNetwork.Provider.of(handlers13)));
2737
+ var ProxmoxNodeNetworkProvider = () => Provider16.effect(ProxmoxNodeNetwork, Effect35.succeed(ProxmoxNodeNetwork.Provider.of(handlers13)));
2237
2738
  // src/proxmox/notification-matcher.ts
2238
2739
  import { Resource as Resource17 } from "alchemy";
2239
2740
  import * as Provider17 from "alchemy/Provider";
2240
- import * as Effect35 from "effect/Effect";
2741
+ import * as Effect36 from "effect/Effect";
2241
2742
 
2242
2743
  // src/proxmox/notification-matcher-form.ts
2243
2744
  var list = (value) => Array.isArray(value) ? value.map((item) => String(item)) : [];
@@ -2319,15 +2820,19 @@ var handlers14 = pveHandlers({
2319
2820
  attributes: (live, props) => matcherAttributes(live, props.name),
2320
2821
  collection: () => "cluster/notifications/matchers",
2321
2822
  createForm: matcherCreateForm,
2823
+ endpoint: {
2824
+ create: "pve:POST /cluster/notifications/matchers",
2825
+ update: "pve:PUT /cluster/notifications/matchers/{name}"
2826
+ },
2322
2827
  matches: matcherMatches,
2323
2828
  path: (props) => `cluster/notifications/matchers/${props.name}`,
2324
2829
  updateForm: matcherUpdateForm
2325
2830
  });
2326
- var ProxmoxNotificationMatcherProvider = () => Provider17.effect(ProxmoxNotificationMatcher, Effect35.succeed(ProxmoxNotificationMatcher.Provider.of(handlers14)));
2831
+ var ProxmoxNotificationMatcherProvider = () => Provider17.effect(ProxmoxNotificationMatcher, Effect36.succeed(ProxmoxNotificationMatcher.Provider.of(handlers14)));
2327
2832
  // src/proxmox/notification-target.ts
2328
2833
  import { Resource as Resource18 } from "alchemy";
2329
2834
  import * as Provider18 from "alchemy/Provider";
2330
- import * as Effect36 from "effect/Effect";
2835
+ import * as Effect37 from "effect/Effect";
2331
2836
 
2332
2837
  // src/proxmox/notification-target-form.ts
2333
2838
  var REPEATED_KEY = "\x00";
@@ -2383,15 +2888,15 @@ var handlers15 = pveHandlers({
2383
2888
  path: (props) => `cluster/notifications/endpoints/${props.type}/${props.name}`,
2384
2889
  updateForm: shape4
2385
2890
  });
2386
- var ProxmoxNotificationTargetProvider = () => Provider18.effect(ProxmoxNotificationTarget, Effect36.succeed(ProxmoxNotificationTarget.Provider.of(handlers15)));
2891
+ var ProxmoxNotificationTargetProvider = () => Provider18.effect(ProxmoxNotificationTarget, Effect37.succeed(ProxmoxNotificationTarget.Provider.of(handlers15)));
2387
2892
  // src/proxmox/pbs-datastore.ts
2388
2893
  import { Resource as Resource19 } from "alchemy";
2389
2894
  import { isResolved as isResolved8 } from "alchemy/Diff";
2390
2895
  import * as Provider19 from "alchemy/Provider";
2391
- import * as Effect39 from "effect/Effect";
2896
+ import * as Effect41 from "effect/Effect";
2392
2897
 
2393
2898
  // src/proxmox/pbs-datastore-form.ts
2394
- import * as Effect37 from "effect/Effect";
2899
+ import * as Effect38 from "effect/Effect";
2395
2900
  var attributes = (live, props) => ({
2396
2901
  backend: propertyString(live["backend"]),
2397
2902
  comment: text(live["comment"]),
@@ -2443,37 +2948,51 @@ var createForm4 = (props) => ({
2443
2948
  });
2444
2949
  var updateForm = (props) => mutable(props);
2445
2950
  var object2 = (props) => `config/datastore/${props.name}`;
2446
- var readOne = (props) => pve(props.target, "read", "GET", object2(props)).pipe(Effect37.map((live) => live === undefined || typeof live["name"] !== "string" ? undefined : attributes(live, props)), Effect37.orElseSucceed(() => {
2951
+ var readOne = (props) => pve(props.target, "read", "GET", object2(props)).pipe(Effect38.map((live) => live === undefined || typeof live["name"] !== "string" ? undefined : attributes(live, props)), Effect38.orElseSucceed(() => {
2447
2952
  return;
2448
2953
  }));
2449
2954
 
2450
2955
  // src/proxmox/pbs-datastore-guard.ts
2451
- import * as Effect38 from "effect/Effect";
2956
+ import * as Effect39 from "effect/Effect";
2452
2957
  var samePath = (left, right) => {
2453
2958
  const trim = (value) => value.length > 1 ? value.replace(/\/+$/, "") : value;
2454
2959
  return trim(left) === trim(right);
2455
2960
  };
2456
- var guardPath = (live, props) => live.path === "" || samePath(live.path, props.path) ? Effect38.void : Effect38.die(new Error(`${object2(props)}: declared path ${props.path} but PBS holds ${live.path}. \`path\` ` + "is create-only, so this cannot be reconciled and will NOT be planned as a silent " + "noop: backups would keep landing in the old directory behind a green plan. Correct " + "the declaration, or remove the datastore and declare a new one -- the contents live, " + "because `destroy-data` is never sent."));
2457
- var guardBackend = (live, props) => props.backend === undefined || propertyString(props.backend) === live.backend ? Effect38.void : Effect38.die(new Error(`${object2(props)}: declared backend ${props.backend} but PBS holds ` + `${live.backend === "" ? "(none — a local datastore)" : live.backend}. \`backend\` ` + "is create-only: PBS has no update for it, so this cannot be reconciled and will NOT " + "be planned as a silent noop. Correct the declaration to match what is really there, " + "or remove the datastore and declare a new one -- the contents live, because " + "`destroy-data` is never sent."));
2458
- var settle3 = (read3, done) => Effect38.gen(function* () {
2961
+ var guardPath = (live, props) => live.path === "" || samePath(live.path, props.path) ? Effect39.void : Effect39.die(new Error(`${object2(props)}: declared path ${props.path} but PBS holds ${live.path}. \`path\` ` + "is create-only, so this cannot be reconciled and will NOT be planned as a silent " + "noop: backups would keep landing in the old directory behind a green plan. Correct " + "the declaration, or remove the datastore and declare a new one -- the contents live, " + "because `destroy-data` is never sent."));
2962
+ var guardBackend = (live, props) => props.backend === undefined || propertyString(props.backend) === live.backend ? Effect39.void : Effect39.die(new Error(`${object2(props)}: declared backend ${props.backend} but PBS holds ` + `${live.backend === "" ? "(none — a local datastore)" : live.backend}. \`backend\` ` + "is create-only: PBS has no update for it, so this cannot be reconciled and will NOT " + "be planned as a silent noop. Correct the declaration to match what is really there, " + "or remove the datastore and declare a new one -- the contents live, because " + "`destroy-data` is never sent."));
2963
+ var settle3 = (read3, done) => Effect39.gen(function* () {
2459
2964
  for (let attempt = 0;attempt < 60; attempt += 1) {
2460
2965
  const live = yield* read3;
2461
2966
  if (done(live))
2462
2967
  return live;
2463
- yield* Effect38.sleep("2 seconds");
2968
+ yield* Effect39.sleep("2 seconds");
2464
2969
  }
2465
2970
  return yield* read3;
2466
2971
  });
2467
2972
 
2973
+ // src/proxmox/pbs-datastore-endpoint.ts
2974
+ import * as Effect40 from "effect/Effect";
2975
+ var DATASTORE_ENDPOINT = {
2976
+ create: "pbs:POST /config/datastore",
2977
+ update: "pbs:PUT /config/datastore/{name}"
2978
+ };
2979
+ var guardDatastoreForms = (props) => Effect40.gen(function* () {
2980
+ yield* guardForm(DATASTORE_ENDPOINT.create, createForm4(props), true);
2981
+ yield* guardForm(DATASTORE_ENDPOINT.update, updateForm(props), false);
2982
+ });
2983
+
2468
2984
  // src/proxmox/pbs-datastore.ts
2469
2985
  var PbsDatastore = Resource19("Pbs.Datastore", {
2470
2986
  defaultRemovalPolicy: "retain"
2471
2987
  });
2472
- var PbsDatastoreProvider = () => Provider19.effect(PbsDatastore, Effect39.succeed(PbsDatastore.Provider.of({
2473
- list: () => Effect39.succeed([]),
2988
+ var PbsDatastoreProvider = () => Provider19.effect(PbsDatastore, Effect41.succeed(PbsDatastore.Provider.of({
2989
+ list: () => Effect41.succeed([]),
2474
2990
  read: ({ olds }) => readOne(olds),
2475
- diff: ({ news, output }) => Effect39.gen(function* () {
2476
- if (output === undefined || !isResolved8(news))
2991
+ diff: ({ news, output }) => Effect41.gen(function* () {
2992
+ if (!isResolved8(news))
2993
+ return;
2994
+ yield* guardDatastoreForms(news);
2995
+ if (output === undefined)
2477
2996
  return;
2478
2997
  const live = yield* readOne(news);
2479
2998
  if (live === undefined)
@@ -2482,7 +3001,8 @@ var PbsDatastoreProvider = () => Provider19.effect(PbsDatastore, Effect39.succee
2482
3001
  yield* guardBackend(live, news);
2483
3002
  return matches(live, news) ? { action: "noop" } : { action: "update" };
2484
3003
  }),
2485
- reconcile: Effect39.fn(function* ({ news }) {
3004
+ reconcile: Effect41.fn(function* ({ news }) {
3005
+ yield* guardDatastoreForms(news);
2486
3006
  const live = yield* readOne(news);
2487
3007
  let upid;
2488
3008
  if (live === undefined) {
@@ -2497,40 +3017,44 @@ var PbsDatastoreProvider = () => Provider19.effect(PbsDatastore, Effect39.succee
2497
3017
  }
2498
3018
  const after = yield* settle3(readOne(news), (row) => row !== undefined && matches(row, news));
2499
3019
  if (after === undefined || !matches(after, news)) {
2500
- return yield* Effect39.die(new Error(`${object2(news)}: the write returned no error but the datastore still does not ` + `match the declaration after 120s. PBS answered with task ${upid ?? "(none)"} ` + "-- read it with `proxmox-backup-manager task log <upid>`. A slow disk is a " + "plausible cause (see `settle`); a create over an existing chunk store is not, " + "because PBS refuses that outright rather than timing out."));
3020
+ return yield* Effect41.die(new Error(`${object2(news)}: the write returned no error but the datastore still does not ` + `match the declaration after 120s. PBS answered with task ${upid ?? "(none)"} ` + "-- read it with `proxmox-backup-manager task log <upid>`. A slow disk is a " + "plausible cause (see `settle`); a create over an existing chunk store is not, " + "because PBS refuses that outright rather than timing out."));
2501
3021
  }
2502
3022
  return after;
2503
3023
  }),
2504
- delete: Effect39.fn(function* ({ olds }) {
3024
+ delete: Effect41.fn(function* ({ olds }) {
2505
3025
  yield* pve(olds.target, "provision", "DELETE", object2(olds));
2506
3026
  const left = yield* settle3(readOne(olds), (row) => row === undefined);
2507
3027
  if (left !== undefined) {
2508
- return yield* Effect39.die(new Error(`${object2(olds)}: the DELETE returned no error but the datastore is still in ` + "datastore.cfg after 120s. Either a backup, verify or GC task still holds it, " + "or the provision role lacks Datastore.Allocate on /datastore/" + `${olds.name}. Read the task log with \`proxmox-backup-manager task log <upid>\`.`));
3028
+ return yield* Effect41.die(new Error(`${object2(olds)}: the DELETE returned no error but the datastore is still in ` + "datastore.cfg after 120s. Either a backup, verify or GC task still holds it, " + "or the provision role lacks Datastore.Allocate on /datastore/" + `${olds.name}. Read the task log with \`proxmox-backup-manager task log <upid>\`.`));
2509
3029
  }
2510
3030
  })
2511
3031
  })));
2512
3032
  // src/proxmox/pbs-notification-matcher.ts
2513
3033
  import { Resource as Resource20 } from "alchemy";
2514
3034
  import * as Provider20 from "alchemy/Provider";
2515
- import * as Effect40 from "effect/Effect";
3035
+ import * as Effect42 from "effect/Effect";
2516
3036
  var PbsNotificationMatcher = Resource20("Pbs.NotificationMatcher");
2517
3037
  var handlers16 = pveHandlers({
2518
3038
  attributes: (live, props) => matcherAttributes(live, props.name),
2519
3039
  collection: () => "config/notifications/matchers",
2520
3040
  createForm: matcherCreateForm,
3041
+ endpoint: {
3042
+ create: "pbs:POST /config/notifications/matchers",
3043
+ update: "pbs:PUT /config/notifications/matchers/{name}"
3044
+ },
2521
3045
  matches: matcherMatches,
2522
3046
  path: (props) => `config/notifications/matchers/${props.name}`,
2523
3047
  updateForm: matcherUpdateForm
2524
3048
  });
2525
- var PbsNotificationMatcherProvider = () => Provider20.effect(PbsNotificationMatcher, Effect40.succeed(PbsNotificationMatcher.Provider.of(handlers16)));
3049
+ var PbsNotificationMatcherProvider = () => Provider20.effect(PbsNotificationMatcher, Effect42.succeed(PbsNotificationMatcher.Provider.of(handlers16)));
2526
3050
  // src/proxmox/pbs-notification-target.ts
2527
3051
  import { Resource as Resource21 } from "alchemy";
2528
3052
  import * as Provider21 from "alchemy/Provider";
2529
- import * as Effect42 from "effect/Effect";
3053
+ import * as Effect44 from "effect/Effect";
2530
3054
 
2531
3055
  // src/proxmox/pbs-notification-target-lifecycle.ts
2532
3056
  import { isResolved as isResolved9 } from "alchemy/Diff";
2533
- import * as Effect41 from "effect/Effect";
3057
+ import * as Effect43 from "effect/Effect";
2534
3058
 
2535
3059
  // src/proxmox/credential-literals.ts
2536
3060
  var READS_SECRET = /\{\{[^}]*\bsecrets\./;
@@ -2790,12 +3314,12 @@ var targetForm = (props, groups, carry, mode) => {
2790
3314
 
2791
3315
  // src/proxmox/pbs-notification-target-lifecycle.ts
2792
3316
  var path = (props) => `config/notifications/endpoints/${props.type}/${props.name}`;
2793
- var readLive2 = (props) => pve(props.target, "read", "GET", path(props)).pipe(Effect41.map((data) => data == null ? undefined : targetAttributes(data, props)), Effect41.orElseSucceed(() => {
3317
+ var readLive2 = (props) => pve(props.target, "read", "GET", path(props)).pipe(Effect43.map((data) => data == null ? undefined : targetAttributes(data, props)), Effect43.orElseSucceed(() => {
2794
3318
  return;
2795
3319
  }));
2796
3320
  var refuse2 = (props) => {
2797
3321
  const reasons = refusals(props);
2798
- return reasons.length === 0 ? Effect41.void : Effect41.die(new Error(`${path(props)}: ${reasons.join("; ")}.`));
3322
+ return reasons.length === 0 ? Effect43.void : Effect43.die(new Error(`${path(props)}: ${reasons.join("; ")}.`));
2799
3323
  };
2800
3324
  var toCarry = (live, props, groups, sealed) => {
2801
3325
  const header = headerState(live, props, groups) === "stale";
@@ -2807,16 +3331,16 @@ var requireValues = (props, groups, carry) => {
2807
3331
  ...carry.header ? groups.header.missing : [],
2808
3332
  ...carry.sealed ? groups.sealed.missing : []
2809
3333
  ];
2810
- return missing.length === 0 ? Effect41.void : Effect41.die(new Error(`${path(props)}: this write must send write-only values, and ${missing.join(", ")} ` + "is unset or empty in the deploying environment. Export it and deploy again."));
3334
+ return missing.length === 0 ? Effect43.void : Effect43.die(new Error(`${path(props)}: this write must send write-only values, and ${missing.join(", ")} ` + "is unset or empty in the deploying environment. Export it and deploy again."));
2811
3335
  };
2812
3336
  var handlers17 = {
2813
- list: () => Effect41.succeed([]),
2814
- read: ({ olds, output }) => (output === undefined ? refuse2(olds) : Effect41.void).pipe(Effect41.andThen(readLive2(olds)), Effect41.map((live) => live === undefined ? undefined : { ...live, sealed: output?.sealed ?? "" })),
3337
+ list: () => Effect43.succeed([]),
3338
+ read: ({ olds, output }) => (output === undefined ? refuse2(olds) : Effect43.void).pipe(Effect43.andThen(readLive2(olds)), Effect43.map((live) => live === undefined ? undefined : { ...live, sealed: output?.sealed ?? "" })),
2815
3339
  diff: ({
2816
3340
  news,
2817
3341
  olds,
2818
3342
  output
2819
- }) => Effect41.gen(function* () {
3343
+ }) => Effect43.gen(function* () {
2820
3344
  if (output === undefined || !isResolved9(news))
2821
3345
  return;
2822
3346
  if (olds !== undefined && (olds.type !== news.type || olds.name !== news.name)) {
@@ -2828,7 +3352,7 @@ var handlers17 = {
2828
3352
  return { action: "update" };
2829
3353
  return toCarry(live, news, resolveGroups(news), output.sealed) === undefined ? { action: "noop" } : { action: "update" };
2830
3354
  }),
2831
- reconcile: ({ news, output }) => Effect41.gen(function* () {
3355
+ reconcile: ({ news, output }) => Effect43.gen(function* () {
2832
3356
  yield* refuse2(news);
2833
3357
  const groups = resolveGroups(news);
2834
3358
  const live = yield* readLive2(news);
@@ -2850,7 +3374,7 @@ var handlers17 = {
2850
3374
  }
2851
3375
  const after = yield* readLive2(news);
2852
3376
  if (after === undefined) {
2853
- return yield* Effect41.die(new Error(`${path(news)}: the write returned no error but the target is still absent.`));
3377
+ return yield* Effect43.die(new Error(`${path(news)}: the write returned no error but the target is still absent.`));
2854
3378
  }
2855
3379
  return { ...after, sealed };
2856
3380
  }),
@@ -2859,11 +3383,11 @@ var handlers17 = {
2859
3383
 
2860
3384
  // src/proxmox/pbs-notification-target.ts
2861
3385
  var PbsNotificationTarget = Resource21("Pbs.NotificationTarget");
2862
- var PbsNotificationTargetProvider = () => Provider21.effect(PbsNotificationTarget, Effect42.succeed(PbsNotificationTarget.Provider.of(handlers17)));
3386
+ var PbsNotificationTargetProvider = () => Provider21.effect(PbsNotificationTarget, Effect44.succeed(PbsNotificationTarget.Provider.of(handlers17)));
2863
3387
  // src/proxmox/pbs-prune-job.ts
2864
3388
  import { Resource as Resource22 } from "alchemy";
2865
3389
  import * as Provider22 from "alchemy/Provider";
2866
- import * as Effect43 from "effect/Effect";
3390
+ import * as Effect45 from "effect/Effect";
2867
3391
 
2868
3392
  // src/proxmox/pbs-prune-job-form.ts
2869
3393
  var KEEP_KEYS = [
@@ -2934,15 +3458,16 @@ var handlers18 = pveHandlers({
2934
3458
  }),
2935
3459
  collection: () => "config/prune",
2936
3460
  createForm: createBody2,
3461
+ endpoint: { create: "pbs:POST /config/prune", update: "pbs:PUT /config/prune/{id}" },
2937
3462
  matches: (attributes2, props) => attributes2.schedule === props.schedule && attributes2.store === props.store && same6(props.ns, attributes2.ns) && same6(props["max-depth"], attributes2["max-depth"]) && same6(props.disable, attributes2.disable) && same6(props.comment, attributes2.comment) && keepsMatch(attributes2, props),
2938
3463
  path: (props) => `config/prune/${props.id}`,
2939
3464
  updateForm: updateBody3
2940
3465
  });
2941
- var PbsPruneJobProvider = () => Provider22.effect(PbsPruneJob, Effect43.succeed(PbsPruneJob.Provider.of(handlers18)));
3466
+ var PbsPruneJobProvider = () => Provider22.effect(PbsPruneJob, Effect45.succeed(PbsPruneJob.Provider.of(handlers18)));
2942
3467
  // src/proxmox/pbs-sync-job.ts
2943
3468
  import { Resource as Resource23 } from "alchemy";
2944
3469
  import * as Provider23 from "alchemy/Provider";
2945
- import * as Effect44 from "effect/Effect";
3470
+ import * as Effect46 from "effect/Effect";
2946
3471
 
2947
3472
  // src/proxmox/pbs-sync-job-form.ts
2948
3473
  var UNSET4 = -1;
@@ -3011,6 +3536,7 @@ var handlers19 = pveHandlers({
3011
3536
  }),
3012
3537
  collection: () => "config/sync",
3013
3538
  createForm: createBody3,
3539
+ endpoint: { create: "pbs:POST /config/sync", update: "pbs:PUT /config/sync/{id}" },
3014
3540
  matches: (attributes2, props) => attributes2["remove-vanished"] === (props["remove-vanished"] === true) && attributes2.store === props.store && attributes2["remote-store"] === props["remote-store"] && (props.remote === undefined || attributes2.remote === props.remote) && (props.ns === undefined || attributes2.ns === props.ns) && (props["remote-ns"] === undefined || attributes2["remote-ns"] === props["remote-ns"]) && (props.schedule === undefined || attributes2.schedule === props.schedule) && (props.comment === undefined || attributes2.comment === props.comment) && (props.owner === undefined || attributes2.owner === props.owner) && (props["verified-only"] === undefined || attributes2["verified-only"] === props["verified-only"]) && (props["rate-in"] === undefined || attributes2["rate-in"] === props["rate-in"]) && (props["max-depth"] === undefined || attributes2["max-depth"] === props["max-depth"]) && (props["transfer-last"] === undefined || attributes2["transfer-last"] === props["transfer-last"]),
3015
3541
  path: (props) => `config/sync/${props.id}`,
3016
3542
  updateForm: updateBody4
@@ -3018,11 +3544,11 @@ var handlers19 = pveHandlers({
3018
3544
 
3019
3545
  // src/proxmox/pbs-sync-job.ts
3020
3546
  var PbsSyncJob = Resource23("Pbs.SyncJob");
3021
- var PbsSyncJobProvider = () => Provider23.effect(PbsSyncJob, Effect44.succeed(PbsSyncJob.Provider.of(handlers19)));
3547
+ var PbsSyncJobProvider = () => Provider23.effect(PbsSyncJob, Effect46.succeed(PbsSyncJob.Provider.of(handlers19)));
3022
3548
  // src/proxmox/pbs-verify-job.ts
3023
3549
  import { Resource as Resource24 } from "alchemy";
3024
3550
  import * as Provider24 from "alchemy/Provider";
3025
- import * as Effect45 from "effect/Effect";
3551
+ import * as Effect47 from "effect/Effect";
3026
3552
 
3027
3553
  // src/proxmox/pbs-verify-job-form.ts
3028
3554
  var rechecks = (ignoreVerified, outdatedAfter) => {
@@ -3062,15 +3588,16 @@ var handlers20 = pveHandlers({
3062
3588
  },
3063
3589
  collection: () => "config/verify",
3064
3590
  createForm: (props) => ({ ...shape7(props), id: props.id }),
3591
+ endpoint: { create: "pbs:POST /config/verify", update: "pbs:PUT /config/verify/{id}" },
3065
3592
  matches: (attributes2, props) => attributes2.store === props.store && attributes2.schedule === (props.schedule ?? "") && attributes2["ignore-verified"] === (props["ignore-verified"] !== false) && (props["outdated-after"] === undefined || attributes2["outdated-after"] === props["outdated-after"]) && (props["max-depth"] === undefined || attributes2["max-depth"] === props["max-depth"]) && (props.ns === undefined || attributes2.ns === props.ns) && (props.comment === undefined || attributes2.comment === props.comment),
3066
3593
  path: (props) => `config/verify/${props.id}`,
3067
3594
  updateForm: shape7
3068
3595
  });
3069
- var PbsVerifyJobProvider = () => Provider24.effect(PbsVerifyJob, Effect45.succeed(PbsVerifyJob.Provider.of(handlers20)));
3596
+ var PbsVerifyJobProvider = () => Provider24.effect(PbsVerifyJob, Effect47.succeed(PbsVerifyJob.Provider.of(handlers20)));
3070
3597
  // src/proxmox/pool.ts
3071
3598
  import { Resource as Resource25 } from "alchemy";
3072
3599
  import * as Provider25 from "alchemy/Provider";
3073
- import * as Effect46 from "effect/Effect";
3600
+ import * as Effect48 from "effect/Effect";
3074
3601
  var ProxmoxPool = Resource25("Proxmox.Pool");
3075
3602
  var poolCreateForm = (props) => ({
3076
3603
  comment: props.comment ?? "",
@@ -3090,11 +3617,12 @@ var handlers21 = pveHandlers({
3090
3617
  },
3091
3618
  collection: () => "pools",
3092
3619
  createForm: poolCreateForm,
3620
+ endpoint: { create: "pve:POST /pools", update: "pve:PUT /pools/{poolid}" },
3093
3621
  matches: (attributes2, props) => attributes2.comment === (props.comment ?? ""),
3094
3622
  path: (props) => `pools/${props.poolid}`,
3095
3623
  updateForm: poolUpdateForm
3096
3624
  });
3097
- var ProxmoxPoolProvider = () => Provider25.effect(ProxmoxPool, Effect46.succeed(ProxmoxPool.Provider.of(handlers21)));
3625
+ var ProxmoxPoolProvider = () => Provider25.effect(ProxmoxPool, Effect48.succeed(ProxmoxPool.Provider.of(handlers21)));
3098
3626
  // src/proxmox/provision-baseline.ts
3099
3627
  var PROVISION_PRIVILEGES = Object.freeze([
3100
3628
  "Datastore.Allocate",
@@ -3135,8 +3663,8 @@ var PROVISION_DEFAULTS = Object.freeze({
3135
3663
  });
3136
3664
  var ID = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
3137
3665
  var USERID = /^[A-Za-z0-9][A-Za-z0-9._-]*@[A-Za-z][A-Za-z0-9._-]*$/;
3138
- var COMMENT = /^[A-Za-z0-9 ._,()@/+-]*$/;
3139
- var problems2 = (names2) => {
3666
+ var COMMENT = /^[A-Za-z0-9 ._,:()@/+-]*$/;
3667
+ var problems2 = (names2, comments, given) => {
3140
3668
  const found = [];
3141
3669
  if (!ID.test(names2.role))
3142
3670
  found.push(`role \`${names2.role}\` is not a PVE role id`);
@@ -3154,17 +3682,36 @@ var problems2 = (names2) => {
3154
3682
  }
3155
3683
  if (names2.readUser === names2.provisionUser)
3156
3684
  found.push("readUser and provisionUser are one user");
3157
- if (!COMMENT.test(names2.comment))
3158
- found.push("comment holds a character the bootstrap will not quote");
3685
+ const source = (override, field6) => override === undefined ? "comment" : field6;
3686
+ const checked = [
3687
+ [source(given.groupComment, "groupComment"), comments.group],
3688
+ [source(given.provisionComment, "provisionComment"), comments.provision]
3689
+ ];
3690
+ if (names2.readUser !== null) {
3691
+ checked.push([source(given.readComment, "readComment"), comments.read]);
3692
+ }
3693
+ const said = new Set;
3694
+ for (const [field6, comment3] of checked) {
3695
+ if (COMMENT.test(comment3) || said.has(field6))
3696
+ continue;
3697
+ said.add(field6);
3698
+ found.push(`${field6} holds a character the bootstrap will not quote`);
3699
+ }
3159
3700
  return found;
3160
3701
  };
3161
3702
  var provisionBaseline = (names2 = {}) => {
3162
- const given = Object.entries(names2).filter(([, value]) => value !== undefined);
3703
+ const { groupComment, provisionComment, readComment, ...core } = names2;
3704
+ const given = Object.entries(core).filter(([, value]) => value !== undefined);
3163
3705
  const resolved = {
3164
3706
  ...PROVISION_DEFAULTS,
3165
3707
  ...Object.fromEntries(given)
3166
3708
  };
3167
- const found = problems2(resolved);
3709
+ const comments = {
3710
+ group: groupComment ?? resolved.comment,
3711
+ provision: provisionComment ?? resolved.comment,
3712
+ read: readComment ?? resolved.comment
3713
+ };
3714
+ const found = problems2(resolved, comments, names2);
3168
3715
  if (found.length > 0)
3169
3716
  throw new Error(`provision baseline: ${found.join("; ")}`);
3170
3717
  const lanes = [
@@ -3174,10 +3721,10 @@ var provisionBaseline = (names2 = {}) => {
3174
3721
  lanes.push(["read", resolved.readUser, resolved.readRole]);
3175
3722
  return {
3176
3723
  grants: lanes.map(([lane, userid, roleid]) => ({ lane, roleid, userid })),
3177
- group: { comment: resolved.comment, groupid: resolved.mintGroup },
3724
+ group: { comment: comments.group, groupid: resolved.mintGroup },
3178
3725
  role: { privs: PROVISION_PRIVILEGES, roleid: resolved.role },
3179
3726
  users: lanes.map(([lane, userid]) => ({
3180
- comment: resolved.comment,
3727
+ comment: comments[lane],
3181
3728
  groups: [resolved.mintGroup],
3182
3729
  lane,
3183
3730
  userid
@@ -3268,12 +3815,12 @@ echo 'provision baseline: done. Next: point the OpenBao proxmox mount at these u
3268
3815
  // src/proxmox/provision-declare.ts
3269
3816
  import { adopt } from "alchemy/AdoptPolicy";
3270
3817
  import * as RemovalPolicy from "alchemy/RemovalPolicy";
3271
- import * as Effect49 from "effect/Effect";
3818
+ import * as Effect51 from "effect/Effect";
3272
3819
 
3273
3820
  // src/proxmox/role.ts
3274
3821
  import { Resource as Resource26 } from "alchemy";
3275
3822
  import * as Provider26 from "alchemy/Provider";
3276
- import * as Effect47 from "effect/Effect";
3823
+ import * as Effect49 from "effect/Effect";
3277
3824
  var ProxmoxRole = Resource26("Proxmox.Role");
3278
3825
  var canonical2 = (privs) => [...new Set(privs.map((priv) => priv.trim()).filter((priv) => priv.length > 0))].sort();
3279
3826
  var livePrivs = (live) => {
@@ -3286,16 +3833,17 @@ var handlers22 = pveHandlers({
3286
3833
  attributes: (live, props) => ({ privs: livePrivs(live), roleid: props.roleid }),
3287
3834
  collection: () => "access/roles",
3288
3835
  createForm: (props) => ({ privs: canonical2(props.privs).join(","), roleid: props.roleid }),
3836
+ endpoint: { create: "pve:POST /access/roles", update: "pve:PUT /access/roles/{roleid}" },
3289
3837
  matches: (attributes2, props) => canonical2(attributes2.privs).join(",") === canonical2(props.privs).join(","),
3290
3838
  path: (props) => `access/roles/${props.roleid}`,
3291
3839
  updateForm: (props) => ({ privs: canonical2(props.privs).join(",") })
3292
3840
  });
3293
- var ProxmoxRoleProvider = () => Provider26.effect(ProxmoxRole, Effect47.succeed(ProxmoxRole.Provider.of(handlers22)));
3841
+ var ProxmoxRoleProvider = () => Provider26.effect(ProxmoxRole, Effect49.succeed(ProxmoxRole.Provider.of(handlers22)));
3294
3842
 
3295
3843
  // src/proxmox/user.ts
3296
3844
  import { Resource as Resource27 } from "alchemy";
3297
3845
  import * as Provider27 from "alchemy/Provider";
3298
- import * as Effect48 from "effect/Effect";
3846
+ import * as Effect50 from "effect/Effect";
3299
3847
  var ProxmoxUser = Resource27("Proxmox.User");
3300
3848
  var str2 = (value) => typeof value === "string" ? value : "";
3301
3849
  var int2 = (value) => {
@@ -3331,15 +3879,16 @@ var handlers23 = pveHandlers({
3331
3879
  }),
3332
3880
  collection: () => "access/users",
3333
3881
  createForm: (props) => ({ ...shape8(props), userid: props.userid }),
3882
+ endpoint: { create: "pve:POST /access/users", update: "pve:PUT /access/users/{userid}" },
3334
3883
  matches: (attributes2, props) => attributes2.comment === (props.comment ?? "") && attributes2.email === (props.email ?? "") && attributes2.enable === (props.enable !== false) && attributes2.expire === (props.expire ?? 0) && attributes2.firstname === (props.firstname ?? "") && attributes2.lastname === (props.lastname ?? "") && attributes2.groups.join(",") === groupSet(props.groups).join(","),
3335
3884
  path: (props) => `access/users/${props.userid}`,
3336
3885
  updateForm: shape8
3337
3886
  });
3338
- var ProxmoxUserProvider = () => Provider27.effect(ProxmoxUser, Effect48.succeed(ProxmoxUser.Provider.of(handlers23)));
3887
+ var ProxmoxUserProvider = () => Provider27.effect(ProxmoxUser, Effect50.succeed(ProxmoxUser.Provider.of(handlers23)));
3339
3888
 
3340
3889
  // src/proxmox/provision-declare.ts
3341
- var declareProvisionBaseline = (id, target, names2 = {}, options = {}) => Effect49.gen(function* () {
3342
- const baseline = yield* Effect49.try({
3890
+ var declareProvisionBaseline = (id, target, names2 = {}, options = {}) => Effect51.gen(function* () {
3891
+ const baseline = yield* Effect51.try({
3343
3892
  catch: (cause) => cause instanceof Error ? cause : new Error(String(cause)),
3344
3893
  try: () => provisionBaseline(names2)
3345
3894
  });
@@ -3353,13 +3902,13 @@ var declareProvisionBaseline = (id, target, names2 = {}, options = {}) => Effect
3353
3902
  target
3354
3903
  }));
3355
3904
  const group = yield* owned(ProxmoxGroup(`${id}-mint-group`, { ...baseline.group, target }));
3356
- const users = yield* Effect49.all(baseline.users.map((user) => owned(ProxmoxUser(`${id}-${user.lane}-user`, {
3905
+ const users = yield* Effect51.all(baseline.users.map((user) => owned(ProxmoxUser(`${id}-${user.lane}-user`, {
3357
3906
  comment: user.comment,
3358
3907
  groups: [...user.groups],
3359
3908
  target,
3360
3909
  userid: user.userid
3361
3910
  }))));
3362
- const grants = yield* Effect49.all(baseline.grants.map((grant) => owned(ProxmoxAcl(`${id}-${grant.lane}-grant`, {
3911
+ const grants = yield* Effect51.all(baseline.grants.map((grant) => owned(ProxmoxAcl(`${id}-${grant.lane}-grant`, {
3363
3912
  path: "/",
3364
3913
  propagate: true,
3365
3914
  roleid: grant.roleid,
@@ -3372,7 +3921,7 @@ var declareProvisionBaseline = (id, target, names2 = {}, options = {}) => Effect
3372
3921
  // src/proxmox/qemu.ts
3373
3922
  import { Resource as Resource28 } from "alchemy";
3374
3923
  import * as Provider28 from "alchemy/Provider";
3375
- import * as Effect50 from "effect/Effect";
3924
+ import * as Effect52 from "effect/Effect";
3376
3925
  var ProxmoxVm = Resource28("Proxmox.Vm");
3377
3926
  var shape9 = (props) => ({
3378
3927
  cores: String(props.cores ?? 1),
@@ -3398,11 +3947,11 @@ var handlers24 = pveHandlers({
3398
3947
  path: (props) => `nodes/${props.node}/qemu/${String(props.vmid)}/config`,
3399
3948
  updateForm: shape9
3400
3949
  });
3401
- var ProxmoxVmProvider = () => Provider28.effect(ProxmoxVm, Effect50.succeed(ProxmoxVm.Provider.of(handlers24)));
3950
+ var ProxmoxVmProvider = () => Provider28.effect(ProxmoxVm, Effect52.succeed(ProxmoxVm.Provider.of(handlers24)));
3402
3951
  // src/proxmox/replication-job.ts
3403
3952
  import { Resource as Resource29 } from "alchemy";
3404
3953
  import * as Provider29 from "alchemy/Provider";
3405
- import * as Effect51 from "effect/Effect";
3954
+ import * as Effect53 from "effect/Effect";
3406
3955
 
3407
3956
  // src/proxmox/replication-job-form.ts
3408
3957
  var DEFAULT_SCHEDULE = "*/15";
@@ -3448,19 +3997,23 @@ var handlers25 = pveHandlers({
3448
3997
  }),
3449
3998
  collection: () => "cluster/replication",
3450
3999
  createForm: createBody4,
4000
+ endpoint: {
4001
+ create: "pve:POST /cluster/replication",
4002
+ update: "pve:PUT /cluster/replication/{id}"
4003
+ },
3451
4004
  matches: (attributes2, props) => attributes2.remove_job === "" && attributes2.schedule === (props.schedule ?? DEFAULT_SCHEDULE) && attributes2.disable === (props.disable === true) && attributes2.comment === (props.comment ?? "") && (props.rate === undefined || attributes2.rate === props.rate),
3452
4005
  path: (props) => `cluster/replication/${jobId(props)}`,
3453
4006
  updateForm: updateBody5
3454
4007
  });
3455
- var ProxmoxReplicationJobProvider = () => Provider29.effect(ProxmoxReplicationJob, Effect51.succeed(ProxmoxReplicationJob.Provider.of(handlers25)));
4008
+ var ProxmoxReplicationJobProvider = () => Provider29.effect(ProxmoxReplicationJob, Effect53.succeed(ProxmoxReplicationJob.Provider.of(handlers25)));
3456
4009
  // src/proxmox/sdn-apply.ts
3457
4010
  import { Resource as Resource30 } from "alchemy";
3458
4011
  import { isResolved as isResolved10 } from "alchemy/Diff";
3459
4012
  import * as Provider30 from "alchemy/Provider";
3460
- import * as Effect53 from "effect/Effect";
4013
+ import * as Effect55 from "effect/Effect";
3461
4014
 
3462
4015
  // src/proxmox/sdn-apply-read.ts
3463
- import * as Effect52 from "effect/Effect";
4016
+ import * as Effect54 from "effect/Effect";
3464
4017
  var MARKED = [
3465
4018
  "cluster/sdn/zones",
3466
4019
  "cluster/sdn/vnets",
@@ -3469,7 +4022,7 @@ var MARKED = [
3469
4022
  "cluster/sdn/route-maps"
3470
4023
  ];
3471
4024
  var subsystemAbsent = (error) => error instanceof PveError && error.status === 501;
3472
- var markedPending = (target) => Effect52.all(MARKED.map((collection2) => pve(target, "read", "GET", `${collection2}?pending=1`).pipe(Effect52.map((rows) => (rows ?? []).filter((row) => row.state !== undefined).length), Effect52.catchIf(subsystemAbsent, () => Effect52.succeed(0)))), { concurrency: "unbounded" }).pipe(Effect52.map((counts) => counts.reduce((total, count2) => total + count2, 0)));
4025
+ var markedPending = (target) => Effect54.all(MARKED.map((collection2) => pve(target, "read", "GET", `${collection2}?pending=1`).pipe(Effect54.map((rows) => (rows ?? []).filter((row) => row.state !== undefined).length), Effect54.catchIf(subsystemAbsent, () => Effect54.succeed(0)))), { concurrency: "unbounded" }).pipe(Effect54.map((counts) => counts.reduce((total, count2) => total + count2, 0)));
3473
4026
  var fabricStaged = (staged, running) => {
3474
4027
  if (staged === undefined && running === undefined)
3475
4028
  return 0;
@@ -3477,9 +4030,9 @@ var fabricStaged = (staged, running) => {
3477
4030
  return "inconsistent";
3478
4031
  return staged === running ? 0 : 1;
3479
4032
  };
3480
- var fabricPending = (target) => Effect52.all(["pending", "running"].map((view) => pve(target, "read", "GET", `cluster/sdn/fabrics/all?${view}=1`).pipe(Effect52.map(canonical3), Effect52.catchIf(subsystemAbsent, () => Effect52.succeed(undefined)))), { concurrency: "unbounded" }).pipe(Effect52.flatMap(([staged, running]) => {
4033
+ var fabricPending = (target) => Effect54.all(["pending", "running"].map((view) => pve(target, "read", "GET", `cluster/sdn/fabrics/all?${view}=1`).pipe(Effect54.map(canonical3), Effect54.catchIf(subsystemAbsent, () => Effect54.succeed(undefined)))), { concurrency: "unbounded" }).pipe(Effect54.flatMap(([staged, running]) => {
3481
4034
  const verdict = fabricStaged(staged, running);
3482
- return verdict === "inconsistent" ? Effect52.die(new Error("cluster/sdn/fabrics/all answered 501 for one of ?pending=1 and ?running=1 and not " + "the other. Refusing to read that as a staged fabric change.")) : Effect52.succeed(verdict);
4035
+ return verdict === "inconsistent" ? Effect54.die(new Error("cluster/sdn/fabrics/all answered 501 for one of ?pending=1 and ?running=1 and not " + "the other. Refusing to read that as a staged fabric change.")) : Effect54.succeed(verdict);
3483
4036
  }));
3484
4037
  var canonical3 = (value) => JSON.stringify(sortDeep(value));
3485
4038
  var sortDeep = (value) => {
@@ -3492,39 +4045,39 @@ var sortDeep = (value) => {
3492
4045
  }
3493
4046
  return value;
3494
4047
  };
3495
- var sdnPendingCount = (target) => Effect52.all([markedPending(target), fabricPending(target)], { concurrency: "unbounded" }).pipe(Effect52.map(([marked, fabric]) => marked + fabric));
4048
+ var sdnPendingCount = (target) => Effect54.all([markedPending(target), fabricPending(target)], { concurrency: "unbounded" }).pipe(Effect54.map(([marked, fabric]) => marked + fabric));
3496
4049
 
3497
4050
  // src/proxmox/sdn-apply.ts
3498
4051
  var ProxmoxSdnApply = Resource30("Proxmox.SdnApply");
3499
- var read3 = (props) => sdnPendingCount(props.target).pipe(Effect53.map((pending) => ({ pending })));
3500
- var ProxmoxSdnApplyProvider = () => Provider30.effect(ProxmoxSdnApply, Effect53.succeed(ProxmoxSdnApply.Provider.of({
3501
- list: () => Effect53.succeed([]),
3502
- read: Effect53.fn(function* ({ olds }) {
4052
+ var read3 = (props) => sdnPendingCount(props.target).pipe(Effect55.map((pending) => ({ pending })));
4053
+ var ProxmoxSdnApplyProvider = () => Provider30.effect(ProxmoxSdnApply, Effect55.succeed(ProxmoxSdnApply.Provider.of({
4054
+ list: () => Effect55.succeed([]),
4055
+ read: Effect55.fn(function* ({ olds }) {
3503
4056
  return yield* read3(olds);
3504
4057
  }),
3505
- diff: Effect53.fn(function* ({ news }) {
4058
+ diff: Effect55.fn(function* ({ news }) {
3506
4059
  if (!isResolved10(news))
3507
4060
  return;
3508
4061
  const { pending } = yield* read3(news);
3509
4062
  return pending === 0 ? { action: "noop" } : { action: "update" };
3510
4063
  }),
3511
- reconcile: Effect53.fn(function* ({ news }) {
4064
+ reconcile: Effect55.fn(function* ({ news }) {
3512
4065
  const staged = yield* read3(news);
3513
4066
  if (staged.pending === 0)
3514
4067
  return staged;
3515
4068
  yield* pve(news.target, "provision", "PUT", "cluster/sdn");
3516
4069
  const after = yield* read3(news);
3517
4070
  if (after.pending !== 0) {
3518
- return yield* Effect53.die(new Error(`PUT /cluster/sdn returned no error but ${String(after.pending)} SDN object(s) ` + "are still staged. The running config does not match the declared one -- check " + "`pvesh get /cluster/sdn/zones --pending 1` for the objects PVE refused."));
4071
+ return yield* Effect55.die(new Error(`PUT /cluster/sdn returned no error but ${String(after.pending)} SDN object(s) ` + "are still staged. The running config does not match the declared one -- check " + "`pvesh get /cluster/sdn/zones --pending 1` for the objects PVE refused."));
3519
4072
  }
3520
4073
  return after;
3521
4074
  }),
3522
- delete: () => Effect53.void
4075
+ delete: () => Effect55.void
3523
4076
  })));
3524
4077
  // src/proxmox/sdn-subnet.ts
3525
4078
  import { Resource as Resource31 } from "alchemy";
3526
4079
  import * as Provider31 from "alchemy/Provider";
3527
- import * as Effect54 from "effect/Effect";
4080
+ import * as Effect56 from "effect/Effect";
3528
4081
 
3529
4082
  // src/proxmox/sdn-subnet-form.ts
3530
4083
  var subnetId = (props) => `${props.zone}-${props.cidr.replace("/", "-")}`;
@@ -3580,11 +4133,11 @@ var handlers26 = pveHandlers({
3580
4133
  path: (props) => `cluster/sdn/vnets/${props.vnet}/subnets/${subnetId(props)}`,
3581
4134
  updateForm: updateForm2
3582
4135
  });
3583
- var ProxmoxSdnSubnetProvider = () => Provider31.effect(ProxmoxSdnSubnet, Effect54.succeed(ProxmoxSdnSubnet.Provider.of(handlers26)));
4136
+ var ProxmoxSdnSubnetProvider = () => Provider31.effect(ProxmoxSdnSubnet, Effect56.succeed(ProxmoxSdnSubnet.Provider.of(handlers26)));
3584
4137
  // src/proxmox/sdn-vnet.ts
3585
4138
  import { Resource as Resource32 } from "alchemy";
3586
4139
  import * as Provider32 from "alchemy/Provider";
3587
- import * as Effect55 from "effect/Effect";
4140
+ import * as Effect57 from "effect/Effect";
3588
4141
  var ProxmoxSdnVnet = Resource32("Proxmox.SdnVnet");
3589
4142
  var tagOf = (value) => typeof value === "number" ? value : undefined;
3590
4143
  var shape11 = (props) => {
@@ -3617,15 +4170,16 @@ var handlers27 = pveHandlers({
3617
4170
  }),
3618
4171
  collection: () => "cluster/sdn/vnets",
3619
4172
  createForm: (props) => ({ ...shape11(props), vnet: props.vnet }),
4173
+ endpoint: { create: "pve:POST /cluster/sdn/vnets", update: "pve:PUT /cluster/sdn/vnets/{vnet}" },
3620
4174
  matches: (attributes2, props) => attributes2.zone === props.zone && attributes2.alias === (props.alias ?? "") && attributes2.tag === props.tag && attributes2.vlanaware === (props.vlanaware === true) && attributes2.isolatePorts === (props.isolatePorts === true),
3621
4175
  path: (props) => `cluster/sdn/vnets/${props.vnet}`,
3622
4176
  updateForm: updateForm3
3623
4177
  });
3624
- var ProxmoxSdnVnetProvider = () => Provider32.effect(ProxmoxSdnVnet, Effect55.succeed(ProxmoxSdnVnet.Provider.of(handlers27)));
4178
+ var ProxmoxSdnVnetProvider = () => Provider32.effect(ProxmoxSdnVnet, Effect57.succeed(ProxmoxSdnVnet.Provider.of(handlers27)));
3625
4179
  // src/proxmox/sdn-zone.ts
3626
4180
  import { Resource as Resource33 } from "alchemy";
3627
4181
  import * as Provider33 from "alchemy/Provider";
3628
- import * as Effect56 from "effect/Effect";
4182
+ import * as Effect58 from "effect/Effect";
3629
4183
  var ProxmoxSdnZone = Resource33("Proxmox.SdnZone");
3630
4184
  var sameIpam = (live, declared2) => (live === "" ? "pve" : live) === (declared2 === undefined || declared2 === "" ? "pve" : declared2);
3631
4185
  var body6 = (props) => ({
@@ -3669,6 +4223,7 @@ var handlers28 = pveHandlers({
3669
4223
  },
3670
4224
  collection: () => "cluster/sdn/zones",
3671
4225
  createForm: (props) => ({ ...body6(props), type: props.type, zone: props.zone }),
4226
+ endpoint: { create: "pve:POST /cluster/sdn/zones", update: "pve:PUT /cluster/sdn/zones/{zone}" },
3672
4227
  matches: (attributes2, props) => attributes2.mtu === (props.mtu ?? 0) && attributes2.nodes === csv(props.nodes) && sameIpam(attributes2.ipam, props.ipam) && attributes2.dns === (props.dns ?? "") && attributes2.dnszone === (props.dnszone ?? "") && attributes2.peers === csv(props.peers) && attributes2["vrf-vxlan"] === (props["vrf-vxlan"] ?? 0) && attributes2.tag === (props.tag ?? 0) && attributes2.bridge === (props.bridge ?? ""),
3673
4228
  path: (props) => `cluster/sdn/zones/${props.zone}`,
3674
4229
  updateForm: (props) => {
@@ -3677,11 +4232,11 @@ var handlers28 = pveHandlers({
3677
4232
  return withClears(fields, clear);
3678
4233
  }
3679
4234
  });
3680
- var ProxmoxSdnZoneProvider = () => Provider33.effect(ProxmoxSdnZone, Effect56.succeed(ProxmoxSdnZone.Provider.of(handlers28)));
4235
+ var ProxmoxSdnZoneProvider = () => Provider33.effect(ProxmoxSdnZone, Effect58.succeed(ProxmoxSdnZone.Provider.of(handlers28)));
3681
4236
  // src/proxmox/storage.ts
3682
4237
  import { Resource as Resource34 } from "alchemy";
3683
4238
  import * as Provider34 from "alchemy/Provider";
3684
- import * as Effect57 from "effect/Effect";
4239
+ import * as Effect59 from "effect/Effect";
3685
4240
  var ProxmoxStorage = Resource34("Proxmox.Storage", {
3686
4241
  defaultRemovalPolicy: "retain"
3687
4242
  });
@@ -3722,18 +4277,19 @@ var handlers29 = pveHandlers({
3722
4277
  storage: props.storage,
3723
4278
  type: props.type
3724
4279
  }),
4280
+ endpoint: { create: "pve:POST /storage", update: "pve:PUT /storage/{storage}" },
3725
4281
  matches: (attributes2, props) => sameList2(props.content, attributes2.content) && sameList2(props.nodes, attributes2.nodes) && sameList2(props["prune-backups"], attributes2["prune-backups"]) && same7(props.disable, attributes2.disable) && same7(props.shared, attributes2.shared) && same7(props.preallocation, attributes2.preallocation) && same7(props.comment, attributes2.comment) && same7(props.maxfiles, attributes2.maxfiles),
3726
4282
  path: (props) => `storage/${props.storage}`,
3727
4283
  updateForm: mutable2
3728
4284
  });
3729
- var ProxmoxStorageProvider = () => Provider34.effect(ProxmoxStorage, Effect57.succeed(ProxmoxStorage.Provider.of(handlers29)));
4285
+ var ProxmoxStorageProvider = () => Provider34.effect(ProxmoxStorage, Effect59.succeed(ProxmoxStorage.Provider.of(handlers29)));
3730
4286
  // src/proxmox/zfs-pool.ts
3731
4287
  import { Resource as Resource35 } from "alchemy";
3732
4288
  import * as Provider35 from "alchemy/Provider";
3733
- import * as Effect59 from "effect/Effect";
4289
+ import * as Effect61 from "effect/Effect";
3734
4290
 
3735
4291
  // src/proxmox/zfs-pool-write.ts
3736
- import * as Effect58 from "effect/Effect";
4292
+ import * as Effect60 from "effect/Effect";
3737
4293
  var createForm6 = (props) => ({
3738
4294
  devices: props.devices.join(","),
3739
4295
  name: props.name,
@@ -3743,21 +4299,21 @@ var createForm6 = (props) => ({
3743
4299
  ...props["draid-config"] === undefined ? {} : { "draid-config": props["draid-config"] }
3744
4300
  });
3745
4301
  var SETTLE_ATTEMPTS = 30;
3746
- var createPool = (props, collection2, path2, read4) => Effect58.gen(function* () {
4302
+ var createPool = (props, collection2, path2, read4) => Effect60.gen(function* () {
3747
4303
  const existing = yield* read4(props);
3748
4304
  if (existing !== undefined)
3749
4305
  return existing;
3750
4306
  const upid = yield* pve(props.target, "provision", "POST", collection2, createForm6(props));
3751
- const settle4 = (attempts) => Effect58.gen(function* () {
4307
+ const settle4 = (attempts) => Effect60.gen(function* () {
3752
4308
  const live = yield* read4(props);
3753
4309
  if (live !== undefined || attempts <= 0)
3754
4310
  return live;
3755
- yield* Effect58.sleep("2 seconds");
4311
+ yield* Effect60.sleep("2 seconds");
3756
4312
  return yield* settle4(attempts - 1);
3757
4313
  });
3758
4314
  const after = yield* settle4(SETTLE_ATTEMPTS);
3759
4315
  if (after === undefined) {
3760
- return yield* Effect58.die(new Error(`${path2}: POST returned ${text(upid, "(no UPID)")} but the pool has not appeared. That ` + "POST only forks a `zfscreate` worker, so the task holds the real error -- read it " + `with \`pvesh get /nodes/${props.node}/tasks/<upid>/log\`. NOTHING WAS RETRIED AND ` + "NOTHING WAS CLEANED UP: if the worker reached `zpool create`, the declared devices " + "have already been written to."));
4316
+ return yield* Effect60.die(new Error(`${path2}: POST returned ${text(upid, "(no UPID)")} but the pool has not appeared. That ` + "POST only forks a `zfscreate` worker, so the task holds the real error -- read it " + `with \`pvesh get /nodes/${props.node}/tasks/<upid>/log\`. NOTHING WAS RETRIED AND ` + "NOTHING WAS CLEANED UP: if the worker reached `zpool create`, the declared devices " + "have already been written to."));
3761
4317
  }
3762
4318
  return after;
3763
4319
  });
@@ -3792,12 +4348,12 @@ var spec2 = {
3792
4348
  var ops5 = pveOperations(spec2);
3793
4349
  var handlers30 = {
3794
4350
  ...pveHandlers(spec2),
3795
- delete: Effect59.fn(function* ({ olds }) {
4351
+ delete: Effect61.fn(function* ({ olds }) {
3796
4352
  yield* destroyPool(olds, spec2.path(olds));
3797
4353
  }),
3798
4354
  reconcile: ({ news }) => createPool(news, spec2.collection(news), spec2.path(news), ops5.read)
3799
4355
  };
3800
- var ProxmoxZfsPoolProvider = () => Provider35.effect(ProxmoxZfsPool, Effect59.succeed(ProxmoxZfsPool.Provider.of(handlers30)));
4356
+ var ProxmoxZfsPoolProvider = () => Provider35.effect(ProxmoxZfsPool, Effect61.succeed(ProxmoxZfsPool.Provider.of(handlers30)));
3801
4357
  export {
3802
4358
  PROVISION_DEFAULTS,
3803
4359
  PROVISION_PRIVILEGES,
@@ -3861,10 +4417,13 @@ export {
3861
4417
  ProxmoxVmProvider,
3862
4418
  ProxmoxZfsPoolProvider,
3863
4419
  alertmanagerAlertBody,
4420
+ constraintsFor,
3864
4421
  declareProvisionBaseline,
4422
+ formViolations,
3865
4423
  provisionBaseline,
3866
- provisionBootstrap
4424
+ provisionBootstrap,
4425
+ violations
3867
4426
  };
3868
4427
 
3869
- //# debugId=98691C143D2F953764756E2164756E21
4428
+ //# debugId=78DC295524EE261D64756E2164756E21
3870
4429
  //# sourceMappingURL=index.js.map