@homeflare/distilled-proxmox 0.3.0 → 0.3.2

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 (50) hide show
  1. package/README.md +37 -10
  2. package/dist/config-not-found.test.d.ts +2 -0
  3. package/dist/config-not-found.test.d.ts.map +1 -0
  4. package/dist/config-not-found.test.js +125 -0
  5. package/dist/config-not-found.test.js.map +1 -0
  6. package/dist/errors.d.ts +2 -2
  7. package/dist/errors.d.ts.map +1 -1
  8. package/dist/errors.js.map +1 -1
  9. package/dist/lxc-config-errors.test.d.ts +2 -0
  10. package/dist/lxc-config-errors.test.d.ts.map +1 -0
  11. package/dist/lxc-config-errors.test.js +48 -0
  12. package/dist/lxc-config-errors.test.js.map +1 -0
  13. package/dist/protocol-form.d.ts +18 -0
  14. package/dist/protocol-form.d.ts.map +1 -0
  15. package/dist/protocol-form.js +37 -0
  16. package/dist/protocol-form.js.map +1 -0
  17. package/dist/protocol-form.test.d.ts +2 -0
  18. package/dist/protocol-form.test.d.ts.map +1 -0
  19. package/dist/protocol-form.test.js +68 -0
  20. package/dist/protocol-form.test.js.map +1 -0
  21. package/dist/protocol.d.ts.map +1 -1
  22. package/dist/protocol.js +25 -2
  23. package/dist/protocol.js.map +1 -1
  24. package/dist/replication-firewall-errors.test.d.ts +2 -0
  25. package/dist/replication-firewall-errors.test.d.ts.map +1 -0
  26. package/dist/replication-firewall-errors.test.js +76 -0
  27. package/dist/replication-firewall-errors.test.js.map +1 -0
  28. package/dist/services/cluster.d.ts +50 -10
  29. package/dist/services/cluster.d.ts.map +1 -1
  30. package/dist/services/cluster.js +50 -10
  31. package/dist/services/cluster.js.map +1 -1
  32. package/dist/services/nodes.d.ts +25 -5
  33. package/dist/services/nodes.d.ts.map +1 -1
  34. package/dist/services/nodes.js +24 -5
  35. package/dist/services/nodes.js.map +1 -1
  36. package/dist/services/pools.d.ts +16 -6
  37. package/dist/services/pools.d.ts.map +1 -1
  38. package/dist/services/pools.js +18 -6
  39. package/dist/services/pools.js.map +1 -1
  40. package/package.json +1 -1
  41. package/src/config-not-found.test.ts +202 -0
  42. package/src/errors.ts +4 -12
  43. package/src/lxc-config-errors.test.ts +100 -0
  44. package/src/protocol-form.test.ts +102 -0
  45. package/src/protocol-form.ts +49 -0
  46. package/src/protocol.ts +30 -2
  47. package/src/replication-firewall-errors.test.ts +126 -0
  48. package/src/services/cluster.ts +97 -20
  49. package/src/services/nodes.ts +39 -10
  50. package/src/services/pools.ts +27 -12
package/README.md CHANGED
@@ -34,12 +34,27 @@ every failed write can carry) and non-retryable `BadRequest` fallback, and
34
34
  request that couldn't reach its target node) on the task-management
35
35
  operations the vendor schema itself marks `proxyto: "node"`.
36
36
 
37
- Also ships `awaitTask` (`./Task`), because a PVE POST/PUT/DELETE that
38
- queues a long-running action answers 200 with a bare task id the moment it
39
- is QUEUED, not once it finishes — `awaitTask` polls the generated
40
- `getNodeTaskStatus` operation until an `exitstatus` appears, and fails
41
- unless it is EXACTLY `"OK"` (PVE also answers `"OK (warnings)"`, which is
42
- not success).
37
+ Task polling belongs in the Alchemy provider: the SDK exports the generated
38
+ `getNodeTaskStatus` operation, and the caller owns its bound and failure policy.
39
+
40
+ Pool, backup-job, metric-server and network-interface missing reads expose precise typed tags
41
+ from vendor source at pve-manager 9.2.11. Backup-job absence uses a sole nested
42
+ `errors.id`; network-interface absence uses the exact sole `errors.iface` — on both its
43
+ detail GET and its PUT (both call the same vendor check; DELETE raises it too but is
44
+ deliberately left untyped, tracked separately). Both use the parameter-verification
45
+ envelope; other validation errors retain their structured failure. PVE form arrays use
46
+ repeated keys, preserving commas within a rule/property value. Scalar comma lists and
47
+ query binding are unchanged. Protocol fixtures exercise both the accepted and rejected
48
+ shapes.
49
+
50
+ `PoolNotFound` keeps a non-retryable client-error category despite PVE's wire
51
+ status 500; the patch matcher records the actual status independently.
52
+
53
+ Container config GET, PUT and DELETE also expose `LxcConfigNotFound`, backed by
54
+ installed pve-container 6.1.13 and libpve-guest-common-perl 6.0.5 source — all three
55
+ call the same `load_config` and raise byte-identical text. This proves only the
56
+ node-local config is missing: providers must still check the cluster-wide vmid
57
+ with the same credential before considering creation.
43
58
 
44
59
  ```ts
45
60
  import * as Proxmox from '@distilled.cloud/proxmox'; // aliased onto this package — see docs/distilled-interim.md
@@ -63,6 +78,19 @@ Kit code never imports `@homeflare/distilled-proxmox` directly — always
63
78
  so the eventual cutover to the real published package is a one-line alias
64
79
  swap with no import changes anywhere in the kit.
65
80
 
81
+ ## Source provenance
82
+
83
+ This SDK source matches local distilled commit
84
+ `07d587885b198e85bdd5b37ce3c7406c7b29c0ba` byte for byte. Its patch chain adds
85
+ precise interface, container-config, replication-job and firewall-alias absence
86
+ without treating unrelated validation or server failures as missing resources.
87
+ The patch files record pinned PVE 9.2.11 and installed package versions/hashes.
88
+
89
+ `ReplicationJobNotFound` covers the vendor's read/update/delete exceptions;
90
+ ordinary DELETE still marks asynchronous cleanup without force or keep.
91
+ `FirewallAliasNotFound` covers only the exact sole-field validation error on
92
+ GET/PUT. Alias DELETE is already idempotent, so other failures propagate.
93
+
66
94
  ## Updating it
67
95
 
68
96
  From the distilled clone's `homeflare/proxmox` worktree:
@@ -70,13 +98,12 @@ From the distilled clone's `homeflare/proxmox` worktree:
70
98
  ```sh
71
99
  DISTILLED_SPECS_LOCAL=1 pnpm --filter @distilled.cloud/proxmox run convert
72
100
  DISTILLED_SPECS_LOCAL=1 pnpm --filter @distilled.cloud/proxmox run generate
73
- pnpm --filter @distilled.cloud/proxmox run typecheck
101
+ pnpm typecheck:ci
74
102
  pnpm format && pnpm specs:check
75
103
  ```
76
104
 
77
- Then copy `src/` here verbatim, bump this package's own `version` (a patch
78
- release — the generated content changed, not this package's own shape), add
79
- a changeset, and let the kit's normal release flow publish it.
105
+ Then copy `src/` here verbatim and add a changeset. The normal release flow
106
+ versions and publishes the package.
80
107
 
81
108
  ## License
82
109
 
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=config-not-found.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-not-found.test.d.ts","sourceRoot":"","sources":["../src/config-not-found.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,125 @@
1
+ /** PVE 9.2.11 vendor-source fixtures; missing BackupJob GET also measured 2026-09-24. */
2
+ import { describe, expect, test } from "bun:test";
3
+ import * as Effect from "effect/Effect";
4
+ import * as Layer from "effect/Layer";
5
+ import * as HttpClient from "effect/unstable/http/HttpClient";
6
+ import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse";
7
+ import { credentials } from "./credentials.js";
8
+ import * as Retry from "./retry.js";
9
+ import * as pools from "./services/pools.js";
10
+ import * as cluster from "./services/cluster.js";
11
+ import * as nodes from "./services/nodes.js";
12
+ const call = (op, status, body) => Effect.runPromise(op.pipe(Retry.none, Effect.provide(credentials({
13
+ baseUrl: "https://pve.test:8006",
14
+ tokenId: "test@pve!test",
15
+ secret: "fake",
16
+ })), Effect.provide(Layer.succeed(HttpClient.HttpClient, HttpClient.make((request) => Effect.succeed(HttpClientResponse.fromWeb(request, Response.json(body, { status })))))), Effect.flip));
17
+ describe("precise config absence tags", () => {
18
+ for (const [name, op] of [
19
+ ["item read", pools.getPool({ poolid: "absent" })],
20
+ ["index read", pools.listPools({ poolid: "absent" })],
21
+ ["item update", pools.putPool({ poolid: "absent" })],
22
+ ["collection update", pools.putPools({ poolid: "absent" })],
23
+ ["item delete", pools.deletePool({ poolid: "absent" })],
24
+ ["collection delete", pools.deletePools({ poolid: "absent" })],
25
+ ]) {
26
+ test(`Pool ${name}`, async () => {
27
+ expect(await call(op, 500, {
28
+ data: null,
29
+ message: "pool 'absent' does not exist\n",
30
+ })).toMatchObject({ _tag: "PoolNotFound" });
31
+ });
32
+ }
33
+ for (const [name, op] of [
34
+ ["read", cluster.getClusterBackup({ id: "absent" })],
35
+ ["delete", cluster.deleteClusterBackup({ id: "absent" })],
36
+ ["volumes", cluster.getClusterBackupIncludedVolumes({ id: "absent" })],
37
+ ]) {
38
+ test(`BackupJob ${name}`, async () => {
39
+ expect(await call(op, 400, {
40
+ data: null,
41
+ message: "Parameter verification failed.\n",
42
+ errors: { id: "No such job 'absent'" },
43
+ })).toMatchObject({ _tag: "BackupJobNotFound" });
44
+ });
45
+ }
46
+ test("common HTTP failures remain in every operation's typed error union", async () => {
47
+ const op = pools.getPool({ poolid: "absent" }).pipe(Effect.catchTag("NotFound", () => Effect.fail({ _tag: "HandledNotFound" })), Effect.catchTag("Forbidden", () => Effect.fail({ _tag: "HandledForbidden" })));
48
+ expect(await call(op, 404, { message: "missing", data: null })).toMatchObject({ _tag: "HandledNotFound" });
49
+ expect(await call(op, 403, { message: "denied", data: null })).toMatchObject({ _tag: "HandledForbidden" });
50
+ });
51
+ test("MetricServer read and update have distinct absence messages", async () => {
52
+ expect(await call(cluster.getClusterMetricsServer({ id: "absent" }), 500, {
53
+ data: null,
54
+ message: "status server entry 'absent' does not exist\n",
55
+ })).toMatchObject({ _tag: "MetricServerNotFound" });
56
+ expect(await call(cluster.putClusterMetricsServer({
57
+ id: "absent",
58
+ server: "localhost",
59
+ port: "8086",
60
+ }), 500, {
61
+ data: null,
62
+ message: "no such server 'absent'\n",
63
+ })).toMatchObject({ _tag: "MetricServerNotFound" });
64
+ });
65
+ test("unrelated, multi-field, and differently named validation are not absence", async () => {
66
+ for (const errors of [
67
+ { id: "invalid configuration ID" },
68
+ { id: "No such job 'absent'", schedule: "invalid schedule" },
69
+ { comment: "No such job 'absent'" },
70
+ ]) {
71
+ expect(await call(cluster.getClusterBackup({ id: "absent" }), 400, {
72
+ data: null,
73
+ message: "Parameter verification failed.\n",
74
+ errors,
75
+ })).toMatchObject({ _tag: "ParameterVerificationFailed", errors });
76
+ }
77
+ });
78
+ test("a pool containing guests and a generic server failure propagate", async () => {
79
+ for (const message of [
80
+ "pool 'absent' is not empty (contains VM 100)\n",
81
+ "unrelated failure",
82
+ ]) {
83
+ expect(await call(pools.deletePool({ poolid: "absent" }), 500, {
84
+ data: null,
85
+ message,
86
+ })).toMatchObject({ _tag: "InternalServerError" });
87
+ }
88
+ });
89
+ test("missing backup text is not attached to unrelated operations", async () => {
90
+ expect(await call(cluster.deleteClusterMetricsServer({ id: "absent" }), 400, {
91
+ data: null,
92
+ message: "Parameter verification failed.\n",
93
+ errors: { id: "No such job 'absent'" },
94
+ })).toMatchObject({ _tag: "ParameterVerificationFailed" });
95
+ });
96
+ test("network absence is the exact sole iface error on GET and PUT", async () => {
97
+ const body = {
98
+ data: null,
99
+ message: "Parameter verification failed.\n",
100
+ errors: { iface: "interface does not exist" },
101
+ };
102
+ expect(await call(nodes.getNodeNetwork({ node: "n2", iface: "vmbr9" }), 400, body)).toMatchObject({ _tag: "NetworkInterfaceNotFound" });
103
+ // ⛔ RETAG REGRESSION'S OWN CASE — PVE's `update_network` (PUT) raises the byte-identical
104
+ // 400 (Network.pm:750) that `network_config` (GET) raises (:852); a consumer's catchTag
105
+ // keyed on this tag must see the same absence signal from either operation.
106
+ expect(await call(nodes.putNodeNetwork2({ node: "n2", iface: "vmbr9", type: "bridge" }), 400, body)).toMatchObject({ _tag: "NetworkInterfaceNotFound" });
107
+ for (const errors of [
108
+ { iface: "invalid interface name" },
109
+ { iface: "interface does not exist", type: "invalid type" },
110
+ { other: "interface does not exist" },
111
+ ]) {
112
+ expect(await call(nodes.getNodeNetwork({ node: "n2", iface: "vmbr9" }), 400, {
113
+ ...body,
114
+ errors,
115
+ })).toMatchObject({ _tag: "ParameterVerificationFailed", errors });
116
+ }
117
+ for (const [status, tag] of [
118
+ [403, "Forbidden"],
119
+ [500, "InternalServerError"],
120
+ ]) {
121
+ expect(await call(nodes.getNodeNetwork({ node: "n2", iface: "vmbr9" }), status, body)).toMatchObject({ _tag: tag });
122
+ }
123
+ });
124
+ });
125
+ //# sourceMappingURL=config-not-found.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-not-found.test.js","sourceRoot":"","sources":["../src/config-not-found.test.ts"],"names":[],"mappings":"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"}
package/dist/errors.d.ts CHANGED
@@ -35,7 +35,7 @@
35
35
  * see the patch file).
36
36
  */
37
37
  export { BadGateway, BadRequest, Conflict, ConfigError, Forbidden, GatewayTimeout, InternalServerError, Locked, NotFound, ServiceUnavailable, TooManyRequests, Unauthorized, UnprocessableEntity, HTTP_STATUS_MAP, DEFAULT_ERRORS, API_ERRORS, } from "@distilled.cloud/core/errors";
38
- import type { BadRequest, DefaultErrors as CoreDefaultErrors } from "@distilled.cloud/core/errors";
38
+ import type { API_ERRORS } from "@distilled.cloud/core/errors";
39
39
  import * as Schema from "effect/Schema";
40
40
  import * as Category from "@distilled.cloud/core/category";
41
41
  declare const ParameterVerificationFailed_base: Schema.Class<ParameterVerificationFailed, Schema.TaggedStruct<"ParameterVerificationFailed", {
@@ -131,5 +131,5 @@ export type ClientErrors = UnknownProxmoxError | ProxmoxParseError;
131
131
  * header: it rides only the specific operations `patches/nodes/
132
132
  * task-polling.json` adds it to.
133
133
  */
134
- export type DefaultErrors = CoreDefaultErrors | BadRequest | ParameterVerificationFailed | ClientErrors;
134
+ export type DefaultErrors = InstanceType<(typeof API_ERRORS)[number]> | ParameterVerificationFailed | ClientErrors;
135
135
  //# sourceMappingURL=errors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,OAAO,EACL,UAAU,EACV,UAAU,EACV,QAAQ,EACR,WAAW,EACX,SAAS,EACT,cAAc,EACd,mBAAmB,EACnB,MAAM,EACN,QAAQ,EACR,kBAAkB,EAClB,eAAe,EACf,YAAY,EACZ,mBAAmB,EACnB,eAAe,EACf,cAAc,EACd,UAAU,GACX,MAAM,8BAA8B,CAAC;AAOtC,OAAO,KAAK,EACV,UAAU,EACV,aAAa,IAAI,iBAAiB,EACnC,MAAM,8BAA8B,CAAC;AAEtC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,QAAQ,MAAM,gCAAgC,CAAC;;;;;;;;;AAE3D;;;;;;;;GAQG;AACH,qBAAa,2BAA4B,SAAQ,gCAMb;CAAG;;;;;;;;;;AAEvC;;;;;;;;GAQG;AACH,qBAAa,sBAAuB,SAAQ,2BAKc;CAAG;;;;;;;;;;AAE7D;;;GAGG;AACH,qBAAa,mBAAoB,SAAQ,wBAOT;CAAG;;;;;;;;;AAEnC,kCAAkC;AAClC,qBAAa,iBAAkB,SAAQ,sBAMR;CAAG;;;;;;AAElC;;;;;;;;GAQG;AACH,qBAAa,iBAAkB,SAAQ,sBAOtC;CAAG;AAEJ;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,mBAAmB,GAAG,iBAAiB,CAAC;AAEnE;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GACrB,iBAAiB,GACjB,UAAU,GACV,2BAA2B,GAC3B,YAAY,CAAC"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,OAAO,EACL,UAAU,EACV,UAAU,EACV,QAAQ,EACR,WAAW,EACX,SAAS,EACT,cAAc,EACd,mBAAmB,EACnB,MAAM,EACN,QAAQ,EACR,kBAAkB,EAClB,eAAe,EACf,YAAY,EACZ,mBAAmB,EACnB,eAAe,EACf,cAAc,EACd,UAAU,GACX,MAAM,8BAA8B,CAAC;AAGtC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAE/D,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,QAAQ,MAAM,gCAAgC,CAAC;;;;;;;;;AAE3D;;;;;;;;GAQG;AACH,qBAAa,2BAA4B,SAAQ,gCAMb;CAAG;;;;;;;;;;AAEvC;;;;;;;;GAQG;AACH,qBAAa,sBAAuB,SAAQ,2BAKc;CAAG;;;;;;;;;;AAE7D;;;GAGG;AACH,qBAAa,mBAAoB,SAAQ,wBAOT;CAAG;;;;;;;;;AAEnC,kCAAkC;AAClC,qBAAa,iBAAkB,SAAQ,sBAMR;CAAG;;;;;;AAElC;;;;;;;;GAQG;AACH,qBAAa,iBAAkB,SAAQ,sBAOtC;CAAG;AAEJ;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,mBAAmB,GAAG,iBAAiB,CAAC;AAEnE;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GACrB,YAAY,CAAC,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC,GACzC,2BAA2B,GAC3B,YAAY,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,OAAO,EACL,UAAU,EACV,UAAU,EACV,QAAQ,EACR,WAAW,EACX,SAAS,EACT,cAAc,EACd,mBAAmB,EACnB,MAAM,EACN,QAAQ,EACR,kBAAkB,EAClB,eAAe,EACf,YAAY,EACZ,mBAAmB,EACnB,eAAe,EACf,cAAc,EACd,UAAU,GACX,MAAM,8BAA8B,CAAC;AAYtC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,QAAQ,MAAM,gCAAgC,CAAC;AAE3D;;;;;;;;GAQG;AACH,MAAM,OAAO,2BAA4B,SAAQ,MAAM,CAAC,WAAW,EAA+B,CAChG,6BAA6B,EAC7B;IACE,OAAO,EAAE,MAAM,CAAC,MAAM;IACtB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC;CACpD,CACF,CAAC,IAAI,CAAC,QAAQ,CAAC,mBAAmB,CAAC;CAAG;AAEvC;;;;;;;;GAQG;AACH,MAAM,OAAO,sBAAuB,SAAQ,MAAM,CAAC,WAAW,EAA0B,CACtF,wBAAwB,EACxB;IACE,OAAO,EAAE,MAAM,CAAC,MAAM;CACvB,CACF,CAAC,IAAI,CAAC,QAAQ,CAAC,eAAe,EAAE,QAAQ,CAAC,aAAa,EAAE,CAAC;CAAG;AAE7D;;;GAGG;AACH,MAAM,OAAO,mBAAoB,SAAQ,MAAM,CAAC,WAAW,EAAuB,CAChF,qBAAqB,EACrB;IACE,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC;IACvC,IAAI,EAAE,MAAM,CAAC,OAAO;CACrB,CACF,CAAC,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC;CAAG;AAEnC,kCAAkC;AAClC,MAAM,OAAO,iBAAkB,SAAQ,MAAM,CAAC,WAAW,EAAqB,CAC5E,mBAAmB,EACnB;IACE,IAAI,EAAE,MAAM,CAAC,OAAO;IACpB,KAAK,EAAE,MAAM,CAAC,OAAO;CACtB,CACF,CAAC,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC;CAAG;AAElC;;;;;;;;GAQG;AACH,MAAM,OAAO,iBAAkB,SAAQ,MAAM,CAAC,WAAW,EAAqB,CAC5E,mBAAmB,EACnB;IACE,IAAI,EAAE,MAAM,CAAC,MAAM;IACnB,IAAI,EAAE,MAAM,CAAC,MAAM;IACnB,UAAU,EAAE,MAAM,CAAC,MAAM;CAC1B,CACF;CAAG"}
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,OAAO,EACL,UAAU,EACV,UAAU,EACV,QAAQ,EACR,WAAW,EACX,SAAS,EACT,cAAc,EACd,mBAAmB,EACnB,MAAM,EACN,QAAQ,EACR,kBAAkB,EAClB,eAAe,EACf,YAAY,EACZ,mBAAmB,EACnB,eAAe,EACf,cAAc,EACd,UAAU,GACX,MAAM,8BAA8B,CAAC;AAKtC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,QAAQ,MAAM,gCAAgC,CAAC;AAE3D;;;;;;;;GAQG;AACH,MAAM,OAAO,2BAA4B,SAAQ,MAAM,CAAC,WAAW,EAA+B,CAChG,6BAA6B,EAC7B;IACE,OAAO,EAAE,MAAM,CAAC,MAAM;IACtB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC;CACpD,CACF,CAAC,IAAI,CAAC,QAAQ,CAAC,mBAAmB,CAAC;CAAG;AAEvC;;;;;;;;GAQG;AACH,MAAM,OAAO,sBAAuB,SAAQ,MAAM,CAAC,WAAW,EAA0B,CACtF,wBAAwB,EACxB;IACE,OAAO,EAAE,MAAM,CAAC,MAAM;CACvB,CACF,CAAC,IAAI,CAAC,QAAQ,CAAC,eAAe,EAAE,QAAQ,CAAC,aAAa,EAAE,CAAC;CAAG;AAE7D;;;GAGG;AACH,MAAM,OAAO,mBAAoB,SAAQ,MAAM,CAAC,WAAW,EAAuB,CAChF,qBAAqB,EACrB;IACE,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC;IACvC,IAAI,EAAE,MAAM,CAAC,OAAO;CACrB,CACF,CAAC,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC;CAAG;AAEnC,kCAAkC;AAClC,MAAM,OAAO,iBAAkB,SAAQ,MAAM,CAAC,WAAW,EAAqB,CAC5E,mBAAmB,EACnB;IACE,IAAI,EAAE,MAAM,CAAC,OAAO;IACpB,KAAK,EAAE,MAAM,CAAC,OAAO;CACtB,CACF,CAAC,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC;CAAG;AAElC;;;;;;;;GAQG;AACH,MAAM,OAAO,iBAAkB,SAAQ,MAAM,CAAC,WAAW,EAAqB,CAC5E,mBAAmB,EACnB;IACE,IAAI,EAAE,MAAM,CAAC,MAAM;IACnB,IAAI,EAAE,MAAM,CAAC,MAAM;IACnB,UAAU,EAAE,MAAM,CAAC,MAAM;CAC1B,CACF;CAAG"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=lxc-config-errors.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lxc-config-errors.test.d.ts","sourceRoot":"","sources":["../src/lxc-config-errors.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Exact installed pve-container6.1.13/libpve-guest-common-perl6.0.5 absence; never generic500.
3
+ * ⛔ ATTACHED TO GET, PUT AND DELETE — all three call `AbstractConfig::load_config` first
4
+ * (patches/nodes/lxc-config-errors.json's own provenance) and raise byte-identical text; a
5
+ * PUT/DELETE against a container config a caller believes exists gets the same typed absence
6
+ * a GET would, not a generic 500 a retry policy or catchTag could mistake for transient.
7
+ */
8
+ import { expect, test } from "bun:test";
9
+ import * as Effect from "effect/Effect";
10
+ import * as HttpClient from "effect/unstable/http/HttpClient";
11
+ import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse";
12
+ import { credentials } from "./credentials.js";
13
+ import * as Retry from "./retry.js";
14
+ import * as nodes from "./services/nodes.js";
15
+ const missing = "Configuration file 'nodes/pve-test/lxc/900.conf' does not exist\n";
16
+ const call = (op, status, message) => Effect.runPromise(op.pipe(Retry.none, Effect.provide(credentials({
17
+ baseUrl: "https://pve.test:8006",
18
+ tokenId: "test@pve!test",
19
+ secret: "fake",
20
+ })), Effect.provideService(HttpClient.HttpClient, HttpClient.make((request) => Effect.succeed(HttpClientResponse.fromWeb(request, Response.json({ data: null, message }, { status }))))), Effect.flip));
21
+ test("only exact container-config absence on GET becomes LxcConfigNotFound", async () => {
22
+ const get = () => nodes.getNodeLxcConfig({ node: "pve-test", vmid: "900" });
23
+ expect(await call(get(), 500, missing)).toMatchObject({
24
+ _tag: "LxcConfigNotFound",
25
+ });
26
+ for (const message of [
27
+ undefined,
28
+ "got timeout\n",
29
+ missing.replace("lxc", "qemu-server"),
30
+ missing + "other error",
31
+ ]) {
32
+ expect(await call(get(), 500, message)).toMatchObject({
33
+ _tag: "InternalServerError",
34
+ });
35
+ }
36
+ expect(await call(get(), 403, missing)).toMatchObject({ _tag: "Forbidden" });
37
+ });
38
+ test("the same exact absence on PUT and DELETE also becomes LxcConfigNotFound", async () => {
39
+ expect(await call(nodes.putNodeLxcConfig({ node: "pve-test", vmid: "900" }), 500, missing)).toMatchObject({ _tag: "LxcConfigNotFound" });
40
+ expect(await call(nodes.deleteNodeLxc({ node: "pve-test", vmid: "900" }), 500, missing)).toMatchObject({ _tag: "LxcConfigNotFound" });
41
+ // ⛔ STILL NOT ABSENCE ON PUT/DELETE EITHER — a generic failure on the write paths must stay
42
+ // generic, exactly like the GET-only assertions above.
43
+ for (const message of ["got timeout\n", missing + "other error"]) {
44
+ expect(await call(nodes.putNodeLxcConfig({ node: "pve-test", vmid: "900" }), 500, message)).toMatchObject({ _tag: "InternalServerError" });
45
+ expect(await call(nodes.deleteNodeLxc({ node: "pve-test", vmid: "900" }), 500, message)).toMatchObject({ _tag: "InternalServerError" });
46
+ }
47
+ });
48
+ //# sourceMappingURL=lxc-config-errors.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lxc-config-errors.test.js","sourceRoot":"","sources":["../src/lxc-config-errors.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * PVE's form arrays use repeated keys, never Stripe's indexed fields.
3
+ *
4
+ * PVE 9.2.11 notification schemas declare arrays (mailto, header, target,
5
+ * match-field, delete). The vendor UI submits repeated keys; its Perl
6
+ * URL-encoded decoder joins them with NUL before schema validation. The
7
+ * existing kit notification-target-form.ts records that decoder/UI evidence
8
+ * (2026-09-14); no production multi-header write was made for this fix.
9
+ *
10
+ * Keep core's credentials, labels, schema key mapping, and query encoding.
11
+ * Only top-level scalar list entries in a form body change: header[0]=a
12
+ * becomes header=a. A property-string item's commas remain inside the item.
13
+ * A scalar comma list, an embedded NUL, or a GET/DELETE query is unchanged.
14
+ */
15
+ import * as API from "@distilled.cloud/core/api";
16
+ import * as Layer from "effect/Layer";
17
+ export declare const withPveFormArrays: (base: Layer.Layer<API.Protocol>) => Layer.Layer<API.Protocol>;
18
+ //# sourceMappingURL=protocol-form.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol-form.d.ts","sourceRoot":"","sources":["../src/protocol-form.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,GAAG,MAAM,2BAA2B,CAAC;AAEjD,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAmBtC,eAAO,MAAM,iBAAiB,SACtB,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,KAC9B,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAWE,CAAC"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * PVE's form arrays use repeated keys, never Stripe's indexed fields.
3
+ *
4
+ * PVE 9.2.11 notification schemas declare arrays (mailto, header, target,
5
+ * match-field, delete). The vendor UI submits repeated keys; its Perl
6
+ * URL-encoded decoder joins them with NUL before schema validation. The
7
+ * existing kit notification-target-form.ts records that decoder/UI evidence
8
+ * (2026-09-14); no production multi-header write was made for this fix.
9
+ *
10
+ * Keep core's credentials, labels, schema key mapping, and query encoding.
11
+ * Only top-level scalar list entries in a form body change: header[0]=a
12
+ * becomes header=a. A property-string item's commas remain inside the item.
13
+ * A scalar comma list, an embedded NUL, or a GET/DELETE query is unchanged.
14
+ */
15
+ import * as API from "@distilled.cloud/core/api";
16
+ import * as Effect from "effect/Effect";
17
+ import * as Layer from "effect/Layer";
18
+ import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest";
19
+ const repeatedFormKeys = (request) => {
20
+ if (request.body._tag !== "Uint8Array" ||
21
+ request.body.contentType !== "application/x-www-form-urlencoded")
22
+ return request;
23
+ const fields = new URLSearchParams(new TextDecoder().decode(request.body.body));
24
+ const repeated = [];
25
+ for (const [key, value] of fields) {
26
+ repeated.push([key.replace(/^([^\[\]]+)\[\d+\]$/, "$1"), value]);
27
+ }
28
+ return HttpClientRequest.bodyUrlParams(request, repeated);
29
+ };
30
+ export const withPveFormArrays = (base) => Layer.effect(API.Protocol, Effect.gen(function* () {
31
+ const protocol = yield* API.Protocol;
32
+ return API.Protocol.of({
33
+ ...protocol,
34
+ encode: (args) => protocol.encode(args).pipe(Effect.map(repeatedFormKeys)),
35
+ });
36
+ })).pipe(Layer.provide(base));
37
+ //# sourceMappingURL=protocol-form.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol-form.js","sourceRoot":"","sources":["../src/protocol-form.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,GAAG,MAAM,2BAA2B,CAAC;AACjD,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,KAAK,iBAAiB,MAAM,wCAAwC,CAAC;AAE5E,MAAM,gBAAgB,GAAG,CAAC,OAA4C,EAAE,EAAE;IACxE,IACE,OAAO,CAAC,IAAI,CAAC,IAAI,KAAK,YAAY;QAClC,OAAO,CAAC,IAAI,CAAC,WAAW,KAAK,mCAAmC;QAEhE,OAAO,OAAO,CAAC;IACjB,MAAM,MAAM,GAAG,IAAI,eAAe,CAChC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAC5C,CAAC;IACF,MAAM,QAAQ,GAAuB,EAAE,CAAC;IACxC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,EAAE,CAAC;QAClC,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,qBAAqB,EAAE,IAAI,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;IACnE,CAAC;IACD,OAAO,iBAAiB,CAAC,aAAa,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;AAC5D,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAC/B,IAA+B,EACJ,EAAE,CAC7B,KAAK,CAAC,MAAM,CACV,GAAG,CAAC,QAAQ,EACZ,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC;IACrC,OAAO,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;QACrB,GAAG,QAAQ;QACX,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CACf,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;KAC3D,CAAC,CAAC;AACL,CAAC,CAAC,CACH,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=protocol-form.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol-form.test.d.ts","sourceRoot":"","sources":["../src/protocol-form.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,68 @@
1
+ /** PVE 9.2.11 schema arrays must survive the vendor's repeated-key decoder. */
2
+ import { expect, test } from "bun:test";
3
+ import * as Effect from "effect/Effect";
4
+ import * as Layer from "effect/Layer";
5
+ import * as HttpClient from "effect/unstable/http/HttpClient";
6
+ import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest";
7
+ import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse";
8
+ import { credentials } from "./credentials.js";
9
+ import * as Retry from "./retry.js";
10
+ import * as cluster from "./services/cluster.js";
11
+ import * as nodes from "./services/nodes.js";
12
+ const capture = async (op) => {
13
+ const requests = [];
14
+ await Effect.runPromise(op.pipe(Retry.none, Effect.provide(credentials({
15
+ baseUrl: "https://pve.test:8006",
16
+ tokenId: "test@pve!test",
17
+ secret: "fake",
18
+ })), Effect.provide(Layer.succeed(HttpClient.HttpClient, HttpClient.make((request) => {
19
+ requests.push(request);
20
+ return Effect.succeed(HttpClientResponse.fromWeb(request, Response.json({ data: null })));
21
+ })))));
22
+ const request = requests[0];
23
+ const form = request.body._tag === "Uint8Array"
24
+ ? new URLSearchParams(new TextDecoder().decode(request.body.body))
25
+ : new URLSearchParams();
26
+ return { request, form };
27
+ };
28
+ test("matcher arrays repeat the wire key; commas within a rule stay inside that item", async () => {
29
+ const { request, form } = await capture(cluster.createClusterNotificationMatcher({
30
+ name: "matcher",
31
+ target: ["mail", "webhook"],
32
+ match_field: ["exact:type=vzdump", "regex:hostname=one,two"],
33
+ }));
34
+ expect(request.method).toBe("POST");
35
+ expect(form.getAll("target")).toEqual(["mail", "webhook"]);
36
+ expect(form.getAll("match-field")).toEqual([
37
+ "exact:type=vzdump",
38
+ "regex:hostname=one,two",
39
+ ]);
40
+ expect([...form.keys()].some((key) => key.includes("["))).toBe(false);
41
+ });
42
+ test("webhook property-string headers remain separate values", async () => {
43
+ const headers = ["name=X-One,value=YQ==", "name=X-Two,value=Yg=="];
44
+ const { form } = await capture(cluster.createClusterNotificationEndpointWebhook({
45
+ name: "webhook",
46
+ method: "post",
47
+ url: "https://example.com/hook",
48
+ header: headers,
49
+ }));
50
+ expect(form.getAll("header")).toEqual(headers);
51
+ });
52
+ test("array delete fields repeat while scalar comma lists remain scalar", async () => {
53
+ const { form } = await capture(cluster.putClusterNotificationMatcher({
54
+ name: "matcher",
55
+ delete: ["match-field", "match-calendar"],
56
+ }));
57
+ expect(form.getAll("delete")).toEqual(["match-field", "match-calendar"]);
58
+ const scalar = await capture(cluster.putClusterBackup({ id: "job", vmid: "100,101" }));
59
+ expect(scalar.form.getAll("vmid")).toEqual(["100,101"]);
60
+ });
61
+ test("DELETE stays bodyless and its flags stay query parameters", async () => {
62
+ const { request, form } = await capture(nodes.deleteNodeCephFs({ node: "node", name: "fs", remove_pools: "1" }));
63
+ expect(request.method).toBe("DELETE");
64
+ expect(request.body._tag).toBe("Empty");
65
+ expect(new URL(request.url).searchParams.get("remove-pools")).toBe("1");
66
+ expect([...form]).toEqual([]);
67
+ });
68
+ //# sourceMappingURL=protocol-form.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol-form.test.js","sourceRoot":"","sources":["../src/protocol-form.test.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,KAAK,UAAU,MAAM,iCAAiC,CAAC;AAC9D,OAAO,KAAK,iBAAiB,MAAM,wCAAwC,CAAC;AAC5E,OAAO,KAAK,kBAAkB,MAAM,yCAAyC,CAAC;AAC9E,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C,OAAO,KAAK,KAAK,MAAM,YAAY,CAAC;AACpC,OAAO,KAAK,OAAO,MAAM,uBAAuB,CAAC;AACjD,OAAO,KAAK,KAAK,MAAM,qBAAqB,CAAC;AAE7C,MAAM,OAAO,GAAG,KAAK,EAAQ,EAAyC,EAAE,EAAE;IACxE,MAAM,QAAQ,GAA0C,EAAE,CAAC;IAC3D,MAAM,MAAM,CAAC,UAAU,CACrB,EAAE,CAAC,IAAI,CACL,KAAK,CAAC,IAAI,EACV,MAAM,CAAC,OAAO,CACZ,WAAW,CAAC;QACV,OAAO,EAAE,uBAAuB;QAChC,OAAO,EAAE,eAAe;QACxB,MAAM,EAAE,MAAM;KACf,CAAC,CACH,EACD,MAAM,CAAC,OAAO,CACZ,KAAK,CAAC,OAAO,CACX,UAAU,CAAC,UAAU,EACrB,UAAU,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE;QAC1B,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACvB,OAAO,MAAM,CAAC,OAAO,CACnB,kBAAkB,CAAC,OAAO,CACxB,OAAO,EACP,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAC9B,CACF,CAAC;IACJ,CAAC,CAAC,CACH,CACF,CACF,CACF,CAAC;IACF,MAAM,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAE,CAAC;IAC7B,MAAM,IAAI,GACR,OAAO,CAAC,IAAI,CAAC,IAAI,KAAK,YAAY;QAChC,CAAC,CAAC,IAAI,eAAe,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClE,CAAC,CAAC,IAAI,eAAe,EAAE,CAAC;IAC5B,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAC3B,CAAC,CAAC;AAEF,IAAI,CAAC,gFAAgF,EAAE,KAAK,IAAI,EAAE;IAChG,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,MAAM,OAAO,CACrC,OAAO,CAAC,gCAAgC,CAAC;QACvC,IAAI,EAAE,SAAS;QACf,MAAM,EAAE,CAAC,MAAM,EAAE,SAAS,CAAC;QAC3B,WAAW,EAAE,CAAC,mBAAmB,EAAE,wBAAwB,CAAC;KAC7D,CAAC,CACH,CAAC;IACF,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACpC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;IAC3D,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC;QACzC,mBAAmB;QACnB,wBAAwB;KACzB,CAAC,CAAC;IACH,MAAM,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACxE,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,wDAAwD,EAAE,KAAK,IAAI,EAAE;IACxE,MAAM,OAAO,GAAG,CAAC,uBAAuB,EAAE,uBAAuB,CAAC,CAAC;IACnE,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,OAAO,CAC5B,OAAO,CAAC,wCAAwC,CAAC;QAC/C,IAAI,EAAE,SAAS;QACf,MAAM,EAAE,MAAM;QACd,GAAG,EAAE,0BAA0B;QAC/B,MAAM,EAAE,OAAO;KAChB,CAAC,CACH,CAAC;IACF,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;AACjD,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,mEAAmE,EAAE,KAAK,IAAI,EAAE;IACnF,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,OAAO,CAC5B,OAAO,CAAC,6BAA6B,CAAC;QACpC,IAAI,EAAE,SAAS;QACf,MAAM,EAAE,CAAC,aAAa,EAAE,gBAAgB,CAAC;KAC1C,CAAC,CACH,CAAC;IACF,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,aAAa,EAAE,gBAAgB,CAAC,CAAC,CAAC;IACzE,MAAM,MAAM,GAAG,MAAM,OAAO,CAC1B,OAAO,CAAC,gBAAgB,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CACzD,CAAC;IACF,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;AAC1D,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,2DAA2D,EAAE,KAAK,IAAI,EAAE;IAC3E,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,MAAM,OAAO,CACrC,KAAK,CAAC,gBAAgB,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,CAAC,CACxE,CAAC;IACF,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACtC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACxC,MAAM,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACxE,MAAM,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;AAChC,CAAC,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAoEA,OAAO,KAAK,KAAK,KAAK,MAAM,cAAc,CAAC;AAE3C,OAAO,KAAK,KAAK,UAAU,MAAM,iCAAiC,CAAC;AACnE,OAAO,KAAK,KAAK,eAAe,MAAM,sCAAsC,CAAC;AAC7E,OAAO,KAAK,KAAK,GAAG,MAAM,2BAA2B,CAAC;AACtD,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,8BAA8B,CAAC;AAKtC,OAAO,EAAE,WAAW,EAAe,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAGL,KAAK,aAAa,EACnB,MAAM,aAAa,CAAC;AAErB;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GACtB,aAAa,GACb,WAAW,GACX,eAAe,CAAC,eAAe,CAAC;AAEpC,0EAA0E;AAC1E,MAAM,MAAM,gBAAgB,GAAG,WAAW,GAAG,UAAU,CAAC,UAAU,CAAC;AA6BnE,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CA2ElD,CAAC"}
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAoEA,OAAO,KAAK,KAAK,KAAK,MAAM,cAAc,CAAC;AAE3C,OAAO,KAAK,KAAK,UAAU,MAAM,iCAAiC,CAAC;AACnE,OAAO,KAAK,KAAK,eAAe,MAAM,sCAAsC,CAAC;AAC7E,OAAO,KAAK,KAAK,GAAG,MAAM,2BAA2B,CAAC;AACtD,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,8BAA8B,CAAC;AAKtC,OAAO,EAAE,WAAW,EAAe,MAAM,kBAAkB,CAAC;AAE5D,OAAO,EAGL,KAAK,aAAa,EACnB,MAAM,aAAa,CAAC;AAErB;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GACtB,aAAa,GACb,WAAW,GACX,eAAe,CAAC,eAAe,CAAC;AAEpC,0EAA0E;AAC1E,MAAM,MAAM,gBAAgB,GAAG,WAAW,GAAG,UAAU,CAAC,UAAU,CAAC;AAuDnE,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CA4ErD,CAAC"}
package/dist/protocol.js CHANGED
@@ -70,6 +70,7 @@ import * as Redacted from "effect/Redacted";
70
70
  import { BadRequest, HTTP_STATUS_MAP, } from "@distilled.cloud/core/errors";
71
71
  import { makeRestProtocol, } from "@distilled.cloud/core/protocol-rest";
72
72
  import { Credentials } from "./credentials.js";
73
+ import { withPveFormArrays } from "./protocol-form.js";
73
74
  import { ParameterVerificationFailed, UnknownProxmoxError, } from "./errors.js";
74
75
  /**
75
76
  * PVE's failure envelope is `{"data": null, "message": "...", "errors"?:
@@ -83,6 +84,28 @@ const errorEnvelope = (body) => {
83
84
  if (body === null || typeof body !== "object")
84
85
  return undefined;
85
86
  const b = body;
87
+ // PVE missing backup/interface/alias exceptions carry the useful message in one
88
+ // field (Backup.pm:406,458; Network.pm:852; Firewall/Aliases.pm:203). Expose
89
+ // ONLY those exact sole-field details to the operation's typed matcher.
90
+ // Multiple errors, other fields, and unrelated validation stay unchanged.
91
+ const errors = b.errors;
92
+ if ((b.message === "Parameter verification failed." ||
93
+ b.message === "Parameter verification failed.\n") &&
94
+ errors !== null &&
95
+ typeof errors === "object" &&
96
+ !Array.isArray(errors) &&
97
+ Object.keys(errors).length === 1) {
98
+ if ("id" in errors &&
99
+ typeof errors.id === "string" &&
100
+ /^No such job '[^']+'$/.test(errors.id))
101
+ return { message: errors.id };
102
+ if ("name" in errors && errors.name === "no such alias") {
103
+ return { message: errors.name };
104
+ }
105
+ if ("iface" in errors && errors.iface === "interface does not exist") {
106
+ return { message: errors.iface };
107
+ }
108
+ }
86
109
  return { message: typeof b.message === "string" ? b.message : undefined };
87
110
  };
88
111
  /**
@@ -97,7 +120,7 @@ const PROXMOX_STATUS_MAP = {
97
120
  ...HTTP_STATUS_MAP,
98
121
  };
99
122
  delete PROXMOX_STATUS_MAP[400];
100
- export const ProxmoxProtocol = makeRestProtocol({
123
+ export const ProxmoxProtocol = withPveFormArrays(makeRestProtocol({
101
124
  // Resolved on the CALLING fiber per request (the layer is memoized per
102
125
  // process); the Credentials service holds an effect, so a token rotated
103
126
  // between calls is picked up without rebuilding the layer.
@@ -165,5 +188,5 @@ export const ProxmoxProtocol = makeRestProtocol({
165
188
  }
166
189
  return new UnknownProxmoxError({ status, message, body });
167
190
  },
168
- });
191
+ }));
169
192
  //# sourceMappingURL=protocol.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=replication-firewall-errors.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replication-firewall-errors.test.d.ts","sourceRoot":"","sources":["../src/replication-firewall-errors.test.ts"],"names":[],"mappings":""}