@homeflare/alchemy 0.19.1 → 0.21.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 (198) hide show
  1. package/README.md +25 -0
  2. package/dist/caddy/access-forward-auth.d.ts +56 -0
  3. package/dist/caddy/access-forward-auth.d.ts.map +1 -0
  4. package/dist/caddy/index.d.ts +6 -0
  5. package/dist/caddy/index.d.ts.map +1 -1
  6. package/dist/caddy/index.js +169 -75
  7. package/dist/caddy/index.js.map +10 -7
  8. package/dist/cloudflare/mcp-server-api.d.ts +135 -0
  9. package/dist/cloudflare/mcp-server-api.d.ts.map +1 -0
  10. package/dist/cloudflare/mcp-server-form.d.ts +116 -0
  11. package/dist/cloudflare/mcp-server-form.d.ts.map +1 -0
  12. package/dist/index.d.ts +1 -0
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/launchd/file-converge.d.ts +70 -0
  15. package/dist/launchd/file-converge.d.ts.map +1 -0
  16. package/dist/launchd/file-identity.d.ts +32 -0
  17. package/dist/launchd/file-identity.d.ts.map +1 -0
  18. package/dist/launchd/host-file-form.d.ts +2 -0
  19. package/dist/launchd/host-file-form.d.ts.map +1 -1
  20. package/dist/launchd/host-file-lifecycle.d.ts +11 -24
  21. package/dist/launchd/host-file-lifecycle.d.ts.map +1 -1
  22. package/dist/launchd/host-file.d.ts.map +1 -1
  23. package/dist/launchd/index.js +173 -120
  24. package/dist/launchd/index.js.map +12 -10
  25. package/dist/launchd/local-runner.d.ts.map +1 -1
  26. package/dist/launchd/runner.d.ts +17 -2
  27. package/dist/launchd/runner.d.ts.map +1 -1
  28. package/dist/launchd/sudo-runner.d.ts +11 -0
  29. package/dist/launchd/sudo-runner.d.ts.map +1 -1
  30. package/dist/linux/index.js +37 -11
  31. package/dist/linux/index.js.map +10 -10
  32. package/dist/linux/region.d.ts +11 -0
  33. package/dist/linux/region.d.ts.map +1 -1
  34. package/dist/linux/remote-file-lifecycle.d.ts.map +1 -1
  35. package/dist/linux/remote-file-plan.d.ts +22 -0
  36. package/dist/linux/remote-file-plan.d.ts.map +1 -1
  37. package/dist/linux/remote-file.d.ts +4 -1
  38. package/dist/linux/remote-file.d.ts.map +1 -1
  39. package/dist/linux/sudo-listing.d.ts +47 -0
  40. package/dist/linux/sudo-listing.d.ts.map +1 -0
  41. package/dist/linux/unit-handlers.d.ts.map +1 -1
  42. package/dist/linux/unit-preflight.d.ts +10 -1
  43. package/dist/linux/unit-preflight.d.ts.map +1 -1
  44. package/dist/proxmox/credential-literals.d.ts +1 -1
  45. package/dist/proxmox/credential-literals.d.ts.map +1 -1
  46. package/dist/proxmox/generated/pbs/nodes-node.d.ts +1 -1
  47. package/dist/proxmox/generated/pbs/nodes-node.d.ts.map +1 -1
  48. package/dist/proxmox/generated/pbs.d.ts +7 -0
  49. package/dist/proxmox/generated/pbs.d.ts.map +1 -1
  50. package/dist/proxmox/generated/pve.d.ts +7 -0
  51. package/dist/proxmox/generated/pve.d.ts.map +1 -1
  52. package/dist/proxmox/index.d.ts +16 -4
  53. package/dist/proxmox/index.d.ts.map +1 -1
  54. package/dist/proxmox/index.js +5 -2
  55. package/dist/proxmox/index.js.map +9 -9
  56. package/dist/proxmox/pbs-notification-target-form.d.ts +1 -1
  57. package/dist/proxmox/pbs-notification-target-form.d.ts.map +1 -1
  58. package/dist/proxmox/pbs-notification-target.d.ts +1 -1
  59. package/dist/proxmox/pbs-notification-target.d.ts.map +1 -1
  60. package/dist/release/archive.d.ts +11 -0
  61. package/dist/release/archive.d.ts.map +1 -0
  62. package/dist/release/binary-claim.d.ts +53 -0
  63. package/dist/release/binary-claim.d.ts.map +1 -0
  64. package/dist/release/binary-diff.d.ts +38 -0
  65. package/dist/release/binary-diff.d.ts.map +1 -0
  66. package/dist/release/binary-form.d.ts +118 -0
  67. package/dist/release/binary-form.d.ts.map +1 -0
  68. package/dist/release/binary-lifecycle.d.ts +55 -0
  69. package/dist/release/binary-lifecycle.d.ts.map +1 -0
  70. package/dist/release/binary-read.d.ts +11 -0
  71. package/dist/release/binary-read.d.ts.map +1 -0
  72. package/dist/release/binary.d.ts +68 -0
  73. package/dist/release/binary.d.ts.map +1 -0
  74. package/dist/release/catalog.d.ts +76 -0
  75. package/dist/release/catalog.d.ts.map +1 -0
  76. package/dist/release/checksums.d.ts +22 -0
  77. package/dist/release/checksums.d.ts.map +1 -0
  78. package/dist/release/declared-pins.d.ts +4 -0
  79. package/dist/release/declared-pins.d.ts.map +1 -0
  80. package/dist/release/download.d.ts +44 -0
  81. package/dist/release/download.d.ts.map +1 -0
  82. package/dist/release/fake-deploy.d.ts +48 -0
  83. package/dist/release/fake-deploy.d.ts.map +1 -0
  84. package/dist/release/fake-engine.d.ts +8 -0
  85. package/dist/release/fake-engine.d.ts.map +1 -0
  86. package/dist/release/fake-install.d.ts +39 -0
  87. package/dist/release/fake-install.d.ts.map +1 -0
  88. package/dist/release/fake-provider.d.ts +58 -0
  89. package/dist/release/fake-provider.d.ts.map +1 -0
  90. package/dist/release/fake-release.d.ts +95 -0
  91. package/dist/release/fake-release.d.ts.map +1 -0
  92. package/dist/release/index.d.ts +24 -0
  93. package/dist/release/index.d.ts.map +1 -0
  94. package/dist/release/index.js +1365 -0
  95. package/dist/release/index.js.map +35 -0
  96. package/dist/release/providers.d.ts +19 -0
  97. package/dist/release/providers.d.ts.map +1 -0
  98. package/dist/release/refused.d.ts +39 -0
  99. package/dist/release/refused.d.ts.map +1 -0
  100. package/dist/release/tar.d.ts +14 -0
  101. package/dist/release/tar.d.ts.map +1 -0
  102. package/dist/release/victoria.d.ts +36 -0
  103. package/dist/release/victoria.d.ts.map +1 -0
  104. package/dist/{proxmox → secrets}/write-only.d.ts.map +1 -1
  105. package/docs/adopted-deploys.md +13 -0
  106. package/docs/linux-host.md +5 -1
  107. package/docs/provider-standard.md +138 -0
  108. package/docs/release-binary-catalogs.md +113 -0
  109. package/docs/release-binary-measured.md +163 -0
  110. package/docs/release-binary-refusals.md +57 -0
  111. package/docs/release-binary-upstream.md +163 -0
  112. package/docs/release-binary.md +154 -0
  113. package/docs/upstream-conformance.md +164 -0
  114. package/package.json +5 -1
  115. package/src/caddy/access-forward-auth.test.ts +63 -0
  116. package/src/caddy/access-forward-auth.ts +120 -0
  117. package/src/caddy/index.ts +10 -0
  118. package/src/caddy/providers.test.ts +4 -0
  119. package/src/cloudflare/mcp-server-api.ts +294 -0
  120. package/src/cloudflare/mcp-server-form.ts +219 -0
  121. package/src/index.ts +1 -0
  122. package/src/launchd/file-converge.ts +233 -0
  123. package/src/launchd/file-identity.ts +69 -0
  124. package/src/launchd/host-file-alias.test.ts +65 -0
  125. package/src/launchd/host-file-form.ts +2 -1
  126. package/src/launchd/host-file-lifecycle.ts +25 -144
  127. package/src/launchd/host-file.ts +8 -2
  128. package/src/launchd/local-runner.test.ts +12 -1
  129. package/src/launchd/local-runner.ts +2 -0
  130. package/src/launchd/runner.ts +17 -2
  131. package/src/launchd/sudo-runner.ts +11 -0
  132. package/src/linux/region.ts +15 -0
  133. package/src/linux/remote-file-lifecycle.test.ts +0 -88
  134. package/src/linux/remote-file-lifecycle.ts +25 -7
  135. package/src/linux/remote-file-plan.ts +37 -1
  136. package/src/linux/remote-file-region.test.ts +181 -0
  137. package/src/linux/remote-file.ts +4 -1
  138. package/src/linux/sudo-listing.ts +122 -0
  139. package/src/linux/unit-handlers.ts +12 -5
  140. package/src/linux/unit-preflight.ts +20 -4
  141. package/src/linux/unit-rename.test.ts +76 -0
  142. package/src/proxmox/ceph-adopt.test.ts +210 -0
  143. package/src/proxmox/credential-literals.ts +1 -1
  144. package/src/proxmox/generated/pbs/nodes-node.ts +1 -1
  145. package/src/proxmox/generated/pbs.ts +7 -0
  146. package/src/proxmox/generated/pve.ts +7 -0
  147. package/src/proxmox/index.test.ts +7 -4
  148. package/src/proxmox/index.ts +16 -4
  149. package/src/proxmox/pbs-notification-target-form.ts +1 -1
  150. package/src/proxmox/pbs-notification-target-lifecycle.ts +1 -1
  151. package/src/proxmox/pbs-notification-target-wire.ts +1 -1
  152. package/src/proxmox/pbs-notification-target.ts +1 -1
  153. package/src/release/adopt-parity.test.ts +106 -0
  154. package/src/release/apply-pins.test.ts +128 -0
  155. package/src/release/archive.ts +94 -0
  156. package/src/release/binary-alias.test.ts +109 -0
  157. package/src/release/binary-claim.ts +80 -0
  158. package/src/release/binary-claims.test.ts +47 -0
  159. package/src/release/binary-diff.ts +73 -0
  160. package/src/release/binary-form.ts +229 -0
  161. package/src/release/binary-lifecycle.test.ts +90 -0
  162. package/src/release/binary-lifecycle.ts +202 -0
  163. package/src/release/binary-read.ts +55 -0
  164. package/src/release/binary-refusals.test.ts +192 -0
  165. package/src/release/binary-sudo.test.ts +93 -0
  166. package/src/release/binary.ts +181 -0
  167. package/src/release/catalog.test.ts +96 -0
  168. package/src/release/catalog.ts +175 -0
  169. package/src/release/checksums.test.ts +35 -0
  170. package/src/release/checksums.ts +43 -0
  171. package/src/release/declared-pins.ts +39 -0
  172. package/src/release/download.test.ts +138 -0
  173. package/src/release/download.ts +110 -0
  174. package/src/release/error-channel.test.ts +69 -0
  175. package/src/release/fake-deploy.ts +87 -0
  176. package/src/release/fake-engine.ts +50 -0
  177. package/src/release/fake-install.ts +59 -0
  178. package/src/release/fake-provider.ts +68 -0
  179. package/src/release/fake-release.ts +185 -0
  180. package/src/release/fixtures/victoria/darwin-arm64-assets.txt +22 -0
  181. package/src/release/fixtures/victoria/victoria-logs-darwin-arm64-v1.52.0_checksums.txt +2 -0
  182. package/src/release/fixtures/victoria/victoria-metrics-darwin-arm64-v1.151.0_checksums.txt +2 -0
  183. package/src/release/fixtures/victoria/victoria-traces-darwin-arm64-v0.10.0_checksums.txt +2 -0
  184. package/src/release/fixtures/victoria/vmutils-darwin-arm64-v1.151.0_checksums.txt +8 -0
  185. package/src/release/index.ts +41 -0
  186. package/src/release/plan.test.ts +189 -0
  187. package/src/release/provider-refusals.test.ts +48 -0
  188. package/src/release/providers.test.ts +174 -0
  189. package/src/release/providers.ts +24 -0
  190. package/src/release/recovery-read.test.ts +44 -0
  191. package/src/release/refused.ts +40 -0
  192. package/src/release/tar.test.ts +142 -0
  193. package/src/release/tar.ts +195 -0
  194. package/src/release/victoria.test.ts +108 -0
  195. package/src/release/victoria.ts +154 -0
  196. package/src/{proxmox → secrets}/write-only.ts +6 -0
  197. /package/dist/{proxmox → secrets}/write-only.d.ts +0 -0
  198. /package/src/{proxmox → secrets}/write-only.test.ts +0 -0
@@ -0,0 +1,36 @@
1
+ /**
2
+ * VictoriaMetrics' release archives, pinned — the first data set for Release.Binary, and vendor
3
+ * facts only: every digest copied from the vendor's own checksum file for that archive and
4
+ * cross-checked against GitHub's asset digest ON THE RECORDED DATE. Nothing here names a host, a
5
+ * path, a user or an estate. victoria.test.ts holds every pin to the committed checksum files
6
+ * (fixtures/victoria/) — ⚠️ consistency, not provenance: it cannot re-read GitHub, so a fixture
7
+ * edited together with its pins passes, and review is the gate for that.
8
+ *
9
+ * ★ PINNED IN CODE, NOT FETCHED AT APPLY. The checksum file is published by the same account, on
10
+ * the same release, over the same channel as the archive it describes — and these releases are
11
+ * MUTABLE (`"immutable": false` on v1.151.0, v1.52.0 and v0.10.0, measured 2026-09-22; the
12
+ * vendor's own release Makefile says to delete an asset in the UI to re-upload it). Whoever can
13
+ * swap an archive can swap its checksum file in the same minute, and a provider that trusts
14
+ * whatever checksum file it finds would install the new bytes and call them verified. A pin
15
+ * committed here changes only through a reviewed kit release, so a swapped asset is a REFUSAL
16
+ * that names the digest it expected. It also means an apply makes one request, not two, and a
17
+ * plan knows exactly which bytes it will install without touching the network.
18
+ * ⚠️ TRUST ROOT. At apply, these pins alone: the transport (TLS, the redirect, the CDN) can fail
19
+ * a download, never pass other bytes. The pins' own root is the GitHub release over TLS as read
20
+ * on RECORDED (trust on first use), uploaded from a maintainer's personal account.
21
+ * VictoriaMetrics publishes no signature, cosign bundle, SLSA provenance or GitHub artifact
22
+ * attestation for these archives (measured 2026-09-22: no .sig/.asc/.pem/.bundle assets; the
23
+ * attestations API answers 404 for all four digests, next to a positive control that answers
24
+ * 200). A checksum file proves the download is not corrupt — integrity — not who built it.
25
+ * ⛔ A NEW VERSION IS A NEW ENTRY, WALKED DOWN THE SAME WAY — docs/release-binary-catalogs.md.
26
+ * Never `latest`, never sorted by publish date: VictoriaLogs v1.51.1 was published AFTER v1.52.0.
27
+ * ⛔ EXACT NAMES. `-enterprise`, `-cluster` and `-enterprise-cluster` archives sit beside each of
28
+ * these with every prefix in common, so each asset name below is written out whole — never
29
+ * composed from a package and a version, which is how a template drifts onto a sibling.
30
+ * ★ THE VENDOR'S `-prod` SUFFIX IS DROPPED ON PURPOSE: every member is `<binary>-prod`, and
31
+ * installing it as `<binary>` keeps a job's argv[0], `ps` and `pgrep` names identical to the
32
+ * builds it replaces. The map is written out per package, never derived.
33
+ */
34
+ import type { ReleaseCatalog } from './catalog.ts';
35
+ export declare const VICTORIA_RELEASES: ReleaseCatalog;
36
+ //# sourceMappingURL=victoria.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"victoria.d.ts","sourceRoot":"","sources":["../../src/release/victoria.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAKnD,eAAO,MAAM,iBAAiB,EAAE,cAmH/B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"write-only.d.ts","sourceRoot":"","sources":["../../src/proxmox/write-only.ts"],"names":[],"mappings":"AA2BA,+FAA+F;AAC/F,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,+FAA+F;AAC/F,MAAM,MAAM,WAAW,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;AAEvE;;;;;;;;GAQG;AACH,eAAO,MAAM,UAAU,SACf,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,QAClC,WAAW,KACf;IAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;CAS1F,CAAC;AAqBF;;;;;;GAMG;AACH,eAAO,MAAM,IAAI,WAAY,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,SAAS,MAAM,KAAG,MAI9E,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,WAAW,WAAY,MAAM,UAAU,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,KAAG,OAStF,CAAC"}
1
+ {"version":3,"file":"write-only.d.ts","sourceRoot":"","sources":["../../src/secrets/write-only.ts"],"names":[],"mappings":"AAiCA,+FAA+F;AAC/F,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,+FAA+F;AAC/F,MAAM,MAAM,WAAW,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;AAEvE;;;;;;;;GAQG;AACH,eAAO,MAAM,UAAU,SACf,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,QAClC,WAAW,KACf;IAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;CAS1F,CAAC;AAqBF;;;;;;GAMG;AACH,eAAO,MAAM,IAAI,WAAY,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,SAAS,MAAM,KAAG,MAI9E,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,WAAW,WAAY,MAAM,UAAU,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,KAAG,OAStF,CAAC"}
@@ -54,6 +54,9 @@ API token on the cluster, just as it does for `alchemy plan`.
54
54
  | `Pbs.Datastore` | `config/datastore/{name}` | read (PBS) | own: GET, path/backend guards, PUT skipped on `matches`, settle GET — no write |
55
55
  | `Proxmox.SdnApply` | 5 × `cluster/sdn/*?pending=1` + fabrics `?pending` / `?running` | read | own: counts staged objects; zero returns at once — **no `PUT /cluster/sdn`** |
56
56
  | `Proxmox.CephPool` ⁴ | `nodes/{node}/ceph/pool/{name}/status?verbose=1` | read | own: GET, **PUT skipped on `matches` (was: always `PUT nodes/{node}/ceph/pool/{name}`)**, settle GET |
57
+ | `Proxmox.CephDaemon` ⁶ | `nodes/{node}/ceph/{mon,mgr,mds}` (whole list, by `name`) | read | shared path, and no `updateForm`: GET, GET — no write |
58
+ | `Proxmox.CephFs` ⁶ | `nodes/{node}/ceph/fs` (whole list, by `name`) | read | own: GET, present so returned untouched — no write |
59
+ | `Proxmox.CephOsd` ⁶ | `nodes/{node}/ceph/osd` (the CRUSH tree) | read | own: GET, host/class asserted; POSTs only with `dev` declared — no write |
57
60
 
58
61
  ¹ These object reads are gated on an allocate privilege, so the family reads with the provision
59
62
  lease (`readRole`, resource.ts). That is still a read.
@@ -73,6 +76,13 @@ compares a seal kept in the previous state ([pbs-notifications.md](./pbs-notific
73
76
  adopted target has no seal, so its secret values are presence-only and adoption writes nothing.
74
77
  Pinned by `src/proxmox/pbs-notification-target-state.test.ts`.
75
78
 
79
+ ⁶ Exported from the barrel 2026-09-22. None has a PUT, and CephDaemon and CephFs compare nothing,
80
+ so a present object is `noop` and can never plan a replace. All three retain on destroy: removing
81
+ the declaration plans `orphaned` and sends no DELETE. `Proxmox.CephFlag` stays Provider-only (a
82
+ declared flag reasserts a maintenance toggle on every deploy). Pinned by
83
+ `src/proxmox/ceph-adopt.test.ts`, whose mutation check (retain flipped to destroy) shows nine
84
+ DELETEs, one per mon, mgr and mds.
85
+
76
86
  ## Before the fix: `Proxmox.CephPool`
77
87
 
78
88
  `reconcile` read the pool and, whenever it existed, PUT the declared `size`, `min_size`,
@@ -90,6 +100,9 @@ list shows six such tasks (2026-09-13 and 2026-09-20), one per declared pool per
90
100
  - `src/proxmox/adopt-noop.test.ts` does the same for each reconcile shape: the shared path
91
101
  (`Proxmox.Pool`), the wrapper (`Proxmox.Acl`), and the two hand-written ones (`Pbs.Datastore`,
92
102
  `Proxmox.SdnApply`). A drifted Pool row proves the harness sees a write when there is one.
103
+ - `src/proxmox/ceph-adopt.test.ts` adopts a reference cluster's nine daemons, one CephFS and six
104
+ OSDs (GETs only), undeclares all sixteen (no DELETE), and refuses an OSD on the wrong host
105
+ without a write.
93
106
  - `src/verify/verify.test.ts` pins Alchemy's own behaviour: `adopted` for a match and for a
94
107
  drift, and a reconcile for both. If an Alchemy upgrade changes that, the test fails and this
95
108
  page needs re-reading.
@@ -107,6 +107,9 @@ one declared line
107
107
  - ⛔ **Ambiguity is a refusal.** Two BEGIN markers, an END before its BEGIN, or
108
108
  a BEGIN with no END each name the line rather than guess.
109
109
  - **Delete removes only the block**, leaving the rest byte-identical.
110
+ - ⛔ **The region's name and comment token are its identity.** Changing either
111
+ moves the block: the new one is written and verified, then the old one goes.
112
+ Switching between whole-file and region mode at one path is refused.
110
113
  - ⚠️ A file with no final newline gains exactly one when the block is appended.
111
114
  That is the only byte added outside the block, and it is not taken back.
112
115
 
@@ -164,7 +167,8 @@ unit reads converged — the same limit the Mac side records.
164
167
  before that swap is promised. A file byte-identical to this render is the
165
168
  declaration's own leftover, not someone else's unit. A rename the plan could
166
169
  not see (a name still an Output) gets the same checks at apply, before the
167
- old unit is stopped.
170
+ old unit is stopped; one whose `content` is still an Output gets the checks
171
+ that need only the name — deletable, writable, not masked — in the plan.
168
172
 
169
173
  ### The directive set is systemd's, not the kit's
170
174
 
@@ -0,0 +1,138 @@
1
+ # The provider standard
2
+
3
+ Status: in force. Verified 2026-09-22 against `alchemy@2.0.0-beta.79` (tag
4
+ `v2.0.0-beta.79`, commit `473c3959`) and the distilled commit that tag pins (`c2a78002`).
5
+
6
+ This page is the kit-side statement of the house standard for a custom Alchemy provider.
7
+ The full rule set is the `alchemy-provider-standard` skill in the estate's workflow plugin.
8
+ It has 46 numbered rules (S1–S46) and 14 house differences (H1–H14), and every rule cites
9
+ `alchemy-run/alchemy` as `<path>@v2.0.0-beta.79#<section>`. This page keeps the rule numbers
10
+ so that a review comment can name the rule a change breaks. How each family measures up is
11
+ in [upstream-conformance.md](./upstream-conformance.md).
12
+
13
+ ★ **Why this package holds itself to upstream's bar.** Everything in `@homeflare/alchemy`
14
+ should be good enough to contribute to Alchemy. So the kit builds only what upstream lacks,
15
+ and builds it the way upstream would. A house re-implementation of something Alchemy
16
+ already ships is a finding, even when it works.
17
+
18
+ ## Three questions before any code
19
+
20
+ 1. **Does upstream ship the resource?** Look under `packages/alchemy/src/<Cloud>/` at the
21
+ pinned tag. If it does, use it. `github/` does exactly this: it composes
22
+ `GitHub.Repository` and `GitHub.Ruleset`. `website.ts` does it too, over
23
+ `Website.Astro` and `Website.Vite`. A missing prop means an upstream PR, never a fork
24
+ (S1).
25
+ 2. **Does `@distilled.cloud/<vendor>` exist?** If it does, every call goes through it,
26
+ with its typed error unions and Credentials layer. A second SDK or a hand-rolled client
27
+ is a finding. On 2026-09-22 npm had distilled packages for Forgejo, GitHub, Kubernetes
28
+ and Docker, among others, and none for Proxmox, OpenBao, Caddy, Talos, NetBox or UniFi
29
+ (S23).
30
+ 3. **Does Alchemy ship the helper?** Hashing (`alchemy/Util/sha256`), polling
31
+ (`alchemy/Util/poll`), atomic writes (`alchemy/Util/AtomicFile`), equality
32
+ (`deepEqual` in `alchemy/Diff`), names (`alchemy/PhysicalName`), tags (`alchemy/Tags`),
33
+ ownership (`alchemy/AdoptPolicy`), credentials (`alchemy/Auth`) and the test harness
34
+ (`alchemy/Test/Bun`) all exist. Use them.
35
+
36
+ ## The contract, in brief
37
+
38
+ - **Shape (S2, S3).** Declare the interface as
39
+ `Resource<"Vendor.Service.Type", Props, Attrs, never, Providers>`, and the constructor as
40
+ `const X = Resource<X>(…)`. The type string starts with the vendor's own name. The
41
+ contract and its provider share a file, and helpers go in sibling files that `index.ts`
42
+ does not export.
43
+ - **Props (S5).** Props are plain types, never `Input<T>`, and every field has JSDoc.
44
+ - **Diff (S6).** `diff` narrows with `isResolved`. It returns `undefined` for an ordinary
45
+ update and `replace` only for identity changes.
46
+ - **Read (S7, S8).** `read` works with `output: undefined`. It answers `undefined`, plain
47
+ attributes or `Unowned(attrs)`. This package answers `Unowned` for any live object this
48
+ stack cannot prove it made (H1; [ownership.md](./ownership.md)).
49
+ - **Reconcile (S9, S10).** `reconcile` is one flow: observe, ensure, sync, return. It
50
+ never trusts `output` as proof, and it makes zero writes when nothing drifted.
51
+ - **Delete (S11, S14).** `delete` is idempotent and bounded. A family whose deletion
52
+ breaks its consumers defaults to `retain`, and still implements `delete` in full.
53
+ - **List (S12).** `list` enumerates every object of the type in its scope, or returns `[]`
54
+ with the reason written down. A family whose objects carry ownership marks filters to
55
+ owned ones, as upstream's third-party providers do; the core contract does not ask for it. `nuke` is declared wherever nuke must never reach the object.
56
+ - **Scope (S15).** The account, zone and region are resolved inside each operation. They
57
+ are never props.
58
+
59
+ ## Code, errors, credentials
60
+
61
+ - **Effect only (S19).** Provider code, helpers and tests use `FileSystem`, `Path`,
62
+ `HttpClient` and `ChildProcessSpawner`. They never use `async`/`await`, a raw `Promise`,
63
+ `node:fs`, `node:fs/promises`, `node:os`, `node:path` or bare `fetch`. A synchronous,
64
+ CPU-only Node call (`node:crypto` `createHash`, `Buffer`) goes inside `Effect.sync`, which
65
+ is upstream's own example. When a promise cannot be avoided, use `Effect.tryPromise`,
66
+ never `Effect.promise`.
67
+ - **No defects (S20).** Lifecycle operations contain no `Effect.orDie` and no
68
+ `Effect.die`. A refusal is a typed error.
69
+ - **Typed errors (S21, S22).** Handle errors with `Effect.catchTag` over the SDK's union.
70
+ Where the kit generates its own client from a vendor schema, the status-to-tag mapping
71
+ lives in that client, once, and never in a resource.
72
+ - **Credentials (S24).** Credentials come from an `alchemy/Auth` provider and a lazy
73
+ Credentials service. Short-lived credentials are minted per call, never reused, and never
74
+ taken as props.
75
+ - **Secrets (S25).** A secret value is never a prop or an attribute. Alchemy persists
76
+ `Redacted` values in plaintext, and only one state store encrypts them.
77
+ - **Bounded waits (S26).** A retry stops after at most 8 to 10 attempts and under about a
78
+ minute. A poll never runs past about 90 seconds.
79
+
80
+ ## The Bun line for this package (S42–S45)
81
+
82
+ ★ This package is Bun to author and runtime-neutral to consume. It builds with
83
+ `bun build --target node`, and consumers run `dist/` on Node.
84
+
85
+ - **`src/**` provider code** (resources, providers, clients, and helpers a provider calls)
86
+ never calls `Bun.*` and never imports `bun:*` or the `node:*` modules S19 names. A Node
87
+ consumer calling an exported helper that uses `Bun.YAML` fails with
88
+ `ReferenceError: Bun is not defined` at the call site. `node:crypto`, `node:http` and
89
+ `node:child_process` all load on Node. Where they break the standard, they break the
90
+ Effect-only rule, not the runtime.
91
+ - **Tests, fakes, scripts and codegen** are Bun-native. That means `bun:test` (never
92
+ `node:test`, never vitest), `Bun.serve` fakes on loopback, `Bun.YAML`, `Bun.file()`,
93
+ `Bun.Glob` and `Bun.CryptoHasher`. Upstream runs its scripts and its test runner on Bun
94
+ too.
95
+ - **A module that truly needs Bun** is named for Bun, sits behind its own subpath, or
96
+ guards on `typeof globalThis.Bun`. The package root never reaches it.
97
+
98
+ The files currently on the wrong side of the line are listed in
99
+ [upstream-conformance.md](./upstream-conformance.md#the-bun-line).
100
+
101
+ ## Versions (S37–S41)
102
+
103
+ - **One aligned set.** `effect`, `@distilled.cloud/*` and `@effect/platform-*` are pinned
104
+ exactly to what the pinned `alchemy` release was built and tested against. They move only
105
+ in the same PR as the `alchemy` bump.
106
+ - **Every family records the vendor version it was walked against, in code.**
107
+ - For a generated client, the record is the schema manifest entry and the generated
108
+ header. `codegen/manifest.json` holds the product, version, sha256, fetch date and
109
+ source role for PVE, PBS, UniFi and NetBox.
110
+ - For a distilled-backed family, it is the chain: `alchemy` version, distilled package
111
+ version, distilled commit, spec-mirror commit and date, and Smithy service version.
112
+ - For a CLI or OS-service family, it is the binary or OS version, the command that
113
+ reports it, and the date, in that family's docs page.
114
+ - **And in its changeset.** A provider change names the vendor version it was walked
115
+ against. A pin move names every link, old and new.
116
+
117
+ | family | vendor version walked against | recorded in |
118
+ | ------------------- | -------------------------------------- | --------------------------------- |
119
+ | `proxmox` (PVE) | pve-manager 9.2.11 | `codegen/manifest.json` + headers |
120
+ | `proxmox` (PBS) | proxmox-backup-server 4.2.6-1 | `codegen/manifest.json` + headers |
121
+ | `netbox` | NetBox 4.7.0 OpenAPI | `codegen/manifest.json` + headers |
122
+ | `cloudflare` | distilled 1.0.0-rc.12 (API v4) | `package.json` pins only |
123
+ | `openbao` | OpenBao 2.6.2 | source comments only ⚠️ |
124
+ | `caddy` | Caddy 2.11.4 | source comments only ⚠️ |
125
+ | `forgejo` | Forgejo 16.0.3 | source comments only ⚠️ |
126
+ | `talos` | Talos v1.13 docs (never measured live) | source comments only ⚠️ |
127
+ | `launchd` / `linux` | macOS 27 / systemd 257 | source comments only ⚠️ |
128
+
129
+ ⚠️ These rows record a version only in prose, so no test catches a drift. `schemas/manifest.json`
130
+ also repeats the PVE, PBS and UniFi entries, which means there are two manifests for one
131
+ fact.
132
+
133
+ ## Where this package differs from upstream on purpose
134
+
135
+ Each difference is written down with its reason. H1 is strict `Unowned`. H2 is the
136
+ apply-time ownership re-check in `ownership/`. H4 is `retain` by default. H5 is `adopt(true)`
137
+ on each resource. H6 is `hf-adopt-verify`. [upstream-conformance.md](./upstream-conformance.md)
138
+ marks each one justified or a gap. Anything not listed there is a gap by definition.
@@ -0,0 +1,113 @@
1
+ # Release binaries — the data sets, and adding one
2
+
3
+ Status: active
4
+ Verified: 2026-09-22
5
+
6
+ `Release.Binary` takes one pinned archive as props and knows no vendor. What
7
+ each vendor published lives in a **data set** beside it (`src/release/<vendor>.ts`),
8
+ typed `ReleaseCatalog`, and `catalogBinary(dataSet, request)` turns one entry
9
+ into props. This page is what is in the data sets, and the rule for adding to
10
+ them. The guide is [release-binary.md](./release-binary.md).
11
+
12
+ ## `VICTORIA_RELEASES` — the only data set today
13
+
14
+ | package | version | asset (darwin-arm64) | binaries (installed ← member) |
15
+ | ------------------ | ------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
16
+ | `victoria-metrics` | 1.151.0 | `victoria-metrics-darwin-arm64-v1.151.0.tar.gz` | `victoria-metrics` ← `victoria-metrics-prod` |
17
+ | `victoria-logs` | 1.52.0 | `victoria-logs-darwin-arm64-v1.52.0.tar.gz` | `victoria-logs` ← `victoria-logs-prod` |
18
+ | `victoria-traces` | 0.10.0 | `victoria-traces-darwin-arm64-v0.10.0.tar.gz` | `victoria-traces` ← `victoria-traces-prod` |
19
+ | `vmutils` | 1.151.0 | `vmutils-darwin-arm64-v1.151.0.tar.gz` | `vmagent`, `vmalert`, `vmalert-tool`, `vmauth`, `vmbackup`, `vmctl`, `vmrestore`, each ← `<name>-prod` |
20
+
21
+ These are the versions the Mac host runs today, pinned for parity first. An
22
+ upgrade (VictoriaMetrics 1.152.0, VictoriaTraces 0.11.1 exist) is its own
23
+ change that re-pins both digest layers.
24
+
25
+ - ⛔ **The single-node `victoria-metrics` archive**, not `-cluster`, which holds
26
+ vminsert, vmselect and vmstorage instead.
27
+ - ★ **The `-prod` suffix is dropped on purpose.** The installed names keep a
28
+ job's argv[0], `ps` and `pgrep` names identical to the builds they replace.
29
+ The map is written out per package, never derived.
30
+ - **vmalert-logs is not a binary.** It is `vmalert` run with
31
+ `--rule.defaultRuleType=vlogs`: one `ReleaseBinary`, two jobs.
32
+ - **A consumer's path.** `catalogDirectory(root, request)` plus the name gives
33
+ `<root>/<package>-<version>/<binary>`, e.g.
34
+ `<root>/vmutils-1.151.0/vmalert`. That is the layout homeflare-mini's
35
+ `binaryPath()` already renders, so its argv and goldens need no path change.
36
+ A stack that keeps its own digest slots (the mini's `PINS`) can compare them
37
+ with `catalogBinary(...).sha256` in its own tests: two witnesses, one source.
38
+
39
+ ## Adding a version of a vendor already here
40
+
41
+ Walk it down from the vendor, never from memory:
42
+
43
+ 1. `gh api repos/<owner>/<repo>/releases/tags/<tag>`: the exact asset names,
44
+ sizes and `digest`s, and `immutable`.
45
+ 2. Fetch the checksum file. Its SHA-256 must equal GitHub's digest of it, and
46
+ its archive line must equal the archive's digest.
47
+ 3. Stream the archive through `tar -tvzf -`, saving nothing: root-level
48
+ regular files with the expected member names, nothing else.
49
+ 4. Add the entry with `recorded` set to that day. Commit the checksum file
50
+ under `src/release/fixtures/<vendor>/` and add the asset names to the
51
+ listing there. `<vendor>.test.ts` then holds the pins to it.
52
+ 5. Re-check attestations with a positive control, so a 404 means something:
53
+ `gh api repos/<owner>/<repo>/attestations/sha256:<digest>` next to a repo
54
+ known to publish them (`cli/cli` answered 2 on 2026-09-22).
55
+
56
+ ⛔ Never `latest`, and never sort by publish date. VictoriaLogs v1.51.1 was
57
+ published after v1.52.0, and VictoriaMetrics interleaves LTS lines.
58
+
59
+ ## Adding a vendor
60
+
61
+ ⛔ **Only after that vendor's own walk-down measures two things**, recorded in
62
+ its data module's header with the date:
63
+
64
+ - **Its checksum format, byte-exact.** `checksums.ts` parses one measured shape,
65
+ `sha256sum` text mode listing the archive and every member. Most vendors the
66
+ estate census found (landscape PR 88) list **archives only**: Prometheus's
67
+ `sha256sums.txt`, vector's `-SHA256SUMS`, pyroscope's and OpenBao's
68
+ `checksums.txt`. For those the member digest is not a vendor fact. It is
69
+ computed from a verified archive, and the data set must say so rather than
70
+ file it beside the vendor's lines. That field does not exist yet; it arrives
71
+ with its first consumer.
72
+ - **Its binary's linkage** (`otool -L` on the extracted member, or `ldd`).
73
+ Vendor binaries were measured self-contained **only for the five Victoria
74
+ ones** (system libraries and `/usr/lib/libresolv.9.dylib` only). That is not
75
+ a property of "a Go binary". The Nix vector links jemalloc, librdkafka,
76
+ oniguruma and libiconv from `/nix/store`, and whether the vendor tarball
77
+ bundles them is unknown until someone inspects the extracted member.
78
+
79
+ Also carried over from the Victoria walk-down, and checked again per vendor:
80
+ exact asset names against near-identical siblings; the tar layout; the
81
+ `immutable` flag; signatures and attestations with a positive control; and
82
+ where `-version` prints, if anything ever runs it (Victoria's goes to stderr).
83
+
84
+ The queue, from the census, each **not yet walked for this resource**:
85
+ alertmanager, blackbox_exporter, node_exporter and postgres_exporter
86
+ (Prometheus `sha256sums.txt`), unpoller (detached `.sig`; 5.2.7 renames
87
+ `darwin_arm64` to `darwin_all`), pyroscope, vector, and `bao` (OpenBao, with
88
+ GPG and Sigstore to check).
89
+
90
+ ⚠️ REASONED NOT MEASURED: the Prometheus-family archives wrap their files in a
91
+ directory, and `tar.ts` refuses a directory entry (and any PAX header) for the
92
+ whole archive. Walking those down includes a reader change, not only a data
93
+ set ([release-binary-upstream.md](./release-binary-upstream.md), gap 11).
94
+
95
+ ## What this resource is not for
96
+
97
+ - **A whole tree.** Grafana needs its homepath and plugins, and an interpreter
98
+ like python-build-standalone needs its `lib/`. That is a **tree mode**:
99
+ unpack an archive into a directory. It is a separate, later capability with
100
+ its own walk-down, not a flag bent onto `Release.Binary`. Grafana's darwin
101
+ builds also come from grafana.com's downloads API, not a GitHub release. (The
102
+ house's stdlib-only Python jobs are tracked for a Bun/TS rewrite instead of a
103
+ vendored interpreter, decided 2026-09-23.)
104
+ - **Another source kind.** Only GitHub release downloads, whose URL is
105
+ `https://github.com/<repo>/releases/download/<tag>/<asset>`. A second kind
106
+ (grafana.com JSON, Codeberg) arrives with its first consumer.
107
+ - **Another archive format.** Only `.tar.gz`/`.tgz`. A `.zip` or a bare
108
+ binary is refused at plan rather than downloaded and then not unpacked.
109
+ - **Builds from source.** Postgres, pgBackRest and Valkey ship no darwin
110
+ binary. Caddy with its plugins and cloudflare-exporter are to be built by the
111
+ public `taslabs-net/homeflare-builds` repo, which publishes GitHub releases
112
+ with a `SHA256SUMS` file (decided 2026-09-23). Each then becomes an ordinary
113
+ data set, after its own walk-down like any vendor's.
@@ -0,0 +1,163 @@
1
+ # Release binaries — measured, and the limits
2
+
3
+ Status: active
4
+ Verified: 2026-09-22
5
+
6
+ What [release-binary.md](./release-binary.md) rests on, how each fact was read,
7
+ and what is still reasoned rather than measured. Every vendor fact here is
8
+ VictoriaMetrics': the only data set so far. Host class: the Mac mini the stack is for
9
+ (macOS 27.2, arm64), bun 1.4.0. No binary was executed for any of this.
10
+
11
+ ## The vendor side (read-only, `gh api` and `curl`)
12
+
13
+ | archive (darwin-arm64) | bytes | members (all regular, root-level) |
14
+ | ----------------------------- | ----------- | ------------------------------------ |
15
+ | `victoria-metrics-…-v1.151.0` | 12,739,105 | `victoria-metrics-prod` |
16
+ | `victoria-logs-…-v1.52.0` | 10,943,697 | `victoria-logs-prod` |
17
+ | `victoria-traces-…-v0.10.0` | 11,126,077 | `victoria-traces-prod` |
18
+ | `vmutils-…-v1.151.0` | 123,584,800 | seven `-prod` tools, 264 MB unpacked |
19
+
20
+ - **Asset names are exact**, and `-cluster`, `-enterprise`,
21
+ `-enterprise-cluster` (and `vlutils`) archives sit beside them with every
22
+ prefix in common — twelve siblings in the darwin-arm64 listing alone. The
23
+ listing is committed as `src/release/fixtures/victoria/darwin-arm64-assets.txt`.
24
+ - **Each checksum file hashes both layers**: line 1 the archive, then one line
25
+ per `-prod` member, in `sha256sum` text mode (`<hex> <name>`, LF, trailing
26
+ newline). For all four, GitHub's asset `digest` equals line 1, and GitHub's
27
+ digest of the checksum file equals the SHA-256 of the committed fixture.
28
+ - **Re-read the same day by the supply-chain review** (`gh api …/releases/tags/<tag>`):
29
+ all four archives' and checksum files' sizes and `digest`s still equal the pins
30
+ and the recorded checksum-file digests. Nothing had been swapped since recording.
31
+ - **Releases are mutable** (`"immutable": false`), uploaded from maintainers'
32
+ personal accounts. **No signatures or attestations** exist for the archives:
33
+ no `.sig`/`.asc`/`.pem`/`.bundle` assets, and the attestations API answers 404
34
+ for all four digests next to a positive control (`cli/cli`) that answers 200.
35
+ - **Tar format**: `tar -tvzf -` on each streamed archive (nothing saved) shows
36
+ only root-level regular files with short names — no directories, links, PAX
37
+ or GNU long-name headers. Owners differ by release (`builder`, `valyala`,
38
+ `ubuntu`), which is why the archive's mode and owner are never used.
39
+
40
+ ## The provider against the real archives
41
+
42
+ The provider, as `Victoria.Binary` through `victoriaProviders(localRunner())`,
43
+ installed all five binaries the stack needs into a scratch directory (never
44
+ `/opt`), 2026-09-22. ⚠️ **That was the code before it was generalised into
45
+ `Release.Binary`, and the generalised resource has NOT been re-run against the
46
+ real archives** (a download needs the operator's go-ahead). What carried over
47
+ unchanged: `download.ts`, `tar.ts`, the two checks in `archive.ts` and the write
48
+ through `file-converge.ts`. What changed: where the pins come from (props,
49
+ filled by `catalogBinary()`) and the URL, now composed from repo, tag and asset
50
+ and held equal to the old whole URLs by `victoria.test.ts`.
51
+
52
+ - **Every digest verified**, both layers, and every installed file's SHA-256 is
53
+ its pin. One run took 1.7–5.9 s on the mini's link.
54
+ - **One download for vmagent + vmalert** installed concurrently: `fetch` was
55
+ called once for the shared vmutils archive (counted by wrapping `fetch`).
56
+ - **`codesign -v` is valid** on all five as written: the vendor binaries are
57
+ ad-hoc, linker-signed (`flags=0x20002`, no TeamIdentifier) and the write is
58
+ byte-identical, so the embedded signature survives. The running Nix builds are
59
+ the same signature class, and launchd runs them.
60
+ - **No `com.apple.quarantine`**, only `com.apple.provenance` — the same as the
61
+ running Nix binaries.
62
+ - **No `/nix/store` library** (`otool -L`, all five): `libSystem`,
63
+ `/usr/lib/libresolv.9.dylib`, CoreFoundation, Security. ⚠️ `libresolv.9.dylib`
64
+ is not on disk; it is in the dyld shared cache — a preflight that checks the
65
+ file exists would refuse a working binary.
66
+ - **Memory (peak RSS, bun 1.4.0)**: victoria-traces alone ~200 MB; vmalert
67
+ alone ~520 MB; vmagent + vmalert together ~590–720 MB; all five together
68
+ ~750–930 MB. 🔴 Before the gzip input was fed in slices, vmalert alone peaked
69
+ at 1,116 MB: the whole archive inflated in one step and 264 MB of output queued
70
+ ahead of the reader (archive.ts). Before the download filled one pinned-size
71
+ buffer, the chunk list plus its concatenated copy held the archive twice.
72
+ - **Under node** (v26.7.0), the bundled extractor reads a gzipped tar the same
73
+ way (`DecompressionStream` is the web standard in both runtimes).
74
+
75
+ ## Through Alchemy's own engine (fakes, not a host)
76
+
77
+ `src/release/plan.test.ts` runs alchemy@2.0.0-beta.79's Plan and Apply over
78
+ in-memory state, with `HostDirectory` declared in front of the binary, a fake
79
+ host that models `mkdir`/`rmdir`, and a fake HTTP server:
80
+
81
+ - **A first deploy** runs `mkdir`, one `GET`, one write, in that order: the
82
+ `directory: dir.path` Output orders them.
83
+ - **A version bump** (`HostDirectory('vmutils-<version>')`, `ReleaseBinary('vmalert')`)
84
+ plans `replace` / `create` / `delete` and runs: mkdir new, GET new, write new,
85
+ **then** remove old, **then** rmdir old. That was reasoned from `Apply.ts`
86
+ before; it is measured now, on the engine, not on a host.
87
+ - **A version outside the data set** fails in the stack program: zero requests,
88
+ zero host calls.
89
+ - **A new pin in the same directory** fails at plan: the directory already
90
+ exists, so its path is resolved when `diff` runs.
91
+ - 🔴 **The same bytes re-pinned while the directory is being updated** (its
92
+ path unresolved at plan): with `diff` answering `replace`, the deploy
93
+ SUCCEEDED and the binary was gone. The new generation accepted the identical
94
+ file as its own, and Phase 2 deleted the old generation at the same path.
95
+ `diff` now answers `update` there, and reconcile's in-place guard refuses it
96
+ with the binary kept (`plan.test.ts` pins the refusal).
97
+
98
+ ## The install path on a real filesystem (red team, 2026-09-22)
99
+
100
+ Measured on macOS 27.2 in temp directories only: no live host, no sudo.
101
+
102
+ - 🔴 **One file under two spellings was lost, silently** (fixed;
103
+ `binary-alias.test.ts`). Respelling the directory through a symlinked parent,
104
+ or by case alone on case-insensitive APFS, planned a `replace`: the new
105
+ generation took the identical file as its resumed install, then Phase 2
106
+ deleted the old generation's path, which was the same file. The deploy
107
+ succeeded with no binary on disk. Now `diff` compares device and inode
108
+ (`launchd/file-identity.ts`) and answers `update`, and a move never removes
109
+ an old path that is the file it just verified.
110
+ - 🔴 **An execute-only mode (`0o111`) was written and then unreadable** (fixed).
111
+ The read-back threw EACCES, the file stayed with no state, and every later
112
+ probe threw the same. Now the mode must be owner-readable, and a read-back
113
+ that throws rolls the create back like one that mismatches.
114
+ - 🔴 **Two resources at one path both owned it** (fixed; `binary-claims.test.ts`).
115
+ `vmalert` and `vmalert-logs` declared at one path both created it; the deploy
116
+ that dropped `vmalert-logs` deleted the file `vmalert` still declared, and
117
+ reported `vmalert` as `noop`. A second claim on a path in one run is refused.
118
+ - ⚠️ **`/usr/bin/install -S` chmods after the rename.** An lstat poller racing a
119
+ 300 MB install saw the new inode at the path as `0600`, then `0755` 0.2–0.4 ms
120
+ later, six runs out of six. Never torn and never wider than declared. localRunner
121
+ sets the mode before its rename. Closing the window under `sudoRunner` changes
122
+ the sudo allowlist, so it has not been done.
123
+ - ⚠️ **`install` copies extended attributes**, `com.apple.quarantine` included.
124
+ Files this process writes through `node:fs` carried only
125
+ `com.apple.provenance`, so no staged file is quarantined today.
126
+ - ★ **Tar-slip cannot reach the disk by construction.** The installed path is
127
+ `<directory>/<name>`, with `name` one `[A-Za-z0-9._-]` segment; an archive
128
+ entry's name only selects bytes and never becomes a path.
129
+
130
+ ## Reasoned, not measured
131
+
132
+ - ⚠️ **REASONED NOT MEASURED: a quarantined binary would not start under
133
+ launchd.** Adoption (`--adopt`) and the resume rule accept a file by digest,
134
+ mode and owner, and never read xattrs. A binary that was downloaded by a
135
+ browser and placed by hand would be adopted with its quarantine. Remove it
136
+ (`xattr -d com.apple.quarantine`) before adopting.
137
+
138
+ - ⚠️ **REASONED NOT MEASURED: a vendor binary runs under launchd from the
139
+ installed path.** Strongly suggested — same signature class as the running
140
+ Nix builds, valid signature, no quarantine — but nothing was executed.
141
+ - ⚠️ **REASONED NOT MEASURED: `sudoRunner` installs under the real
142
+ `/opt/<estate>/obs`.** Exercised only over the kit's fake sudo host
143
+ (`binary-sudo.test.ts`: one staged file, one `install -S -m 0755 -o 0 -g 0`,
144
+ the staged file removed on success and on failure).
145
+ - ⚠️ **`HostDirectory` under `sudoRunner` does not work today** (measured with
146
+ the kit's fakes during the walk-down): `mkdir` is not on the sudo allowlist,
147
+ and `checkWrite` refuses a directory. A stack that deploys as itself cannot
148
+ create `<root>/<package>-<version>` through sudo yet; deploy as root, or
149
+ pre-create the directory. Fixing it is a sudo-seam change with its own review.
150
+ - ⚠️ **REASONED NOT MEASURED: `HostDirectory`'s argv (`mkdir -m`, `chmod`,
151
+ `chown`, `rmdir`) behaves the same on macOS** — its header says so.
152
+ - ⚠️ **REASONED NOT MEASURED: the process-memory panels.** The vendor darwin
153
+ builds are `CGO_ENABLED=0` (their Makefile), and VictoriaMetrics' metrics
154
+ library reads darwin RSS only with cgo — the reason the house Nix module
155
+ builds with cgo. Expect `process_resident_memory_bytes` to go missing after
156
+ the swap until someone decides what replaces it. A consumer decision, not
157
+ this provider's.
158
+ - ⚠️ **The platform is not checked against the host.** A `darwin-arm64` pin on
159
+ a Linux host installs a binary that will not start; the job's first launch is
160
+ where that shows. The platform is the data set's key, not a prop.
161
+ - ⚠️ **A stamped vendor build prints `-version` to stderr**, not stdout
162
+ (`lib/buildinfo`, read). Identity here is the SHA-256 alone; if a check ever
163
+ runs `-version`, read stderr, and say which build (Nix or vendor) it was.
@@ -0,0 +1,57 @@
1
+ # Release binaries — every refusal
2
+
3
+ Status: active
4
+ Verified: 2026-09-22
5
+
6
+ Everything `Release.Binary` refuses, by where it is refused. The guide is
7
+ [release-binary.md](./release-binary.md). ★ One page per subject: the guide says
8
+ what a declaration does, this says what stops it, and a new refusal lands here.
9
+
10
+ In the stack program (`catalogBinary`): a package, version, platform or
11
+ binary the data set does not pin, including `v1.151.0`, `1.151.0-enterprise`,
12
+ `victoria-metrics-cluster`, `vmalert-prod` and inherited keys like
13
+ `constructor`.
14
+
15
+ At plan (the probe and `diff`), before anything is touched: a pin that is
16
+ missing or not a plain value (an Output pin would be a fetch; a first deploy
17
+ is never diffed, so reconcile refuses it there, before any host call, from the
18
+ props as declared); a repo that is not `owner/name`, or whose name is `.` or
19
+ `..`; a tag, asset or name that is not one safe path segment; an asset that is
20
+ not a `.tar.gz` or `.tgz` (a glob is not a name); a size that is not an
21
+ integer from 1 byte to 1 GiB (it is allocated up front); a digest that is not
22
+ 64 lower-case hex; a member that is absolute or climbs. Also a relative or
23
+ unnormalised directory; a mode that is setuid, setgid, sticky, group- or
24
+ world-writable, or not owner-readable and owner-executable; an owner or group
25
+ that is not a valid name or id; a new pin at the same path.
26
+
27
+ ⚠️ **Not on a first deploy with `directory: dir.path`.** Alchemy neither
28
+ probes nor diffs a create whose props hold an Output, so there every check
29
+ above runs at apply, after `HostDirectory` has made the directory (measured: a
30
+ group-writable mode planned `create`, then the apply ran `mkdir` and refused).
31
+ Nothing is written to the binary's path. A failed first install never bricks
32
+ the next plan: the recovery read of its row answers "nothing recovered"
33
+ (`binary-read.ts`).
34
+
35
+ At plan, only under `--adopt` (the probe then hands `diff` what it read): a
36
+ file at the path that is not the pinned binary, or not a regular file.
37
+
38
+ At apply, before the download: a second resource installing the same path in
39
+ one deploy (two jobs that run one binary share one `ReleaseBinary`; measured,
40
+ two owners meant dropping either deleted the other's file); a missing
41
+ directory, one that is a symlink, or one that group or other may write; a
42
+ symlink or directory at the path; a file the resource does not own (without
43
+ `--adopt`, even the pinned bytes; with it, anything but the pinned bytes); a
44
+ chown without root.
45
+
46
+ From the vendor: a status other than 200; a body past or short of the pinned
47
+ size; an archive whose SHA-256 is not the pin; an archive entry that is
48
+ absolute, has a `..` segment, is a hard link, symlink, directory, device, FIFO
49
+ or contiguous file, is a GNU long-name or PAX header, is named twice, fails its
50
+ header checksum, uses base-256, is neither ustar nor GNU, a lone zero block,
51
+ data after the end, a truncated archive; the declared member missing; the
52
+ member's SHA-256 not its pin. Every one of these ends `Nothing was written.`
53
+
54
+ After the write: a file that does not read back as declared (a create is
55
+ removed). Errors are tagged (`BinaryRefused`, `DownloadFailed`,
56
+ `ChecksumMismatch`, `ArchiveRefused`) and carry the house refusal sentence.
57
+ ⚠️ Tagged, not typed: no caller can `catchTag` one yet (`error-channel.test.ts`).