@homeflare/alchemy 0.20.0 → 0.22.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 (191) hide show
  1. package/README.md +27 -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.d.ts +2 -0
  24. package/dist/launchd/index.d.ts.map +1 -1
  25. package/dist/launchd/index.js +225 -120
  26. package/dist/launchd/index.js.map +13 -10
  27. package/dist/launchd/local-runner.d.ts.map +1 -1
  28. package/dist/launchd/port-claims.d.ts +31 -0
  29. package/dist/launchd/port-claims.d.ts.map +1 -0
  30. package/dist/launchd/runner.d.ts +17 -2
  31. package/dist/launchd/runner.d.ts.map +1 -1
  32. package/dist/launchd/sudo-runner.d.ts +11 -0
  33. package/dist/launchd/sudo-runner.d.ts.map +1 -1
  34. package/dist/linux/index.js.map +3 -3
  35. package/dist/linux/sudo-listing.d.ts +47 -0
  36. package/dist/linux/sudo-listing.d.ts.map +1 -0
  37. package/dist/proxmox/credential-literals.d.ts +1 -1
  38. package/dist/proxmox/credential-literals.d.ts.map +1 -1
  39. package/dist/proxmox/generated/pbs/nodes-node.d.ts +1 -1
  40. package/dist/proxmox/generated/pbs/nodes-node.d.ts.map +1 -1
  41. package/dist/proxmox/generated/pbs.d.ts +7 -0
  42. package/dist/proxmox/generated/pbs.d.ts.map +1 -1
  43. package/dist/proxmox/generated/pve.d.ts +7 -0
  44. package/dist/proxmox/generated/pve.d.ts.map +1 -1
  45. package/dist/proxmox/index.d.ts +1 -1
  46. package/dist/proxmox/index.d.ts.map +1 -1
  47. package/dist/proxmox/index.js +2 -2
  48. package/dist/proxmox/index.js.map +9 -9
  49. package/dist/proxmox/pbs-notification-target-form.d.ts +1 -1
  50. package/dist/proxmox/pbs-notification-target-form.d.ts.map +1 -1
  51. package/dist/proxmox/pbs-notification-target.d.ts +1 -1
  52. package/dist/proxmox/pbs-notification-target.d.ts.map +1 -1
  53. package/dist/release/archive.d.ts +11 -0
  54. package/dist/release/archive.d.ts.map +1 -0
  55. package/dist/release/binary-claim.d.ts +53 -0
  56. package/dist/release/binary-claim.d.ts.map +1 -0
  57. package/dist/release/binary-diff.d.ts +38 -0
  58. package/dist/release/binary-diff.d.ts.map +1 -0
  59. package/dist/release/binary-form.d.ts +118 -0
  60. package/dist/release/binary-form.d.ts.map +1 -0
  61. package/dist/release/binary-lifecycle.d.ts +55 -0
  62. package/dist/release/binary-lifecycle.d.ts.map +1 -0
  63. package/dist/release/binary-read.d.ts +11 -0
  64. package/dist/release/binary-read.d.ts.map +1 -0
  65. package/dist/release/binary.d.ts +68 -0
  66. package/dist/release/binary.d.ts.map +1 -0
  67. package/dist/release/catalog.d.ts +113 -0
  68. package/dist/release/catalog.d.ts.map +1 -0
  69. package/dist/release/checksums.d.ts +22 -0
  70. package/dist/release/checksums.d.ts.map +1 -0
  71. package/dist/release/declared-pins.d.ts +4 -0
  72. package/dist/release/declared-pins.d.ts.map +1 -0
  73. package/dist/release/download.d.ts +44 -0
  74. package/dist/release/download.d.ts.map +1 -0
  75. package/dist/release/fake-deploy.d.ts +48 -0
  76. package/dist/release/fake-deploy.d.ts.map +1 -0
  77. package/dist/release/fake-engine.d.ts +8 -0
  78. package/dist/release/fake-engine.d.ts.map +1 -0
  79. package/dist/release/fake-install.d.ts +39 -0
  80. package/dist/release/fake-install.d.ts.map +1 -0
  81. package/dist/release/fake-provider.d.ts +58 -0
  82. package/dist/release/fake-provider.d.ts.map +1 -0
  83. package/dist/release/fake-release.d.ts +95 -0
  84. package/dist/release/fake-release.d.ts.map +1 -0
  85. package/dist/release/index.d.ts +25 -0
  86. package/dist/release/index.d.ts.map +1 -0
  87. package/dist/release/index.js +1425 -0
  88. package/dist/release/index.js.map +36 -0
  89. package/dist/release/openbao.d.ts +35 -0
  90. package/dist/release/openbao.d.ts.map +1 -0
  91. package/dist/release/providers.d.ts +19 -0
  92. package/dist/release/providers.d.ts.map +1 -0
  93. package/dist/release/refused.d.ts +39 -0
  94. package/dist/release/refused.d.ts.map +1 -0
  95. package/dist/release/tar.d.ts +14 -0
  96. package/dist/release/tar.d.ts.map +1 -0
  97. package/dist/release/victoria.d.ts +36 -0
  98. package/dist/release/victoria.d.ts.map +1 -0
  99. package/dist/{proxmox → secrets}/write-only.d.ts.map +1 -1
  100. package/docs/launchd-ports.md +109 -0
  101. package/docs/launchd.md +5 -0
  102. package/docs/release-binary-catalogs.md +138 -0
  103. package/docs/release-binary-measured.md +163 -0
  104. package/docs/release-binary-openbao.md +151 -0
  105. package/docs/release-binary-refusals.md +57 -0
  106. package/docs/release-binary-upstream.md +163 -0
  107. package/docs/release-binary.md +165 -0
  108. package/package.json +5 -1
  109. package/src/caddy/access-forward-auth.test.ts +63 -0
  110. package/src/caddy/access-forward-auth.ts +120 -0
  111. package/src/caddy/index.ts +10 -0
  112. package/src/caddy/providers.test.ts +4 -0
  113. package/src/cloudflare/mcp-server-api.ts +294 -0
  114. package/src/cloudflare/mcp-server-form.ts +219 -0
  115. package/src/index.ts +1 -0
  116. package/src/launchd/file-converge.ts +233 -0
  117. package/src/launchd/file-identity.ts +69 -0
  118. package/src/launchd/host-file-alias.test.ts +65 -0
  119. package/src/launchd/host-file-form.ts +2 -1
  120. package/src/launchd/host-file-lifecycle.ts +25 -144
  121. package/src/launchd/host-file.ts +8 -2
  122. package/src/launchd/index.ts +2 -0
  123. package/src/launchd/local-runner.test.ts +12 -1
  124. package/src/launchd/local-runner.ts +2 -0
  125. package/src/launchd/port-claims.test.ts +124 -0
  126. package/src/launchd/port-claims.ts +102 -0
  127. package/src/launchd/providers.test.ts +3 -0
  128. package/src/launchd/runner.ts +17 -2
  129. package/src/launchd/sudo-runner.ts +11 -0
  130. package/src/linux/sudo-listing.ts +122 -0
  131. package/src/proxmox/credential-literals.ts +1 -1
  132. package/src/proxmox/generated/pbs/nodes-node.ts +1 -1
  133. package/src/proxmox/generated/pbs.ts +7 -0
  134. package/src/proxmox/generated/pve.ts +7 -0
  135. package/src/proxmox/index.ts +1 -1
  136. package/src/proxmox/pbs-notification-target-form.ts +1 -1
  137. package/src/proxmox/pbs-notification-target-lifecycle.ts +1 -1
  138. package/src/proxmox/pbs-notification-target-wire.ts +1 -1
  139. package/src/proxmox/pbs-notification-target.ts +1 -1
  140. package/src/release/adopt-parity.test.ts +106 -0
  141. package/src/release/apply-pins.test.ts +128 -0
  142. package/src/release/archive.ts +94 -0
  143. package/src/release/binary-alias.test.ts +109 -0
  144. package/src/release/binary-claim.ts +80 -0
  145. package/src/release/binary-claims.test.ts +47 -0
  146. package/src/release/binary-diff.ts +73 -0
  147. package/src/release/binary-form.ts +229 -0
  148. package/src/release/binary-lifecycle.test.ts +90 -0
  149. package/src/release/binary-lifecycle.ts +202 -0
  150. package/src/release/binary-read.ts +55 -0
  151. package/src/release/binary-refusals.test.ts +192 -0
  152. package/src/release/binary-sudo.test.ts +93 -0
  153. package/src/release/binary.ts +181 -0
  154. package/src/release/catalog.test.ts +167 -0
  155. package/src/release/catalog.ts +238 -0
  156. package/src/release/checksums.test.ts +35 -0
  157. package/src/release/checksums.ts +43 -0
  158. package/src/release/declared-pins.ts +39 -0
  159. package/src/release/download.test.ts +138 -0
  160. package/src/release/download.ts +110 -0
  161. package/src/release/error-channel.test.ts +69 -0
  162. package/src/release/fake-deploy.ts +87 -0
  163. package/src/release/fake-engine.ts +50 -0
  164. package/src/release/fake-install.ts +59 -0
  165. package/src/release/fake-provider.ts +68 -0
  166. package/src/release/fake-release.ts +185 -0
  167. package/src/release/fixtures/openbao/checksums.txt +83 -0
  168. package/src/release/fixtures/openbao/checksums.txt.gpgsig +0 -0
  169. package/src/release/fixtures/openbao/darwin-assets.txt +12 -0
  170. package/src/release/fixtures/openbao/openbao-gpg-pub-20240618.asc +109 -0
  171. package/src/release/fixtures/victoria/darwin-arm64-assets.txt +22 -0
  172. package/src/release/fixtures/victoria/victoria-logs-darwin-arm64-v1.52.0_checksums.txt +2 -0
  173. package/src/release/fixtures/victoria/victoria-metrics-darwin-arm64-v1.151.0_checksums.txt +2 -0
  174. package/src/release/fixtures/victoria/victoria-traces-darwin-arm64-v0.10.0_checksums.txt +2 -0
  175. package/src/release/fixtures/victoria/vmutils-darwin-arm64-v1.151.0_checksums.txt +8 -0
  176. package/src/release/index.ts +44 -0
  177. package/src/release/openbao.test.ts +120 -0
  178. package/src/release/openbao.ts +86 -0
  179. package/src/release/plan.test.ts +189 -0
  180. package/src/release/provider-refusals.test.ts +48 -0
  181. package/src/release/providers.test.ts +175 -0
  182. package/src/release/providers.ts +24 -0
  183. package/src/release/recovery-read.test.ts +44 -0
  184. package/src/release/refused.ts +40 -0
  185. package/src/release/tar.test.ts +142 -0
  186. package/src/release/tar.ts +195 -0
  187. package/src/release/victoria.test.ts +108 -0
  188. package/src/release/victoria.ts +154 -0
  189. package/src/{proxmox → secrets}/write-only.ts +6 -0
  190. /package/dist/{proxmox → secrets}/write-only.d.ts +0 -0
  191. /package/src/{proxmox → secrets}/write-only.test.ts +0 -0
@@ -0,0 +1,39 @@
1
+ declare const BinaryRefused_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
2
+ readonly _tag: "BinaryRefused";
3
+ } & Readonly<A>;
4
+ /** The declaration itself: a version no catalog pins, a malformed pin, a bad path or mode. */
5
+ export declare class BinaryRefused extends BinaryRefused_base<{
6
+ readonly message: string;
7
+ }> {
8
+ }
9
+ declare const DownloadFailed_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
10
+ readonly _tag: "DownloadFailed";
11
+ } & Readonly<A>;
12
+ /** The download: a status that is not 200, a body that is not the pinned size, a dead transport. */
13
+ export declare class DownloadFailed extends DownloadFailed_base<{
14
+ readonly message: string;
15
+ /** Worth another attempt: a transport failure or a 5xx, never a wrong size or a 404. */
16
+ readonly retryable: boolean;
17
+ }> {
18
+ }
19
+ declare const ChecksumMismatch_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
20
+ readonly _tag: "ChecksumMismatch";
21
+ } & Readonly<A>;
22
+ /** Bytes that do not hash to their pin — the archive, or a member extracted from it. */
23
+ export declare class ChecksumMismatch extends ChecksumMismatch_base<{
24
+ readonly message: string;
25
+ readonly subject: string;
26
+ readonly expected: string;
27
+ readonly actual: string;
28
+ }> {
29
+ }
30
+ declare const ArchiveRefused_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
31
+ readonly _tag: "ArchiveRefused";
32
+ } & Readonly<A>;
33
+ /** An archive this reader will not trust: an unsafe or unexpected entry, a missing member. */
34
+ export declare class ArchiveRefused extends ArchiveRefused_base<{
35
+ readonly message: string;
36
+ }> {
37
+ }
38
+ export {};
39
+ //# sourceMappingURL=refused.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refused.d.ts","sourceRoot":"","sources":["../../src/release/refused.ts"],"names":[],"mappings":";;;AAgBA,8FAA8F;AAC9F,qBAAa,aAAc,SAAQ,mBAAkC;IACnE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,CAAC;CAAG;;;;AAEL,oGAAoG;AACpG,qBAAa,cAAe,SAAQ,oBAAmC;IACrE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,wFAAwF;IACxF,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B,CAAC;CAAG;;;;AAEL,wFAAwF;AACxF,qBAAa,gBAAiB,SAAQ,sBAAqC;IACzE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,CAAC;CAAG;;;;AAEL,8FAA8F;AAC9F,qBAAa,cAAe,SAAQ,oBAAmC;IACrE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,CAAC;CAAG"}
@@ -0,0 +1,14 @@
1
+ /** Concatenate chunks into one buffer. */
2
+ export declare const concatBytes: (parts: readonly Uint8Array[]) => Uint8Array;
3
+ export type TarContents = {
4
+ /** The asked-for regular files, by exact name. An asked-for name that is absent is absent here. */
5
+ readonly members: ReadonlyMap<string, Uint8Array>;
6
+ /** Every entry name, in archive order — so a refusal can say what the archive did hold. */
7
+ readonly names: readonly string[];
8
+ };
9
+ /** A push parser: `push` each chunk of the unzipped stream, then `finish`. Either may throw. */
10
+ export declare const tarReader: (wanted: ReadonlySet<string>) => {
11
+ finish: () => TarContents;
12
+ push: (chunk: Uint8Array) => void;
13
+ };
14
+ //# sourceMappingURL=tar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tar.d.ts","sourceRoot":"","sources":["../../src/release/tar.ts"],"names":[],"mappings":"AAqFA,0CAA0C;AAC1C,eAAO,MAAM,WAAW,UAAW,SAAS,UAAU,EAAE,KAAG,UAQ1D,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,mGAAmG;IACnG,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAClD,2FAA2F;IAC3F,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CACnC,CAAC;AAEF,gGAAgG;AAChG,eAAO,MAAM,SAAS,WAAY,WAAW,CAAC,MAAM,CAAC;kBAoFhC,WAAW;kBAlCT,UAAU,KAAG,IAAI;CAwCvC,CAAC"}
@@ -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"}
@@ -0,0 +1,109 @@
1
+ # Port collisions, before a job is declared — `claimPorts()`
2
+
3
+ Status: active
4
+ Verified: 2026-09-23
5
+
6
+ `claimPorts(claims)` refuses two of a stack's own jobs sharing one port number, in the stack's own
7
+ program, before `alchemy plan` asks a single provider anything. It is not a provider and it is not
8
+ a `LaunchdJob` prop — it is a plain function a host stack calls itself, the same shape as
9
+ [`catalogBinary()`](../src/release/catalog.ts) (see [release-binary-upstream.md](./release-binary-upstream.md)).
10
+
11
+ ```ts
12
+ import { claimPorts } from '@homeflare/alchemy/launchd';
13
+
14
+ claimPorts([
15
+ { owner: 'vmalert', port: 8880 },
16
+ { owner: 'vmalert-logs', port: 8881, address: '127.0.0.1' },
17
+ ]);
18
+ // ...then declare the jobs themselves, as normal.
19
+ ```
20
+
21
+ ## Why the stack program, not a provider
22
+
23
+ Two structural facts about Alchemy 2.0.0-beta.79 rule out a provider-side check:
24
+
25
+ - **A provider never diffs a first `create` whose props still hold an unresolved `Output`.**
26
+ Every mini job's `programArguments` carries a `HostFile` config digest as an `Output`, so a
27
+ check living in `LaunchdJob`'s `diff` would be skipped on exactly the deploy that matters most —
28
+ the first one.
29
+ - **A provider never sees its sibling resources.** Nothing in the `LaunchdJob` handlers can read
30
+ what port another declared job holds; the stack program is the only place that sees the whole
31
+ set at once.
32
+
33
+ So `claimPorts` runs where the stack itself calls it, ordered however the stack likes (typically
34
+ right before the jobs it describes), and fails the **plan** — nothing is fetched, written, or
35
+ `launchctl`'d — if two claims collide.
36
+
37
+ ## Why it keys on the port number alone
38
+
39
+ `house/nix/homeflare-config/hosts/macmini/modules/lib-ports.nix` (a different repo —
40
+ `homeflare/homeflare`, read at `92cff7f`, 2026-09-23) is the check this replaces for a
41
+ kit-declared job. Its `assertNoCollision` is:
42
+
43
+ ```nix
44
+ dupes = lib.subtractLists (lib.unique vals) vals; # vals = lib.attrValues ports
45
+ ```
46
+
47
+ — every port **value** in the registry, address and protocol never read. `portClaimProblems`
48
+ reproduces that rule rather than a looser one, because that is the strictness the registry already
49
+ enforces across the whole estate, and a kit-side check that disagreed with it would be a second,
50
+ inconsistent source of truth.
51
+
52
+ ### The measured bind table
53
+
54
+ What that trades away is real: the OS itself is looser than the registry. Measured on the mini
55
+ (Darwin 27.2.0) with `/usr/bin/python3` sockets, `SO_REUSEADDR` set on both sides, loopback high
56
+ ports — first on 2026-09-23 at 47913–47917, **re-measured independently the same day** at
57
+ 47923–47927 with the same result:
58
+
59
+ | first bind | second bind | outcome |
60
+ | ------------------- | ----------- | ------------------------ |
61
+ | `127.0.0.1` | `127.0.0.1` | **refused** — EADDRINUSE |
62
+ | `0.0.0.0` | `127.0.0.1` | both bind |
63
+ | `127.0.0.1` | `0.0.0.0` | both bind |
64
+ | `[::]` (`V6ONLY=0`) | `127.0.0.1` | both bind |
65
+ | `[::1]` | `127.0.0.1` | both bind |
66
+
67
+ Only an **exact** address+port duplicate fails at bind time; a wildcard next to a specific address
68
+ on the same port silently splits traffic between two listeners instead of refusing. `claimPorts`
69
+ refuses that pairing anyway — the port number is the key, exactly like the registry — because
70
+ "the OS allowed it" is not the same claim as "the estate meant two things to share a port."
71
+
72
+ - **Every mini daemon this affects is Go**, and Go sets `SO_REUSEADDR` on **every** darwin
73
+ listener unconditionally: `setDefaultListenerSockopts` in `net/sockopt_bsd.go` (Go 1.26.5,
74
+ installed at `/opt/homebrew/Cellar/go/1.26.5`, `//go:build darwin || …`) —
75
+ `syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)`, no opt-out. The
76
+ Victoria family, Prometheus-family exporters and OpenBao are all Go, so the table above is their
77
+ actual behaviour, not a worst case.
78
+ - **vector is Rust.** Whether it also sets `SO_REUSEADDR` by default is **unmeasured** — say so
79
+ rather than assume Go's default carries over.
80
+ - **launchd itself has no port concept to collide on.** `man 5 launchd.plist`'s only socket-shaped
81
+ key is `Sockets` (launch-on-demand socket activation via `launch_activate_socket(3)`), and no
82
+ `LaunchdJobProps` today exposes it — a daemon binds its own port itself, launchd never mediates
83
+ it. So this check is squarely about daemons colliding with each other at bind time, not about
84
+ anything launchd enforces.
85
+
86
+ ## What it cannot see
87
+
88
+ - **A job the stack does not pass.** `claimPorts` takes exactly the array a stack builds and
89
+ hands it; there is no argv-parsing, no config-file reading and no host inspection here to
90
+ recover a port a stack forgot to include. Leaving a job's claim out of the list is invisible to
91
+ this check, the same way a port left out of `lib-ports.nix` was invisible on 2026-09-02.
92
+ - **A daemon still on Nix**, unless the migrating stack also passes that daemon's port as a claim
93
+ of its own (owner naming the Nix job, so the message is legible during a cutover). Until a job
94
+ moves off Nix, `lib-ports.nix` is still the thing keeping it collision-free; `claimPorts` only
95
+ starts covering a port once a kit stack declares the job that binds it.
96
+ - **Two protocols of the same port number.** Keying on the port alone means a daemon on `tcp/9094`
97
+ and another on `udp/9094` would be refused as a collision, same as the Nix registry. No mini job
98
+ does that today (checked against the registry at `92cff7f`); if one needs to, the key widens
99
+ deliberately, in its own change — not silently here.
100
+
101
+ ## The error is a defect, not a typed channel
102
+
103
+ `claimPorts` throws `PortRefused` (a `Data.TaggedError`), and like `catalogBinary()`, that throw
104
+ inside a stack program's `Effect.gen` body is a **defect** — `Effect.catchTag('PortRefused', …)`
105
+ never sees it, only `Effect.runPromiseExit` plus `Cause.hasDies`/`Cause.squash`. This is the exact
106
+ gap [release-binary-upstream.md](./release-binary-upstream.md) names as gap 10 for
107
+ `catalogBinary()`; it applies here unchanged because it is the same shape of function in the same
108
+ kind of caller, not a new hole. `port-claims.test.ts` measures it the same way
109
+ `error-channel.test.ts` measures `catalogBinary()`'s.
package/docs/launchd.md CHANGED
@@ -165,6 +165,11 @@ Nix job is booted out first. Per job:
165
165
  - **Rolling back** is the same three steps reversed: destroy (or remove) the resource, restore the
166
166
  job in the Nix config, activate.
167
167
 
168
+ ## Port collisions
169
+
170
+ `claimPorts()` refuses two of a stack's own jobs on one port, in the stack program, before any
171
+ job is declared: [launchd-ports.md](./launchd-ports.md).
172
+
168
173
  ## The runner
169
174
 
170
175
  `HostRunner` (see `src/launchd/runner.ts`) is `exec(argv)` (never through a shell), `readFile`,
@@ -0,0 +1,138 @@
1
+ # Release binaries — the data sets, and adding one
2
+
3
+ Status: active
4
+ Verified: 2026-09-23
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`
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
+ ## `OPENBAO_RELEASES` — the first data set with a `computed` member
40
+
41
+ | package | version | asset (darwin_arm64) | binaries (installed ← member) |
42
+ | --------- | ------- | ----------------------------------- | ----------------------------------------------------------- |
43
+ | `openbao` | 2.6.2 | `openbao_2.6.2_darwin_arm64.tar.gz` | `bao` ← `bao` (member digest: `computed`, ours — see below) |
44
+
45
+ Walked down 2026-09-23, every command in
46
+ [release-binary-openbao.md](./release-binary-openbao.md).
47
+
48
+ - ★ **Platform key is `darwin_arm64`, underscore** — OpenBao's own asset
49
+ spelling, not Victoria's `darwin-arm64`. Each data set spells the platform
50
+ the way that vendor's asset names do; nothing here picks one estate-wide
51
+ convention (`openbao.ts`'s header has the rule and the one-line change if a
52
+ reviewer wants it unified).
53
+ - ★ **The member digest is `computed`, not `members`.** `checksums.txt` lists
54
+ archives and every `.sbom.json`, never `bao` — the case this page used to
55
+ call out as "that field does not exist yet". It exists now: `catalogBinary`,
56
+ `catalogProblems` and `identifyBinary` all read `members` first, then
57
+ `computed`, and refuse a member pinned in both (`catalog.ts`).
58
+ - ★ **GPG checked, Sigstore read but not verified.** `gpgv` against the key
59
+ published at openbao.org confirmed the signature over `checksums.txt`; the
60
+ key's own fingerprint matches OpenBao's install docs. `cosign` is not on
61
+ the mini, so the Sigstore bundle's workflow identity is recorded as
62
+ unverified, not as a passing check.
63
+
64
+ ## Adding a version of a vendor already here
65
+
66
+ Walk it down from the vendor, never from memory:
67
+
68
+ 1. `gh api repos/<owner>/<repo>/releases/tags/<tag>`: the exact asset names,
69
+ sizes and `digest`s, and `immutable`.
70
+ 2. Fetch the checksum file. Its SHA-256 must equal GitHub's digest of it, and
71
+ its archive line must equal the archive's digest.
72
+ 3. Stream the archive through `tar -tvzf -`, saving nothing: root-level
73
+ regular files with the expected member names, nothing else.
74
+ 4. Add the entry with `recorded` set to that day. Commit the checksum file
75
+ under `src/release/fixtures/<vendor>/` and add the asset names to the
76
+ listing there. `<vendor>.test.ts` then holds the pins to it.
77
+ 5. Re-check attestations with a positive control, so a 404 means something:
78
+ `gh api repos/<owner>/<repo>/attestations/sha256:<digest>` next to a repo
79
+ known to publish them (`cli/cli` answered 2 on 2026-09-22).
80
+
81
+ ⛔ Never `latest`, and never sort by publish date. VictoriaLogs v1.51.1 was
82
+ published after v1.52.0, and VictoriaMetrics interleaves LTS lines.
83
+
84
+ ## Adding a vendor
85
+
86
+ ⛔ **Only after that vendor's own walk-down measures two things**, recorded in
87
+ its data module's header with the date:
88
+
89
+ - **Its checksum format, byte-exact.** `checksums.ts` parses one measured shape,
90
+ `sha256sum` text mode listing the archive and every member. Most vendors the
91
+ estate census found (landscape PR 88) list **archives only**: Prometheus's
92
+ `sha256sums.txt`, vector's `-SHA256SUMS`, pyroscope's and OpenBao's
93
+ `checksums.txt`. For those the member digest is not a vendor fact. It is
94
+ computed from a verified archive, and the data set says so under
95
+ `computed` rather than filing it beside the vendor's `members` lines
96
+ (`catalog.ts`; `OPENBAO_RELEASES` above is the first consumer).
97
+ - **Its binary's linkage** (`otool -L` on the extracted member, or `ldd`).
98
+ Vendor binaries were measured self-contained **only for the five Victoria
99
+ ones** (system libraries and `/usr/lib/libresolv.9.dylib` only). That is not
100
+ a property of "a Go binary". The Nix vector links jemalloc, librdkafka,
101
+ oniguruma and libiconv from `/nix/store`, and whether the vendor tarball
102
+ bundles them is unknown until someone inspects the extracted member.
103
+
104
+ Also carried over from the Victoria walk-down, and checked again per vendor:
105
+ exact asset names against near-identical siblings; the tar layout; the
106
+ `immutable` flag; signatures and attestations with a positive control; and
107
+ where `-version` prints, if anything ever runs it (Victoria's goes to stderr).
108
+
109
+ The queue, from the census, each **not yet walked for this resource**:
110
+ alertmanager, blackbox_exporter, node_exporter and postgres_exporter
111
+ (Prometheus `sha256sums.txt`), unpoller (detached `.sig`; 5.2.7 renames
112
+ `darwin_arm64` to `darwin_all`), pyroscope and vector. `bao` (OpenBao) is
113
+ walked — see `OPENBAO_RELEASES` above.
114
+
115
+ ⚠️ REASONED NOT MEASURED: the Prometheus-family archives wrap their files in a
116
+ directory, and `tar.ts` refuses a directory entry (and any PAX header) for the
117
+ whole archive. Walking those down includes a reader change, not only a data
118
+ set ([release-binary-upstream.md](./release-binary-upstream.md), gap 11).
119
+
120
+ ## What this resource is not for
121
+
122
+ - **A whole tree.** Grafana needs its homepath and plugins, and an interpreter
123
+ like python-build-standalone needs its `lib/`. That is a **tree mode**:
124
+ unpack an archive into a directory. It is a separate, later capability with
125
+ its own walk-down, not a flag bent onto `Release.Binary`. Grafana's darwin
126
+ builds also come from grafana.com's downloads API, not a GitHub release. (The
127
+ house's stdlib-only Python jobs are tracked for a Bun/TS rewrite instead of a
128
+ vendored interpreter, decided 2026-09-23.)
129
+ - **Another source kind.** Only GitHub release downloads, whose URL is
130
+ `https://github.com/<repo>/releases/download/<tag>/<asset>`. A second kind
131
+ (grafana.com JSON, Codeberg) arrives with its first consumer.
132
+ - **Another archive format.** Only `.tar.gz`/`.tgz`. A `.zip` or a bare
133
+ binary is refused at plan rather than downloaded and then not unpacked.
134
+ - **Builds from source.** Postgres, pgBackRest and Valkey ship no darwin
135
+ binary. Caddy with its plugins and cloudflare-exporter are to be built by the
136
+ public `taslabs-net/homeflare-builds` repo, which publishes GitHub releases
137
+ with a `SHA256SUMS` file (decided 2026-09-23). Each then becomes an ordinary
138
+ 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.