@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,22 @@
1
+ victoria-logs-darwin-arm64-v1.52.0-enterprise.tar.gz
2
+ victoria-logs-darwin-arm64-v1.52.0-enterprise_checksums.txt
3
+ victoria-logs-darwin-arm64-v1.52.0.tar.gz
4
+ victoria-logs-darwin-arm64-v1.52.0_checksums.txt
5
+ victoria-metrics-darwin-arm64-v1.151.0-cluster.tar.gz
6
+ victoria-metrics-darwin-arm64-v1.151.0-cluster_checksums.txt
7
+ victoria-metrics-darwin-arm64-v1.151.0-enterprise-cluster.tar.gz
8
+ victoria-metrics-darwin-arm64-v1.151.0-enterprise-cluster_checksums.txt
9
+ victoria-metrics-darwin-arm64-v1.151.0-enterprise.tar.gz
10
+ victoria-metrics-darwin-arm64-v1.151.0-enterprise_checksums.txt
11
+ victoria-metrics-darwin-arm64-v1.151.0.tar.gz
12
+ victoria-metrics-darwin-arm64-v1.151.0_checksums.txt
13
+ victoria-traces-darwin-arm64-v0.10.0.tar.gz
14
+ victoria-traces-darwin-arm64-v0.10.0_checksums.txt
15
+ vlutils-darwin-arm64-v1.52.0-enterprise.tar.gz
16
+ vlutils-darwin-arm64-v1.52.0-enterprise_checksums.txt
17
+ vlutils-darwin-arm64-v1.52.0.tar.gz
18
+ vlutils-darwin-arm64-v1.52.0_checksums.txt
19
+ vmutils-darwin-arm64-v1.151.0-enterprise.tar.gz
20
+ vmutils-darwin-arm64-v1.151.0-enterprise_checksums.txt
21
+ vmutils-darwin-arm64-v1.151.0.tar.gz
22
+ vmutils-darwin-arm64-v1.151.0_checksums.txt
@@ -0,0 +1,2 @@
1
+ 3157d4b6181d8a7e3e30918e2cbfcd4cc4cb66263e3ef21ea91e4f20f8980883 victoria-logs-darwin-arm64-v1.52.0.tar.gz
2
+ 9e48809e314902782959b3a4e683a3087476759d0293791a0564921c676ca7be victoria-logs-prod
@@ -0,0 +1,2 @@
1
+ 8792437c4c0b63719fed7b44ad18d4b4167bbe6861ab3e84c7bb010c922e7582 victoria-metrics-darwin-arm64-v1.151.0.tar.gz
2
+ 3c493a95ac1e9034a3261b230c4d62f3b186a04aafcdbacc99bfc110dc5de6a1 victoria-metrics-prod
@@ -0,0 +1,2 @@
1
+ cbe83f1d409cbb85fbf1c890c4a9b7fd34c1d74acec4dce2e0ebe46fcb260e38 victoria-traces-darwin-arm64-v0.10.0.tar.gz
2
+ 4759ec89a466129b9eefc22244a111e1f489739340747805fd93b58fd0f7b2dd victoria-traces-prod
@@ -0,0 +1,8 @@
1
+ 27d68bac90e28929214091ed9d27f2e9fef25a080407e2c8661ea9b52dd6b183 vmutils-darwin-arm64-v1.151.0.tar.gz
2
+ d3b6fa1f71ef48beac619f15f8adfa7610e76605caf37db1ebfbb379b6692329 vmagent-prod
3
+ 62845795167e9ff47890e83ac4ac5934693c105767db88591c95a08f3c4fb354 vmalert-prod
4
+ cbffb9495e4bb1457c9d45e0a7064b3a3a29b2cfd1ae24bfe8f00ec327c26998 vmalert-tool-prod
5
+ 398df9e0f5b7035d037e39ee9ff66a10ddde5333d832602463aa4d2865fdb508 vmauth-prod
6
+ 1974a2ee211b49bf581287808e010846f6c47ff135276214c6da628567777b9e vmbackup-prod
7
+ fc824df7215b01b57921ed46afde7fea6b6698adf13d9ea8e3fe36e194ef8190 vmrestore-prod
8
+ 998b111eee206b98e6d25fdf2fbaa455ecc17c7f9e7c3b2373e127cef762459f vmctl-prod
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Release binaries for Alchemy — one binary out of a pinned release archive, installed onto a host
3
+ * and verified against digests the stack pins in code, plus the vendor data sets those pins come
4
+ * from (VictoriaMetrics' is the first).
5
+ *
6
+ * ⛔ THIS BARREL IS THE PUBLIC API, AND IT IS DELIBERATELY SMALLER THAN THE DIRECTORY. The tar
7
+ * reader, the downloader, the checksum parser, the lifecycle and the test seams are internals; an
8
+ * `export *` would publish them as API and make the next refactor a breaking change.
9
+ * ⛔ IT INSTALLS; IT NEVER STARTS. Run the binary with `LaunchdJob` (the launchd subpath) or a
10
+ * systemd unit, putting `ReleaseBinary`'s `path` in its argv. See docs/release-binary.md.
11
+ * ★ THE SEAM IS SHARED, NOT COPIED. `HostRunner` is re-exported so a stack types its runner without
12
+ * importing from another subpath.
13
+ */
14
+ export type {
15
+ PinnedDownload,
16
+ ReleaseArchive,
17
+ ReleaseBinaryAttributes,
18
+ ReleaseBinaryProps,
19
+ } from './binary-form.ts';
20
+ export { releaseBinaryPath, releaseUrl } from './binary-form.ts';
21
+ export { ReleaseBinary, ReleaseBinaryProvider } from './binary.ts';
22
+ export type {
23
+ CatalogPackage,
24
+ CatalogRequest,
25
+ ChecksumSignature,
26
+ ComputedDigest,
27
+ IdentifiedBinary,
28
+ PinnedArchive,
29
+ PinnedBinary,
30
+ ReleaseCatalog,
31
+ } from './catalog.ts';
32
+ export { catalogBinary, catalogDirectory, catalogProblems, identifyBinary } from './catalog.ts';
33
+ export { OPENBAO_RELEASES } from './openbao.ts';
34
+ export { releaseProviders } from './providers.ts';
35
+ export { ArchiveRefused, BinaryRefused, ChecksumMismatch, DownloadFailed } from './refused.ts';
36
+ export { VICTORIA_RELEASES } from './victoria.ts';
37
+ export type {
38
+ ExecResult,
39
+ FileStat,
40
+ HostRunner,
41
+ HostUser,
42
+ WriteOptions,
43
+ } from '../launchd/runner.ts';
44
+ export { HostRunnerService, hostRunnerLayer } from '../launchd/runner.ts';
@@ -0,0 +1,120 @@
1
+ /**
2
+ * The REAL OpenBao data set against the vendor's own files — no network: fixtures/openbao/ holds
3
+ * checksums.txt, its detached GPG signature and the signing key byte-for-byte as fetched
4
+ * 2026-09-23 (each one's SHA-256 equals GitHub's / openbao.org's digest for it that day).
5
+ *
6
+ * ★ THE CHAIN THIS PROVES: fixture bytes = the recorded digest → the strict parser reads exactly
7
+ * its 83 lines → none of them names `bao` → the pin is filed under `computed`, never `members`.
8
+ * The GPG check is NOT re-run here (gpgv needs a binary this repo does not vendor); it is a
9
+ * recorded one-time measurement (docs/release-binary-openbao.md), same status as Victoria's
10
+ * "consistency, not provenance" note.
11
+ */
12
+ import { describe, expect, test } from 'bun:test';
13
+ import { sha256Hex } from '../launchd/job-form.ts';
14
+ import { releaseUrl } from './binary-form.ts';
15
+ import { catalogBinary } from './catalog.ts';
16
+ import { parseChecksums } from './checksums.ts';
17
+ import { OPENBAO_RELEASES } from './openbao.ts';
18
+
19
+ const fixture = (name: string) => Bun.file(new URL(`fixtures/openbao/${name}`, import.meta.url));
20
+ const PLATFORM = 'darwin_arm64';
21
+ const archive = OPENBAO_RELEASES.packages.openbao?.versions['2.6.2']?.[PLATFORM];
22
+ if (archive === undefined)
23
+ throw new Error('fixture setup: OPENBAO_RELEASES has no darwin_arm64 pin');
24
+
25
+ describe('the OpenBao data set is the vendor checksum file', () => {
26
+ test('one package, one version, one platform', () => {
27
+ expect(Object.keys(OPENBAO_RELEASES.packages)).toEqual(['openbao']);
28
+ expect(Object.keys(OPENBAO_RELEASES.packages.openbao?.versions ?? {})).toEqual(['2.6.2']);
29
+ expect(Object.keys(OPENBAO_RELEASES.packages.openbao?.versions['2.6.2'] ?? {})).toEqual([
30
+ PLATFORM,
31
+ ]);
32
+ });
33
+
34
+ test('archive pin: repo, tag, asset, URL', () => {
35
+ expect([archive.repo, archive.tag, archive.asset]).toEqual([
36
+ 'openbao/openbao',
37
+ 'v2.6.2',
38
+ 'openbao_2.6.2_darwin_arm64.tar.gz',
39
+ ]);
40
+ expect(releaseUrl(archive.repo, archive.tag, archive.asset)).toBe(
41
+ `https://github.com/openbao/openbao/releases/download/v2.6.2/openbao_2.6.2_darwin_arm64.tar.gz`,
42
+ );
43
+ });
44
+
45
+ test('checksums.txt: 83 lines, byte-identical to the recorded digest, archive line equals the pin', async () => {
46
+ const bytes = new Uint8Array(await fixture('checksums.txt').arrayBuffer());
47
+ expect(sha256Hex(bytes)).toBe(archive.checksums.sha256);
48
+ expect(archive.checksums.url).toBe(
49
+ 'https://github.com/openbao/openbao/releases/download/v2.6.2/checksums.txt',
50
+ );
51
+ const lines = parseChecksums(new TextDecoder().decode(bytes));
52
+ expect(lines.size).toBe(83);
53
+ expect(lines.get(archive.asset)).toBe(archive.sha256);
54
+ });
55
+
56
+ test('no line names the `bao` member — it is not a vendor fact', async () => {
57
+ const bytes = new Uint8Array(await fixture('checksums.txt').arrayBuffer());
58
+ const lines = parseChecksums(new TextDecoder().decode(bytes));
59
+ expect(lines.has('bao')).toBe(false);
60
+ expect(archive.members).toEqual({});
61
+ });
62
+
63
+ test('the member digest is filed under `computed`, dated, not under `members`', () => {
64
+ expect(archive.computed?.bao).toEqual({
65
+ recorded: '2026-09-23',
66
+ sha256: 'd476d17e81a35e6d70dd7e86a8ab2a3664313525118f1f1cfe0130e7a2b95f3a',
67
+ });
68
+ });
69
+
70
+ test('the checksums file is GPG-signed, and the signature and key are pinned', async () => {
71
+ const sigBytes = new Uint8Array(await fixture('checksums.txt.gpgsig').arrayBuffer());
72
+ const keyBytes = new Uint8Array(await fixture('openbao-gpg-pub-20240618.asc').arrayBuffer());
73
+ const sig = archive.checksums.signature;
74
+ if (sig === undefined) throw new Error('fixture setup: no checksums.signature pinned');
75
+ expect(sig.kind).toBe('openpgp');
76
+ expect(sha256Hex(sigBytes)).toBe(sig.sha256);
77
+ expect(sha256Hex(keyBytes)).toBe(sig.key.sha256);
78
+ expect(sig.key.fingerprint).toBe('66D15FDD87287219C8E15478D200CD702853E6D0');
79
+ });
80
+
81
+ test('the asset is in the committed darwin listing, and no sibling is pinned', async () => {
82
+ const assets = (await fixture('darwin-assets.txt').text()).trim().split('\n');
83
+ expect(assets).toContain(archive.asset);
84
+ // ★ Every OTHER darwin asset (the amd64 archive, and every .sbom.json / .gpgsig / .sigstore.json
85
+ // next to both) is listed here so a reviewer can see what was excluded, but pinned nowhere.
86
+ const siblings = assets.filter((name) => name !== archive.asset);
87
+ expect(siblings).toEqual([
88
+ 'openbao_2.6.2_darwin_amd64.tar.gz',
89
+ 'openbao_2.6.2_darwin_amd64.tar.gz.gpgsig',
90
+ 'openbao_2.6.2_darwin_amd64.tar.gz.sbom.json',
91
+ 'openbao_2.6.2_darwin_amd64.tar.gz.sbom.json.gpgsig',
92
+ 'openbao_2.6.2_darwin_amd64.tar.gz.sbom.json.sigstore.json',
93
+ 'openbao_2.6.2_darwin_amd64.tar.gz.sigstore.json',
94
+ 'openbao_2.6.2_darwin_arm64.tar.gz.gpgsig',
95
+ 'openbao_2.6.2_darwin_arm64.tar.gz.sbom.json',
96
+ 'openbao_2.6.2_darwin_arm64.tar.gz.sbom.json.gpgsig',
97
+ 'openbao_2.6.2_darwin_arm64.tar.gz.sbom.json.sigstore.json',
98
+ 'openbao_2.6.2_darwin_arm64.tar.gz.sigstore.json',
99
+ ]);
100
+ expect(siblings.some((name) => name.startsWith('openbao-hsm_'))).toBe(false);
101
+ const pinnedPlatforms = Object.keys(OPENBAO_RELEASES.packages.openbao?.versions['2.6.2'] ?? {});
102
+ expect(pinnedPlatforms).not.toContain('darwin_amd64');
103
+ });
104
+
105
+ test('bao out of the data set: the computed member of the one pinned archive', () => {
106
+ const request = { binary: 'bao', package: 'openbao', platform: PLATFORM, version: '2.6.2' };
107
+ expect(catalogBinary(OPENBAO_RELEASES, request)).toEqual({
108
+ archive: {
109
+ asset: 'openbao_2.6.2_darwin_arm64.tar.gz',
110
+ repo: 'openbao/openbao',
111
+ sha256: '4e495376174accc0e014d31e9901f518a974f966850c839f626347eaac05fd52',
112
+ size: 76_833_570,
113
+ tag: 'v2.6.2',
114
+ },
115
+ member: 'bao',
116
+ name: 'bao',
117
+ sha256: 'd476d17e81a35e6d70dd7e86a8ab2a3664313525118f1f1cfe0130e7a2b95f3a',
118
+ });
119
+ });
120
+ });
@@ -0,0 +1,86 @@
1
+ /**
2
+ * OpenBao's release archives, pinned — the second data set for Release.Binary, walked down
3
+ * 2026-09-23 (packages/alchemy/docs/release-binary-openbao.md has every command).
4
+ *
5
+ * ★ THE MEMBER DIGEST IS OURS, NOT THE VENDOR'S. `checksums.txt` lists 83 lines: every archive and
6
+ * every `.sbom.json`, never `bao` itself (measured 2026-09-23 — see checksums.ts's header on why
7
+ * that is normal for this vendor). So `bao`'s SHA-256 sits under `computed`, hashed from the one
8
+ * archive whose OWN SHA-256 already matched the checksums-file pin — never filed under `members`,
9
+ * which is vendor lines only.
10
+ * ★ PLATFORM KEY IS THE VENDOR'S OWN SPELLING: `darwin_arm64`, underscore — OpenBao's asset names
11
+ * read `openbao_2.6.2_darwin_arm64.tar.gz`. Victoria's catalog spells the same idea
12
+ * `darwin-arm64`, hyphen, because THAT vendor's asset names use a hyphen. Nothing here picks one
13
+ * estate-wide convention; a consumer names the platform its data set actually uses. If a reviewer
14
+ * would rather the estate pick one spelling, that is a one-line change to this rule, not to any
15
+ * consumer, made in the same PR that adds the second spelling.
16
+ * ★ GPG, MEASURED, TWO INDEPENDENT CHANNELS: `gpgv` against the dearmored key from openbao.org
17
+ * returned "Good signature from OpenBao <openbao@lists.lfedge.org>" over `checksums.txt` using
18
+ * subkey E617DCD4065C2AFC0B2CF7A7BA8BC08C0F691F94 of primary 66D15FDD87287219C8E15478D200CD702853E6D0
19
+ * (both fingerprints published in OpenBao's own install.mdx at this tag). ⚠️ TRUST ON FIRST USE
20
+ * ACROSS TWO CHANNELS ONE PROJECT RUNS: the release asset and openbao.org are both OpenBao's own
21
+ * infrastructure, so this is not a third party attesting to the key — it raises the bar over no
22
+ * check at all (a swap needs both channels, not one), it does not reach independent provenance.
23
+ * ⛔ SIGSTORE WAS NOT CHECKED: `cosign` is not installed on the mini (measured 2026-09-23). The
24
+ * bundle's certificate SAN (read, not verified) names
25
+ * `https://github.com/openbao/openbao/.github/workflows/release.yml@refs/heads/release/2.6.x`,
26
+ * issuer `https://token.actions.githubusercontent.com` — recorded here as an UNVERIFIED identity,
27
+ * never as a passing check. "GPG or Sigstore" is met by the GPG half alone.
28
+ * ⚠️ THE BUILD THIS PINS IS NOT THE ONE THE VAULT SERVER RUNS. Read from goreleaser.other.yaml at
29
+ * v2.6.2: `CGO_ENABLED=0`, build tag `ui`. Expected to differ from the Nix build in nothing an
30
+ * agent (approle auto-auth, templates, exec) depends on; nobody has run this binary to confirm —
31
+ * docs/release-binary-openbao.md says so and stops there, by house rule (never execute `bao`).
32
+ */
33
+ import type { ReleaseCatalog } from './catalog.ts';
34
+
35
+ const RECORDED = '2026-09-23';
36
+ const REPO = 'openbao/openbao';
37
+ const TAG = 'v2.6.2';
38
+ const DOWNLOAD = `https://github.com/${REPO}/releases/download/${TAG}`;
39
+
40
+ export const OPENBAO_RELEASES: ReleaseCatalog = {
41
+ packages: {
42
+ openbao: {
43
+ // ★ The member is named `bao` itself — no `-prod`-style suffix; the vendor tarball unpacks
44
+ // CHANGELOG.md, LICENSE, README.md and `bao` at its root (measured, full stream, 2026-09-23).
45
+ binaries: { bao: 'bao' },
46
+ versions: {
47
+ '2.6.2': {
48
+ darwin_arm64: {
49
+ asset: 'openbao_2.6.2_darwin_arm64.tar.gz',
50
+ checksums: {
51
+ recorded: RECORDED,
52
+ sha256: '3d4a19fdc54a86fd94ce59ea91316dbf4304180bb27ae818e78e85c7f2164645',
53
+ signature: {
54
+ kind: 'openpgp',
55
+ key: {
56
+ fingerprint: '66D15FDD87287219C8E15478D200CD702853E6D0',
57
+ sha256: '1862a196422947124282e026ea4e09d6f7c5d383b628d90c17906936e6e5a8e0',
58
+ url: 'https://openbao.org/assets/openbao-gpg-pub-20240618.asc',
59
+ },
60
+ sha256: '40f294d642a2b4e9adac766ca6b8b2633e15b3dbfdf63969dedbf46d94df53e8',
61
+ url: `${DOWNLOAD}/checksums.txt.gpgsig`,
62
+ verified: RECORDED,
63
+ },
64
+ url: `${DOWNLOAD}/checksums.txt`,
65
+ },
66
+ // ★ NOT a vendor fact — see the header. Hashed from THIS archive after its own SHA-256
67
+ // (below) already matched the checksums.txt pin; confirmed two ways
68
+ // (extractMembers and `tar -xzOf … bao | shasum -a 256`) and against `tar -tvzf -`.
69
+ computed: {
70
+ bao: {
71
+ recorded: RECORDED,
72
+ sha256: 'd476d17e81a35e6d70dd7e86a8ab2a3664313525118f1f1cfe0130e7a2b95f3a',
73
+ },
74
+ },
75
+ members: {},
76
+ repo: REPO,
77
+ sha256: '4e495376174accc0e014d31e9901f518a974f966850c839f626347eaac05fd52',
78
+ size: 76_833_570,
79
+ tag: TAG,
80
+ },
81
+ },
82
+ },
83
+ },
84
+ },
85
+ vendor: 'OpenBao',
86
+ };
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Release.Binary through Alchemy's OWN Plan and Apply (openbao/fake-stack.ts, in-memory state), with
3
+ * `HostDirectory` declared in front of it, over a fake host that models mkdir/rmdir
4
+ * (linux/fake-linux-host.ts) and a fake HttpClient. No network, no real filesystem.
5
+ *
6
+ * ★ WHAT ONLY THE ENGINE CAN SAY. A handler test shows what `diff` answers; this shows what the
7
+ * engine then DOES with it, in which order: that `directory: dir.path` orders the directory
8
+ * first; that a version bump writes the new binary before the old one goes; that a version
9
+ * outside the catalog fails the plan with nothing fetched and nothing touched.
10
+ */
11
+ import { describe, expect, test } from 'bun:test';
12
+ import * as Effect from 'effect/Effect';
13
+ import * as Layer from 'effect/Layer';
14
+ import * as HttpClient from 'effect/unstable/http/HttpClient';
15
+ import * as HttpClientResponse from 'effect/unstable/http/HttpClientResponse';
16
+ import { sha256Hex } from '../launchd/job-form.ts';
17
+ import { type HostRunner, hostRunnerLayer } from '../launchd/runner.ts';
18
+ import { HostDirectory, HostDirectoryProvider } from '../linux/directory.ts';
19
+ import { fakeLinuxHost } from '../linux/fake-linux-host.ts';
20
+ import { fakeStack } from '../openbao/fake-stack.ts';
21
+ import { releaseUrl } from './binary-form.ts';
22
+ import { ReleaseBinary, makeReleaseBinaryProvider } from './binary.ts';
23
+ import { type PinnedBinary, catalogBinary, catalogDirectory } from './catalog.ts';
24
+ import {
25
+ BINARY,
26
+ VMALERT_REQUEST,
27
+ VMUTILS_URL,
28
+ bytesOf,
29
+ gzip,
30
+ syntheticRelease,
31
+ tarOf,
32
+ } from './fake-release.ts';
33
+ import { VICTORIA_RELEASES } from './victoria.ts';
34
+
35
+ const ROOT = '/opt/example/bin';
36
+ const NEXT = bytesOf('#!fake vmalert 1.152.0\n');
37
+
38
+ /** vmutils 1.151.0 (synthetic, at its real URL) and a synthetic 1.152.0 beside it. */
39
+ const releases = () => {
40
+ const current = syntheticRelease();
41
+ const next = gzip(tarOf([{ bytes: NEXT, name: 'vmalert-prod' }]));
42
+ const nextPins: PinnedBinary = {
43
+ archive: {
44
+ asset: 'vmutils-darwin-arm64-v1.152.0.tar.gz',
45
+ repo: 'VictoriaMetrics/VictoriaMetrics',
46
+ sha256: sha256Hex(next),
47
+ size: next.length,
48
+ tag: 'v1.152.0',
49
+ },
50
+ member: 'vmalert-prod',
51
+ name: 'vmalert',
52
+ sha256: sha256Hex(NEXT),
53
+ };
54
+ const nextUrl = releaseUrl(nextPins.archive.repo, nextPins.archive.tag, nextPins.archive.asset);
55
+ const served: Record<string, Uint8Array> = { [VMUTILS_URL]: current.vmutils, [nextUrl]: next };
56
+ return { current: catalogBinary(current.catalog, VMALERT_REQUEST), next: nextPins, served };
57
+ };
58
+
59
+ /** One fake host and one fake server; `events` is every host change and request, in order. */
60
+ const harness = () => {
61
+ const { current, next, served } = releases();
62
+ const fake = fakeLinuxHost({ dirs: { [ROOT]: 0 }, euid: 0 });
63
+ const events: string[] = [];
64
+ const base = fake.runner;
65
+ const runner: HostRunner = {
66
+ ...base,
67
+ exec: (argv) => {
68
+ events.push(argv.filter((arg) => arg !== '--').join(' '));
69
+ return base.exec(argv);
70
+ },
71
+ removeFile: (path) => {
72
+ events.push(`remove ${path}`);
73
+ return base.removeFile(path);
74
+ },
75
+ writeFileAtomic: (path, bytes, options) => {
76
+ events.push(`write ${path}`);
77
+ return base.writeFileAtomic(path, bytes, options);
78
+ },
79
+ };
80
+ const client = HttpClient.make((request, url) => {
81
+ events.push(`GET ${url.toString()}`);
82
+ const bytes = served[url.toString()];
83
+ const body = bytes === undefined ? null : new Blob([bytes.slice()]);
84
+ return Effect.succeed(
85
+ HttpClientResponse.fromWeb(request, new Response(body, { status: body ? 200 : 404 })),
86
+ );
87
+ });
88
+ const providers = Layer.mergeAll(makeReleaseBinaryProvider(), HostDirectoryProvider()).pipe(
89
+ Layer.provide(
90
+ Layer.mergeAll(hostRunnerLayer(runner), Layer.succeed(HttpClient.HttpClient, client)),
91
+ ),
92
+ );
93
+ const stack = fakeStack(providers, {}, 'ReleaseStack');
94
+ /** The stack body: one versioned directory and the binary in it, ordered by `dir.path`. */
95
+ const declare = (pins: PinnedBinary, version: string) =>
96
+ Effect.gen(function* () {
97
+ const path = catalogDirectory(ROOT, { package: 'vmutils', version });
98
+ const dir = yield* HostDirectory(`vmutils-${version}`, { mode: 0o755, path });
99
+ yield* ReleaseBinary('vmalert', { ...pins, directory: dir.path });
100
+ });
101
+ return { current, declare, events, fake, next, stack };
102
+ };
103
+
104
+ const OLD = `${ROOT}/vmutils-1.151.0`;
105
+ const NEW = `${ROOT}/vmutils-1.152.0`;
106
+
107
+ describe('through the engine', () => {
108
+ test('a version outside the catalog fails the plan: nothing fetched, nothing touched', async () => {
109
+ const h = harness();
110
+ const body = Effect.gen(function* () {
111
+ const pins = catalogBinary(VICTORIA_RELEASES, { ...VMALERT_REQUEST, version: '1.152.0' });
112
+ yield* ReleaseBinary('vmalert', { ...pins, directory: OLD });
113
+ });
114
+ await expect(h.stack.deploy(body)).rejects.toThrow('vmutils 1.152.0 is not in the catalog');
115
+ expect(h.events).toEqual([]);
116
+ });
117
+
118
+ test('the directory first, then one download, then the one write', async () => {
119
+ const h = harness();
120
+ const planned = await h.stack.deploy(h.declare(h.current, '1.151.0'));
121
+ expect(planned).toEqual({ vmalert: 'create', 'vmutils-1.151.0': 'create' });
122
+ expect(h.events).toEqual([`mkdir -m 755 ${OLD}`, `GET ${VMUTILS_URL}`, `write ${OLD}/vmalert`]);
123
+ expect(h.fake.files.get(`${OLD}/vmalert`)?.bytes).toEqual(BINARY.vmalert);
124
+ });
125
+
126
+ test('a second deploy of the same declaration changes nothing and fetches nothing', async () => {
127
+ const h = harness();
128
+ await h.stack.deploy(h.declare(h.current, '1.151.0'));
129
+ h.events.length = 0;
130
+ await h.stack.deploy(h.declare(h.current, '1.151.0'));
131
+ expect(h.events).toEqual([]);
132
+ });
133
+
134
+ test('a version bump: the new binary is written BEFORE the old one goes, then the old directory', async () => {
135
+ const h = harness();
136
+ await h.stack.deploy(h.declare(h.current, '1.151.0'));
137
+ h.events.length = 0;
138
+ const planned = await h.stack.deploy(h.declare(h.next, '1.152.0'));
139
+ expect(planned).toEqual({
140
+ vmalert: 'replace',
141
+ 'vmutils-1.151.0': 'delete',
142
+ 'vmutils-1.152.0': 'create',
143
+ });
144
+ // ★ MEASURED HERE, not reasoned from Apply.ts: create-before-delete, and the old directory
145
+ // only once it is empty — a job whose argv holds `vmalert.path` moves before its file goes.
146
+ expect(h.events).toEqual([
147
+ `mkdir -m 755 ${NEW}`,
148
+ `GET ${releaseUrl(h.next.archive.repo, h.next.archive.tag, h.next.archive.asset)}`,
149
+ `write ${NEW}/vmalert`,
150
+ `remove ${OLD}/vmalert`,
151
+ `rmdir ${OLD}`,
152
+ ]);
153
+ expect(h.fake.files.get(`${NEW}/vmalert`)?.bytes).toEqual(NEXT);
154
+ expect([...h.fake.files.keys()]).toEqual([`${NEW}/vmalert`]);
155
+ });
156
+
157
+ test('a new pin in the SAME directory fails the plan; the installed binary is untouched', async () => {
158
+ const h = harness();
159
+ await h.stack.deploy(h.declare(h.current, '1.151.0'));
160
+ h.events.length = 0;
161
+ await expect(h.stack.deploy(h.declare(h.next, '1.151.0'))).rejects.toThrow(
162
+ 'would overwrite it in place',
163
+ );
164
+ // ★ Refused by diff, at plan: the directory was already created, so its path is resolved.
165
+ expect(h.events).toEqual([]);
166
+ expect(h.fake.files.get(`${OLD}/vmalert`)?.bytes).toEqual(BINARY.vmalert);
167
+ });
168
+
169
+ test('the same bytes re-pinned while the directory is updated: refused at apply, binary kept', async () => {
170
+ const h = harness();
171
+ const body = (mode: number, tag: string) =>
172
+ Effect.gen(function* () {
173
+ const dir = yield* HostDirectory('vmutils-1.151.0', { mode, path: OLD });
174
+ const archive = { ...h.current.archive, tag };
175
+ yield* ReleaseBinary('vmalert', { ...h.current, archive, directory: dir.path });
176
+ });
177
+ await h.stack.deploy(body(0o755, 'v1.151.0'));
178
+ h.events.length = 0;
179
+ // The directory's update leaves its path unresolved at plan, so diff cannot see the path.
180
+ await expect(h.stack.deploy(body(0o750, 'v1.151.1'))).rejects.toThrow(
181
+ 'would overwrite it in place',
182
+ );
183
+ // 🔴 MEASURED 2026-09-22 with diff answering `replace` here instead of `update`: the deploy
184
+ // SUCCEEDED and the binary was gone — the new generation took the identical file as its own,
185
+ // then Phase 2 deleted the old generation at the same path (binary-diff.ts).
186
+ expect(h.events).toEqual([`chmod 750 ${OLD}`]);
187
+ expect(h.fake.files.get(`${OLD}/vmalert`)?.bytes).toEqual(BINARY.vmalert);
188
+ });
189
+ });
@@ -0,0 +1,48 @@
1
+ /**
2
+ * The provider's plan-time refusals, through the engine-facing handlers (fake-provider.ts): each
3
+ * with zero requests and zero writes. (A version outside a catalog never reaches a handler: it
4
+ * fails in the stack program — catalog.test.ts, plan.test.ts.)
5
+ */
6
+ import { describe, expect, test } from 'bun:test';
7
+ import * as Effect from 'effect/Effect';
8
+ import type { ReleaseBinaryProps } from './binary-form.ts';
9
+ import { VMUTILS_DIR } from './fake-release.ts';
10
+ import { call, harness, ids, must } from './fake-provider.ts';
11
+
12
+ describe('plan-time refusals: zero requests, zero writes', () => {
13
+ test('the adoption probe refuses a malformed pin before touching the host', async () => {
14
+ const h = harness();
15
+ const next = { ...h.VMALERT, sha256: 'v1.151.0' };
16
+ const probe = h.run((p) => must(p.read)({ ...ids, olds: next as never, output: undefined }));
17
+ await expect(probe).rejects.toThrow('sha256 must be 64 lower-case hex digits');
18
+ expect(h.requests).toEqual([]);
19
+ expect(h.fake.calls).toEqual([]);
20
+ });
21
+
22
+ test('diff refuses a pin that is not a plain value, even while the directory is an Output', async () => {
23
+ const h = harness();
24
+ const output = await h.run((p) => h.reconcile(p, h.VMALERT));
25
+ h.requests.length = 0;
26
+ // ⛔ A pin computed during the deploy is a fetch by another name.
27
+ const pending = {
28
+ ...h.VMALERT,
29
+ directory: Effect.succeed(VMUTILS_DIR),
30
+ sha256: Effect.succeed(h.VMALERT.sha256),
31
+ };
32
+ const diff = h.run((p) =>
33
+ must(p.diff)({ ...ids, ...call, news: pending as never, olds: h.VMALERT as never, output }),
34
+ );
35
+ await expect(diff).rejects.toThrow('sha256 is required, as a plain string');
36
+ expect(h.requests).toEqual([]);
37
+ });
38
+
39
+ test('diff refuses a new pin at the same path', async () => {
40
+ const h = harness();
41
+ const output = await h.run((p) => h.reconcile(p, h.VMALERT));
42
+ const bumped: ReleaseBinaryProps = { ...h.VMALERT, sha256: 'e'.repeat(64) };
43
+ const diff = h.run((p) =>
44
+ must(p.diff)({ ...ids, ...call, news: bumped as never, olds: h.VMALERT as never, output }),
45
+ );
46
+ await expect(diff).rejects.toThrow('would overwrite it in place');
47
+ });
48
+ });