@homeflare/alchemy 0.22.0 → 0.24.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 (118) hide show
  1. package/README.md +15 -1
  2. package/dist/litellm/client.d.ts +76 -0
  3. package/dist/litellm/client.d.ts.map +1 -0
  4. package/dist/litellm/credentials.d.ts +48 -0
  5. package/dist/litellm/credentials.d.ts.map +1 -0
  6. package/dist/litellm/fake-litellm.d.ts +36 -0
  7. package/dist/litellm/fake-litellm.d.ts.map +1 -0
  8. package/dist/litellm/fake-stack.d.ts +30 -0
  9. package/dist/litellm/fake-stack.d.ts.map +1 -0
  10. package/dist/litellm/generated/pass-through.d.ts +71 -0
  11. package/dist/litellm/generated/pass-through.d.ts.map +1 -0
  12. package/dist/litellm/index.d.ts +13 -0
  13. package/dist/litellm/index.d.ts.map +1 -0
  14. package/dist/litellm/index.js +270 -0
  15. package/dist/litellm/index.js.map +14 -0
  16. package/dist/litellm/pass-through-endpoint.d.ts +94 -0
  17. package/dist/litellm/pass-through-endpoint.d.ts.map +1 -0
  18. package/dist/litellm/pass-through-form.d.ts +40 -0
  19. package/dist/litellm/pass-through-form.d.ts.map +1 -0
  20. package/dist/litellm/providers.d.ts +16 -0
  21. package/dist/litellm/providers.d.ts.map +1 -0
  22. package/dist/postgres/connection.d.ts +50 -0
  23. package/dist/postgres/connection.d.ts.map +1 -0
  24. package/dist/postgres/database-attrs.d.ts +79 -0
  25. package/dist/postgres/database-attrs.d.ts.map +1 -0
  26. package/dist/postgres/database-sql.d.ts +73 -0
  27. package/dist/postgres/database-sql.d.ts.map +1 -0
  28. package/dist/postgres/database.d.ts +63 -0
  29. package/dist/postgres/database.d.ts.map +1 -0
  30. package/dist/postgres/errors.d.ts +86 -0
  31. package/dist/postgres/errors.d.ts.map +1 -0
  32. package/dist/postgres/fake-sql.d.ts +22 -0
  33. package/dist/postgres/fake-sql.d.ts.map +1 -0
  34. package/dist/postgres/index.d.ts +16 -0
  35. package/dist/postgres/index.d.ts.map +1 -0
  36. package/dist/postgres/index.js +238 -0
  37. package/dist/postgres/index.js.map +15 -0
  38. package/dist/postgres/providers.d.ts +14 -0
  39. package/dist/postgres/providers.d.ts.map +1 -0
  40. package/dist/proxmox/api-token-form.d.ts +12 -1
  41. package/dist/proxmox/api-token-form.d.ts.map +1 -1
  42. package/dist/proxmox/api-token.d.ts +8 -3
  43. package/dist/proxmox/api-token.d.ts.map +1 -1
  44. package/dist/proxmox/generated/pve/cluster-sdn-fabrics.d.ts +13 -13
  45. package/dist/proxmox/generated/pve/cluster-sdn-fabrics.d.ts.map +1 -1
  46. package/dist/proxmox/index.d.ts +9 -2
  47. package/dist/proxmox/index.d.ts.map +1 -1
  48. package/dist/proxmox/index.js +17 -9
  49. package/dist/proxmox/index.js.map +8 -7
  50. package/dist/proxmox/zfs-pool-spec.d.ts +27 -0
  51. package/dist/proxmox/zfs-pool-spec.d.ts.map +1 -0
  52. package/dist/proxmox/zfs-pool-write.d.ts +48 -7
  53. package/dist/proxmox/zfs-pool-write.d.ts.map +1 -1
  54. package/dist/proxmox/zfs-pool.d.ts +52 -22
  55. package/dist/proxmox/zfs-pool.d.ts.map +1 -1
  56. package/dist/release/archive.d.ts +6 -2
  57. package/dist/release/archive.d.ts.map +1 -1
  58. package/dist/release/binary-form.d.ts +14 -9
  59. package/dist/release/binary-form.d.ts.map +1 -1
  60. package/dist/release/binary-pins.d.ts +17 -0
  61. package/dist/release/binary-pins.d.ts.map +1 -0
  62. package/dist/release/catalog.d.ts.map +1 -1
  63. package/dist/release/index.js +90 -40
  64. package/dist/release/index.js.map +8 -7
  65. package/dist/release/tar.d.ts +9 -2
  66. package/dist/release/tar.d.ts.map +1 -1
  67. package/docs/adopted-deploys.md +14 -0
  68. package/docs/litellm.md +110 -0
  69. package/docs/peers.md +8 -1
  70. package/docs/postgres.md +125 -0
  71. package/docs/release-binary-catalogs.md +40 -4
  72. package/docs/release-binary-refusals.md +22 -8
  73. package/docs/release-binary-upstream.md +33 -15
  74. package/package.json +10 -1
  75. package/src/litellm/client.ts +195 -0
  76. package/src/litellm/credentials.ts +73 -0
  77. package/src/litellm/fake-litellm.ts +114 -0
  78. package/src/litellm/fake-stack.ts +100 -0
  79. package/src/litellm/generated/pass-through.ts +73 -0
  80. package/src/litellm/index.ts +42 -0
  81. package/src/litellm/pass-through-endpoint-delete-swallow.test.ts +66 -0
  82. package/src/litellm/pass-through-endpoint-replace.test.ts +84 -0
  83. package/src/litellm/pass-through-endpoint.test.ts +208 -0
  84. package/src/litellm/pass-through-endpoint.ts +258 -0
  85. package/src/litellm/pass-through-form.test.ts +128 -0
  86. package/src/litellm/pass-through-form.ts +136 -0
  87. package/src/litellm/providers.ts +19 -0
  88. package/src/postgres/connection.ts +62 -0
  89. package/src/postgres/database-attrs.ts +87 -0
  90. package/src/postgres/database-bigint-serialization.test.ts +140 -0
  91. package/src/postgres/database-lifecycle.test.ts +113 -0
  92. package/src/postgres/database-provenance.test.ts +118 -0
  93. package/src/postgres/database-refusals.test.ts +135 -0
  94. package/src/postgres/database-sql.ts +174 -0
  95. package/src/postgres/database.ts +193 -0
  96. package/src/postgres/errors.ts +131 -0
  97. package/src/postgres/fake-sql.ts +123 -0
  98. package/src/postgres/fixtures/pg-REL_18_6-create-database-synopsis.txt +26 -0
  99. package/src/postgres/fixtures/pg-REL_18_6-pg_database.h.txt +67 -0
  100. package/src/postgres/index.ts +30 -0
  101. package/src/postgres/providers.ts +16 -0
  102. package/src/proxmox/api-token-adopt.test.ts +138 -0
  103. package/src/proxmox/api-token-form.ts +19 -2
  104. package/src/proxmox/api-token.ts +8 -3
  105. package/src/proxmox/generated/pve/cluster-sdn-fabrics.ts +23 -13
  106. package/src/proxmox/index.test.ts +5 -2
  107. package/src/proxmox/index.ts +9 -2
  108. package/src/proxmox/zfs-pool-adopt.test.ts +141 -0
  109. package/src/proxmox/zfs-pool-spec.ts +76 -0
  110. package/src/proxmox/zfs-pool-write.ts +63 -9
  111. package/src/proxmox/zfs-pool.ts +55 -97
  112. package/src/release/archive.ts +10 -4
  113. package/src/release/binary-form.ts +24 -76
  114. package/src/release/binary-pins.ts +95 -0
  115. package/src/release/binary-root.test.ts +122 -0
  116. package/src/release/catalog.ts +10 -2
  117. package/src/release/tar-root.test.ts +114 -0
  118. package/src/release/tar.ts +78 -12
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The `PveSpec` for `Proxmox.ZfsPool`: where a pool lives, what its GET reports, and the vendor
3
+ * endpoint its create form is checked against.
4
+ *
5
+ * ★ SPLIT OUT OF zfs-pool.ts TO KEEP THAT FILE UNDER THE 250-LINE CAP, and the seam is the same
6
+ * one metric-server-form.ts and zfs-pool-write.ts already cut on: zfs-pool.ts answers "what is
7
+ * a zpool and when has it changed", this file answers "what PVE's own GET/{node}/disks/zfs
8
+ * collection and item say, read into that shape". `leafDevices` moved with `spec` because it
9
+ * exists for exactly one field of `spec.attributes` and nothing else in this package calls it.
10
+ *
11
+ * ⚠️ THE `import type` BACK TO zfs-pool.ts IS A CYCLE ON PAPER ONLY — type-only, erased before
12
+ * anything runs, so the resource's public shape stays in the file that declares the resource.
13
+ * Same reasoning as zfs-pool-write.ts.
14
+ */
15
+ import type { PveSpec } from './resource.ts';
16
+ import type { ZfsPoolAttributes, ZfsPoolProps } from './zfs-pool.ts';
17
+ /**
18
+ * The device paths at the bottom of PVE's vdev tree, in the order ZFS reports them.
19
+ *
20
+ * ⚠️ "NO CHILDREN" IS THE LEAF TEST, AND IT IS NOT A GUESS — MEASURED in the node's `preparetree`,
21
+ * which sets `leaf` to 0 when there are children and 1 otherwise. Reading the flag would work
22
+ * equally well; recursing on `children` needs no second field to be present and is what makes
23
+ * nested sections (mirrors inside a raid10, `spares`, `cache`) flatten correctly.
24
+ */
25
+ export declare const leafDevices: (children: unknown) => string[];
26
+ export declare const spec: PveSpec<ZfsPoolProps, ZfsPoolAttributes>;
27
+ //# sourceMappingURL=zfs-pool-spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zfs-pool-spec.d.ts","sourceRoot":"","sources":["../../src/proxmox/zfs-pool-spec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAG7C,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAErE;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,aAAc,OAAO,KAAG,MAAM,EAO9C,CAAC;AAET,eAAO,MAAM,IAAI,EAAE,OAAO,CAAC,YAAY,EAAE,iBAAiB,CAuCzD,CAAC"}
@@ -35,15 +35,44 @@ import type { ZfsPoolAttributes, ZfsPoolProps } from './zfs-pool.ts';
35
35
  * declaration asked for, and this is the one call that writes to physical disks.
36
36
  */
37
37
  /**
38
- * The vendor rules the create form is checked against at plan time.
38
+ * The vendor rules a FULL declaration's create form is checked against at plan time.
39
39
  *
40
40
  * ⛔ HERE RATHER THAN INLINE IN zfs-pool.ts BECAUSE THAT FILE IS ALREADY OVER THE 250-LINE CAP,
41
41
  * and a `pve:POST …` literal has to live in a non-test source file for the generator to table
42
- * it at all. The spec imports it on the line it already imports `createForm` from.
42
+ * it at all. zfs-pool-spec.ts imports it on the line it already imports `createForm` from.
43
43
  * ⚠️ NO UPDATE KEY: PVE registers no PUT under `/nodes/{node}/disks/zfs`, which is exactly why
44
44
  * the spec declares no `updateForm` and a changed prop is a REPLACE.
45
+ * ⚠️ `spec.endpoint` DOES NOT RETURN THIS CONSTANT DIRECTLY — see the ★ on `zfsPoolEndpoint`
46
+ * below, and the ⛔ on `devices`/`raidlevel` in zfs-pool.ts (decision 9, 2026-09-23).
45
47
  */
46
48
  export declare const ZFS_POOL_ENDPOINT: EndpointPair;
49
+ /**
50
+ * ★ `PveSpec['endpoint']` AS A FUNCTION OF PROPS, THE WAY notification-target.ts AND
51
+ * ceph-daemon.ts ALREADY DO IT FOR A DIFFERENT REASON (resource-spec.ts's ⚠️ on that field).
52
+ * Here the reason is decision 9's adopt-only mode: `guardCreate` runs on the FIRST plan of
53
+ * ANY declaration (resource.ts `ops.diff`, `output === undefined` branch) and checks the
54
+ * create form's REQUIRED fields whenever `endpoint.create` resolves to a key — and PVE marks
55
+ * both `devices` and `raidlevel` required (measured, codegen/constraints's `pve-nodes-disks`
56
+ * row). An adopt-only declaration deliberately omits both, so returning the real key there
57
+ * would refuse a MATCHING adoption at plan time, before `read` ever ran — exactly the gap
58
+ * `guardCreate`/`guardUpdate` being exported (resource.ts's ★) says this family must not fall
59
+ * through. Returning `{}` (no `create` key) makes `guardCreate` a no-op (`constraint-guard.ts`:
60
+ * `create === undefined ? Effect.void : …`) for an adopt-only declaration, while a FULL one
61
+ * (`devices` and `raidlevel` both present) is checked exactly as before.
62
+ * ⚠️ THE RUNTIME BACKSTOP IS `createPool` BELOW, NOT THIS FUNCTION. Skipping the plan-time guard
63
+ * for an adopt-only declaration does not skip the check that the pool is actually there —
64
+ * `createPool` refuses before any POST when the read comes back absent and either field is
65
+ * undefined, which is what a genuinely absent adopt-only declaration hits at apply time.
66
+ */
67
+ export declare const zfsPoolEndpoint: (props: ZfsPoolProps) => EndpointPair;
68
+ /**
69
+ * ⚠️ EVERY KEY IS OMITTED WHEN ABSENT, `devices` AND `raidlevel` INCLUDED — not just the
70
+ * optional ones. An adopt-only declaration (both undefined, decision 9) must produce a form
71
+ * `createPool`'s own guard can inspect for their absence, and one this function itself does
72
+ * not throw building: `props.devices.join(',')` on an undefined `devices` is exactly the
73
+ * `TypeError` the plan-time guard hit before this change, because `guardCreate` evaluates
74
+ * `spec.createForm(props)` eagerly (resource-guard.ts) — before it even looks at `presence`.
75
+ */
47
76
  export declare const createForm: (props: ZfsPoolProps) => Record<string, string>;
48
77
  /**
49
78
  * Create the pool if it is absent, and otherwise leave it completely alone.
@@ -69,14 +98,26 @@ export declare const createForm: (props: ZfsPoolProps) => Record<string, string>
69
98
  * ⚠️ SHORT POLLS WITH A HARD CAP, NOT ONE LONG WAIT. `read` cannot tell "not yet" from "forbidden"
70
99
  * — both are `undefined` — so the loop gives up with the UPID rather than retrying forever, and
71
100
  * the UPID is the only thing that leads to the worker's real error.
101
+ *
102
+ * ⛔ THE ADOPT-ONLY GUARD, RIGHT AFTER THE READ AND BEFORE ANYTHING ELSE. Decision 9 (2026-09-23):
103
+ * omitting `devices` and `raidlevel` declares a pool this resource may only ADOPT, for a pool
104
+ * PVE's own schema cannot fully describe — n1's `speed` is a stripe, and `raidlevel`'s enum has
105
+ * no stripe (zfs-pool.ts). `zfsPoolEndpoint` above already keeps the vendor constraint guard
106
+ * from refusing such a declaration at PLAN time; this is the matching refusal at APPLY time, for
107
+ * the one case that guard cannot see — the pool genuinely is not there. MEASURED without it: a
108
+ * tolerant `createForm` sends `POST … {name: "rpool"}` with no `devices`/`raidlevel`, which PVE's
109
+ * own schema would 400 on eventually, but only after the request left this process; refusing
110
+ * here is the same `Effect.die` idiom as every other refusal in this package, and it costs
111
+ * nothing this pool did not already lack.
72
112
  */
73
113
  export declare const createPool: <E, R>(props: ZfsPoolProps, collection: string, path: string, read: (props: ZfsPoolProps) => Effect.Effect<ZfsPoolAttributes | undefined, E, R>) => Effect.Effect<ZfsPoolAttributes, E | Error, R | import("effect/unstable/http/HttpClient").HttpClient>;
74
114
  /**
75
- * ⛔ `DELETE /nodes/{node}/disks/zfs/{name}` RUNS `zpool destroy` AND THE DATASETS GO WITH IT.
76
- * It is implemented rather than stubbed, because a `delete` that silently does nothing lies to
77
- * whoever reads the plan. What keeps a deleted line from destroying a pool is the resource's
78
- * `defaultRemovalPolicy: 'retain'` above — an orphaned pool is forgotten, not destroyed, and a
79
- * caller who means it says `.pipe(RemovalPolicy.destroy())`.
115
+ * ⛔ `DELETE /nodes/{node}/disks/zfs/{name}` RUNS `zpool destroy` AND THE DATASETS GO WITH IT. Per
116
+ * S14 (alchemy-provider-standard), it is implemented rather than stubbed — a `delete` that
117
+ * silently does nothing lies to whoever reads the plan — and what keeps an ordinary removed
118
+ * declaration from reaching it is the resource's `defaultRemovalPolicy: 'retain'` (zfs-pool.ts):
119
+ * an orphaned pool is forgotten, not destroyed, unless a caller opts in with
120
+ * `.pipe(RemovalPolicy.destroy())`. See the corrected header in zfs-pool.ts.
80
121
  *
81
122
  * ⚠️ `cleanup-config` IS NOT SENT. It would additionally remove the PVE storage entry pointing
82
123
  * at this pool, which is a different object with its own resource (`Proxmox.Storage`); one
@@ -1 +1 @@
1
- {"version":3,"file":"zfs-pool-write.d.ts","sourceRoot":"","sources":["../../src/proxmox/zfs-pool-write.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAExC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAErE;;;;;;;;;;;;;GAaG;AACH;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,EAAE,YAA6D,CAAC;AAE9F,eAAO,MAAM,UAAU,UAAW,YAAY,KAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAOpE,CAAC;AAKH;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,UAAU,GAAI,CAAC,EAAE,CAAC,SACtB,YAAY,cACP,MAAM,QACZ,MAAM,QACN,CAAC,KAAK,EAAE,YAAY,KAAK,MAAM,CAAC,MAAM,CAAC,iBAAiB,GAAG,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC,0GAmC/E,CAAC;AAEL;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,WAAW,SAAU,YAAY,QAAQ,MAAM,wFACb,CAAC"}
1
+ {"version":3,"file":"zfs-pool-write.d.ts","sourceRoot":"","sources":["../../src/proxmox/zfs-pool-write.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAExC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAErE;;;;;;;;;;;;;GAaG;AACH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB,EAAE,YAA6D,CAAC;AAE9F;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,eAAe,UAAW,YAAY,KAAG,YACiC,CAAC;AAExF;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,UAAW,YAAY,KAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAOpE,CAAC;AAKH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,eAAO,MAAM,UAAU,GAAI,CAAC,EAAE,CAAC,SACtB,YAAY,cACP,MAAM,QACZ,MAAM,QACN,CAAC,KAAK,EAAE,YAAY,KAAK,MAAM,CAAC,MAAM,CAAC,iBAAiB,GAAG,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC,0GA8C/E,CAAC;AAEL;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,WAAW,SAAU,YAAY,QAAQ,MAAM,wFACb,CAAC"}
@@ -6,20 +6,22 @@
6
6
  * moment the worker runs. Every other decision in this file follows from that one sentence, and
7
7
  * each of them is written out below rather than left to be inferred.
8
8
  *
9
- * ⛔ DESTROY IS REFUSED. `delete` MAKES NO API CALL — removing this resource from a stack leaves
10
- * the zpool and its data exactly where they are, and Alchemy simply forgets about them. The
11
- * asymmetry is the whole argument: an ORPHANED pool is recoverable (declare it again, or destroy
12
- * it by hand once a human has looked at which disks are in it), a DESTROYED pool is not. Alchemy
13
- * deletes for reasons that have nothing to do with anybody intending it — a line removed from a
14
- * stack file, a resource renamed so the old id is dropped, a whole stack torn down by the wrong
15
- * command, a replace triggered by a prop nobody meant to touch. Handing `zpool destroy` to any
16
- * of those is not a feature. MEASURED in the node's own source: the DELETE handler forks
17
- * `zfsremove`, which runs `zpool destroy <name>` unconditionally, and with `cleanup-disks` then
18
- * calls `wipe_blockdev` on every member. Neither flag is offered as a prop, because there is no
19
- * call here for them to reach.
20
- * ★ THE OPERATOR'S PATH IS `pvesh delete /nodes/<node>/disks/zfs/<name>`, typed by a person who
21
- * has just looked at the pool. Do not "complete the CRUD" by wiring `delete` to `ops.destroy`:
22
- * that one line turns a text edit into an unrecoverable one.
9
+ * ⛔ ★ CORRECTED 2026-09-23 — THIS PARAGRAPH PREVIOUSLY SAID "DESTROY IS REFUSED" AND "`delete`
10
+ * MAKES NO API CALL". THAT WAS FALSE: MEASURED against this file's own `handlers.delete` below,
11
+ * which calls `destroyPool`, which sends `DELETE /nodes/{node}/disks/zfs/{name}` — the node's
12
+ * own source forks `zfsremove`, which runs `zpool destroy <name>` unconditionally, and with
13
+ * `cleanup-disks` then calls `wipe_blockdev` on every member (neither flag is offered as a prop,
14
+ * because there is no call here for them to reach). `delete` is FULLY IMPLEMENTED, per S14
15
+ * (alchemy-provider-standard): a `delete` that silently does nothing lies to whoever reads the
16
+ * plan. What actually keeps an ordinary removed declaration from reaching it is
17
+ * `defaultRemovalPolicy: 'retain'` below — Alchemy SKIPS `provider.delete` entirely for an
18
+ * orphaned or destroyed resource under `retain`, so the state row drops and the zpool and its
19
+ * data are left exactly where they are. The asymmetry is still the whole argument: an ORPHANED
20
+ * pool is recoverable (declare it again, or destroy it by hand once a human has looked at which
21
+ * disks are in it), a DESTROYED pool is not — which is why `retain` is the default rather than a
22
+ * documented caution, and why `delete` runs only for a caller who opts in by name with
23
+ * `.pipe(RemovalPolicy.destroy())`. See the ★ on `defaultRemovalPolicy` in resource-spec.ts, and
24
+ * zfs-pool-adopt.test.ts's mutation row, which pins exactly one DELETE reaching the fake cluster.
23
25
  *
24
26
  * ⛔ THERE IS NO PUT ON THIS FAMILY AT ALL. MEASURED from the cluster's own published schema
25
27
  * (`/usr/share/pve-docs/api-viewer/apidoc.js`, read on node-b 2026-09-13): `/nodes/{node}/disks/zfs`
@@ -47,19 +49,37 @@
47
49
  * ⚠️ TWO HANDLERS ARE OVERRIDDEN AND THE OTHER THREE COME FROM THE FACTORY — see the ★ above
48
50
  * `handlers`. `delete` is the ⛔ above. `reconcile` is because the create is a FORKED WORKER, so
49
51
  * the factory's immediate read-back would report a successful create as a failure; the measured
50
- * detail is in the ⛔ on `createPool` in zfs-pool-create.ts.
52
+ * detail, and the adopt-only refusal decision 9 adds, are both in `createPool` in
53
+ * zfs-pool-write.ts.
51
54
  *
52
55
  * ⚠️ PRIVILEGES, FROM THE SCHEMA. Read and diff need `Sys.Audit` on `/` — both GETs check it.
53
56
  * Reconcile needs `Sys.Modify` on `/` for the POST. `Datastore.Allocate` on `/storage` is NOT
54
- * needed, because `add_storage` is not offered — see the ⛔ on `ZfsPoolProps`. `delete` needs
55
- * nothing at all, since it calls nothing.
57
+ * needed, because `add_storage` is not offered — see the ⛔ on `ZfsPoolProps`.
58
+ * ★ CORRECTED 2026-09-23, ALONGSIDE THE HEADER ABOVE: `delete` needs `Sys.Modify` on `/` too,
59
+ * for the DELETE it now sends under `.pipe(RemovalPolicy.destroy())` — the whole `disks/zfs`
60
+ * family checks it, not only the POST. MEASURED the same day as the rest of this paragraph:
61
+ * `ceph-osd.ts`'s own privilege comparison names `disks/zfs` as one of the sibling families
62
+ * whose write verbs DO carry a `Sys.Modify` check, unlike `ceph/osd`'s create and delete, which
63
+ * carry no permissions block at all and 403 for any identity but `root@pam`. This line
64
+ * previously said delete "needs nothing at all, since it calls nothing", which repeated the same
65
+ * mistake the header above corrects: `delete` calls the DELETE endpoint, so it needs whatever
66
+ * that endpoint checks.
56
67
  */
57
68
  import { Resource } from 'alchemy';
58
69
  import * as Provider from 'alchemy/Provider';
70
+ import type { NodesNodeDisksZfsPostParams } from './generated/pve.ts';
59
71
  import { type PveRequirements, type WithTarget } from './resource.ts';
60
- /** PVE's layouts. ⚠️ Each has a minimum disk count PVE enforces, and `raid10` needs an even one. */
61
- export type ZfsRaidLevel = 'single' | 'mirror' | 'raid10' | 'raidz' | 'raidz2' | 'raidz3' | 'draid' | 'draid2' | 'draid3';
62
- export type ZfsCompression = 'on' | 'off' | 'gzip' | 'lz4' | 'lzjb' | 'zle' | 'zstd';
72
+ /**
73
+ * PVE's layouts, generated (2026-09-23) from `NodesNodeDisksZfsPostParams['raidlevel']`
74
+ * (`generated/pve.ts`, manifest `pve-apidoc` pve-manager 9.2.11 sha256 9def8f13…) rather than
75
+ * hand-typed, per decision 9. ⚠️ Each has a minimum disk count PVE enforces, and `raid10` needs an
76
+ * even one. ⛔ THERE IS NO `stripe` IN THIS ENUM. n1's `speed` pool is one (decision 14 accepts it
77
+ * as-is): `ZfsPoolProps` below is why a stripe pool is declarable at all — as an ADOPT-ONLY
78
+ * declaration with no `raidlevel`, never as a full one.
79
+ */
80
+ export type ZfsRaidLevel = NonNullable<NodesNodeDisksZfsPostParams['raidlevel']>;
81
+ /** Generated the same way, from `NodesNodeDisksZfsPostParams['compression']`. */
82
+ export type ZfsCompression = NonNullable<NodesNodeDisksZfsPostParams['compression']>;
63
83
  /**
64
84
  * ⛔ THERE IS NO `add_storage`, `cleanup-config` OR `cleanup-disks` PROP, AND THAT IS NOT AN
65
85
  * OVERSIGHT. The last two belong to the DELETE this resource refuses to make. `add_storage=1`
@@ -91,9 +111,19 @@ export interface ZfsPoolProps extends WithTarget {
91
111
  * `csv` sorts, and sorting this list silently re-pairs the mirrors into a different pool.
92
112
  * ⚠️ PVE's only guard is `assert_disk_unused`, i.e. its own `disk_is_used` over the same `used`
93
113
  * column the Disks view shows. A blank disk it considers free is wiped with no confirmation.
114
+ *
115
+ * ⛔ OPTIONAL SINCE DECISION 9 (2026-09-23), AND OMITTING IT MEANS SOMETHING SPECIFIC: leaving
116
+ * both this and `raidlevel` undeclared declares an ADOPT-ONLY pool — one this resource may
117
+ * read and report but never build. That is the only shape available for a pool PVE's own POST
118
+ * schema cannot fully describe, such as n1's `speed`, whose layout is a stripe and whose
119
+ * `raidlevel` enum (above) has no stripe to declare. `createForm` (zfs-pool-write.ts) omits
120
+ * both keys when they are absent, and `createPool` refuses before any POST if the pool it
121
+ * would need to build is not already there — an adopt-only declaration never creates. Declare
122
+ * both fields together for a pool this resource should build from scratch; declaring only one
123
+ * of the two is refused the same way, since PVE's POST requires both.
94
124
  */
95
- devices: readonly string[];
96
- raidlevel: ZfsRaidLevel;
125
+ devices?: readonly string[];
126
+ raidlevel?: ZfsRaidLevel;
97
127
  /**
98
128
  * Sector size exponent, 9–16, PVE default 12.
99
129
  *
@@ -1 +1 @@
1
- {"version":3,"file":"zfs-pool.d.ts","sourceRoot":"","sources":["../../src/proxmox/zfs-pool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAE7C,OAAO,EACL,KAAK,eAAe,EAEpB,KAAK,UAAU,EAGhB,MAAM,eAAe,CAAC;AAIvB,oGAAoG;AACpG,MAAM,MAAM,YAAY,GACpB,QAAQ,GACR,QAAQ,GACR,QAAQ,GACR,OAAO,GACP,QAAQ,GACR,QAAQ,GACR,OAAO,GACP,QAAQ,GACR,QAAQ,CAAC;AAEb,MAAM,MAAM,cAAc,GAAG,IAAI,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,CAAC;AAErF;;;;;;;;GAQG;AACH,MAAM,WAAW,YAAa,SAAQ,UAAU;IAC9C;;;;;OAKG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,+FAA+F;IAC/F,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;;;;;;;OAYG;IACH,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,SAAS,EAAE,YAAY,CAAC;IACxB;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,WAAW,CAAC,EAAE,cAAc,CAAC;IAC7B;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,mFAAmF;IACnF,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAC;IACd,4DAA4D;IAC5D,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;OAOG;IACH,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAe,SAAQ,QAAQ,CAC9C,iBAAiB,EACjB,YAAY,EACZ,iBAAiB,EACjB,KAAK,EACL,eAAe,CAChB;CAAG;AAEJ,mGAAmG;AACnG,eAAO,MAAM,cAAc,iDAEzB,CAAC;AA8EH,eAAO,MAAM,sBAAsB,oIACoD,CAAC"}
1
+ {"version":3,"file":"zfs-pool.d.ts","sourceRoot":"","sources":["../../src/proxmox/zfs-pool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkEG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAE7C,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,oBAAoB,CAAC;AACtE,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,UAAU,EAA8B,MAAM,eAAe,CAAC;AAIlG;;;;;;;GAOG;AACH,MAAM,MAAM,YAAY,GAAG,WAAW,CAAC,2BAA2B,CAAC,WAAW,CAAC,CAAC,CAAC;AAEjF,iFAAiF;AACjF,MAAM,MAAM,cAAc,GAAG,WAAW,CAAC,2BAA2B,CAAC,aAAa,CAAC,CAAC,CAAC;AAErF;;;;;;;;GAQG;AACH,MAAM,WAAW,YAAa,SAAQ,UAAU;IAC9C;;;;;OAKG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,+FAA+F;IAC/F,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,SAAS,CAAC,EAAE,YAAY,CAAC;IACzB;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,WAAW,CAAC,EAAE,cAAc,CAAC;IAC7B;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,mFAAmF;IACnF,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAC;IACd,4DAA4D;IAC5D,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;OAOG;IACH,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAe,SAAQ,QAAQ,CAC9C,iBAAiB,EACjB,YAAY,EACZ,iBAAiB,EACjB,KAAK,EACL,eAAe,CAChB;CAAG;AAEJ,mGAAmG;AACnG,eAAO,MAAM,cAAc,iDAEzB,CAAC;AAqBH,eAAO,MAAM,sBAAsB,oIACoD,CAAC"}
@@ -1,7 +1,11 @@
1
1
  import type { PinnedDownload } from './binary-form.ts';
2
2
  import { type TarContents } from './tar.ts';
3
- /** Gunzip and read a tar, keeping only `wanted`. Throws ArchiveRefused. */
4
- export declare const extractMembers: (gzipped: Uint8Array, wanted: ReadonlySet<string>) => Promise<TarContents>;
3
+ /**
4
+ * Gunzip and read a tar, keeping only `wanted`. Throws ArchiveRefused.
5
+ * @param root The one declared leading directory to strip (tar.ts) — a directory-wrapped vendor
6
+ * archive (the Prometheus family, measured 2026-09-23) without it, or one that carries no root.
7
+ */
8
+ export declare const extractMembers: (gzipped: Uint8Array, wanted: ReadonlySet<string>, root?: string) => Promise<TarContents>;
5
9
  /**
6
10
  * The declared member's bytes out of a downloaded archive, both layers checked.
7
11
  * ⚠️ `archive` may be shared with another resource's extraction (download.ts sharingInFlight):
@@ -1 +1 @@
1
- {"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../../src/release/archive.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EAAE,KAAK,WAAW,EAAa,MAAM,UAAU,CAAC;AAKvD,2EAA2E;AAC3E,eAAO,MAAM,cAAc,YAChB,UAAU,UACX,WAAW,CAAC,MAAM,CAAC,KAC1B,OAAO,CAAC,WAAW,CA8BrB,CAAC;AAUF;;;;GAIG;AACH,eAAO,MAAM,cAAc,YAChB,UAAU,WACV,cAAc,KACtB,OAAO,CAAC,UAAU,CAepB,CAAC"}
1
+ {"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../../src/release/archive.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EAAE,KAAK,WAAW,EAAa,MAAM,UAAU,CAAC;AAKvD;;;;GAIG;AACH,eAAO,MAAM,cAAc,YAChB,UAAU,UACX,WAAW,CAAC,MAAM,CAAC,SACpB,MAAM,KACZ,OAAO,CAAC,WAAW,CA8BrB,CAAC;AAUF;;;;GAIG;AACH,eAAO,MAAM,cAAc,YAChB,UAAU,WACV,cAAc,KACtB,OAAO,CAAC,UAAU,CAgBpB,CAAC"}
@@ -28,6 +28,17 @@ export interface ReleaseArchive {
28
28
  readonly size: number;
29
29
  /** The archive's SHA-256, lower-case hex, as the vendor's checksum file lists it. */
30
30
  readonly sha256: string;
31
+ /**
32
+ * The one directory the vendor wraps every entry in, exactly as its own archive names it —
33
+ * `'alertmanager-0.33.1.darwin-arm64'`, never `'alertmanager-0.33.1.darwin-arm64/'` (no trailing
34
+ * slash). Pinned from the walk-down, never derived from `asset` or `tag`: the vendor spells its
35
+ * wrapper however it likes (`darwin_arm64`, an `-enterprise` suffix, no version at all), so a
36
+ * computed name would drift onto a sibling the moment one vendor's naming changes. Omit it for a
37
+ * flat archive (Victoria, OpenBao); the tar reader then refuses a directory entry exactly as it
38
+ * always has (tar.ts).
39
+ * @default undefined — no wrapping directory; the archive is flat.
40
+ */
41
+ readonly root?: string;
31
42
  }
32
43
  export interface ReleaseBinaryProps {
33
44
  /** The pinned archive the binary comes out of. `catalogBinary()` fills it from a data set. */
@@ -77,6 +88,8 @@ export type PinnedDownload = {
77
88
  readonly member: string;
78
89
  /** The member's pinned SHA-256. */
79
90
  readonly memberSha256: string;
91
+ /** The one declared leading directory to strip, if the archive has one — `archive.root`. */
92
+ readonly root?: string;
80
93
  };
81
94
  export declare const DEFAULT_BINARY_MODE = 493;
82
95
  /**
@@ -95,14 +108,7 @@ export declare const releaseBinaryPath: (props: {
95
108
  /** The download a declaration means. ⚠️ Validate first (binaryProblems): this trusts its input. */
96
109
  export declare const pinnedDownload: (props: ReleaseBinaryProps) => PinnedDownload;
97
110
  export declare const modeProblems: (mode: number) => string[];
98
- export declare const MAX_ARCHIVE: number;
99
- /** Everything wrong with the pins alone — plain strings in any declaration, resolved or not. */
100
- export declare const pinProblems: (props: {
101
- readonly archive?: Partial<ReleaseArchive>;
102
- readonly member?: unknown;
103
- readonly sha256?: unknown;
104
- readonly name?: unknown;
105
- }) => string[];
111
+ export { MAX_ARCHIVE, pinProblems } from './binary-pins.ts';
106
112
  type Pins = Pick<ReleaseBinaryProps, 'archive' | 'member' | 'sha256'>;
107
113
  /** Whether two declarations pin different bytes: another archive, member or member digest. */
108
114
  export declare const pinsMoved: (olds: Pins, news: Pins) => boolean;
@@ -114,5 +120,4 @@ export declare const pinsMoved: (olds: Pins, news: Pins) => boolean;
114
120
  export declare const inPlaceRefusal: (olds: Pins, news: Pins) => string;
115
121
  /** Everything wrong with a declaration, before any host or network is asked anything. */
116
122
  export declare const binaryProblems: (props: ReleaseBinaryProps) => string[];
117
- export {};
118
123
  //# sourceMappingURL=binary-form.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"binary-form.d.ts","sourceRoot":"","sources":["../../src/release/binary-form.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"binary-form.d.ts","sourceRoot":"","sources":["../../src/release/binary-form.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAIvE,oFAAoF;AACpF,MAAM,WAAW,cAAc;IAC7B,kGAAkG;IAClG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,2DAA2D;IAC3D,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,yFAAyF;IACzF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,qFAAqF;IACrF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;;;;;;;;OASG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,kBAAkB;IACjC,8FAA8F;IAC9F,OAAO,EAAE,cAAc,CAAC;IACxB,oGAAoG;IACpG,MAAM,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;OAMG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,yEAAyE;IACzE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,uBAAwB,SAAQ,kBAAkB;IACjE,8CAA8C;IAC9C,GAAG,EAAE,MAAM,CAAC;IACZ,uEAAuE;IACvE,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,4FAA4F;AAC5F,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,oCAAoC;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,oDAAoD;IACpD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,mCAAmC;IACnC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,4FAA4F;IAC5F,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,eAAO,MAAM,mBAAmB,MAAQ,CAAC;AAEzC;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,SAAU,MAAM,OAAO,MAAM,SAAS,MAAM,KAAG,MACN,CAAC;AAEjE,iEAAiE;AACjE,eAAO,MAAM,iBAAiB,UAAW;IACvC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB,KAAG,MAA4C,CAAC;AAEjD,mGAAmG;AACnG,eAAO,MAAM,cAAc,UAAW,kBAAkB,KAAG,cAQzD,CAAC;AAEH,eAAO,MAAM,YAAY,SAAU,MAAM,KAAG,MAAM,EAUjD,CAAC;AAKF,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAO5D,KAAK,IAAI,GAAG,IAAI,CAAC,kBAAkB,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAC,CAAC;AAEtE,8FAA8F;AAC9F,eAAO,MAAM,SAAS,SAAU,IAAI,QAAQ,IAAI,KAAG,OAGkB,CAAC;AAEtE;;;;GAIG;AACH,eAAO,MAAM,cAAc,SAAU,IAAI,QAAQ,IAAI,KAAG,MAGqB,CAAC;AAE9E,yFAAyF;AACzF,eAAO,MAAM,cAAc,UAAW,kBAAkB,KAAG,MAAM,EAMhE,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * `pinProblems` — everything wrong with a declaration's pins alone, as plain strings, checked
3
+ * before a plan resolves a directory or a host is asked anything. Split out of binary-form.ts
4
+ * 2026-09-23 to keep that file under the house's 250-line cap (it was 229, heading for ~250 once
5
+ * `archive.root` needed its own validation and JSDoc). binary-form.ts re-exports `pinProblems` and
6
+ * `MAX_ARCHIVE`, so nothing that imported them moves.
7
+ */
8
+ import type { ReleaseArchive } from './binary-form.ts';
9
+ export declare const MAX_ARCHIVE: number;
10
+ /** Everything wrong with the pins alone — plain strings in any declaration, resolved or not. */
11
+ export declare const pinProblems: (props: {
12
+ readonly archive?: Partial<ReleaseArchive>;
13
+ readonly member?: unknown;
14
+ readonly sha256?: unknown;
15
+ readonly name?: unknown;
16
+ }) => string[];
17
+ //# sourceMappingURL=binary-pins.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"binary-pins.d.ts","sourceRoot":"","sources":["../../src/release/binary-pins.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAGvD,eAAO,MAAM,WAAW,QAAU,CAAC;AAcnC,gGAAgG;AAChG,eAAO,MAAM,WAAW,UAAW;IACjC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;IAC3C,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;CACzB,KAAG,MAAM,EAgET,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../src/release/catalog.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../src/release/catalog.ts"],"names":[],"mappings":"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"}
@@ -71,31 +71,7 @@ var fileProblems = (props) => {
71
71
  return found;
72
72
  };
73
73
 
74
- // src/release/binary-form.ts
75
- var DEFAULT_BINARY_MODE = 493;
76
- var releaseUrl = (repo, tag, asset) => `https://github.com/${repo}/releases/download/${tag}/${asset}`;
77
- var releaseBinaryPath = (props) => `${props.directory}/${props.name}`;
78
- var pinnedDownload = (props) => ({
79
- member: props.member,
80
- memberSha256: props.sha256,
81
- sha256: props.archive.sha256,
82
- size: props.archive.size,
83
- url: releaseUrl(props.archive.repo, props.archive.tag, props.archive.asset)
84
- });
85
- var modeProblems = (mode) => {
86
- if (!Number.isSafeInteger(mode) || mode < 0 || mode > 4095)
87
- return ["mode must be 0–0o7777"];
88
- const found = [];
89
- if ((mode & 3584) !== 0)
90
- found.push("mode must not set setuid, setgid or sticky");
91
- if ((mode & 18) !== 0)
92
- found.push("mode must not be group- or world-writable");
93
- if ((mode & 64) === 0)
94
- found.push("mode must let the owner execute it");
95
- if ((mode & 256) === 0)
96
- found.push("mode must let the owner read it (it is re-hashed)");
97
- return found;
98
- };
74
+ // src/release/binary-pins.ts
99
75
  var HEX64 = /^[0-9a-f]{64}$/;
100
76
  var MAX_ARCHIVE = 1 << 30;
101
77
  var REPO = /^[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?\/[A-Za-z0-9._-]+$/;
@@ -132,6 +108,15 @@ var pinProblems = (props) => {
132
108
  else if (!TARBALL.test(asset))
133
109
  found.push(`archive.asset must be a .tar.gz or .tgz: "${asset}"`);
134
110
  }
111
+ if (archive.root !== undefined) {
112
+ if (typeof archive.root !== "string") {
113
+ found.push("archive.root must be a plain string when given");
114
+ } else {
115
+ const problem = segmentProblem("archive.root", archive.root);
116
+ if (problem !== undefined)
117
+ found.push(problem);
118
+ }
119
+ }
135
120
  const size = archive.size;
136
121
  if (typeof size !== "number" || !Number.isSafeInteger(size) || size <= 0 || size > MAX_ARCHIVE) {
137
122
  found.push(`archive.size must be the asset's byte count, 1 to ${String(MAX_ARCHIVE)}`);
@@ -150,7 +135,34 @@ var pinProblems = (props) => {
150
135
  }
151
136
  return found;
152
137
  };
153
- var ARCHIVE_KEYS = ["repo", "tag", "asset", "size", "sha256"];
138
+
139
+ // src/release/binary-form.ts
140
+ var DEFAULT_BINARY_MODE = 493;
141
+ var releaseUrl = (repo, tag, asset) => `https://github.com/${repo}/releases/download/${tag}/${asset}`;
142
+ var releaseBinaryPath = (props) => `${props.directory}/${props.name}`;
143
+ var pinnedDownload = (props) => ({
144
+ member: props.member,
145
+ memberSha256: props.sha256,
146
+ sha256: props.archive.sha256,
147
+ size: props.archive.size,
148
+ url: releaseUrl(props.archive.repo, props.archive.tag, props.archive.asset),
149
+ ...props.archive.root === undefined ? {} : { root: props.archive.root }
150
+ });
151
+ var modeProblems = (mode) => {
152
+ if (!Number.isSafeInteger(mode) || mode < 0 || mode > 4095)
153
+ return ["mode must be 0–0o7777"];
154
+ const found = [];
155
+ if ((mode & 3584) !== 0)
156
+ found.push("mode must not set setuid, setgid or sticky");
157
+ if ((mode & 18) !== 0)
158
+ found.push("mode must not be group- or world-writable");
159
+ if ((mode & 64) === 0)
160
+ found.push("mode must let the owner execute it");
161
+ if ((mode & 256) === 0)
162
+ found.push("mode must let the owner read it (it is re-hashed)");
163
+ return found;
164
+ };
165
+ var ARCHIVE_KEYS = ["repo", "tag", "asset", "size", "sha256", "root"];
154
166
  var pinsMoved = (olds, news) => olds.member !== news.member || olds.sha256 !== news.sha256 || ARCHIVE_KEYS.some((key) => olds.archive[key] !== news.archive[key]);
155
167
  var inPlaceRefusal = (olds, news) => `a new pin (${news.archive.tag} ${news.member} ${news.sha256}) at the path of the installed ` + `one (${olds.archive.tag} ${olds.member} ${olds.sha256}) would overwrite it in place; give each ` + "version its own directory (catalogDirectory), so an upgrade is a new path";
156
168
  var binaryProblems = (props) => [
@@ -778,7 +790,7 @@ var concatBytes = (parts) => {
778
790
  }
779
791
  return out;
780
792
  };
781
- var tarReader = (wanted) => {
793
+ var tarReader = (wanted, root) => {
782
794
  const header = new Uint8Array(BLOCK);
783
795
  const members = new Map;
784
796
  const names = [];
@@ -790,6 +802,8 @@ var tarReader = (wanted) => {
790
802
  let zeros = 0;
791
803
  let name = "";
792
804
  let parts;
805
+ let rootSeen = false;
806
+ const rootPrefix = root === undefined ? undefined : `${root}/`;
793
807
  const endEntry = () => {
794
808
  if (parts !== undefined)
795
809
  members.set(name, concatBytes(parts));
@@ -812,20 +826,45 @@ var tarReader = (wanted) => {
812
826
  if (!posix && magic !== "ustar \x00")
813
827
  throw refuse("an entry header is neither ustar nor GNU");
814
828
  const prefix = posix ? text(header, 345, 155) : "";
815
- name = prefix === "" ? text(header, 0, 100) : `${prefix}/${text(header, 0, 100)}`;
816
- const problem = nameProblem(name);
829
+ const rawName = prefix === "" ? text(header, 0, 100) : `${prefix}/${text(header, 0, 100)}`;
830
+ const problem = nameProblem(rawName);
817
831
  if (problem !== undefined)
818
- throw refuse(`entry ${JSON.stringify(name)} ${problem}`);
832
+ throw refuse(`entry ${JSON.stringify(rawName)} ${problem}`);
833
+ if (rootPrefix !== undefined && rawName === rootPrefix) {
834
+ if (rootSeen) {
835
+ throw refuse(`entry "${rawName}" reuses the declared root's own name, appearing twice`);
836
+ }
837
+ const type2 = String.fromCharCode(header[156] ?? 0);
838
+ const size = octal(header, 124, 12, `entry "${rawName}"'s size`);
839
+ if (type2 !== "5" || size !== 0) {
840
+ throw refuse(`entry "${rawName}" is the declared root but is not an empty directory`);
841
+ }
842
+ rootSeen = true;
843
+ phase = "header";
844
+ return;
845
+ }
846
+ let stripped = rawName;
847
+ if (rootPrefix !== undefined) {
848
+ if (!rawName.startsWith(rootPrefix)) {
849
+ throw refuse(`entry "${rawName}" is outside the declared root "${rootPrefix}"`);
850
+ }
851
+ stripped = rawName.slice(rootPrefix.length);
852
+ const strippedProblem = nameProblem(stripped);
853
+ if (strippedProblem !== undefined) {
854
+ throw refuse(`entry "${rawName}" strips to ${JSON.stringify(stripped)}, which ${strippedProblem}`);
855
+ }
856
+ }
857
+ name = stripped;
819
858
  const type = String.fromCharCode(header[156] ?? 0);
820
859
  if (type !== "0" && type !== "\x00") {
821
860
  const kind = KINDS[type] ?? `of unknown type ${JSON.stringify(type)}`;
822
- throw refuse(`entry "${name}" is ${kind}; only regular files are accepted`);
861
+ throw refuse(`entry "${rawName}" is ${kind}; only regular files are accepted`);
823
862
  }
824
863
  if (seen.has(name))
825
- throw refuse(`entry "${name}" appears twice`);
864
+ throw refuse(`entry "${rawName}" appears twice`);
826
865
  seen.add(name);
827
866
  names.push(name);
828
- remaining = octal(header, 124, 12, `entry "${name}"'s size`);
867
+ remaining = octal(header, 124, 12, `entry "${rawName}"'s size`);
829
868
  padding = (BLOCK - remaining % BLOCK) % BLOCK;
830
869
  parts = wanted.has(name) ? [] : undefined;
831
870
  phase = "data";
@@ -869,6 +908,9 @@ var tarReader = (wanted) => {
869
908
  const finish = () => {
870
909
  if (phase !== "end")
871
910
  throw refuse("the archive stops before its end-of-archive marker");
911
+ if (rootPrefix !== undefined && !rootSeen) {
912
+ throw refuse(`the declared root "${rootPrefix}" never appears in the archive`);
913
+ }
872
914
  return { members, names };
873
915
  };
874
916
  return { finish, push };
@@ -876,8 +918,8 @@ var tarReader = (wanted) => {
876
918
 
877
919
  // src/release/archive.ts
878
920
  var SLICE = 1 << 20;
879
- var extractMembers = async (gzipped, wanted) => {
880
- const tar = tarReader(wanted);
921
+ var extractMembers = async (gzipped, wanted, root) => {
922
+ const tar = tarReader(wanted, root);
881
923
  let offset = 0;
882
924
  const source = new ReadableStream({
883
925
  pull(controller) {
@@ -917,11 +959,12 @@ var verifiedMember = async (archive, release) => {
917
959
  const archiveSha = sha256Hex(archive);
918
960
  if (archiveSha !== release.sha256)
919
961
  throw mismatch(release.url, release.sha256, archiveSha);
920
- const { members, names } = await extractMembers(archive, new Set([release.member]));
962
+ const { members, names } = await extractMembers(archive, new Set([release.member]), release.root);
921
963
  const bytes = members.get(release.member);
922
964
  if (bytes === undefined) {
965
+ const under = release.root === undefined ? "" : ` under root "${release.root}/"`;
923
966
  throw new ArchiveRefused({
924
- message: `${release.url} has no member named exactly "${release.member}" (it has ${names.map((n) => `"${n}"`).join(", ") || "none"})`
967
+ message: `${release.url} has no member named exactly "${release.member}"${under} (it has ${names.map((n) => `"${n}"`).join(", ") || "none"})`
925
968
  });
926
969
  }
927
970
  const memberSha = sha256Hex(bytes);
@@ -1214,9 +1257,16 @@ var catalogBinary = (catalog, request) => {
1214
1257
  message: `Release.Binary ${request.binary}: ${problems.join("; ")}. Nothing was fetched.`
1215
1258
  });
1216
1259
  }
1217
- const { asset, repo, sha256: archiveSha256, size, tag } = archive;
1260
+ const { asset, repo, root, sha256: archiveSha256, size, tag } = archive;
1218
1261
  return {
1219
- archive: { asset, repo, sha256: archiveSha256, size, tag },
1262
+ archive: {
1263
+ asset,
1264
+ repo,
1265
+ sha256: archiveSha256,
1266
+ size,
1267
+ tag,
1268
+ ...root === undefined ? {} : { root }
1269
+ },
1220
1270
  member,
1221
1271
  name: request.binary,
1222
1272
  sha256
@@ -1421,5 +1471,5 @@ export {
1421
1471
  releaseUrl
1422
1472
  };
1423
1473
 
1424
- //# debugId=95AC8419C10B267864756E2164756E21
1474
+ //# debugId=684C5D2BD8F11B9A64756E2164756E21
1425
1475
  //# sourceMappingURL=index.js.map