@homeflare/alchemy 0.12.0 → 0.14.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 (88) hide show
  1. package/README.md +7 -0
  2. package/dist/github/index.d.ts +18 -0
  3. package/dist/github/index.d.ts.map +1 -0
  4. package/dist/github/index.js +161 -0
  5. package/dist/github/index.js.map +12 -0
  6. package/dist/github/repo-policy-form.d.ts +86 -0
  7. package/dist/github/repo-policy-form.d.ts.map +1 -0
  8. package/dist/github/repo-policy-guards.d.ts +72 -0
  9. package/dist/github/repo-policy-guards.d.ts.map +1 -0
  10. package/dist/github/repo-policy.d.ts +21 -0
  11. package/dist/github/repo-policy.d.ts.map +1 -0
  12. package/dist/index.d.ts +1 -0
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/proxmox/alertmanager-body.d.ts +54 -0
  15. package/dist/proxmox/alertmanager-body.d.ts.map +1 -0
  16. package/dist/proxmox/credential-literals.d.ts +36 -0
  17. package/dist/proxmox/credential-literals.d.ts.map +1 -0
  18. package/dist/proxmox/fake-pbs-notify.d.ts +23 -0
  19. package/dist/proxmox/fake-pbs-notify.d.ts.map +1 -0
  20. package/dist/proxmox/fake-pve.d.ts +5 -0
  21. package/dist/proxmox/fake-pve.d.ts.map +1 -1
  22. package/dist/proxmox/generated/pbs.d.ts +278 -0
  23. package/dist/proxmox/generated/pbs.d.ts.map +1 -1
  24. package/dist/proxmox/generated/pve.d.ts +57 -0
  25. package/dist/proxmox/generated/pve.d.ts.map +1 -1
  26. package/dist/proxmox/index.d.ts +8 -0
  27. package/dist/proxmox/index.d.ts.map +1 -1
  28. package/dist/proxmox/index.js +681 -183
  29. package/dist/proxmox/index.js.map +14 -4
  30. package/dist/proxmox/notification-matcher-form.d.ts +100 -0
  31. package/dist/proxmox/notification-matcher-form.d.ts.map +1 -0
  32. package/dist/proxmox/notification-matcher.d.ts +46 -0
  33. package/dist/proxmox/notification-matcher.d.ts.map +1 -0
  34. package/dist/proxmox/pbs-notification-matcher.d.ts +53 -0
  35. package/dist/proxmox/pbs-notification-matcher.d.ts.map +1 -0
  36. package/dist/proxmox/pbs-notification-target-form.d.ts +63 -0
  37. package/dist/proxmox/pbs-notification-target-form.d.ts.map +1 -0
  38. package/dist/proxmox/pbs-notification-target-lifecycle.d.ts +100 -0
  39. package/dist/proxmox/pbs-notification-target-lifecycle.d.ts.map +1 -0
  40. package/dist/proxmox/pbs-notification-target-wire.d.ts +30 -0
  41. package/dist/proxmox/pbs-notification-target-wire.d.ts.map +1 -0
  42. package/dist/proxmox/pbs-notification-target.d.ts +123 -0
  43. package/dist/proxmox/pbs-notification-target.d.ts.map +1 -0
  44. package/dist/proxmox/resource.d.ts +4 -3
  45. package/dist/proxmox/resource.d.ts.map +1 -1
  46. package/dist/proxmox/write-only.d.ts +35 -0
  47. package/dist/proxmox/write-only.d.ts.map +1 -0
  48. package/dist/verify/fake-engine.d.ts +5 -0
  49. package/dist/verify/fake-engine.d.ts.map +1 -1
  50. package/docs/adopted-deploys.md +8 -0
  51. package/docs/pbs-alertmanager-body.md +73 -0
  52. package/docs/pbs-notifications.md +163 -0
  53. package/docs/proxmox.md +3 -0
  54. package/docs/repo-policy-ruleset-hazards.md +65 -0
  55. package/docs/repo-policy.md +155 -0
  56. package/package.json +5 -1
  57. package/src/github/index.ts +23 -0
  58. package/src/github/repo-policy-form.test.ts +239 -0
  59. package/src/github/repo-policy-form.ts +173 -0
  60. package/src/github/repo-policy-guards.ts +179 -0
  61. package/src/github/repo-policy.test.ts +58 -0
  62. package/src/github/repo-policy.ts +65 -0
  63. package/src/index.ts +1 -0
  64. package/src/proxmox/alertmanager-body.ts +125 -0
  65. package/src/proxmox/credential-literals.test.ts +148 -0
  66. package/src/proxmox/credential-literals.ts +74 -0
  67. package/src/proxmox/fake-pbs-notify.ts +108 -0
  68. package/src/proxmox/fake-pve.ts +12 -2
  69. package/src/proxmox/generated/pbs.ts +329 -0
  70. package/src/proxmox/generated/pve.ts +62 -0
  71. package/src/proxmox/index.test.ts +3 -0
  72. package/src/proxmox/index.ts +18 -0
  73. package/src/proxmox/notification-matcher-form.ts +221 -0
  74. package/src/proxmox/notification-matcher.test.ts +234 -0
  75. package/src/proxmox/notification-matcher.ts +76 -0
  76. package/src/proxmox/pbs-notification-matcher.ts +81 -0
  77. package/src/proxmox/pbs-notification-target-drift.test.ts +83 -0
  78. package/src/proxmox/pbs-notification-target-form.ts +229 -0
  79. package/src/proxmox/pbs-notification-target-lifecycle.ts +177 -0
  80. package/src/proxmox/pbs-notification-target-state.test.ts +191 -0
  81. package/src/proxmox/pbs-notification-target-wire.ts +112 -0
  82. package/src/proxmox/pbs-notification-target.test.ts +226 -0
  83. package/src/proxmox/pbs-notification-target.ts +139 -0
  84. package/src/proxmox/pbs-notifications-stack.test.ts +94 -0
  85. package/src/proxmox/resource.ts +6 -6
  86. package/src/proxmox/write-only.test.ts +75 -0
  87. package/src/proxmox/write-only.ts +106 -0
  88. package/src/verify/fake-engine.ts +8 -1
@@ -230,7 +230,7 @@ var formToSend = (matches, live, props, form) => matches(live, props) || Object.
230
230
 
231
231
  // src/proxmox/resource.ts
232
232
  var pveOperations = (spec) => {
233
- const read = (props) => pve(props.target, spec.readRole ?? "read", "GET", spec.path(props)).pipe(Effect5.map((data) => data === undefined ? undefined : spec.attributes(data, props)), Effect5.orElseSucceed(() => {
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(() => {
234
234
  return;
235
235
  }));
236
236
  return {
@@ -336,6 +336,56 @@ var ProxmoxAclProvider = () => Provider.effect(ProxmoxAcl, Effect6.succeed(Proxm
336
336
  yield* pve(olds.target, "provision", "PUT", "access/acl", removal);
337
337
  })
338
338
  })));
339
+ // src/proxmox/alertmanager-body.ts
340
+ var literal = (label, value) => {
341
+ if (/[{}\\]/.test(value) || value === "") {
342
+ throw new Error(`alertmanagerAlertBody: ${label} must be non-empty and hold no '{', '}' or '\\'`);
343
+ }
344
+ return JSON.stringify(value);
345
+ };
346
+ var absoluteUrl = (value) => {
347
+ const text = literal("generatorURL", value);
348
+ let protocol = "";
349
+ try {
350
+ protocol = new URL(value).protocol;
351
+ } catch {}
352
+ if (protocol !== "https:" && protocol !== "http:") {
353
+ throw new Error("alertmanagerAlertBody: generatorURL must be an absolute http(s) URL");
354
+ }
355
+ return text;
356
+ };
357
+ var FIELD_LABELS = [
358
+ ["job_type", "type"],
359
+ ["job_id", "job-id"],
360
+ ["datastore", "datastore"],
361
+ ["hostname", "hostname"]
362
+ ];
363
+ var alertmanagerAlertBody = (options = {}) => {
364
+ const alertname = literal("alertname", options.alertname ?? "PbsNotification");
365
+ const source = literal("source", options.source ?? "pbs");
366
+ const labels = FIELD_LABELS.map(([label, field]) => ` {{#if fields.${field}}}"${label}": {{ json fields.${field} }},{{/if}}`);
367
+ const link = options.generatorURL === undefined ? "" : `,
368
+ "generatorURL": ${absoluteUrl(options.generatorURL)}`;
369
+ return [
370
+ "[",
371
+ " {",
372
+ ' "labels": {',
373
+ ...labels,
374
+ ' "severity": {{ json severity }},',
375
+ ` "alertname": ${alertname},`,
376
+ ` "source": ${source}`,
377
+ " },",
378
+ ' "annotations": {',
379
+ ' "summary": {{ json title }},',
380
+ ' "description": {{ json message }},',
381
+ ' "timestamp": "{{ timestamp }}"',
382
+ ` }${link}`,
383
+ " }",
384
+ "]",
385
+ ""
386
+ ].join(`
387
+ `);
388
+ };
339
389
  // src/proxmox/api-token.ts
340
390
  import { Resource as Resource2 } from "alchemy";
341
391
  import * as Provider2 from "alchemy/Provider";
@@ -2184,11 +2234,101 @@ var handlers13 = pveHandlers({
2184
2234
  updateForm: updateBody2
2185
2235
  });
2186
2236
  var ProxmoxNodeNetworkProvider = () => Provider16.effect(ProxmoxNodeNetwork, Effect34.succeed(ProxmoxNodeNetwork.Provider.of(handlers13)));
2187
- // src/proxmox/notification-target.ts
2237
+ // src/proxmox/notification-matcher.ts
2188
2238
  import { Resource as Resource17 } from "alchemy";
2189
2239
  import * as Provider17 from "alchemy/Provider";
2190
2240
  import * as Effect35 from "effect/Effect";
2191
2241
 
2242
+ // src/proxmox/notification-matcher-form.ts
2243
+ var list = (value) => Array.isArray(value) ? value.map((item) => String(item)) : [];
2244
+ var items = (values, canonical) => values.map((item) => canonical(item.trim())).filter((item) => item !== "");
2245
+ var severity = (item) => item.split(",").map((part) => part.trim()).filter((part) => part !== "").sort().join(",");
2246
+ var field4 = (item) => {
2247
+ if (!item.startsWith("exact:"))
2248
+ return item;
2249
+ const at = item.indexOf("=");
2250
+ if (at < 0)
2251
+ return item;
2252
+ const values = item.slice(at + 1).split(",");
2253
+ return `${item.slice(0, at)}=${values.map((value) => value.trim()).join(",")}`;
2254
+ };
2255
+ var same3 = (item) => item;
2256
+ var canonicalRules = (fields) => ({
2257
+ "match-calendar": items(fields["match-calendar"] ?? [], same3).sort(),
2258
+ "match-field": items(fields["match-field"] ?? [], field4).sort(),
2259
+ "match-severity": items(fields["match-severity"] ?? [], severity).sort(),
2260
+ targets: items(fields.targets, same3).sort()
2261
+ });
2262
+ var matcherAttributes = (live, name) => ({
2263
+ comment: text(live["comment"]),
2264
+ disable: bool(live["disable"]),
2265
+ "invert-match": bool(live["invert-match"]),
2266
+ "match-calendar": items(list(live["match-calendar"]), same3).sort(),
2267
+ "match-field": items(list(live["match-field"]), field4).sort(),
2268
+ "match-severity": items(list(live["match-severity"]), severity).sort(),
2269
+ mode: text(live["mode"], "all") || "all",
2270
+ name,
2271
+ origin: text(live["origin"]),
2272
+ targets: items(list(live["target"]), same3).sort()
2273
+ });
2274
+ var equal = (a, b) => a.length === b.length && a.every((item, index) => item === b[index]);
2275
+ var matcherMatches = (attributes, fields) => {
2276
+ const rules = canonicalRules(fields);
2277
+ return attributes.disable === (fields.disable === true) && attributes["invert-match"] === (fields["invert-match"] === true) && attributes.mode === (fields.mode ?? "all") && equal(attributes.targets, rules.targets) && equal(attributes["match-severity"], rules["match-severity"]) && equal(attributes["match-field"], rules["match-field"]) && equal(attributes["match-calendar"], rules["match-calendar"]) && (fields.comment === undefined || attributes.comment === fields.comment);
2278
+ };
2279
+ var always = (fields) => ({
2280
+ disable: fields.disable === true ? "1" : "0",
2281
+ "invert-match": fields["invert-match"] === true ? "1" : "0",
2282
+ mode: fields.mode ?? "all"
2283
+ });
2284
+ var lists = (fields) => {
2285
+ const rules = canonicalRules(fields);
2286
+ const out = {};
2287
+ if (rules["match-calendar"].length > 0)
2288
+ out["match-calendar"] = rules["match-calendar"];
2289
+ if (rules["match-field"].length > 0)
2290
+ out["match-field"] = rules["match-field"];
2291
+ if (rules["match-severity"].length > 0)
2292
+ out["match-severity"] = rules["match-severity"];
2293
+ if (rules.targets.length > 0)
2294
+ out.target = rules.targets;
2295
+ return out;
2296
+ };
2297
+ var matcherCreateForm = (fields) => ({
2298
+ ...always(fields),
2299
+ ...lists(fields),
2300
+ ...fields.comment === undefined || fields.comment === "" ? {} : { comment: fields.comment },
2301
+ name: fields.name
2302
+ });
2303
+ var matcherUpdateForm = (fields) => {
2304
+ const present = lists(fields);
2305
+ const cleared = ["match-calendar", "match-field", "match-severity", "target"].filter((key) => present[key] === undefined);
2306
+ if (fields.comment === "")
2307
+ cleared.push("comment");
2308
+ return {
2309
+ ...always(fields),
2310
+ ...present,
2311
+ ...fields.comment === undefined || fields.comment === "" ? {} : { comment: fields.comment },
2312
+ ...cleared.length === 0 ? {} : { delete: cleared }
2313
+ };
2314
+ };
2315
+
2316
+ // src/proxmox/notification-matcher.ts
2317
+ var ProxmoxNotificationMatcher = Resource17("Proxmox.NotificationMatcher");
2318
+ var handlers14 = pveHandlers({
2319
+ attributes: (live, props) => matcherAttributes(live, props.name),
2320
+ collection: () => "cluster/notifications/matchers",
2321
+ createForm: matcherCreateForm,
2322
+ matches: matcherMatches,
2323
+ path: (props) => `cluster/notifications/matchers/${props.name}`,
2324
+ updateForm: matcherUpdateForm
2325
+ });
2326
+ var ProxmoxNotificationMatcherProvider = () => Provider17.effect(ProxmoxNotificationMatcher, Effect35.succeed(ProxmoxNotificationMatcher.Provider.of(handlers14)));
2327
+ // src/proxmox/notification-target.ts
2328
+ import { Resource as Resource18 } from "alchemy";
2329
+ import * as Provider18 from "alchemy/Provider";
2330
+ import * as Effect36 from "effect/Effect";
2331
+
2192
2332
  // src/proxmox/notification-target-form.ts
2193
2333
  var REPEATED_KEY = "\x00";
2194
2334
  var portOf = (value) => {
@@ -2217,8 +2357,8 @@ var shape4 = (props) => ({
2217
2357
  });
2218
2358
 
2219
2359
  // src/proxmox/notification-target.ts
2220
- var ProxmoxNotificationTarget = Resource17("Proxmox.NotificationTarget");
2221
- var handlers14 = pveHandlers({
2360
+ var ProxmoxNotificationTarget = Resource18("Proxmox.NotificationTarget");
2361
+ var handlers15 = pveHandlers({
2222
2362
  attributes: (live, props) => ({
2223
2363
  author: text(live["author"]),
2224
2364
  body: text(live["body"]),
@@ -2243,15 +2383,15 @@ var handlers14 = pveHandlers({
2243
2383
  path: (props) => `cluster/notifications/endpoints/${props.type}/${props.name}`,
2244
2384
  updateForm: shape4
2245
2385
  });
2246
- var ProxmoxNotificationTargetProvider = () => Provider17.effect(ProxmoxNotificationTarget, Effect35.succeed(ProxmoxNotificationTarget.Provider.of(handlers14)));
2386
+ var ProxmoxNotificationTargetProvider = () => Provider18.effect(ProxmoxNotificationTarget, Effect36.succeed(ProxmoxNotificationTarget.Provider.of(handlers15)));
2247
2387
  // src/proxmox/pbs-datastore.ts
2248
- import { Resource as Resource18 } from "alchemy";
2388
+ import { Resource as Resource19 } from "alchemy";
2249
2389
  import { isResolved as isResolved8 } from "alchemy/Diff";
2250
- import * as Provider18 from "alchemy/Provider";
2251
- import * as Effect38 from "effect/Effect";
2390
+ import * as Provider19 from "alchemy/Provider";
2391
+ import * as Effect39 from "effect/Effect";
2252
2392
 
2253
2393
  // src/proxmox/pbs-datastore-form.ts
2254
- import * as Effect36 from "effect/Effect";
2394
+ import * as Effect37 from "effect/Effect";
2255
2395
  var attributes = (live, props) => ({
2256
2396
  backend: propertyString(live["backend"]),
2257
2397
  comment: text(live["comment"]),
@@ -2273,8 +2413,8 @@ var attributes = (live, props) => ({
2273
2413
  tuning: propertyString(live["tuning"]),
2274
2414
  "verify-new": bool(live["verify-new"])
2275
2415
  });
2276
- var same3 = (declared, live) => declared === undefined || declared === live;
2277
- var matches = (live, props) => same3(props.comment, live.comment) && same3(props["gc-schedule"]?.trim(), live["gc-schedule"]) && same3(props["prune-schedule"]?.trim(), live["prune-schedule"]) && same3(props["keep-last"], live["keep-last"]) && same3(props["keep-hourly"], live["keep-hourly"]) && same3(props["keep-daily"], live["keep-daily"]) && same3(props["keep-weekly"], live["keep-weekly"]) && same3(props["keep-monthly"], live["keep-monthly"]) && same3(props["keep-yearly"], live["keep-yearly"]) && same3(props["notify-user"], live["notify-user"]) && same3(props["counter-reset-schedule"]?.trim(), live["counter-reset-schedule"]) && (props["notification-thresholds"] === undefined || propertyString(props["notification-thresholds"]) === live["notification-thresholds"]) && same3(props["verify-new"], live["verify-new"]) && (props.notify === undefined || propertyString(props.notify) === live.notify) && (props.tuning === undefined || propertyString(props.tuning) === live.tuning) && (props["maintenance-mode"] === undefined || propertyString(props["maintenance-mode"], "type") === live["maintenance-mode"]);
2416
+ var same4 = (declared, live) => declared === undefined || declared === live;
2417
+ var matches = (live, props) => same4(props.comment, live.comment) && same4(props["gc-schedule"]?.trim(), live["gc-schedule"]) && same4(props["prune-schedule"]?.trim(), live["prune-schedule"]) && same4(props["keep-last"], live["keep-last"]) && same4(props["keep-hourly"], live["keep-hourly"]) && same4(props["keep-daily"], live["keep-daily"]) && same4(props["keep-weekly"], live["keep-weekly"]) && same4(props["keep-monthly"], live["keep-monthly"]) && same4(props["keep-yearly"], live["keep-yearly"]) && same4(props["notify-user"], live["notify-user"]) && same4(props["counter-reset-schedule"]?.trim(), live["counter-reset-schedule"]) && (props["notification-thresholds"] === undefined || propertyString(props["notification-thresholds"]) === live["notification-thresholds"]) && same4(props["verify-new"], live["verify-new"]) && (props.notify === undefined || propertyString(props.notify) === live.notify) && (props.tuning === undefined || propertyString(props.tuning) === live.tuning) && (props["maintenance-mode"] === undefined || propertyString(props["maintenance-mode"], "type") === live["maintenance-mode"]);
2278
2418
  var set2 = (key, value) => value === undefined ? {} : { [key]: value };
2279
2419
  var count = (value) => value === undefined ? undefined : String(value);
2280
2420
  var mutable = (props) => ({
@@ -2303,36 +2443,36 @@ var createForm4 = (props) => ({
2303
2443
  });
2304
2444
  var updateForm = (props) => mutable(props);
2305
2445
  var object2 = (props) => `config/datastore/${props.name}`;
2306
- var readOne = (props) => pve(props.target, "read", "GET", object2(props)).pipe(Effect36.map((live) => live === undefined || typeof live["name"] !== "string" ? undefined : attributes(live, props)), Effect36.orElseSucceed(() => {
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(() => {
2307
2447
  return;
2308
2448
  }));
2309
2449
 
2310
2450
  // src/proxmox/pbs-datastore-guard.ts
2311
- import * as Effect37 from "effect/Effect";
2451
+ import * as Effect38 from "effect/Effect";
2312
2452
  var samePath = (left, right) => {
2313
2453
  const trim = (value) => value.length > 1 ? value.replace(/\/+$/, "") : value;
2314
2454
  return trim(left) === trim(right);
2315
2455
  };
2316
- var guardPath = (live, props) => live.path === "" || samePath(live.path, props.path) ? Effect37.void : Effect37.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."));
2317
- var guardBackend = (live, props) => props.backend === undefined || propertyString(props.backend) === live.backend ? Effect37.void : Effect37.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."));
2318
- var settle3 = (read3, done) => Effect37.gen(function* () {
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* () {
2319
2459
  for (let attempt = 0;attempt < 60; attempt += 1) {
2320
2460
  const live = yield* read3;
2321
2461
  if (done(live))
2322
2462
  return live;
2323
- yield* Effect37.sleep("2 seconds");
2463
+ yield* Effect38.sleep("2 seconds");
2324
2464
  }
2325
2465
  return yield* read3;
2326
2466
  });
2327
2467
 
2328
2468
  // src/proxmox/pbs-datastore.ts
2329
- var PbsDatastore = Resource18("Pbs.Datastore", {
2469
+ var PbsDatastore = Resource19("Pbs.Datastore", {
2330
2470
  defaultRemovalPolicy: "retain"
2331
2471
  });
2332
- var PbsDatastoreProvider = () => Provider18.effect(PbsDatastore, Effect38.succeed(PbsDatastore.Provider.of({
2333
- list: () => Effect38.succeed([]),
2472
+ var PbsDatastoreProvider = () => Provider19.effect(PbsDatastore, Effect39.succeed(PbsDatastore.Provider.of({
2473
+ list: () => Effect39.succeed([]),
2334
2474
  read: ({ olds }) => readOne(olds),
2335
- diff: ({ news, output }) => Effect38.gen(function* () {
2475
+ diff: ({ news, output }) => Effect39.gen(function* () {
2336
2476
  if (output === undefined || !isResolved8(news))
2337
2477
  return;
2338
2478
  const live = yield* readOne(news);
@@ -2342,7 +2482,7 @@ var PbsDatastoreProvider = () => Provider18.effect(PbsDatastore, Effect38.succee
2342
2482
  yield* guardBackend(live, news);
2343
2483
  return matches(live, news) ? { action: "noop" } : { action: "update" };
2344
2484
  }),
2345
- reconcile: Effect38.fn(function* ({ news }) {
2485
+ reconcile: Effect39.fn(function* ({ news }) {
2346
2486
  const live = yield* readOne(news);
2347
2487
  let upid;
2348
2488
  if (live === undefined) {
@@ -2357,22 +2497,373 @@ var PbsDatastoreProvider = () => Provider18.effect(PbsDatastore, Effect38.succee
2357
2497
  }
2358
2498
  const after = yield* settle3(readOne(news), (row) => row !== undefined && matches(row, news));
2359
2499
  if (after === undefined || !matches(after, news)) {
2360
- return yield* Effect38.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."));
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."));
2361
2501
  }
2362
2502
  return after;
2363
2503
  }),
2364
- delete: Effect38.fn(function* ({ olds }) {
2504
+ delete: Effect39.fn(function* ({ olds }) {
2365
2505
  yield* pve(olds.target, "provision", "DELETE", object2(olds));
2366
2506
  const left = yield* settle3(readOne(olds), (row) => row === undefined);
2367
2507
  if (left !== undefined) {
2368
- return yield* Effect38.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>\`.`));
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>\`.`));
2369
2509
  }
2370
2510
  })
2371
2511
  })));
2512
+ // src/proxmox/pbs-notification-matcher.ts
2513
+ import { Resource as Resource20 } from "alchemy";
2514
+ import * as Provider20 from "alchemy/Provider";
2515
+ import * as Effect40 from "effect/Effect";
2516
+ var PbsNotificationMatcher = Resource20("Pbs.NotificationMatcher");
2517
+ var handlers16 = pveHandlers({
2518
+ attributes: (live, props) => matcherAttributes(live, props.name),
2519
+ collection: () => "config/notifications/matchers",
2520
+ createForm: matcherCreateForm,
2521
+ matches: matcherMatches,
2522
+ path: (props) => `config/notifications/matchers/${props.name}`,
2523
+ updateForm: matcherUpdateForm
2524
+ });
2525
+ var PbsNotificationMatcherProvider = () => Provider20.effect(PbsNotificationMatcher, Effect40.succeed(PbsNotificationMatcher.Provider.of(handlers16)));
2526
+ // src/proxmox/pbs-notification-target.ts
2527
+ import { Resource as Resource21 } from "alchemy";
2528
+ import * as Provider21 from "alchemy/Provider";
2529
+ import * as Effect42 from "effect/Effect";
2530
+
2531
+ // src/proxmox/pbs-notification-target-lifecycle.ts
2532
+ import { isResolved as isResolved9 } from "alchemy/Diff";
2533
+ import * as Effect41 from "effect/Effect";
2534
+
2535
+ // src/proxmox/credential-literals.ts
2536
+ var READS_SECRET = /\{\{[^}]*\bsecrets\./;
2537
+ var CREDENTIAL_WORD = /(^|[-_])(api[-_]?key|apikey|key|token|secret|password|passwd|pwd|pass|auth|signature|sig|code)($|[-_])/i;
2538
+ var CREDENTIAL_HEADER = /^(proxy-authorization|authorization|cookie)$/i;
2539
+ var isCredentialName = (name) => CREDENTIAL_HEADER.test(name) || CREDENTIAL_WORD.test(name);
2540
+ var literal2 = (value) => value !== "" && !READS_SECRET.test(value);
2541
+ var literalCredentialHeaders = (header) => Object.entries(header ?? {}).filter(([name, value]) => typeof value === "string" && isCredentialName(name)).filter(([, value]) => literal2(value)).map(([name]) => name).sort();
2542
+ var literalCredentialsInUrl = (url) => {
2543
+ if (url === undefined)
2544
+ return [];
2545
+ const out = [];
2546
+ const authority = /^[a-z][a-z0-9+.-]*:\/\/([^/?#]*)/i.exec(url)?.[1] ?? "";
2547
+ const at = authority.lastIndexOf("@");
2548
+ if (at >= 0) {
2549
+ const userinfo = authority.slice(0, at);
2550
+ const colon = userinfo.indexOf(":");
2551
+ if (colon >= 0 && literal2(userinfo.slice(colon + 1)))
2552
+ out.push("userinfo");
2553
+ }
2554
+ const params = url.replace(/^[^?#]*/, "");
2555
+ for (const pair of params.split(/[?&#]/)) {
2556
+ const eq = pair.indexOf("=");
2557
+ const name = eq < 0 ? pair : pair.slice(0, eq);
2558
+ if (eq >= 0 && isCredentialName(name) && literal2(pair.slice(eq + 1)))
2559
+ out.push(`?${name}`);
2560
+ }
2561
+ return out;
2562
+ };
2563
+
2564
+ // src/proxmox/pbs-notification-target-wire.ts
2565
+ import { Buffer as Buffer2 } from "node:buffer";
2566
+
2567
+ // src/proxmox/write-only.ts
2568
+ import { Buffer } from "node:buffer";
2569
+ import { randomBytes, scryptSync, timingSafeEqual } from "node:crypto";
2570
+ var resolveAll = (refs, env = process.env) => {
2571
+ const values = {};
2572
+ const missing = [];
2573
+ for (const [key, ref] of Object.entries(refs)) {
2574
+ const value = env[ref.fromEnv];
2575
+ if (value === undefined || value === "")
2576
+ missing.push(ref.fromEnv);
2577
+ else
2578
+ values[key] = value;
2579
+ }
2580
+ return { missing: [...new Set(missing)].sort(), values };
2581
+ };
2582
+ var COST = { N: 16384, maxmem: 64 * 1024 * 1024, p: 1, r: 8 };
2583
+ var PREFIX = "scrypt";
2584
+ var canonical = (values) => JSON.stringify(Object.keys(values).sort().map((key) => [key, values[key]]));
2585
+ var digest = (salt, values) => scryptSync(canonical(values), salt, 32, COST);
2586
+ var seal = (values, salt) => {
2587
+ if (Object.keys(values).length === 0)
2588
+ return "";
2589
+ const bytes = salt === undefined ? randomBytes(16) : Buffer.from(salt, "utf8");
2590
+ return `${PREFIX}:${bytes.toString("base64url")}:${digest(bytes, values).toString("base64url")}`;
2591
+ };
2592
+ var sealMatches = (sealed, values) => {
2593
+ if (sealed === "")
2594
+ return Object.keys(values).length === 0;
2595
+ const [prefix, salt, stored, extra] = sealed.split(":");
2596
+ if (prefix !== PREFIX || salt === undefined || stored === undefined || extra !== undefined) {
2597
+ return false;
2598
+ }
2599
+ const expected = Buffer.from(stored, "base64url");
2600
+ const actual = digest(Buffer.from(salt, "base64url"), values);
2601
+ return expected.length === actual.length && timingSafeEqual(expected, actual);
2602
+ };
2603
+
2604
+ // src/proxmox/pbs-notification-target-wire.ts
2605
+ var base64 = (value) => Buffer2.from(value, "utf8").toString("base64");
2606
+ var unbase64 = (value) => Buffer2.from(value, "base64").toString("utf8");
2607
+ var keyAndValue = (item) => {
2608
+ const parts = {};
2609
+ for (const part of item.split(",")) {
2610
+ const at = part.indexOf("=");
2611
+ if (at > 0)
2612
+ parts[part.slice(0, at).trim()] = part.slice(at + 1).trim();
2613
+ }
2614
+ const value = parts["value"];
2615
+ return value === undefined ? { name: parts["name"] ?? "" } : { name: parts["name"] ?? "", value };
2616
+ };
2617
+ var wireItem = (name, value) => `name=${name},value=${base64(value)}`;
2618
+ var list2 = (value) => Array.isArray(value) ? value.map((item) => String(item)) : [];
2619
+ var names = (keys) => [...keys].filter((key) => key !== "").sort().join(",");
2620
+ var headerDigest = (target, pairs2) => seal(pairs2, `hf:pbs-notification-target:${target}:header`);
2621
+ var targetAttributes = (live, props) => {
2622
+ const headers = {};
2623
+ for (const item of list2(live["header"])) {
2624
+ const { name, value } = keyAndValue(item);
2625
+ if (name !== "")
2626
+ headers[name] = unbase64(value ?? "");
2627
+ }
2628
+ const secrets = list2(live["secret"]).map((item) => keyAndValue(item).name);
2629
+ return {
2630
+ author: text(live["author"]),
2631
+ body: unbase64(text(live["body"])),
2632
+ comment: text(live["comment"]),
2633
+ disable: bool(live["disable"]),
2634
+ "from-address": text(live["from-address"]),
2635
+ header: names(Object.keys(headers)),
2636
+ headerDigest: headerDigest(props.name, headers),
2637
+ mailto: addressList(live["mailto"]),
2638
+ "mailto-user": addressList(live["mailto-user"]),
2639
+ method: text(live["method"]),
2640
+ mode: text(live["mode"]),
2641
+ name: props.name,
2642
+ origin: text(live["origin"]),
2643
+ port: portOf(live["port"]),
2644
+ secret: names(secrets),
2645
+ sealed: "",
2646
+ server: text(live["server"]),
2647
+ type: props.type,
2648
+ url: text(live["url"]),
2649
+ username: text(live["username"])
2650
+ };
2651
+ };
2652
+
2653
+ // src/proxmox/pbs-notification-target-form.ts
2654
+ var OWNED = {
2655
+ author: ["sendmail", "smtp"],
2656
+ body: ["webhook"],
2657
+ "from-address": ["sendmail", "smtp"],
2658
+ header: ["webhook"],
2659
+ mailto: ["sendmail", "smtp"],
2660
+ "mailto-user": ["sendmail", "smtp"],
2661
+ method: ["webhook"],
2662
+ mode: ["smtp"],
2663
+ password: ["smtp"],
2664
+ port: ["smtp"],
2665
+ secret: ["webhook"],
2666
+ server: ["smtp"],
2667
+ url: ["webhook"],
2668
+ username: ["smtp"]
2669
+ };
2670
+ var REQUIRED = {
2671
+ sendmail: [],
2672
+ smtp: ["server", "from-address"],
2673
+ webhook: ["url", "method"]
2674
+ };
2675
+ var CLEAN_NAME = /^[^,=\s]+$/;
2676
+ var WRITE_ONLY = "declare it { fromEnv: 'NAME' }, or keep it in 'secret' and write {{ secrets.<name> }}";
2677
+ var refusals = (props) => {
2678
+ const out = [];
2679
+ for (const [field5, types] of Object.entries(OWNED)) {
2680
+ if (props[field5] !== undefined && !types.includes(props.type)) {
2681
+ out.push(`'${field5}' belongs to ${types.join("/")} targets, not ${props.type}`);
2682
+ }
2683
+ }
2684
+ for (const field5 of REQUIRED[props.type]) {
2685
+ if (props[field5] === undefined || props[field5] === "")
2686
+ out.push(`${props.type} needs '${field5}'`);
2687
+ }
2688
+ for (const key of [...Object.keys(props.header ?? {}), ...Object.keys(props.secret ?? {})]) {
2689
+ if (!CLEAN_NAME.test(key))
2690
+ out.push(`'${key}' cannot be a header or secret name`);
2691
+ }
2692
+ for (const name of literalCredentialHeaders(props.header)) {
2693
+ out.push(`header '${name}' holds a literal credential; ${WRITE_ONLY}`);
2694
+ }
2695
+ for (const where of literalCredentialsInUrl(props.url)) {
2696
+ out.push(`'url' ${where} holds a literal credential; ${WRITE_ONLY}`);
2697
+ }
2698
+ return out;
2699
+ };
2700
+ var isFromEnv = (value) => typeof value !== "string";
2701
+ var resolveGroups = (props, env = process.env) => {
2702
+ const headerRefs = {};
2703
+ const headers = {};
2704
+ for (const [name, value] of Object.entries(props.header ?? {})) {
2705
+ if (isFromEnv(value))
2706
+ headerRefs[name] = value;
2707
+ else
2708
+ headers[name] = value;
2709
+ }
2710
+ const header = resolveAll(headerRefs, env);
2711
+ const sealedRefs = {};
2712
+ for (const [name, ref] of Object.entries(props.secret ?? {}))
2713
+ sealedRefs[`secret:${name}`] = ref;
2714
+ if (props.password !== undefined)
2715
+ sealedRefs["password"] = props.password;
2716
+ const sealed = resolveAll(sealedRefs, env);
2717
+ return {
2718
+ header: { missing: header.missing, values: { ...headers, ...header.values } },
2719
+ sealed
2720
+ };
2721
+ };
2722
+ var same5 = (declared, live) => declared === undefined || declared === live;
2723
+ var plainMatches = (live, props) => live.disable === (props.disable === true) && same5(props.comment, live.comment) && same5(props.author, live.author) && same5(props["from-address"], live["from-address"]) && same5(props.server, live.server) && (props.port === undefined || props.port === live.port) && same5(props.mode, live.mode) && same5(props.username, live.username) && same5(props.url, live.url) && same5(props.method, live.method) && same5(props.body, live.body) && (props.mailto === undefined || addressList(props.mailto) === live.mailto) && (props["mailto-user"] === undefined || addressList(props["mailto-user"]) === live["mailto-user"]);
2724
+ var headerState = (live, props, groups) => {
2725
+ if (props.header === undefined)
2726
+ return "match";
2727
+ if (names(Object.keys(props.header)) !== live.header)
2728
+ return "stale";
2729
+ if (groups.header.missing.length > 0)
2730
+ return "unknown";
2731
+ return headerDigest(props.name, groups.header.values) === live.headerDigest ? "match" : "stale";
2732
+ };
2733
+ var sealedState = (live, props, groups, sealed) => {
2734
+ if (props.secret === undefined && props.password === undefined)
2735
+ return "match";
2736
+ if (props.secret !== undefined && names(Object.keys(props.secret)) !== live.secret)
2737
+ return "stale";
2738
+ if (groups.sealed.missing.length > 0 || sealed === "")
2739
+ return "unknown";
2740
+ return sealMatches(sealed, groups.sealed.values) ? "match" : "stale";
2741
+ };
2742
+ var secretItems = (props, groups) => Object.keys(props.secret ?? {}).map((name) => wireItem(name, groups.sealed.values[`secret:${name}`] ?? ""));
2743
+ var headerItems = (props, groups) => Object.keys(props.header ?? {}).map((name) => wireItem(name, groups.header.values[name] ?? ""));
2744
+ var plainPairs = (props) => [
2745
+ ["author", props.author ?? ""],
2746
+ ["body", props.body === undefined ? "" : base64(props.body)],
2747
+ ["comment", props.comment ?? ""],
2748
+ ["from-address", props["from-address"] ?? ""],
2749
+ ["method", props.method ?? ""],
2750
+ ["mode", props.mode ?? ""],
2751
+ ["port", props.port === undefined ? "" : String(props.port)],
2752
+ ["server", props.server ?? ""],
2753
+ ["url", props.url ?? ""],
2754
+ ["username", props.username ?? ""],
2755
+ ["mailto", props.mailto === undefined ? [] : addressList(props.mailto).split(",")],
2756
+ [
2757
+ "mailto-user",
2758
+ props["mailto-user"] === undefined ? [] : addressList(props["mailto-user"]).split(",")
2759
+ ]
2760
+ ];
2761
+ var declared = (props, key) => props[key] !== undefined;
2762
+ var empty = (value) => typeof value === "string" ? value === "" : value.filter((item) => item !== "").length === 0;
2763
+ var targetForm = (props, groups, carry, mode) => {
2764
+ const form = {
2765
+ disable: props.disable === true ? "1" : "0"
2766
+ };
2767
+ const cleared = [];
2768
+ const put = (key, value) => {
2769
+ if (!empty(value))
2770
+ form[key] = value;
2771
+ else if (mode === "update")
2772
+ cleared.push(key);
2773
+ };
2774
+ for (const [key, value] of plainPairs(props))
2775
+ if (declared(props, key))
2776
+ put(key, value);
2777
+ if (carry.header && props.header !== undefined)
2778
+ put("header", headerItems(props, groups));
2779
+ if (carry.sealed && props.secret !== undefined)
2780
+ put("secret", secretItems(props, groups));
2781
+ if (carry.sealed && props.password !== undefined) {
2782
+ form["password"] = groups.sealed.values["password"] ?? "";
2783
+ }
2784
+ if (mode === "create")
2785
+ form["name"] = props.name;
2786
+ if (cleared.length > 0)
2787
+ form["delete"] = cleared;
2788
+ return form;
2789
+ };
2790
+
2791
+ // src/proxmox/pbs-notification-target-lifecycle.ts
2792
+ 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(() => {
2794
+ return;
2795
+ }));
2796
+ var refuse2 = (props) => {
2797
+ const reasons = refusals(props);
2798
+ return reasons.length === 0 ? Effect41.void : Effect41.die(new Error(`${path(props)}: ${reasons.join("; ")}.`));
2799
+ };
2800
+ var toCarry = (live, props, groups, sealed) => {
2801
+ const header = headerState(live, props, groups) === "stale";
2802
+ const sealedStale = sealedState(live, props, groups, sealed) === "stale";
2803
+ return plainMatches(live, props) && !header && !sealedStale ? undefined : { header, sealed: sealedStale };
2804
+ };
2805
+ var requireValues = (props, groups, carry) => {
2806
+ const missing = [
2807
+ ...carry.header ? groups.header.missing : [],
2808
+ ...carry.sealed ? groups.sealed.missing : []
2809
+ ];
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."));
2811
+ };
2812
+ 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 ?? "" })),
2815
+ diff: ({
2816
+ news,
2817
+ olds,
2818
+ output
2819
+ }) => Effect41.gen(function* () {
2820
+ if (output === undefined || !isResolved9(news))
2821
+ return;
2822
+ if (olds !== undefined && (olds.type !== news.type || olds.name !== news.name)) {
2823
+ return { action: "replace", deleteFirst: olds.name === news.name };
2824
+ }
2825
+ yield* refuse2(news);
2826
+ const live = yield* readLive2(news);
2827
+ if (live === undefined)
2828
+ return { action: "update" };
2829
+ return toCarry(live, news, resolveGroups(news), output.sealed) === undefined ? { action: "noop" } : { action: "update" };
2830
+ }),
2831
+ reconcile: ({ news, output }) => Effect41.gen(function* () {
2832
+ yield* refuse2(news);
2833
+ const groups = resolveGroups(news);
2834
+ const live = yield* readLive2(news);
2835
+ let sealed = output?.sealed ?? "";
2836
+ if (live === undefined) {
2837
+ const carry = { header: true, sealed: true };
2838
+ yield* requireValues(news, groups, carry);
2839
+ const collection2 = `config/notifications/endpoints/${news.type}`;
2840
+ yield* pve(news.target, "provision", "POST", collection2, targetForm(news, groups, carry, "create"));
2841
+ sealed = seal(groups.sealed.values);
2842
+ } else {
2843
+ const carry = toCarry(live, news, groups, sealed);
2844
+ if (carry !== undefined) {
2845
+ yield* requireValues(news, groups, carry);
2846
+ yield* pve(news.target, "provision", "PUT", path(news), targetForm(news, groups, carry, "update"));
2847
+ if (carry.sealed)
2848
+ sealed = seal(groups.sealed.values);
2849
+ }
2850
+ }
2851
+ const after = yield* readLive2(news);
2852
+ if (after === undefined) {
2853
+ return yield* Effect41.die(new Error(`${path(news)}: the write returned no error but the target is still absent.`));
2854
+ }
2855
+ return { ...after, sealed };
2856
+ }),
2857
+ delete: ({ olds }) => pve(olds.target, "provision", "DELETE", path(olds))
2858
+ };
2859
+
2860
+ // src/proxmox/pbs-notification-target.ts
2861
+ var PbsNotificationTarget = Resource21("Pbs.NotificationTarget");
2862
+ var PbsNotificationTargetProvider = () => Provider21.effect(PbsNotificationTarget, Effect42.succeed(PbsNotificationTarget.Provider.of(handlers17)));
2372
2863
  // src/proxmox/pbs-prune-job.ts
2373
- import { Resource as Resource19 } from "alchemy";
2374
- import * as Provider19 from "alchemy/Provider";
2375
- import * as Effect39 from "effect/Effect";
2864
+ import { Resource as Resource22 } from "alchemy";
2865
+ import * as Provider22 from "alchemy/Provider";
2866
+ import * as Effect43 from "effect/Effect";
2376
2867
 
2377
2868
  // src/proxmox/pbs-prune-job-form.ts
2378
2869
  var KEEP_KEYS = [
@@ -2407,14 +2898,14 @@ var requireKeeps = (props) => {
2407
2898
  return;
2408
2899
  throw new Error(`Pbs.PruneJob ${props.id}: declares no keep-* window, which tells PBS to keep NOTHING on ` + `datastore '${props.store}'. Every keep-* is optional on the wire, so an omission and a ` + 'deliberate "retain nothing" are indistinguishable; declare at least one window (keep-last, ' + "keep-hourly, keep-daily, keep-weekly, keep-monthly or keep-yearly) or delete the job.");
2409
2900
  };
2410
- var field4 = (name, value) => value === undefined ? {} : { [name]: value };
2901
+ var field5 = (name, value) => value === undefined ? {} : { [name]: value };
2411
2902
  var integer = (value) => value === undefined ? undefined : String(value);
2412
2903
  var shape5 = (props) => ({
2413
2904
  ...keepForm(props),
2414
- ...field4("comment", props.comment),
2415
- ...field4("disable", flag(props.disable)),
2416
- ...field4("max-depth", integer(props["max-depth"])),
2417
- ...field4("ns", props.ns),
2905
+ ...field5("comment", props.comment),
2906
+ ...field5("disable", flag(props.disable)),
2907
+ ...field5("max-depth", integer(props["max-depth"])),
2908
+ ...field5("ns", props.ns),
2418
2909
  schedule: props.schedule,
2419
2910
  store: props.store
2420
2911
  });
@@ -2428,9 +2919,9 @@ var updateBody3 = (props) => {
2428
2919
  };
2429
2920
 
2430
2921
  // src/proxmox/pbs-prune-job.ts
2431
- var PbsPruneJob = Resource19("Pbs.PruneJob");
2432
- var same4 = (declared, live) => declared === undefined || declared === live;
2433
- var handlers15 = pveHandlers({
2922
+ var PbsPruneJob = Resource22("Pbs.PruneJob");
2923
+ var same6 = (declared2, live) => declared2 === undefined || declared2 === live;
2924
+ var handlers18 = pveHandlers({
2434
2925
  attributes: (live, props) => ({
2435
2926
  comment: text(live["comment"], ""),
2436
2927
  disable: bool(live["disable"], false),
@@ -2443,15 +2934,15 @@ var handlers15 = pveHandlers({
2443
2934
  }),
2444
2935
  collection: () => "config/prune",
2445
2936
  createForm: createBody2,
2446
- matches: (attributes2, props) => attributes2.schedule === props.schedule && attributes2.store === props.store && same4(props.ns, attributes2.ns) && same4(props["max-depth"], attributes2["max-depth"]) && same4(props.disable, attributes2.disable) && same4(props.comment, attributes2.comment) && keepsMatch(attributes2, props),
2937
+ 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),
2447
2938
  path: (props) => `config/prune/${props.id}`,
2448
2939
  updateForm: updateBody3
2449
2940
  });
2450
- var PbsPruneJobProvider = () => Provider19.effect(PbsPruneJob, Effect39.succeed(PbsPruneJob.Provider.of(handlers15)));
2941
+ var PbsPruneJobProvider = () => Provider22.effect(PbsPruneJob, Effect43.succeed(PbsPruneJob.Provider.of(handlers18)));
2451
2942
  // src/proxmox/pbs-sync-job.ts
2452
- import { Resource as Resource20 } from "alchemy";
2453
- import * as Provider20 from "alchemy/Provider";
2454
- import * as Effect40 from "effect/Effect";
2943
+ import { Resource as Resource23 } from "alchemy";
2944
+ import * as Provider23 from "alchemy/Provider";
2945
+ import * as Effect44 from "effect/Effect";
2455
2946
 
2456
2947
  // src/proxmox/pbs-sync-job-form.ts
2457
2948
  var UNSET4 = -1;
@@ -2499,7 +2990,7 @@ var createBody3 = (props) => ({
2499
2990
  ...props["sync-direction"] === undefined ? {} : { "sync-direction": props["sync-direction"] }
2500
2991
  });
2501
2992
  var updateBody4 = (props) => shape6(props);
2502
- var handlers16 = pveHandlers({
2993
+ var handlers19 = pveHandlers({
2503
2994
  attributes: (live, props) => ({
2504
2995
  comment: text(live["comment"], ""),
2505
2996
  "group-filter": filters(live["group-filter"]),
@@ -2526,12 +3017,12 @@ var handlers16 = pveHandlers({
2526
3017
  });
2527
3018
 
2528
3019
  // src/proxmox/pbs-sync-job.ts
2529
- var PbsSyncJob = Resource20("Pbs.SyncJob");
2530
- var PbsSyncJobProvider = () => Provider20.effect(PbsSyncJob, Effect40.succeed(PbsSyncJob.Provider.of(handlers16)));
3020
+ var PbsSyncJob = Resource23("Pbs.SyncJob");
3021
+ var PbsSyncJobProvider = () => Provider23.effect(PbsSyncJob, Effect44.succeed(PbsSyncJob.Provider.of(handlers19)));
2531
3022
  // src/proxmox/pbs-verify-job.ts
2532
- import { Resource as Resource21 } from "alchemy";
2533
- import * as Provider21 from "alchemy/Provider";
2534
- import * as Effect41 from "effect/Effect";
3023
+ import { Resource as Resource24 } from "alchemy";
3024
+ import * as Provider24 from "alchemy/Provider";
3025
+ import * as Effect45 from "effect/Effect";
2535
3026
 
2536
3027
  // src/proxmox/pbs-verify-job-form.ts
2537
3028
  var rechecks = (ignoreVerified, outdatedAfter) => {
@@ -2552,8 +3043,8 @@ var shape7 = (props) => ({
2552
3043
  });
2553
3044
 
2554
3045
  // src/proxmox/pbs-verify-job.ts
2555
- var PbsVerifyJob = Resource21("Pbs.VerifyJob");
2556
- var handlers17 = pveHandlers({
3046
+ var PbsVerifyJob = Resource24("Pbs.VerifyJob");
3047
+ var handlers20 = pveHandlers({
2557
3048
  attributes: (live, props) => {
2558
3049
  const ignoreVerified = bool(live["ignore-verified"], true);
2559
3050
  const outdatedAfter = int(live["outdated-after"], -1);
@@ -2575,12 +3066,12 @@ var handlers17 = pveHandlers({
2575
3066
  path: (props) => `config/verify/${props.id}`,
2576
3067
  updateForm: shape7
2577
3068
  });
2578
- var PbsVerifyJobProvider = () => Provider21.effect(PbsVerifyJob, Effect41.succeed(PbsVerifyJob.Provider.of(handlers17)));
3069
+ var PbsVerifyJobProvider = () => Provider24.effect(PbsVerifyJob, Effect45.succeed(PbsVerifyJob.Provider.of(handlers20)));
2579
3070
  // src/proxmox/pool.ts
2580
- import { Resource as Resource22 } from "alchemy";
2581
- import * as Provider22 from "alchemy/Provider";
2582
- import * as Effect42 from "effect/Effect";
2583
- var ProxmoxPool = Resource22("Proxmox.Pool");
3071
+ import { Resource as Resource25 } from "alchemy";
3072
+ import * as Provider25 from "alchemy/Provider";
3073
+ import * as Effect46 from "effect/Effect";
3074
+ var ProxmoxPool = Resource25("Proxmox.Pool");
2584
3075
  var poolCreateForm = (props) => ({
2585
3076
  comment: props.comment ?? "",
2586
3077
  poolid: props.poolid
@@ -2588,7 +3079,7 @@ var poolCreateForm = (props) => ({
2588
3079
  var poolUpdateForm = (props) => ({
2589
3080
  comment: props.comment ?? ""
2590
3081
  });
2591
- var handlers18 = pveHandlers({
3082
+ var handlers21 = pveHandlers({
2592
3083
  attributes: (live, props) => {
2593
3084
  const row = live;
2594
3085
  return {
@@ -2603,7 +3094,7 @@ var handlers18 = pveHandlers({
2603
3094
  path: (props) => `pools/${props.poolid}`,
2604
3095
  updateForm: poolUpdateForm
2605
3096
  });
2606
- var ProxmoxPoolProvider = () => Provider22.effect(ProxmoxPool, Effect42.succeed(ProxmoxPool.Provider.of(handlers18)));
3097
+ var ProxmoxPoolProvider = () => Provider25.effect(ProxmoxPool, Effect46.succeed(ProxmoxPool.Provider.of(handlers21)));
2607
3098
  // src/proxmox/provision-baseline.ts
2608
3099
  var PROVISION_PRIVILEGES = Object.freeze([
2609
3100
  "Datastore.Allocate",
@@ -2645,30 +3136,30 @@ var PROVISION_DEFAULTS = Object.freeze({
2645
3136
  var ID = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
2646
3137
  var USERID = /^[A-Za-z0-9][A-Za-z0-9._-]*@[A-Za-z][A-Za-z0-9._-]*$/;
2647
3138
  var COMMENT = /^[A-Za-z0-9 ._,()@/+-]*$/;
2648
- var problems2 = (names) => {
3139
+ var problems2 = (names2) => {
2649
3140
  const found = [];
2650
- if (!ID.test(names.role))
2651
- found.push(`role \`${names.role}\` is not a PVE role id`);
2652
- if (!ID.test(names.mintGroup))
2653
- found.push(`mintGroup \`${names.mintGroup}\` is not a group id`);
2654
- if (!ID.test(names.readRole))
2655
- found.push(`readRole \`${names.readRole}\` is not a PVE role id`);
2656
- for (const [field5, userid] of [
2657
- ["provisionUser", names.provisionUser],
2658
- ["readUser", names.readUser]
3141
+ if (!ID.test(names2.role))
3142
+ found.push(`role \`${names2.role}\` is not a PVE role id`);
3143
+ if (!ID.test(names2.mintGroup))
3144
+ found.push(`mintGroup \`${names2.mintGroup}\` is not a group id`);
3145
+ if (!ID.test(names2.readRole))
3146
+ found.push(`readRole \`${names2.readRole}\` is not a PVE role id`);
3147
+ for (const [field6, userid] of [
3148
+ ["provisionUser", names2.provisionUser],
3149
+ ["readUser", names2.readUser]
2659
3150
  ]) {
2660
3151
  if (userid !== null && !USERID.test(userid)) {
2661
- found.push(`${field5} \`${userid}\` is not a realm-qualified user id (name@realm)`);
3152
+ found.push(`${field6} \`${userid}\` is not a realm-qualified user id (name@realm)`);
2662
3153
  }
2663
3154
  }
2664
- if (names.readUser === names.provisionUser)
3155
+ if (names2.readUser === names2.provisionUser)
2665
3156
  found.push("readUser and provisionUser are one user");
2666
- if (!COMMENT.test(names.comment))
3157
+ if (!COMMENT.test(names2.comment))
2667
3158
  found.push("comment holds a character the bootstrap will not quote");
2668
3159
  return found;
2669
3160
  };
2670
- var provisionBaseline = (names = {}) => {
2671
- const given = Object.entries(names).filter(([, value]) => value !== undefined);
3161
+ var provisionBaseline = (names2 = {}) => {
3162
+ const given = Object.entries(names2).filter(([, value]) => value !== undefined);
2672
3163
  const resolved = {
2673
3164
  ...PROVISION_DEFAULTS,
2674
3165
  ...Object.fromEntries(given)
@@ -2759,8 +3250,8 @@ else echo 'grant ${grant.userid} ${grant.roleid} /: adding'; pveum acl modify /
2759
3250
  var requireRole = (roleid) => `
2760
3251
  get /access/roles/${roleid} 'print 1' >/dev/null || { echo 'bootstrap: role ${roleid} does not exist; create it, or name another readRole' >&2; exit 1; }
2761
3252
  `;
2762
- var provisionBootstrap = (names = {}) => {
2763
- const baseline = provisionBaseline(names);
3253
+ var provisionBootstrap = (names2 = {}) => {
3254
+ const baseline = provisionBaseline(names2);
2764
3255
  const borrowed = baseline.grants.map((grant) => grant.roleid).filter((roleid) => roleid !== baseline.role.roleid);
2765
3256
  return [
2766
3257
  HEADER,
@@ -2777,35 +3268,35 @@ echo 'provision baseline: done. Next: point the OpenBao proxmox mount at these u
2777
3268
  // src/proxmox/provision-declare.ts
2778
3269
  import { adopt } from "alchemy/AdoptPolicy";
2779
3270
  import * as RemovalPolicy from "alchemy/RemovalPolicy";
2780
- import * as Effect45 from "effect/Effect";
3271
+ import * as Effect49 from "effect/Effect";
2781
3272
 
2782
3273
  // src/proxmox/role.ts
2783
- import { Resource as Resource23 } from "alchemy";
2784
- import * as Provider23 from "alchemy/Provider";
2785
- import * as Effect43 from "effect/Effect";
2786
- var ProxmoxRole = Resource23("Proxmox.Role");
2787
- var canonical = (privs) => [...new Set(privs.map((priv) => priv.trim()).filter((priv) => priv.length > 0))].sort();
3274
+ import { Resource as Resource26 } from "alchemy";
3275
+ import * as Provider26 from "alchemy/Provider";
3276
+ import * as Effect47 from "effect/Effect";
3277
+ var ProxmoxRole = Resource26("Proxmox.Role");
3278
+ var canonical2 = (privs) => [...new Set(privs.map((priv) => priv.trim()).filter((priv) => priv.length > 0))].sort();
2788
3279
  var livePrivs = (live) => {
2789
3280
  const listed = live["privs"];
2790
3281
  if (typeof listed === "string")
2791
- return canonical(listed.split(","));
2792
- return canonical(Object.keys(live).filter((key) => key.includes(".")));
3282
+ return canonical2(listed.split(","));
3283
+ return canonical2(Object.keys(live).filter((key) => key.includes(".")));
2793
3284
  };
2794
- var handlers19 = pveHandlers({
3285
+ var handlers22 = pveHandlers({
2795
3286
  attributes: (live, props) => ({ privs: livePrivs(live), roleid: props.roleid }),
2796
3287
  collection: () => "access/roles",
2797
- createForm: (props) => ({ privs: canonical(props.privs).join(","), roleid: props.roleid }),
2798
- matches: (attributes2, props) => canonical(attributes2.privs).join(",") === canonical(props.privs).join(","),
3288
+ createForm: (props) => ({ privs: canonical2(props.privs).join(","), roleid: props.roleid }),
3289
+ matches: (attributes2, props) => canonical2(attributes2.privs).join(",") === canonical2(props.privs).join(","),
2799
3290
  path: (props) => `access/roles/${props.roleid}`,
2800
- updateForm: (props) => ({ privs: canonical(props.privs).join(",") })
3291
+ updateForm: (props) => ({ privs: canonical2(props.privs).join(",") })
2801
3292
  });
2802
- var ProxmoxRoleProvider = () => Provider23.effect(ProxmoxRole, Effect43.succeed(ProxmoxRole.Provider.of(handlers19)));
3293
+ var ProxmoxRoleProvider = () => Provider26.effect(ProxmoxRole, Effect47.succeed(ProxmoxRole.Provider.of(handlers22)));
2803
3294
 
2804
3295
  // src/proxmox/user.ts
2805
- import { Resource as Resource24 } from "alchemy";
2806
- import * as Provider24 from "alchemy/Provider";
2807
- import * as Effect44 from "effect/Effect";
2808
- var ProxmoxUser = Resource24("Proxmox.User");
3296
+ import { Resource as Resource27 } from "alchemy";
3297
+ import * as Provider27 from "alchemy/Provider";
3298
+ import * as Effect48 from "effect/Effect";
3299
+ var ProxmoxUser = Resource27("Proxmox.User");
2809
3300
  var str2 = (value) => typeof value === "string" ? value : "";
2810
3301
  var int2 = (value) => {
2811
3302
  const parsed = typeof value === "number" ? value : Number(value ?? 0);
@@ -2813,8 +3304,8 @@ var int2 = (value) => {
2813
3304
  };
2814
3305
  var groupSet = (value) => {
2815
3306
  const raw = Array.isArray(value) ? value : typeof value === "string" ? value.split(",") : [];
2816
- const names = raw.map((group) => str2(group).trim()).filter((group) => group !== "");
2817
- return [...new Set(names)].sort();
3307
+ const names2 = raw.map((group) => str2(group).trim()).filter((group) => group !== "");
3308
+ return [...new Set(names2)].sort();
2818
3309
  };
2819
3310
  var tokenNames = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? Object.keys(value).sort() : [];
2820
3311
  var shape8 = (props) => ({
@@ -2826,7 +3317,7 @@ var shape8 = (props) => ({
2826
3317
  groups: groupSet(props.groups).join(","),
2827
3318
  lastname: props.lastname ?? ""
2828
3319
  });
2829
- var handlers20 = pveHandlers({
3320
+ var handlers23 = pveHandlers({
2830
3321
  attributes: (live, props) => ({
2831
3322
  comment: str2(live["comment"]),
2832
3323
  email: str2(live["email"]),
@@ -2844,13 +3335,13 @@ var handlers20 = pveHandlers({
2844
3335
  path: (props) => `access/users/${props.userid}`,
2845
3336
  updateForm: shape8
2846
3337
  });
2847
- var ProxmoxUserProvider = () => Provider24.effect(ProxmoxUser, Effect44.succeed(ProxmoxUser.Provider.of(handlers20)));
3338
+ var ProxmoxUserProvider = () => Provider27.effect(ProxmoxUser, Effect48.succeed(ProxmoxUser.Provider.of(handlers23)));
2848
3339
 
2849
3340
  // src/proxmox/provision-declare.ts
2850
- var declareProvisionBaseline = (id, target, names = {}, options = {}) => Effect45.gen(function* () {
2851
- const baseline = yield* Effect45.try({
3341
+ var declareProvisionBaseline = (id, target, names2 = {}, options = {}) => Effect49.gen(function* () {
3342
+ const baseline = yield* Effect49.try({
2852
3343
  catch: (cause) => cause instanceof Error ? cause : new Error(String(cause)),
2853
- try: () => provisionBaseline(names)
3344
+ try: () => provisionBaseline(names2)
2854
3345
  });
2855
3346
  const owned = (resource) => {
2856
3347
  const kept = resource.pipe(RemovalPolicy.retain());
@@ -2862,13 +3353,13 @@ var declareProvisionBaseline = (id, target, names = {}, options = {}) => Effect4
2862
3353
  target
2863
3354
  }));
2864
3355
  const group = yield* owned(ProxmoxGroup(`${id}-mint-group`, { ...baseline.group, target }));
2865
- const users = yield* Effect45.all(baseline.users.map((user) => owned(ProxmoxUser(`${id}-${user.lane}-user`, {
3356
+ const users = yield* Effect49.all(baseline.users.map((user) => owned(ProxmoxUser(`${id}-${user.lane}-user`, {
2866
3357
  comment: user.comment,
2867
3358
  groups: [...user.groups],
2868
3359
  target,
2869
3360
  userid: user.userid
2870
3361
  }))));
2871
- const grants = yield* Effect45.all(baseline.grants.map((grant) => owned(ProxmoxAcl(`${id}-${grant.lane}-grant`, {
3362
+ const grants = yield* Effect49.all(baseline.grants.map((grant) => owned(ProxmoxAcl(`${id}-${grant.lane}-grant`, {
2872
3363
  path: "/",
2873
3364
  propagate: true,
2874
3365
  roleid: grant.roleid,
@@ -2879,10 +3370,10 @@ var declareProvisionBaseline = (id, target, names = {}, options = {}) => Effect4
2879
3370
  return { baseline, grants, group, role, users };
2880
3371
  });
2881
3372
  // src/proxmox/qemu.ts
2882
- import { Resource as Resource25 } from "alchemy";
2883
- import * as Provider25 from "alchemy/Provider";
2884
- import * as Effect46 from "effect/Effect";
2885
- var ProxmoxVm = Resource25("Proxmox.Vm");
3373
+ import { Resource as Resource28 } from "alchemy";
3374
+ import * as Provider28 from "alchemy/Provider";
3375
+ import * as Effect50 from "effect/Effect";
3376
+ var ProxmoxVm = Resource28("Proxmox.Vm");
2886
3377
  var shape9 = (props) => ({
2887
3378
  cores: String(props.cores ?? 1),
2888
3379
  memory: String(props.memory ?? 512),
@@ -2891,7 +3382,7 @@ var shape9 = (props) => ({
2891
3382
  sockets: String(props.sockets ?? 1),
2892
3383
  ...props.net0 === undefined ? {} : { net0: props.net0 }
2893
3384
  });
2894
- var handlers21 = pveHandlers({
3385
+ var handlers24 = pveHandlers({
2895
3386
  attributes: (live, props) => ({
2896
3387
  cores: num(live["cores"], 1),
2897
3388
  memory: num(live["memory"], 512),
@@ -2907,11 +3398,11 @@ var handlers21 = pveHandlers({
2907
3398
  path: (props) => `nodes/${props.node}/qemu/${String(props.vmid)}/config`,
2908
3399
  updateForm: shape9
2909
3400
  });
2910
- var ProxmoxVmProvider = () => Provider25.effect(ProxmoxVm, Effect46.succeed(ProxmoxVm.Provider.of(handlers21)));
3401
+ var ProxmoxVmProvider = () => Provider28.effect(ProxmoxVm, Effect50.succeed(ProxmoxVm.Provider.of(handlers24)));
2911
3402
  // src/proxmox/replication-job.ts
2912
- import { Resource as Resource26 } from "alchemy";
2913
- import * as Provider26 from "alchemy/Provider";
2914
- import * as Effect47 from "effect/Effect";
3403
+ import { Resource as Resource29 } from "alchemy";
3404
+ import * as Provider29 from "alchemy/Provider";
3405
+ import * as Effect51 from "effect/Effect";
2915
3406
 
2916
3407
  // src/proxmox/replication-job-form.ts
2917
3408
  var DEFAULT_SCHEDULE = "*/15";
@@ -2934,14 +3425,14 @@ var updateBody5 = (props) => ({
2934
3425
  });
2935
3426
 
2936
3427
  // src/proxmox/replication-job.ts
2937
- var ProxmoxReplicationJob = Resource26("Proxmox.ReplicationJob");
3428
+ var ProxmoxReplicationJob = Resource29("Proxmox.ReplicationJob");
2938
3429
  var rateOf = (value) => {
2939
3430
  if (typeof value === "number")
2940
3431
  return value;
2941
3432
  const parsed = Number.parseFloat(text(value));
2942
3433
  return Number.isFinite(parsed) ? parsed : 0;
2943
3434
  };
2944
- var handlers22 = pveHandlers({
3435
+ var handlers25 = pveHandlers({
2945
3436
  attributes: (live, props) => ({
2946
3437
  comment: text(live["comment"], ""),
2947
3438
  disable: bool(live["disable"], false),
@@ -2961,15 +3452,15 @@ var handlers22 = pveHandlers({
2961
3452
  path: (props) => `cluster/replication/${jobId(props)}`,
2962
3453
  updateForm: updateBody5
2963
3454
  });
2964
- var ProxmoxReplicationJobProvider = () => Provider26.effect(ProxmoxReplicationJob, Effect47.succeed(ProxmoxReplicationJob.Provider.of(handlers22)));
3455
+ var ProxmoxReplicationJobProvider = () => Provider29.effect(ProxmoxReplicationJob, Effect51.succeed(ProxmoxReplicationJob.Provider.of(handlers25)));
2965
3456
  // src/proxmox/sdn-apply.ts
2966
- import { Resource as Resource27 } from "alchemy";
2967
- import { isResolved as isResolved9 } from "alchemy/Diff";
2968
- import * as Provider27 from "alchemy/Provider";
2969
- import * as Effect49 from "effect/Effect";
3457
+ import { Resource as Resource30 } from "alchemy";
3458
+ import { isResolved as isResolved10 } from "alchemy/Diff";
3459
+ import * as Provider30 from "alchemy/Provider";
3460
+ import * as Effect53 from "effect/Effect";
2970
3461
 
2971
3462
  // src/proxmox/sdn-apply-read.ts
2972
- import * as Effect48 from "effect/Effect";
3463
+ import * as Effect52 from "effect/Effect";
2973
3464
  var MARKED = [
2974
3465
  "cluster/sdn/zones",
2975
3466
  "cluster/sdn/vnets",
@@ -2978,7 +3469,7 @@ var MARKED = [
2978
3469
  "cluster/sdn/route-maps"
2979
3470
  ];
2980
3471
  var subsystemAbsent = (error) => error instanceof PveError && error.status === 501;
2981
- var markedPending = (target) => Effect48.all(MARKED.map((collection2) => pve(target, "read", "GET", `${collection2}?pending=1`).pipe(Effect48.map((rows) => (rows ?? []).filter((row) => row.state !== undefined).length), Effect48.catchIf(subsystemAbsent, () => Effect48.succeed(0)))), { concurrency: "unbounded" }).pipe(Effect48.map((counts) => counts.reduce((total, count2) => total + count2, 0)));
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)));
2982
3473
  var fabricStaged = (staged, running) => {
2983
3474
  if (staged === undefined && running === undefined)
2984
3475
  return 0;
@@ -2986,11 +3477,11 @@ var fabricStaged = (staged, running) => {
2986
3477
  return "inconsistent";
2987
3478
  return staged === running ? 0 : 1;
2988
3479
  };
2989
- var fabricPending = (target) => Effect48.all(["pending", "running"].map((view) => pve(target, "read", "GET", `cluster/sdn/fabrics/all?${view}=1`).pipe(Effect48.map(canonical2), Effect48.catchIf(subsystemAbsent, () => Effect48.succeed(undefined)))), { concurrency: "unbounded" }).pipe(Effect48.flatMap(([staged, running]) => {
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]) => {
2990
3481
  const verdict = fabricStaged(staged, running);
2991
- return verdict === "inconsistent" ? Effect48.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.")) : Effect48.succeed(verdict);
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);
2992
3483
  }));
2993
- var canonical2 = (value) => JSON.stringify(sortDeep(value));
3484
+ var canonical3 = (value) => JSON.stringify(sortDeep(value));
2994
3485
  var sortDeep = (value) => {
2995
3486
  if (Array.isArray(value)) {
2996
3487
  return value.map(sortDeep).sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)));
@@ -3001,39 +3492,39 @@ var sortDeep = (value) => {
3001
3492
  }
3002
3493
  return value;
3003
3494
  };
3004
- var sdnPendingCount = (target) => Effect48.all([markedPending(target), fabricPending(target)], { concurrency: "unbounded" }).pipe(Effect48.map(([marked, fabric]) => marked + fabric));
3495
+ var sdnPendingCount = (target) => Effect52.all([markedPending(target), fabricPending(target)], { concurrency: "unbounded" }).pipe(Effect52.map(([marked, fabric]) => marked + fabric));
3005
3496
 
3006
3497
  // src/proxmox/sdn-apply.ts
3007
- var ProxmoxSdnApply = Resource27("Proxmox.SdnApply");
3008
- var read3 = (props) => sdnPendingCount(props.target).pipe(Effect49.map((pending) => ({ pending })));
3009
- var ProxmoxSdnApplyProvider = () => Provider27.effect(ProxmoxSdnApply, Effect49.succeed(ProxmoxSdnApply.Provider.of({
3010
- list: () => Effect49.succeed([]),
3011
- read: Effect49.fn(function* ({ olds }) {
3498
+ 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 }) {
3012
3503
  return yield* read3(olds);
3013
3504
  }),
3014
- diff: Effect49.fn(function* ({ news }) {
3015
- if (!isResolved9(news))
3505
+ diff: Effect53.fn(function* ({ news }) {
3506
+ if (!isResolved10(news))
3016
3507
  return;
3017
3508
  const { pending } = yield* read3(news);
3018
3509
  return pending === 0 ? { action: "noop" } : { action: "update" };
3019
3510
  }),
3020
- reconcile: Effect49.fn(function* ({ news }) {
3511
+ reconcile: Effect53.fn(function* ({ news }) {
3021
3512
  const staged = yield* read3(news);
3022
3513
  if (staged.pending === 0)
3023
3514
  return staged;
3024
3515
  yield* pve(news.target, "provision", "PUT", "cluster/sdn");
3025
3516
  const after = yield* read3(news);
3026
3517
  if (after.pending !== 0) {
3027
- return yield* Effect49.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."));
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."));
3028
3519
  }
3029
3520
  return after;
3030
3521
  }),
3031
- delete: () => Effect49.void
3522
+ delete: () => Effect53.void
3032
3523
  })));
3033
3524
  // src/proxmox/sdn-subnet.ts
3034
- import { Resource as Resource28 } from "alchemy";
3035
- import * as Provider28 from "alchemy/Provider";
3036
- import * as Effect50 from "effect/Effect";
3525
+ import { Resource as Resource31 } from "alchemy";
3526
+ import * as Provider31 from "alchemy/Provider";
3527
+ import * as Effect54 from "effect/Effect";
3037
3528
 
3038
3529
  // src/proxmox/sdn-subnet-form.ts
3039
3530
  var subnetId = (props) => `${props.zone}-${props.cidr.replace("/", "-")}`;
@@ -3063,8 +3554,8 @@ var updateForm2 = (props) => {
3063
3554
  };
3064
3555
 
3065
3556
  // src/proxmox/sdn-subnet.ts
3066
- var ProxmoxSdnSubnet = Resource28("Proxmox.SdnSubnet");
3067
- var handlers23 = pveHandlers({
3557
+ var ProxmoxSdnSubnet = Resource31("Proxmox.SdnSubnet");
3558
+ var handlers26 = pveHandlers({
3068
3559
  attributes: (live, props) => {
3069
3560
  const liveVnet = text(live["vnet"]);
3070
3561
  if (liveVnet !== "" && liveVnet !== props.vnet)
@@ -3089,12 +3580,12 @@ var handlers23 = pveHandlers({
3089
3580
  path: (props) => `cluster/sdn/vnets/${props.vnet}/subnets/${subnetId(props)}`,
3090
3581
  updateForm: updateForm2
3091
3582
  });
3092
- var ProxmoxSdnSubnetProvider = () => Provider28.effect(ProxmoxSdnSubnet, Effect50.succeed(ProxmoxSdnSubnet.Provider.of(handlers23)));
3583
+ var ProxmoxSdnSubnetProvider = () => Provider31.effect(ProxmoxSdnSubnet, Effect54.succeed(ProxmoxSdnSubnet.Provider.of(handlers26)));
3093
3584
  // src/proxmox/sdn-vnet.ts
3094
- import { Resource as Resource29 } from "alchemy";
3095
- import * as Provider29 from "alchemy/Provider";
3096
- import * as Effect51 from "effect/Effect";
3097
- var ProxmoxSdnVnet = Resource29("Proxmox.SdnVnet");
3585
+ import { Resource as Resource32 } from "alchemy";
3586
+ import * as Provider32 from "alchemy/Provider";
3587
+ import * as Effect55 from "effect/Effect";
3588
+ var ProxmoxSdnVnet = Resource32("Proxmox.SdnVnet");
3098
3589
  var tagOf = (value) => typeof value === "number" ? value : undefined;
3099
3590
  var shape11 = (props) => {
3100
3591
  const alias = props.alias ?? "";
@@ -3113,7 +3604,7 @@ var updateForm3 = (props) => {
3113
3604
  ];
3114
3605
  return withClears(shape11(props), unset2);
3115
3606
  };
3116
- var handlers24 = pveHandlers({
3607
+ var handlers27 = pveHandlers({
3117
3608
  readRole: "provision",
3118
3609
  attributes: (live, props) => ({
3119
3610
  alias: typeof live["alias"] === "string" ? live["alias"] : "",
@@ -3130,13 +3621,13 @@ var handlers24 = pveHandlers({
3130
3621
  path: (props) => `cluster/sdn/vnets/${props.vnet}`,
3131
3622
  updateForm: updateForm3
3132
3623
  });
3133
- var ProxmoxSdnVnetProvider = () => Provider29.effect(ProxmoxSdnVnet, Effect51.succeed(ProxmoxSdnVnet.Provider.of(handlers24)));
3624
+ var ProxmoxSdnVnetProvider = () => Provider32.effect(ProxmoxSdnVnet, Effect55.succeed(ProxmoxSdnVnet.Provider.of(handlers27)));
3134
3625
  // src/proxmox/sdn-zone.ts
3135
- import { Resource as Resource30 } from "alchemy";
3136
- import * as Provider30 from "alchemy/Provider";
3137
- import * as Effect52 from "effect/Effect";
3138
- var ProxmoxSdnZone = Resource30("Proxmox.SdnZone");
3139
- var sameIpam = (live, declared) => (live === "" ? "pve" : live) === (declared === undefined || declared === "" ? "pve" : declared);
3626
+ import { Resource as Resource33 } from "alchemy";
3627
+ import * as Provider33 from "alchemy/Provider";
3628
+ import * as Effect56 from "effect/Effect";
3629
+ var ProxmoxSdnZone = Resource33("Proxmox.SdnZone");
3630
+ var sameIpam = (live, declared2) => (live === "" ? "pve" : live) === (declared2 === undefined || declared2 === "" ? "pve" : declared2);
3140
3631
  var body6 = (props) => ({
3141
3632
  ...props.bridge === undefined ? {} : { bridge: props.bridge },
3142
3633
  ...props.dns === undefined ? {} : { dns: props.dns },
@@ -3155,7 +3646,7 @@ var clearable = {
3155
3646
  vlan: ["dns", "dnszone", "ipam", "mtu", "nodes"],
3156
3647
  vxlan: ["dns", "dnszone", "ipam", "mtu", "nodes"]
3157
3648
  };
3158
- var handlers25 = pveHandlers({
3649
+ var handlers28 = pveHandlers({
3159
3650
  readRole: "provision",
3160
3651
  attributes: (live, props) => {
3161
3652
  const liveType = text(live["type"]);
@@ -3186,31 +3677,31 @@ var handlers25 = pveHandlers({
3186
3677
  return withClears(fields, clear);
3187
3678
  }
3188
3679
  });
3189
- var ProxmoxSdnZoneProvider = () => Provider30.effect(ProxmoxSdnZone, Effect52.succeed(ProxmoxSdnZone.Provider.of(handlers25)));
3680
+ var ProxmoxSdnZoneProvider = () => Provider33.effect(ProxmoxSdnZone, Effect56.succeed(ProxmoxSdnZone.Provider.of(handlers28)));
3190
3681
  // src/proxmox/storage.ts
3191
- import { Resource as Resource31 } from "alchemy";
3192
- import * as Provider31 from "alchemy/Provider";
3193
- import * as Effect53 from "effect/Effect";
3194
- var ProxmoxStorage = Resource31("Proxmox.Storage", {
3682
+ import { Resource as Resource34 } from "alchemy";
3683
+ import * as Provider34 from "alchemy/Provider";
3684
+ import * as Effect57 from "effect/Effect";
3685
+ var ProxmoxStorage = Resource34("Proxmox.Storage", {
3195
3686
  defaultRemovalPolicy: "retain"
3196
3687
  });
3197
3688
  var str3 = (value, fallback = "") => typeof value === "string" ? value : fallback;
3198
- var field5 = (name, value) => value === undefined ? {} : { [name]: value };
3689
+ var field6 = (name, value) => value === undefined ? {} : { [name]: value };
3199
3690
  var locatorForm = (locator) => ({ ...locator });
3200
3691
  var tokens = (value) => value.split(",").map((part) => part.trim()).filter((part) => part !== "").sort().join(",");
3201
- var sameList2 = (declared, live) => declared === undefined || tokens(declared) === tokens(live);
3202
- var same5 = (declared, live) => declared === undefined || declared === live;
3692
+ var sameList2 = (declared2, live) => declared2 === undefined || tokens(declared2) === tokens(live);
3693
+ var same7 = (declared2, live) => declared2 === undefined || declared2 === live;
3203
3694
  var mutable2 = (props) => ({
3204
- ...field5("comment", props.comment),
3205
- ...field5("content", props.content),
3206
- ...field5("disable", flag(props.disable)),
3207
- ...field5("maxfiles", props.maxfiles === undefined ? undefined : String(props.maxfiles)),
3208
- ...field5("nodes", props.nodes),
3209
- ...field5("preallocation", props.preallocation),
3210
- ...field5("prune-backups", props["prune-backups"]),
3211
- ...field5("shared", flag(props.shared))
3212
- });
3213
- var handlers26 = pveHandlers({
3695
+ ...field6("comment", props.comment),
3696
+ ...field6("content", props.content),
3697
+ ...field6("disable", flag(props.disable)),
3698
+ ...field6("maxfiles", props.maxfiles === undefined ? undefined : String(props.maxfiles)),
3699
+ ...field6("nodes", props.nodes),
3700
+ ...field6("preallocation", props.preallocation),
3701
+ ...field6("prune-backups", props["prune-backups"]),
3702
+ ...field6("shared", flag(props.shared))
3703
+ });
3704
+ var handlers29 = pveHandlers({
3214
3705
  readRole: "provision",
3215
3706
  attributes: (live, props) => ({
3216
3707
  comment: str3(live["comment"]),
@@ -3231,18 +3722,18 @@ var handlers26 = pveHandlers({
3231
3722
  storage: props.storage,
3232
3723
  type: props.type
3233
3724
  }),
3234
- matches: (attributes2, props) => sameList2(props.content, attributes2.content) && sameList2(props.nodes, attributes2.nodes) && sameList2(props["prune-backups"], attributes2["prune-backups"]) && same5(props.disable, attributes2.disable) && same5(props.shared, attributes2.shared) && same5(props.preallocation, attributes2.preallocation) && same5(props.comment, attributes2.comment) && same5(props.maxfiles, attributes2.maxfiles),
3725
+ 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),
3235
3726
  path: (props) => `storage/${props.storage}`,
3236
3727
  updateForm: mutable2
3237
3728
  });
3238
- var ProxmoxStorageProvider = () => Provider31.effect(ProxmoxStorage, Effect53.succeed(ProxmoxStorage.Provider.of(handlers26)));
3729
+ var ProxmoxStorageProvider = () => Provider34.effect(ProxmoxStorage, Effect57.succeed(ProxmoxStorage.Provider.of(handlers29)));
3239
3730
  // src/proxmox/zfs-pool.ts
3240
- import { Resource as Resource32 } from "alchemy";
3241
- import * as Provider32 from "alchemy/Provider";
3242
- import * as Effect55 from "effect/Effect";
3731
+ import { Resource as Resource35 } from "alchemy";
3732
+ import * as Provider35 from "alchemy/Provider";
3733
+ import * as Effect59 from "effect/Effect";
3243
3734
 
3244
3735
  // src/proxmox/zfs-pool-write.ts
3245
- import * as Effect54 from "effect/Effect";
3736
+ import * as Effect58 from "effect/Effect";
3246
3737
  var createForm6 = (props) => ({
3247
3738
  devices: props.devices.join(","),
3248
3739
  name: props.name,
@@ -3252,28 +3743,28 @@ var createForm6 = (props) => ({
3252
3743
  ...props["draid-config"] === undefined ? {} : { "draid-config": props["draid-config"] }
3253
3744
  });
3254
3745
  var SETTLE_ATTEMPTS = 30;
3255
- var createPool = (props, collection2, path, read4) => Effect54.gen(function* () {
3746
+ var createPool = (props, collection2, path2, read4) => Effect58.gen(function* () {
3256
3747
  const existing = yield* read4(props);
3257
3748
  if (existing !== undefined)
3258
3749
  return existing;
3259
3750
  const upid = yield* pve(props.target, "provision", "POST", collection2, createForm6(props));
3260
- const settle4 = (attempts) => Effect54.gen(function* () {
3751
+ const settle4 = (attempts) => Effect58.gen(function* () {
3261
3752
  const live = yield* read4(props);
3262
3753
  if (live !== undefined || attempts <= 0)
3263
3754
  return live;
3264
- yield* Effect54.sleep("2 seconds");
3755
+ yield* Effect58.sleep("2 seconds");
3265
3756
  return yield* settle4(attempts - 1);
3266
3757
  });
3267
3758
  const after = yield* settle4(SETTLE_ATTEMPTS);
3268
3759
  if (after === undefined) {
3269
- return yield* Effect54.die(new Error(`${path}: 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."));
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."));
3270
3761
  }
3271
3762
  return after;
3272
3763
  });
3273
- var destroyPool = (olds, path) => pve(olds.target, "provision", "DELETE", path);
3764
+ var destroyPool = (olds, path2) => pve(olds.target, "provision", "DELETE", path2);
3274
3765
 
3275
3766
  // src/proxmox/zfs-pool.ts
3276
- var ProxmoxZfsPool = Resource32("Proxmox.ZfsPool", {
3767
+ var ProxmoxZfsPool = Resource35("Proxmox.ZfsPool", {
3277
3768
  defaultRemovalPolicy: "retain"
3278
3769
  });
3279
3770
  var leafDevices = (children) => Array.isArray(children) ? children.flatMap((entry) => {
@@ -3299,19 +3790,23 @@ var spec2 = {
3299
3790
  path: (props) => `nodes/${props.node}/disks/zfs/${props.name}`
3300
3791
  };
3301
3792
  var ops5 = pveOperations(spec2);
3302
- var handlers27 = {
3793
+ var handlers30 = {
3303
3794
  ...pveHandlers(spec2),
3304
- delete: Effect55.fn(function* ({ olds }) {
3795
+ delete: Effect59.fn(function* ({ olds }) {
3305
3796
  yield* destroyPool(olds, spec2.path(olds));
3306
3797
  }),
3307
3798
  reconcile: ({ news }) => createPool(news, spec2.collection(news), spec2.path(news), ops5.read)
3308
3799
  };
3309
- var ProxmoxZfsPoolProvider = () => Provider32.effect(ProxmoxZfsPool, Effect55.succeed(ProxmoxZfsPool.Provider.of(handlers27)));
3800
+ var ProxmoxZfsPoolProvider = () => Provider35.effect(ProxmoxZfsPool, Effect59.succeed(ProxmoxZfsPool.Provider.of(handlers30)));
3310
3801
  export {
3311
3802
  PROVISION_DEFAULTS,
3312
3803
  PROVISION_PRIVILEGES,
3313
3804
  PbsDatastore,
3314
3805
  PbsDatastoreProvider,
3806
+ PbsNotificationMatcher,
3807
+ PbsNotificationMatcherProvider,
3808
+ PbsNotificationTarget,
3809
+ PbsNotificationTargetProvider,
3315
3810
  PbsPruneJob,
3316
3811
  PbsPruneJobProvider,
3317
3812
  PbsSyncJob,
@@ -3342,6 +3837,8 @@ export {
3342
3837
  ProxmoxMetricServerProvider,
3343
3838
  ProxmoxNetworkApplyProvider,
3344
3839
  ProxmoxNodeNetworkProvider,
3840
+ ProxmoxNotificationMatcher,
3841
+ ProxmoxNotificationMatcherProvider,
3345
3842
  ProxmoxNotificationTarget,
3346
3843
  ProxmoxNotificationTargetProvider,
3347
3844
  ProxmoxPool,
@@ -3363,10 +3860,11 @@ export {
3363
3860
  ProxmoxUserProvider,
3364
3861
  ProxmoxVmProvider,
3365
3862
  ProxmoxZfsPoolProvider,
3863
+ alertmanagerAlertBody,
3366
3864
  declareProvisionBaseline,
3367
3865
  provisionBaseline,
3368
3866
  provisionBootstrap
3369
3867
  };
3370
3868
 
3371
- //# debugId=FF23B52181D77F1964756E2164756E21
3869
+ //# debugId=98691C143D2F953764756E2164756E21
3372
3870
  //# sourceMappingURL=index.js.map