@homeflare/alchemy 0.37.8 → 0.39.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 (183) hide show
  1. package/README.md +22 -0
  2. package/dist/caddy/admin.d.ts +42 -8
  3. package/dist/caddy/admin.d.ts.map +1 -1
  4. package/dist/caddy/config.d.ts +9 -3
  5. package/dist/caddy/config.d.ts.map +1 -1
  6. package/dist/caddy/fake-caddy.d.ts +5 -0
  7. package/dist/caddy/fake-caddy.d.ts.map +1 -1
  8. package/dist/caddy/index.js +10 -6
  9. package/dist/caddy/index.js.map +5 -5
  10. package/dist/caddy/providers.d.ts +19 -12
  11. package/dist/caddy/providers.d.ts.map +1 -1
  12. package/dist/forgejo/index.js +4 -2
  13. package/dist/forgejo/index.js.map +3 -3
  14. package/dist/forgejo/team-member.d.ts +25 -0
  15. package/dist/forgejo/team-member.d.ts.map +1 -1
  16. package/dist/proxmox/generated/constraints/index.d.ts +1 -1
  17. package/dist/proxmox/generated/constraints/index.d.ts.map +1 -1
  18. package/dist/proxmox/generated/constraints/pve-access.d.ts +1 -1
  19. package/dist/proxmox/generated/constraints/pve-cluster-backup.d.ts +1 -1
  20. package/dist/proxmox/generated/constraints/pve-cluster-ceph.d.ts +1 -1
  21. package/dist/proxmox/generated/constraints/pve-cluster-firewall.d.ts +1 -1
  22. package/dist/proxmox/generated/constraints/pve-cluster-ha.d.ts +1 -1
  23. package/dist/proxmox/generated/constraints/pve-cluster-metrics.d.ts +1 -1
  24. package/dist/proxmox/generated/constraints/pve-cluster-notifications.d.ts +1 -1
  25. package/dist/proxmox/generated/constraints/pve-cluster-replication.d.ts +1 -1
  26. package/dist/proxmox/generated/constraints/pve-cluster-sdn.d.ts +1 -1
  27. package/dist/proxmox/generated/constraints/pve-nodes-ceph.d.ts +1 -1
  28. package/dist/proxmox/generated/constraints/pve-nodes-disks.d.ts +1 -1
  29. package/dist/proxmox/generated/constraints/pve-nodes-lxc.d.ts +1 -1
  30. package/dist/proxmox/generated/constraints/pve-nodes-network.d.ts +1 -1
  31. package/dist/proxmox/generated/constraints/pve-nodes-qemu.d.ts +1 -1
  32. package/dist/proxmox/generated/constraints/pve-nodes-storage.d.ts +23 -0
  33. package/dist/proxmox/generated/constraints/pve-nodes-storage.d.ts.map +1 -0
  34. package/dist/proxmox/generated/constraints/pve-pools.d.ts +1 -1
  35. package/dist/proxmox/generated/constraints/pve-storage.d.ts +1 -1
  36. package/dist/proxmox/index.d.ts +14 -3
  37. package/dist/proxmox/index.d.ts.map +1 -1
  38. package/dist/proxmox/index.js +565 -133
  39. package/dist/proxmox/index.js.map +33 -23
  40. package/dist/proxmox/qemu-form.d.ts +27 -18
  41. package/dist/proxmox/qemu-form.d.ts.map +1 -1
  42. package/dist/proxmox/qemu-judge.d.ts +30 -0
  43. package/dist/proxmox/qemu-judge.d.ts.map +1 -0
  44. package/dist/proxmox/qemu-lifecycle.d.ts +16 -2
  45. package/dist/proxmox/qemu-lifecycle.d.ts.map +1 -1
  46. package/dist/proxmox/qemu-net.d.ts +9 -0
  47. package/dist/proxmox/qemu-net.d.ts.map +1 -0
  48. package/dist/proxmox/qemu-props.d.ts +112 -0
  49. package/dist/proxmox/qemu-props.d.ts.map +1 -0
  50. package/dist/proxmox/qemu-volume.d.ts +17 -0
  51. package/dist/proxmox/qemu-volume.d.ts.map +1 -0
  52. package/dist/proxmox/qemu.d.ts +14 -27
  53. package/dist/proxmox/qemu.d.ts.map +1 -1
  54. package/dist/proxmox/storage-download-errors.d.ts +5 -0
  55. package/dist/proxmox/storage-download-errors.d.ts.map +1 -0
  56. package/dist/proxmox/storage-download-identity.d.ts +27 -0
  57. package/dist/proxmox/storage-download-identity.d.ts.map +1 -0
  58. package/dist/proxmox/storage-download-lifecycle.d.ts +97 -0
  59. package/dist/proxmox/storage-download-lifecycle.d.ts.map +1 -0
  60. package/dist/proxmox/storage-download-props.d.ts +36 -0
  61. package/dist/proxmox/storage-download-props.d.ts.map +1 -0
  62. package/dist/proxmox/storage-download-task.d.ts +17 -0
  63. package/dist/proxmox/storage-download-task.d.ts.map +1 -0
  64. package/dist/proxmox/storage-download.d.ts +44 -0
  65. package/dist/proxmox/storage-download.d.ts.map +1 -0
  66. package/dist/telemetry/fake-collector.d.ts +36 -0
  67. package/dist/telemetry/fake-collector.d.ts.map +1 -0
  68. package/dist/telemetry/index.d.ts +11 -0
  69. package/dist/telemetry/index.d.ts.map +1 -0
  70. package/dist/telemetry/index.js +223 -0
  71. package/dist/telemetry/index.js.map +12 -0
  72. package/dist/telemetry/layer.d.ts +86 -0
  73. package/dist/telemetry/layer.d.ts.map +1 -0
  74. package/dist/telemetry/redact-serialization.d.ts +21 -0
  75. package/dist/telemetry/redact-serialization.d.ts.map +1 -0
  76. package/dist/telemetry/redact.d.ts +46 -0
  77. package/dist/telemetry/redact.d.ts.map +1 -0
  78. package/dist/unifi/drift.d.ts +27 -0
  79. package/dist/unifi/drift.d.ts.map +1 -0
  80. package/dist/unifi/fake-unifi.d.ts +5 -1
  81. package/dist/unifi/fake-unifi.d.ts.map +1 -1
  82. package/dist/unifi/firewall-zone-form.d.ts +6 -0
  83. package/dist/unifi/firewall-zone-form.d.ts.map +1 -1
  84. package/dist/unifi/firewall-zone.d.ts +1 -1
  85. package/dist/unifi/firewall-zone.d.ts.map +1 -1
  86. package/dist/unifi/index.d.ts +4 -3
  87. package/dist/unifi/index.d.ts.map +1 -1
  88. package/dist/unifi/index.js +138 -58
  89. package/dist/unifi/index.js.map +10 -8
  90. package/dist/unifi/network-drift.d.ts +26 -0
  91. package/dist/unifi/network-drift.d.ts.map +1 -0
  92. package/dist/unifi/network-form.d.ts +40 -7
  93. package/dist/unifi/network-form.d.ts.map +1 -1
  94. package/dist/unifi/network.d.ts +1 -0
  95. package/dist/unifi/network.d.ts.map +1 -1
  96. package/dist/unifi/paginate.d.ts +36 -0
  97. package/dist/unifi/paginate.d.ts.map +1 -0
  98. package/dist/unifi/resource.d.ts +36 -1
  99. package/dist/unifi/resource.d.ts.map +1 -1
  100. package/docs/telemetry-spike.md +94 -0
  101. package/docs/telemetry.md +109 -0
  102. package/docs/unifi.md +56 -0
  103. package/package.json +6 -2
  104. package/src/caddy/admin-transport-scope.test.ts +127 -0
  105. package/src/caddy/admin.ts +45 -9
  106. package/src/caddy/config-adopt-provider.test.ts +2 -2
  107. package/src/caddy/config-format-warning.test.ts +9 -3
  108. package/src/caddy/config.ts +20 -4
  109. package/src/caddy/fake-caddy.ts +10 -1
  110. package/src/caddy/providers.test.ts +3 -2
  111. package/src/caddy/providers.ts +18 -11
  112. package/src/forgejo/team-member.test.ts +14 -0
  113. package/src/forgejo/team-member.ts +28 -1
  114. package/src/proxmox/constraints-required.test.ts +1 -0
  115. package/src/proxmox/generated/constraints/index.ts +3 -1
  116. package/src/proxmox/generated/constraints/pve-access.ts +1 -1
  117. package/src/proxmox/generated/constraints/pve-cluster-backup.ts +1 -1
  118. package/src/proxmox/generated/constraints/pve-cluster-ceph.ts +1 -1
  119. package/src/proxmox/generated/constraints/pve-cluster-firewall.ts +1 -1
  120. package/src/proxmox/generated/constraints/pve-cluster-ha.ts +1 -1
  121. package/src/proxmox/generated/constraints/pve-cluster-metrics.ts +1 -1
  122. package/src/proxmox/generated/constraints/pve-cluster-notifications.ts +1 -1
  123. package/src/proxmox/generated/constraints/pve-cluster-replication.ts +1 -1
  124. package/src/proxmox/generated/constraints/pve-cluster-sdn.ts +1 -1
  125. package/src/proxmox/generated/constraints/pve-nodes-ceph.ts +1 -1
  126. package/src/proxmox/generated/constraints/pve-nodes-disks.ts +1 -1
  127. package/src/proxmox/generated/constraints/pve-nodes-lxc.ts +1 -1
  128. package/src/proxmox/generated/constraints/pve-nodes-network.ts +1 -1
  129. package/src/proxmox/generated/constraints/pve-nodes-qemu.ts +1 -1
  130. package/src/proxmox/generated/constraints/pve-nodes-storage.ts +30 -0
  131. package/src/proxmox/generated/constraints/pve-pools.ts +1 -1
  132. package/src/proxmox/generated/constraints/pve-storage.ts +1 -1
  133. package/src/proxmox/index.test.ts +11 -6
  134. package/src/proxmox/index.ts +19 -3
  135. package/src/proxmox/qemu-form.ts +49 -13
  136. package/src/proxmox/qemu-identity.test.ts +30 -0
  137. package/src/proxmox/qemu-judge.test.ts +65 -0
  138. package/src/proxmox/qemu-judge.ts +84 -0
  139. package/src/proxmox/qemu-lifecycle.test.ts +78 -19
  140. package/src/proxmox/qemu-lifecycle.ts +131 -32
  141. package/src/proxmox/qemu-net.test.ts +48 -0
  142. package/src/proxmox/qemu-net.ts +78 -0
  143. package/src/proxmox/qemu-ownership.test.ts +44 -0
  144. package/src/proxmox/qemu-props.test.ts +87 -0
  145. package/src/proxmox/qemu-props.ts +157 -0
  146. package/src/proxmox/qemu-volume.test.ts +80 -0
  147. package/src/proxmox/qemu-volume.ts +121 -0
  148. package/src/proxmox/qemu.ts +15 -29
  149. package/src/proxmox/storage-download-errors.ts +7 -0
  150. package/src/proxmox/storage-download-identity.test.ts +54 -0
  151. package/src/proxmox/storage-download-identity.ts +50 -0
  152. package/src/proxmox/storage-download-lifecycle.ts +226 -0
  153. package/src/proxmox/storage-download-ownership.test.ts +50 -0
  154. package/src/proxmox/storage-download-props.ts +38 -0
  155. package/src/proxmox/storage-download-task.ts +72 -0
  156. package/src/proxmox/storage-download.test.ts +216 -0
  157. package/src/proxmox/storage-download.ts +58 -0
  158. package/src/telemetry/fake-collector.ts +59 -0
  159. package/src/telemetry/http-client-sealing.test.ts +82 -0
  160. package/src/telemetry/index.ts +10 -0
  161. package/src/telemetry/layer.test.ts +204 -0
  162. package/src/telemetry/layer.ts +152 -0
  163. package/src/telemetry/redact-serialization.ts +178 -0
  164. package/src/telemetry/redact.test.ts +124 -0
  165. package/src/telemetry/redact.ts +147 -0
  166. package/src/unifi/drift.test.ts +78 -0
  167. package/src/unifi/drift.ts +56 -0
  168. package/src/unifi/errors-and-secrets.test.ts +127 -0
  169. package/src/unifi/fake-unifi.ts +5 -1
  170. package/src/unifi/firewall-zone-form.ts +31 -2
  171. package/src/unifi/firewall-zone.test.ts +21 -1
  172. package/src/unifi/firewall-zone.ts +1 -1
  173. package/src/unifi/get-only-guard.test.ts +165 -0
  174. package/src/unifi/index.ts +15 -1
  175. package/src/unifi/network-drift.test.ts +207 -0
  176. package/src/unifi/network-drift.ts +80 -0
  177. package/src/unifi/network-form.ts +11 -39
  178. package/src/unifi/network.test.ts +7 -82
  179. package/src/unifi/network.ts +6 -6
  180. package/src/unifi/paginate.test.ts +228 -0
  181. package/src/unifi/paginate.ts +129 -0
  182. package/src/unifi/resource.ts +73 -3
  183. package/src/unifi/write-op-reference.test.ts +236 -0
@@ -10,12 +10,25 @@
10
10
  * caddyconfig/load.go and admin.go, and in the API docs (caddyserver.com/docs/api).
11
11
  * ★ `CaddyAdminService` CARRIES ONLY MESSAGE/GUARD METADATA (endpoint, listener) — never a
12
12
  * credential, and never the transport itself. The transport is `@distilled.cloud/caddy`'s own
13
- * `Credentials` and `HttpClient.HttpClient` services, provided alongside it by `caddyAdminLayer`,
14
- * so admin-calls.ts calls the SDK's operations directly rather than through a house-specific
15
- * `.request()` seam. A consumer plugs in another route to the API (an SSH-forwarded socket today;
16
- * a remote runner later) by providing a different `CaddyTransport` to `caddyAdminLayer`.
13
+ * `Credentials` and `HttpClient.HttpClient` services — admin-calls.ts calls the SDK's operations
14
+ * directly rather than through a house-specific `.request()` seam. A consumer plugs in another
15
+ * route to the API (an SSH-forwarded socket today; a remote runner later) by providing a different
16
+ * `CaddyTransport` to `caddyAdminLayer`.
17
17
  * ⛔ THE ADMIN API HAS NO AUTHENTICATION. Anyone who reaches it can replace every site. It stays on
18
18
  * loopback or a unix socket; localCaddyAdmin() refuses anything else (local-admin.ts).
19
+ * ⛔ `caddyAdminLayer`'s OWN OUTPUT NEVER CARRIES `Credentials`/`HttpClient.HttpClient` DIRECTLY —
20
+ * MEASURED 2026-09-26: it used to (`Layer.mergeAll(CaddyAdminService-layer, transport.layer)`,
21
+ * what `caddyProviders()` then `provideMerge`d into a stack), and a stack that applies its own
22
+ * ambient client OUTSIDE the providers' merge — `Layer.mergeAll(providers…).pipe(Layer.provide
23
+ * (ambient))`, the shape `argocd`/`discord`/`opnsense`/`unifi`'s own `providers.ts` document —
24
+ * never got it back: `Layer.provide(A, B)`'s result outputs only `A`'s own services, so once `A`
25
+ * (the merged providers) already carries `HttpClient.HttpClient` from Caddy, no outer `B` overrides
26
+ * it. Every OTHER fetch-based provider merged alongside `caddyProviders()` (homeflare-mini's
27
+ * Forgejo, LiteLLM) then dialled Caddy's admin API instead of its own target — reproduced in
28
+ * admin-transport-scope.test.ts. `caddyAdminLayer` now stores `transport.layer` itself as a plain
29
+ * VALUE, under `CaddyAdminTransport` — a house-only tag nothing outside caddy/ ever reads — and
30
+ * config.ts is the one place that `Effect.provide`s it, scoped to exactly the effects that call
31
+ * `@distilled.cloud/caddy`'s operations.
19
32
  */
20
33
  import type * as HttpClient from 'effect/unstable/http/HttpClient';
21
34
  import * as Context from 'effect/Context';
@@ -43,7 +56,16 @@ export interface CaddyTarget {
43
56
 
44
57
  /** What `localCaddyAdmin()` (or a consumer's own transport) hands `caddyAdminLayer`. */
45
58
  export interface CaddyTransport extends CaddyTarget {
46
- /** Provides the SDK's own `Credentials` and `HttpClient.HttpClient` — see local-admin.ts. */
59
+ /**
60
+ * Provides the SDK's own `Credentials` and `HttpClient.HttpClient` — see local-admin.ts.
61
+ * ⚠️ BUILT AND TORN DOWN ON EVERY `read`/`diff`/`reconcile` CALL — config.ts's `Effect.provide
62
+ * (transportLayer)` runs fresh each time, scoped to that one call (the scoping fix this file's
63
+ * own ⛔ documents). `localCaddyAdmin()`'s layer is three plain `Layer.succeed` values, so this
64
+ * costs nothing; a transport that opens something stateful (an SSH forward, a connection pool)
65
+ * would open and close it per call instead of once per stack. No such transport exists in the
66
+ * tray today (2026-09-26) — keep `layer` cheap and stateless, or build once and hand config.ts a
67
+ * pre-built `Context` instead, if that changes.
68
+ */
47
69
  readonly layer: Layer.Layer<Credentials | HttpClient.HttpClient>;
48
70
  }
49
71
 
@@ -52,19 +74,33 @@ export class CaddyAdminService extends Context.Service<CaddyAdminService, CaddyT
52
74
  'homeflare/caddy/CaddyAdmin',
53
75
  ) {}
54
76
 
77
+ /**
78
+ * `transport.layer` ITSELF, carried as a plain value rather than merged into the ambient context —
79
+ * the scoping fix (see the file header's ⛔). Only config.ts reads this tag, and only to
80
+ * `Effect.provide` it locally around the handful of effects that actually call
81
+ * `@distilled.cloud/caddy`'s operations; nothing else in this directory, and nothing outside it,
82
+ * has a reason to.
83
+ */
84
+ export class CaddyAdminTransport extends Context.Service<
85
+ CaddyAdminTransport,
86
+ Layer.Layer<Credentials | HttpClient.HttpClient>
87
+ >()('homeflare/caddy/CaddyAdminTransport') {}
88
+
55
89
  /**
56
90
  * Provide a transport to the provider: `CaddyConfigProvider().pipe(Layer.provideMerge(caddyAdminLayer(localCaddyAdmin())))`
57
91
  * — never plain `Layer.provide` here (see providers.ts's own ⚠️): `CaddyConfigProvider()`'s handlers
58
- * keep needing these services every time the engine calls them, not just once while it is built. Merges
59
- * the target metadata with the SDK's `Credentials`/`HttpClient` layers `transport.layer` carries.
92
+ * keep needing `CaddyAdminService`/`CaddyAdminTransport` every time the engine calls them, not just
93
+ * once while it is built. Neither tag is a generic platform service, so merging them into a stack's
94
+ * ambient context (unlike the raw `Credentials`/`HttpClient.HttpClient` this used to carry — the
95
+ * file header's ⛔) is safe: nothing outside this directory looks either up.
60
96
  */
61
97
  export const caddyAdminLayer = (
62
98
  transport: CaddyTransport,
63
- ): Layer.Layer<CaddyAdminService | Credentials | HttpClient.HttpClient> =>
99
+ ): Layer.Layer<CaddyAdminService | CaddyAdminTransport> =>
64
100
  Layer.mergeAll(
65
101
  Layer.succeed(CaddyAdminService, {
66
102
  endpoint: transport.endpoint,
67
103
  listener: transport.listener,
68
104
  }),
69
- transport.layer,
105
+ Layer.succeed(CaddyAdminTransport, transport.layer),
70
106
  );
@@ -9,7 +9,6 @@ import { AdoptPolicy, Unowned } from 'alchemy/AdoptPolicy';
9
9
  import { AlchemyContext } from 'alchemy/AlchemyContext';
10
10
  import * as Effect from 'effect/Effect';
11
11
  import * as Layer from 'effect/Layer';
12
- import type * as Caddy from '@distilled.cloud/caddy';
13
12
  import { type CaddyAdminService, type CaddyTransport, caddyAdminLayer } from './admin.ts';
14
13
  import { CaddyConfig, CaddyConfigProvider } from './config.ts';
15
14
  import { configDigest } from './digest.ts';
@@ -40,9 +39,10 @@ type Deploy = { readonly adopt?: boolean; readonly contextAdopt?: boolean };
40
39
  const withProvider = <A>(
41
40
  admin: CaddyTransport,
42
41
  deploy: Deploy,
42
+ // ★ `CaddyAdminService` ONLY — see providers.test.ts's own `withProvider` for why.
43
43
  use: (
44
44
  p: Effect.Success<typeof CaddyConfig.Provider>,
45
- ) => Effect.Effect<A, unknown, CaddyAdminService | Caddy.CaddyOpContext>,
45
+ ) => Effect.Effect<A, unknown, CaddyAdminService>,
46
46
  ) => {
47
47
  // ⚠️ `Layer.provideMerge`, not `Layer.provide` — providers.ts's own doc on `caddyAdminLayer` says
48
48
  // why: `CaddyConfigProvider()`'s handlers still need these services every time they run, not
@@ -13,7 +13,7 @@ import { AdoptPolicy } from 'alchemy/AdoptPolicy';
13
13
  import * as Effect from 'effect/Effect';
14
14
  import * as Layer from 'effect/Layer';
15
15
  import * as Logger from 'effect/Logger';
16
- import { caddyAdminLayer } from './admin.ts';
16
+ import { type CaddyTransport, caddyAdminLayer } from './admin.ts';
17
17
  import { CaddyConfig, CaddyConfigProvider } from './config.ts';
18
18
  import { diffConfig } from './config-reconcile.ts';
19
19
  import { type FakeCaddy, fakeDefaultCaddy } from './fake-caddy.ts';
@@ -34,6 +34,12 @@ const setup = () => {
34
34
  return made;
35
35
  };
36
36
 
37
+ /** `diffConfig` still needs the raw `Credentials`/`HttpClient` directly — `caddyAdminLayer` alone
38
+ * (admin.ts's own scoping fix) only carries `CaddyAdminService`/`CaddyAdminTransport`; merge
39
+ * `admin.layer` in too, the same fix `fake-caddy.ts`'s `runCaddy` makes for this exact shape. */
40
+ const withTransport = (admin: CaddyTransport) =>
41
+ Layer.mergeAll(caddyAdminLayer(admin), admin.layer);
42
+
37
43
  /** Runs `effect` and returns its result plus every `Effect.logWarning` line it emitted. */
38
44
  const withWarnings = async <A>(effect: Effect.Effect<A, unknown, never>) => {
39
45
  const warnings: string[] = [];
@@ -49,7 +55,7 @@ describe('the formatting warning is surfaced distinctly', () => {
49
55
  const { admin } = setup();
50
56
  const output = { configSha256: 'stub', endpoint: admin.endpoint };
51
57
  const { value, warnings } = await withWarnings(
52
- diffConfig({ caddyfile: UNFORMATTED }, output).pipe(Effect.provide(caddyAdminLayer(admin))),
58
+ diffConfig({ caddyfile: UNFORMATTED }, output).pipe(Effect.provide(withTransport(admin))),
53
59
  );
54
60
  expect(value).toEqual({ action: 'update' });
55
61
  expect(warnings).toHaveLength(1);
@@ -61,7 +67,7 @@ describe('the formatting warning is surfaced distinctly', () => {
61
67
  const { admin } = setup();
62
68
  const output = { configSha256: 'stub', endpoint: admin.endpoint };
63
69
  const { warnings } = await withWarnings(
64
- diffConfig({ caddyfile: SITE }, output).pipe(Effect.provide(caddyAdminLayer(admin))),
70
+ diffConfig({ caddyfile: SITE }, output).pipe(Effect.provide(withTransport(admin))),
65
71
  );
66
72
  expect(warnings).toEqual([]);
67
73
  });
@@ -21,7 +21,13 @@
21
21
  * engine skips that probe, reconcile refuses the same takeover (config-lifecycle.ts).
22
22
  * ⛔ `caddyfile` IS NEVER A SECRET — see config-form.ts; refused before anything is sent.
23
23
  * ⚠️ WHICH CADDY is the transport's (providers.ts): `CaddyAdminService` plus the `Credentials`/
24
- * `HttpClient` layers `caddyAdminLayer` merges — one per stack, not a prop.
24
+ * `HttpClient` layer `CaddyAdminTransport` carries — one per stack, not a prop.
25
+ * ⚠️ `transportLayer` IS `Effect.provide`D LOCALLY IN EACH HANDLER BELOW, NEVER MERGED AHEAD OF
26
+ * TIME. `caddyAdminLayer` (admin.ts) stores it under `CaddyAdminTransport` rather than merging its
27
+ * `Credentials`/`HttpClient.HttpClient` into the ambient context precisely so this file is the ONE
28
+ * place that discharges them — scoped to the handful of effects that actually call
29
+ * `@distilled.cloud/caddy`'s operations, never leaking into a sibling fetch-based provider merged
30
+ * into the same stack (admin.ts's own ⛔; MEASURED 2026-09-26, admin-transport-scope.test.ts).
25
31
  */
26
32
  import { Resource } from 'alchemy';
27
33
  import { Unowned } from 'alchemy/AdoptPolicy';
@@ -30,7 +36,7 @@ import * as Provider from 'alchemy/Provider';
30
36
  import * as Effect from 'effect/Effect';
31
37
  import { resolvedString } from '../launchd/host-effect.ts';
32
38
  import { adoptEnabled } from '../ownership/adopt.ts';
33
- import { CaddyAdminService } from './admin.ts';
39
+ import { CaddyAdminService, CaddyAdminTransport } from './admin.ts';
34
40
  import { isUnreachable } from './caddy-http-client.ts';
35
41
  import type { CaddyConfigAttributes, CaddyConfigProps } from './config-form.ts';
36
42
  import { probeLive, readLive } from './config-lifecycle.ts';
@@ -72,6 +78,7 @@ export const CaddyConfigProvider = () =>
72
78
  CaddyConfig,
73
79
  Effect.gen(function* () {
74
80
  const admin = yield* CaddyAdminService;
81
+ const transportLayer = yield* CaddyAdminTransport;
75
82
  /** The probe, branded for the engine: plain when ours, `Unowned` otherwise. */
76
83
  const probe = (caddyfile: string | undefined, sourceFile: string | undefined) =>
77
84
  Effect.flatMap(probeLive(caddyfile, sourceFile), (found) => {
@@ -104,6 +111,9 @@ export const CaddyConfigProvider = () =>
104
111
  ? yield* probe(resolvedString(olds, 'caddyfile'), resolvedString(olds, 'sourceFile'))
105
112
  : yield* readLive(resolvedString(olds, 'sourceFile'));
106
113
  }).pipe(
114
+ // ★ Scoped to Caddy's own transport — see the file header's ⚠️ — before the catch below,
115
+ // so `isUnreachable` still narrows the SAME raw error this always raised.
116
+ Effect.provide(transportLayer),
107
117
  // ★ Nothing to adopt from a Caddy that is down: plan a create, which is the same load.
108
118
  Effect.catchIf(isUnreachable, planWithoutCaddy(admin, undefined)),
109
119
  ),
@@ -112,7 +122,10 @@ export const CaddyConfigProvider = () =>
112
122
  if (output === undefined) return Effect.succeed(undefined);
113
123
  const update = planWithoutCaddy(admin, { action: 'update' as const });
114
124
  if (isResolved(news)) {
115
- return diffConfig(news, output).pipe(Effect.catchIf(isUnreachable, update));
125
+ return diffConfig(news, output).pipe(
126
+ Effect.provide(transportLayer),
127
+ Effect.catchIf(isUnreachable, update),
128
+ );
116
129
  }
117
130
  /**
118
131
  * ⚠️ `sourceFile` is an Output while its HostFile changes in the same deploy
@@ -123,6 +136,7 @@ export const CaddyConfigProvider = () =>
123
136
  const caddyfile = resolvedString(news, 'caddyfile');
124
137
  if (caddyfile === undefined) return Effect.succeed(undefined);
125
138
  return Effect.as(diffConfig({ caddyfile }, output), { action: 'update' as const }).pipe(
139
+ Effect.provide(transportLayer),
126
140
  Effect.catchIf(isUnreachable, update),
127
141
  );
128
142
  },
@@ -136,7 +150,9 @@ export const CaddyConfigProvider = () =>
136
150
  const sameCaddy = output !== undefined && output.endpoint === admin.endpoint;
137
151
  const takeOver = sameCaddy || (yield* adoptEnabled(fqn));
138
152
  const stored = output === undefined ? {} : { stored: output.configSha256 };
139
- const applied = yield* reconcileConfig(news, { takeOver, ...stored });
153
+ const applied = yield* reconcileConfig(news, { takeOver, ...stored }).pipe(
154
+ Effect.provide(transportLayer),
155
+ );
140
156
  // ★ THE FORMATTING WARNING GETS ITS OWN LINE, NAMING THE FIX — separate from the rest,
141
157
  // which keep logging exactly as before (format-warnings.ts).
142
158
  const { formatting, rest } = splitFormattingWarning(applied.warnings);
@@ -18,6 +18,7 @@
18
18
  * fake really listens on an ephemeral one: a Caddy on its default :2019 behind a forward.
19
19
  */
20
20
  import * as Effect from 'effect/Effect';
21
+ import * as Layer from 'effect/Layer';
21
22
  import type * as Caddy from '@distilled.cloud/caddy';
22
23
  import { type CaddyAdminService, type CaddyTransport, caddyAdminLayer } from './admin.ts';
23
24
  import { localCaddyAdmin } from './local-admin.ts';
@@ -190,8 +191,16 @@ export const fakeDefaultCaddy = (
190
191
  * Runs an Effect that needs `CaddyAdminService` and `@distilled.cloud/caddy`'s own `Credentials`/
191
192
  * `HttpClient` against one transport — the one line every test in this directory used to spend on
192
193
  * `Effect.provide(caddyAdminLayer(admin))` before this helper existed.
194
+ * ⚠️ TWO LAYERS, NOT ONE: `caddyAdminLayer(admin)` only carries `CaddyAdminService` and the
195
+ * `CaddyAdminTransport`-wrapped VALUE of `admin.layer` (admin.ts's own scoping fix) — the
196
+ * lifecycle functions this helper drives (`probeLive`, `reconcileConfig`, …) still need the raw
197
+ * `Credentials`/`HttpClient.HttpClient` directly in context, so `admin.layer` is provided again,
198
+ * built this time, alongside it.
193
199
  */
194
200
  export const runCaddy = <A, E>(
195
201
  effect: Effect.Effect<A, E, CaddyAdminService | Caddy.CaddyOpContext>,
196
202
  admin: CaddyTransport,
197
- ): Promise<A> => Effect.runPromise(effect.pipe(Effect.provide(caddyAdminLayer(admin))));
203
+ ): Promise<A> =>
204
+ Effect.runPromise(
205
+ effect.pipe(Effect.provide(Layer.mergeAll(caddyAdminLayer(admin), admin.layer))),
206
+ );
@@ -7,7 +7,6 @@ import * as Output from 'alchemy/Output';
7
7
  import { Stack } from 'alchemy/Stack';
8
8
  import * as Effect from 'effect/Effect';
9
9
  import * as Layer from 'effect/Layer';
10
- import type * as Caddy from '@distilled.cloud/caddy';
11
10
  import { type CaddyAdminService, caddyAdminLayer } from './admin.ts';
12
11
  import { CaddyConfig, CaddyConfigProvider } from './config.ts';
13
12
  import { type FakeCaddy, fakeCaddy } from './fake-caddy.ts';
@@ -31,9 +30,11 @@ const handler = <F>(name: string, fn: F | undefined): F => {
31
30
  };
32
31
 
33
32
  const withProvider = <A>(
33
+ // ★ `CaddyAdminService` ONLY — `read`/`diff`/`reconcile` no longer need the admin transport from
34
+ // OUTSIDE post-fix: config.ts's handlers `Effect.provide` it locally now (admin.ts's own ⛔).
34
35
  use: (
35
36
  p: Effect.Success<typeof CaddyConfig.Provider>,
36
- ) => Effect.Effect<A, unknown, CaddyAdminService | Caddy.CaddyOpContext>,
37
+ ) => Effect.Effect<A, unknown, CaddyAdminService>,
37
38
  address?: string,
38
39
  ) => {
39
40
  // ★ A Caddy on its default :2019 behind a forward, as in config-lifecycle.test.ts.
@@ -12,17 +12,24 @@
12
12
  * and `HttpClient.HttpClient`.
13
13
  *
14
14
  * ⚠️ `Layer.provideMerge`, NEVER PLAIN `Layer.provide`, for `caddyAdminLayer`. `CaddyConfigProvider()`'s
15
- * `read`/`diff`/`reconcile` handlers each still need `CaddyAdminService` and
16
- * `@distilled.cloud/caddy`'s `Credentials`/`HttpClient` — Alchemy's own `Provider.effect` types
17
- * those as part of the LAYER's overall requirement (`ProviderService`'s per-handler `Req` type
18
- * parameters, `alchemy/Provider.ts`), for exactly this reason. Plain `Layer.provide` SEALS a
19
- * layer — it satisfies `CaddyConfigProvider()`'s requirement only for BUILDING the `Provider`
20
- * value (the `admin` in its outer `Effect.gen`), then hides those services from anything that
21
- * runs LATER, when the returned `read`/`diff`/`reconcile` FUNCTIONS actually execute. MEASURED
22
- * 2026-09-23: with plain `Layer.provide`, `caddyProviders()`'s own `reconcile` handler dies with
23
- * "Service not found: homeflare/caddy/CaddyAdmin" the moment the ENGINE calls it. `provideMerge`
24
- * feeds `caddyAdminLayer`'s output into `CaddyConfigProvider()`'s requirement AND keeps that same
25
- * output live in the result, so every later handler call still runs with it in context.
15
+ * `read`/`diff`/`reconcile` handlers each still need `CaddyAdminService` and `CaddyAdminTransport`
16
+ * — Alchemy's own `Provider.effect` types those as part of the LAYER's overall requirement
17
+ * (`ProviderService`'s per-handler `Req` type parameters, `alchemy/Provider.ts`), for exactly this
18
+ * reason. Plain `Layer.provide` SEALS a layer — it satisfies `CaddyConfigProvider()`'s requirement
19
+ * only for BUILDING the `Provider` value (the `admin`/`transportLayer` in its outer `Effect.gen`),
20
+ * then hides those services from anything that runs LATER, when the returned `read`/`diff`/
21
+ * `reconcile` FUNCTIONS actually execute. MEASURED 2026-09-23: with plain `Layer.provide`,
22
+ * `caddyProviders()`'s own `reconcile` handler dies with "Service not found: homeflare/caddy/
23
+ * CaddyAdmin" the moment the ENGINE calls it. `provideMerge` feeds `caddyAdminLayer`'s output into
24
+ * `CaddyConfigProvider()`'s requirement AND keeps that same output live in the result, so every
25
+ * later handler call still runs with it in context.
26
+ * ⛔ THIS `caddyProviders()` LAYER NEVER CARRIES `Credentials`/`HttpClient.HttpClient` ITSELF (fixed
27
+ * 2026-09-26 — admin.ts's own ⛔ has the mechanism and the measured leak). `caddyAdminLayer`'s
28
+ * output is `CaddyAdminService | CaddyAdminTransport`, both house-only tags safe to `provideMerge`
29
+ * into any host stack; the SDK's own `Credentials`/`HttpClient.HttpClient` stay inside
30
+ * `CaddyAdminTransport`'s stored value, `Effect.provide`d locally by config.ts around exactly the
31
+ * effects that need them. A stack merging `caddyProviders()` with any other fetch-based provider
32
+ * (`forgejoProviders()`, `litellmProviders()`, …) is safe from this file's own composition alone.
26
33
  */
27
34
  import * as Layer from 'effect/Layer';
28
35
  import { type CaddyTransport, caddyAdminLayer } from './admin.ts';
@@ -4,6 +4,7 @@
4
4
  * alongside the SDK's own `NotFound`. Proves the two stay distinct: only the SDK's tag folds.
5
5
  */
6
6
  import { describe, expect, test } from 'bun:test';
7
+ import { readFileSync } from 'node:fs';
7
8
  import * as organization from '@distilled.cloud/forgejo/organization';
8
9
  import * as Effect from 'effect/Effect';
9
10
  import { fakeFailure, fakeForgejo, fakeForgejoLayer } from './fake-forgejo.ts';
@@ -95,3 +96,16 @@ describe('Forgejo.TeamMember spec — the actual exported production functions',
95
96
  expect(await readThrough(fake.fetch)).toBeUndefined();
96
97
  });
97
98
  });
99
+
100
+ test('ForgejoTeamMember declares defaultRemovalPolicy: retain on the Resource() call itself', () => {
101
+ // ⛔ Anchored on the `Resource<ForgejoTeamMember>(...)` CALL, not on the substring appearing
102
+ // anywhere in the file — team-member.ts's own header comment also says the phrase in prose, so
103
+ // a plain `source.toContain(...)` (database-refusals.test.ts's simpler version, safe there
104
+ // because database.ts's header never repeats the phrase) would pass here even if the option
105
+ // were dropped from the actual call. mini PR 82's red team (970e8be) had to pipe
106
+ // `RemovalPolicy.retain()` onto every call site because the kit resource had no default; this
107
+ // proves the kit default carries the fix so a caller no longer has to remember its own.
108
+ const source = readFileSync(new URL('./team-member.ts', import.meta.url), 'utf8');
109
+ const call = source.match(/Resource<ForgejoTeamMember>\([\s\S]*?\);/);
110
+ expect(call?.[0]).toContain("defaultRemovalPolicy: 'retain'");
111
+ });
@@ -22,6 +22,31 @@
22
22
  * cannot create a team.
23
23
  *
24
24
  * ⛔ TOKEN NEEDS `write:organization` TO ADD OR REMOVE, and an identity allowed to manage the team.
25
+ *
26
+ * ⛔ `defaultRemovalPolicy: 'retain'` — FOUND 2026-09-25 by reading the source, not by measuring
27
+ * anything (mini PR 82's red team, `homeflare-mini` 970e8be): before this, `ForgejoRepository`/
28
+ * `ForgejoOrgLabel` defaulted to retain in the kit itself but this family did not, so alchemy's
29
+ * own engine fallback (`destroy`) applied to it — every caller had to remember its own
30
+ * `.pipe(RemovalPolicy.retain())`, the way mini's `src/forgejo/declare.ts` did for
31
+ * `forgejo-provision`'s row. Dropping a `TeamMember` declaration — toggling a feature gate off,
32
+ * renaming the resource id — must not silently call `organization.orgRemoveTeamMember` and
33
+ * revoke a real membership; that is the same "removal from the stack is not removal from
34
+ * Forgejo" posture `ForgejoOrgLabel` and `ForgejoBranchProtection` already take, this family
35
+ * exists to attach a stack's identity to a team it never creates or destroys (`teamIdOf` below
36
+ * only ever locates the team, never manages it), and the same trade-off alchemy's own
37
+ * `GitHub.Collaborator` and `GitHub.TeamAccess` make for the identical reason — "preventing
38
+ * accidental lockout" (`GitHub/Collaborator.ts:127`, `GitHub/TeamAccess.ts:145` in the pinned
39
+ * alchemy source). The trade-off, stated plainly: removing a declaration no longer revokes
40
+ * access on its own — the plan shows that row as `orphaned`, not `delete`. Opt into a real
41
+ * revoke with `.pipe(RemovalPolicy.destroy())`; `destroy` below is fully implemented.
42
+ *
43
+ * ⚠️ THE DEFAULT ONLY PROTECTS A ROW ALREADY IN STATE ONCE THIS RELEASE IS DEPLOYED. Alchemy's
44
+ * plan reads `removalPolicy` off the *persisted row* (`Plan.ts`'s delete-node builder), not off
45
+ * this default; the default only reaches an existing row's state on a deploy where the resource
46
+ * plans as a no-op (`Apply.ts`'s noop path rewrites it and logs "removal policy destroy →
47
+ * retain"). A consumer that bumps to this release and drops a `TeamMember` declaration in the
48
+ * SAME deploy still gets `delete`, not `orphaned` — see the changeset for the two-deploy shape
49
+ * this implies for both adopting retain and opting back into destroy.
25
50
  */
26
51
  import { Resource } from 'alchemy';
27
52
  import * as Provider from 'alchemy/Provider';
@@ -52,7 +77,9 @@ export interface ForgejoTeamMember extends Resource<
52
77
  ForgejoRequirements
53
78
  > {}
54
79
 
55
- export const ForgejoTeamMember = Resource<ForgejoTeamMember>('Forgejo.TeamMember');
80
+ export const ForgejoTeamMember = Resource<ForgejoTeamMember>('Forgejo.TeamMember', {
81
+ defaultRemovalPolicy: 'retain',
82
+ });
56
83
 
57
84
  export class ForgejoTeamNotFoundError extends Data.TaggedError('ForgejoTeamNotFoundError')<{
58
85
  readonly message: string;
@@ -80,6 +80,7 @@ describe('every create this package makes, and what the vendor requires of it',
80
80
  'pve:POST /nodes/{node}/lxc': ['ostemplate', 'vmid'],
81
81
  'pve:POST /nodes/{node}/network': ['iface', 'type'],
82
82
  'pve:POST /nodes/{node}/qemu': ['vmid'],
83
+ 'pve:POST /nodes/{node}/storage/{storage}/download-url': ['content', 'filename', 'url'],
83
84
  'pve:POST /pools': ['poolid'],
84
85
  'pve:POST /storage': ['storage', 'type'],
85
86
  });
@@ -22,6 +22,7 @@ import { PVE_NODES_DISKS_CONSTRAINTS } from './pve-nodes-disks.ts';
22
22
  import { PVE_NODES_LXC_CONSTRAINTS } from './pve-nodes-lxc.ts';
23
23
  import { PVE_NODES_NETWORK_CONSTRAINTS } from './pve-nodes-network.ts';
24
24
  import { PVE_NODES_QEMU_CONSTRAINTS } from './pve-nodes-qemu.ts';
25
+ import { PVE_NODES_STORAGE_CONSTRAINTS } from './pve-nodes-storage.ts';
25
26
  import { PVE_POOLS_CONSTRAINTS } from './pve-pools.ts';
26
27
  import { PVE_STORAGE_CONSTRAINTS } from './pve-storage.ts';
27
28
  import { PBS_CONFIG_CONSTRAINTS } from './pbs-config.ts';
@@ -41,6 +42,7 @@ export const PROXMOX_CONSTRAINTS: Readonly<Record<string, EndpointConstraints>>
41
42
  ...PVE_NODES_LXC_CONSTRAINTS,
42
43
  ...PVE_NODES_NETWORK_CONSTRAINTS,
43
44
  ...PVE_NODES_QEMU_CONSTRAINTS,
45
+ ...PVE_NODES_STORAGE_CONSTRAINTS,
44
46
  ...PVE_POOLS_CONSTRAINTS,
45
47
  ...PVE_STORAGE_CONSTRAINTS,
46
48
  ...PBS_CONFIG_CONSTRAINTS,
@@ -52,4 +54,4 @@ export const PROXMOX_CONSTRAINTS: Readonly<Record<string, EndpointConstraints>>
52
54
  * ★ A DIGEST OF THE DATA, NOT OF THE FILE TEXT, so reformatting is not a stale generation while
53
55
  * changing a 128 to a 129 by hand is. `tests/schema-manifest.test.ts` recomputes it.
54
56
  */
55
- export const PROXMOX_CONSTRAINTS_DIGEST = '56eba80750ddb1d2';
57
+ export const PROXMOX_CONSTRAINTS_DIGEST = '8b6745a471839974';
@@ -6,7 +6,7 @@
6
6
  * sha256 9def8f13611184ee, read on a PVE cluster node from
7
7
  * /usr/share/pve-docs/api-viewer/apidoc.js
8
8
  *
9
- * 58 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
9
+ * 59 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
10
10
  * this package writes to, named in its own source. Every other vendor write endpoint is UNTABLED
11
11
  * and therefore unchecked at plan time.
12
12
  *
@@ -6,7 +6,7 @@
6
6
  * sha256 9def8f13611184ee, read on a PVE cluster node from
7
7
  * /usr/share/pve-docs/api-viewer/apidoc.js
8
8
  *
9
- * 58 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
9
+ * 59 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
10
10
  * this package writes to, named in its own source. Every other vendor write endpoint is UNTABLED
11
11
  * and therefore unchecked at plan time.
12
12
  *
@@ -6,7 +6,7 @@
6
6
  * sha256 9def8f13611184ee, read on a PVE cluster node from
7
7
  * /usr/share/pve-docs/api-viewer/apidoc.js
8
8
  *
9
- * 58 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
9
+ * 59 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
10
10
  * this package writes to, named in its own source. Every other vendor write endpoint is UNTABLED
11
11
  * and therefore unchecked at plan time.
12
12
  *
@@ -6,7 +6,7 @@
6
6
  * sha256 9def8f13611184ee, read on a PVE cluster node from
7
7
  * /usr/share/pve-docs/api-viewer/apidoc.js
8
8
  *
9
- * 58 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
9
+ * 59 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
10
10
  * this package writes to, named in its own source. Every other vendor write endpoint is UNTABLED
11
11
  * and therefore unchecked at plan time.
12
12
  *
@@ -6,7 +6,7 @@
6
6
  * sha256 9def8f13611184ee, read on a PVE cluster node from
7
7
  * /usr/share/pve-docs/api-viewer/apidoc.js
8
8
  *
9
- * 58 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
9
+ * 59 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
10
10
  * this package writes to, named in its own source. Every other vendor write endpoint is UNTABLED
11
11
  * and therefore unchecked at plan time.
12
12
  *
@@ -6,7 +6,7 @@
6
6
  * sha256 9def8f13611184ee, read on a PVE cluster node from
7
7
  * /usr/share/pve-docs/api-viewer/apidoc.js
8
8
  *
9
- * 58 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
9
+ * 59 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
10
10
  * this package writes to, named in its own source. Every other vendor write endpoint is UNTABLED
11
11
  * and therefore unchecked at plan time.
12
12
  *
@@ -6,7 +6,7 @@
6
6
  * sha256 9def8f13611184ee, read on a PVE cluster node from
7
7
  * /usr/share/pve-docs/api-viewer/apidoc.js
8
8
  *
9
- * 58 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
9
+ * 59 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
10
10
  * this package writes to, named in its own source. Every other vendor write endpoint is UNTABLED
11
11
  * and therefore unchecked at plan time.
12
12
  *
@@ -6,7 +6,7 @@
6
6
  * sha256 9def8f13611184ee, read on a PVE cluster node from
7
7
  * /usr/share/pve-docs/api-viewer/apidoc.js
8
8
  *
9
- * 58 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
9
+ * 59 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
10
10
  * this package writes to, named in its own source. Every other vendor write endpoint is UNTABLED
11
11
  * and therefore unchecked at plan time.
12
12
  *
@@ -6,7 +6,7 @@
6
6
  * sha256 9def8f13611184ee, read on a PVE cluster node from
7
7
  * /usr/share/pve-docs/api-viewer/apidoc.js
8
8
  *
9
- * 58 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
9
+ * 59 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
10
10
  * this package writes to, named in its own source. Every other vendor write endpoint is UNTABLED
11
11
  * and therefore unchecked at plan time.
12
12
  *
@@ -6,7 +6,7 @@
6
6
  * sha256 9def8f13611184ee, read on a PVE cluster node from
7
7
  * /usr/share/pve-docs/api-viewer/apidoc.js
8
8
  *
9
- * 58 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
9
+ * 59 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
10
10
  * this package writes to, named in its own source. Every other vendor write endpoint is UNTABLED
11
11
  * and therefore unchecked at plan time.
12
12
  *
@@ -6,7 +6,7 @@
6
6
  * sha256 9def8f13611184ee, read on a PVE cluster node from
7
7
  * /usr/share/pve-docs/api-viewer/apidoc.js
8
8
  *
9
- * 58 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
9
+ * 59 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
10
10
  * this package writes to, named in its own source. Every other vendor write endpoint is UNTABLED
11
11
  * and therefore unchecked at plan time.
12
12
  *
@@ -6,7 +6,7 @@
6
6
  * sha256 9def8f13611184ee, read on a PVE cluster node from
7
7
  * /usr/share/pve-docs/api-viewer/apidoc.js
8
8
  *
9
- * 58 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
9
+ * 59 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
10
10
  * this package writes to, named in its own source. Every other vendor write endpoint is UNTABLED
11
11
  * and therefore unchecked at plan time.
12
12
  *
@@ -6,7 +6,7 @@
6
6
  * sha256 9def8f13611184ee, read on a PVE cluster node from
7
7
  * /usr/share/pve-docs/api-viewer/apidoc.js
8
8
  *
9
- * 58 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
9
+ * 59 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
10
10
  * this package writes to, named in its own source. Every other vendor write endpoint is UNTABLED
11
11
  * and therefore unchecked at plan time.
12
12
  *
@@ -6,7 +6,7 @@
6
6
  * sha256 9def8f13611184ee, read on a PVE cluster node from
7
7
  * /usr/share/pve-docs/api-viewer/apidoc.js
8
8
  *
9
- * 58 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
9
+ * 59 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
10
10
  * this package writes to, named in its own source. Every other vendor write endpoint is UNTABLED
11
11
  * and therefore unchecked at plan time.
12
12
  *
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Generated pve-manager parameter constraints for `/nodes/{node}/storage` — DO NOT EDIT BY HAND.
3
+ *
4
+ * Run: bun codegen/constraints.ts
5
+ * Manifest entry: `pve-apidoc` — pve-manager 9.2.11/f6997e698c7933ea
6
+ * sha256 9def8f13611184ee, read on a PVE cluster node from
7
+ * /usr/share/pve-docs/api-viewer/apidoc.js
8
+ *
9
+ * 59 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
10
+ * this package writes to, named in its own source. Every other vendor write endpoint is UNTABLED
11
+ * and therefore unchecked at plan time.
12
+ *
13
+ * ⚠️ A `patternSource` with no `pattern` beside it is a rule that could NOT be carried into a
14
+ * JavaScript RegExp faithfully (codegen/pattern.ts). It is recorded and NOT enforced.
15
+ * ⚠️ `pattern` IS NOT THE VENDOR'S SPELLING. For PVE it is anchored, because PVE applies
16
+ * `m/^$pattern$/` itself (JSONSchema.pm); for PBS it is the vendor's own, which already
17
+ * carries its anchors. `patternSource` is the spelling to quote at a human — param-rules.ts.
18
+ * ⚠️ `each: true` means the value rules describe every ELEMENT of a repeated key, because the
19
+ * parameter is an array and stated its limits on `items`.
20
+ */
21
+ import type { EndpointConstraints } from '../../constraints.ts';
22
+
23
+ export const PVE_NODES_STORAGE_CONSTRAINTS: Readonly<Record<string, EndpointConstraints>> = {
24
+ "pve:POST /nodes/{node}/storage/{storage}/download-url": {
25
+ "checksum-algorithm": {"enum":["md5","sha1","sha224","sha256","sha384","sha512"],"type":"string"},
26
+ "content": {"enum":["iso","vztmpl","import"],"format":"pve-storage-content","required":true,"type":"string"},
27
+ "filename": {"maxLength":255,"required":true,"type":"string"},
28
+ "url": {"pattern":"^https?://.*\\n?$","patternSource":"https?://.*","required":true,"type":"string"},
29
+ },
30
+ };
@@ -6,7 +6,7 @@
6
6
  * sha256 9def8f13611184ee, read on a PVE cluster node from
7
7
  * /usr/share/pve-docs/api-viewer/apidoc.js
8
8
  *
9
- * 58 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
9
+ * 59 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
10
10
  * this package writes to, named in its own source. Every other vendor write endpoint is UNTABLED
11
11
  * and therefore unchecked at plan time.
12
12
  *
@@ -6,7 +6,7 @@
6
6
  * sha256 9def8f13611184ee, read on a PVE cluster node from
7
7
  * /usr/share/pve-docs/api-viewer/apidoc.js
8
8
  *
9
- * 58 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
9
+ * 59 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
10
10
  * this package writes to, named in its own source. Every other vendor write endpoint is UNTABLED
11
11
  * and therefore unchecked at plan time.
12
12
  *