@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.
- package/README.md +27 -0
- package/dist/caddy/access-forward-auth.d.ts +56 -0
- package/dist/caddy/access-forward-auth.d.ts.map +1 -0
- package/dist/caddy/index.d.ts +6 -0
- package/dist/caddy/index.d.ts.map +1 -1
- package/dist/caddy/index.js +169 -75
- package/dist/caddy/index.js.map +10 -7
- package/dist/cloudflare/mcp-server-api.d.ts +135 -0
- package/dist/cloudflare/mcp-server-api.d.ts.map +1 -0
- package/dist/cloudflare/mcp-server-form.d.ts +116 -0
- package/dist/cloudflare/mcp-server-form.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/launchd/file-converge.d.ts +70 -0
- package/dist/launchd/file-converge.d.ts.map +1 -0
- package/dist/launchd/file-identity.d.ts +32 -0
- package/dist/launchd/file-identity.d.ts.map +1 -0
- package/dist/launchd/host-file-form.d.ts +2 -0
- package/dist/launchd/host-file-form.d.ts.map +1 -1
- package/dist/launchd/host-file-lifecycle.d.ts +11 -24
- package/dist/launchd/host-file-lifecycle.d.ts.map +1 -1
- package/dist/launchd/host-file.d.ts.map +1 -1
- package/dist/launchd/index.d.ts +2 -0
- package/dist/launchd/index.d.ts.map +1 -1
- package/dist/launchd/index.js +225 -120
- package/dist/launchd/index.js.map +13 -10
- package/dist/launchd/local-runner.d.ts.map +1 -1
- package/dist/launchd/port-claims.d.ts +31 -0
- package/dist/launchd/port-claims.d.ts.map +1 -0
- package/dist/launchd/runner.d.ts +17 -2
- package/dist/launchd/runner.d.ts.map +1 -1
- package/dist/launchd/sudo-runner.d.ts +11 -0
- package/dist/launchd/sudo-runner.d.ts.map +1 -1
- package/dist/linux/index.js.map +3 -3
- package/dist/linux/sudo-listing.d.ts +47 -0
- package/dist/linux/sudo-listing.d.ts.map +1 -0
- package/dist/proxmox/credential-literals.d.ts +1 -1
- package/dist/proxmox/credential-literals.d.ts.map +1 -1
- package/dist/proxmox/generated/pbs/nodes-node.d.ts +1 -1
- package/dist/proxmox/generated/pbs/nodes-node.d.ts.map +1 -1
- package/dist/proxmox/generated/pbs.d.ts +7 -0
- package/dist/proxmox/generated/pbs.d.ts.map +1 -1
- package/dist/proxmox/generated/pve.d.ts +7 -0
- package/dist/proxmox/generated/pve.d.ts.map +1 -1
- package/dist/proxmox/index.d.ts +1 -1
- package/dist/proxmox/index.d.ts.map +1 -1
- package/dist/proxmox/index.js +2 -2
- package/dist/proxmox/index.js.map +9 -9
- package/dist/proxmox/pbs-notification-target-form.d.ts +1 -1
- package/dist/proxmox/pbs-notification-target-form.d.ts.map +1 -1
- package/dist/proxmox/pbs-notification-target.d.ts +1 -1
- package/dist/proxmox/pbs-notification-target.d.ts.map +1 -1
- package/dist/release/archive.d.ts +11 -0
- package/dist/release/archive.d.ts.map +1 -0
- package/dist/release/binary-claim.d.ts +53 -0
- package/dist/release/binary-claim.d.ts.map +1 -0
- package/dist/release/binary-diff.d.ts +38 -0
- package/dist/release/binary-diff.d.ts.map +1 -0
- package/dist/release/binary-form.d.ts +118 -0
- package/dist/release/binary-form.d.ts.map +1 -0
- package/dist/release/binary-lifecycle.d.ts +55 -0
- package/dist/release/binary-lifecycle.d.ts.map +1 -0
- package/dist/release/binary-read.d.ts +11 -0
- package/dist/release/binary-read.d.ts.map +1 -0
- package/dist/release/binary.d.ts +68 -0
- package/dist/release/binary.d.ts.map +1 -0
- package/dist/release/catalog.d.ts +113 -0
- package/dist/release/catalog.d.ts.map +1 -0
- package/dist/release/checksums.d.ts +22 -0
- package/dist/release/checksums.d.ts.map +1 -0
- package/dist/release/declared-pins.d.ts +4 -0
- package/dist/release/declared-pins.d.ts.map +1 -0
- package/dist/release/download.d.ts +44 -0
- package/dist/release/download.d.ts.map +1 -0
- package/dist/release/fake-deploy.d.ts +48 -0
- package/dist/release/fake-deploy.d.ts.map +1 -0
- package/dist/release/fake-engine.d.ts +8 -0
- package/dist/release/fake-engine.d.ts.map +1 -0
- package/dist/release/fake-install.d.ts +39 -0
- package/dist/release/fake-install.d.ts.map +1 -0
- package/dist/release/fake-provider.d.ts +58 -0
- package/dist/release/fake-provider.d.ts.map +1 -0
- package/dist/release/fake-release.d.ts +95 -0
- package/dist/release/fake-release.d.ts.map +1 -0
- package/dist/release/index.d.ts +25 -0
- package/dist/release/index.d.ts.map +1 -0
- package/dist/release/index.js +1425 -0
- package/dist/release/index.js.map +36 -0
- package/dist/release/openbao.d.ts +35 -0
- package/dist/release/openbao.d.ts.map +1 -0
- package/dist/release/providers.d.ts +19 -0
- package/dist/release/providers.d.ts.map +1 -0
- package/dist/release/refused.d.ts +39 -0
- package/dist/release/refused.d.ts.map +1 -0
- package/dist/release/tar.d.ts +14 -0
- package/dist/release/tar.d.ts.map +1 -0
- package/dist/release/victoria.d.ts +36 -0
- package/dist/release/victoria.d.ts.map +1 -0
- package/dist/{proxmox → secrets}/write-only.d.ts.map +1 -1
- package/docs/launchd-ports.md +109 -0
- package/docs/launchd.md +5 -0
- package/docs/release-binary-catalogs.md +138 -0
- package/docs/release-binary-measured.md +163 -0
- package/docs/release-binary-openbao.md +151 -0
- package/docs/release-binary-refusals.md +57 -0
- package/docs/release-binary-upstream.md +163 -0
- package/docs/release-binary.md +165 -0
- package/package.json +5 -1
- package/src/caddy/access-forward-auth.test.ts +63 -0
- package/src/caddy/access-forward-auth.ts +120 -0
- package/src/caddy/index.ts +10 -0
- package/src/caddy/providers.test.ts +4 -0
- package/src/cloudflare/mcp-server-api.ts +294 -0
- package/src/cloudflare/mcp-server-form.ts +219 -0
- package/src/index.ts +1 -0
- package/src/launchd/file-converge.ts +233 -0
- package/src/launchd/file-identity.ts +69 -0
- package/src/launchd/host-file-alias.test.ts +65 -0
- package/src/launchd/host-file-form.ts +2 -1
- package/src/launchd/host-file-lifecycle.ts +25 -144
- package/src/launchd/host-file.ts +8 -2
- package/src/launchd/index.ts +2 -0
- package/src/launchd/local-runner.test.ts +12 -1
- package/src/launchd/local-runner.ts +2 -0
- package/src/launchd/port-claims.test.ts +124 -0
- package/src/launchd/port-claims.ts +102 -0
- package/src/launchd/providers.test.ts +3 -0
- package/src/launchd/runner.ts +17 -2
- package/src/launchd/sudo-runner.ts +11 -0
- package/src/linux/sudo-listing.ts +122 -0
- package/src/proxmox/credential-literals.ts +1 -1
- package/src/proxmox/generated/pbs/nodes-node.ts +1 -1
- package/src/proxmox/generated/pbs.ts +7 -0
- package/src/proxmox/generated/pve.ts +7 -0
- package/src/proxmox/index.ts +1 -1
- package/src/proxmox/pbs-notification-target-form.ts +1 -1
- package/src/proxmox/pbs-notification-target-lifecycle.ts +1 -1
- package/src/proxmox/pbs-notification-target-wire.ts +1 -1
- package/src/proxmox/pbs-notification-target.ts +1 -1
- package/src/release/adopt-parity.test.ts +106 -0
- package/src/release/apply-pins.test.ts +128 -0
- package/src/release/archive.ts +94 -0
- package/src/release/binary-alias.test.ts +109 -0
- package/src/release/binary-claim.ts +80 -0
- package/src/release/binary-claims.test.ts +47 -0
- package/src/release/binary-diff.ts +73 -0
- package/src/release/binary-form.ts +229 -0
- package/src/release/binary-lifecycle.test.ts +90 -0
- package/src/release/binary-lifecycle.ts +202 -0
- package/src/release/binary-read.ts +55 -0
- package/src/release/binary-refusals.test.ts +192 -0
- package/src/release/binary-sudo.test.ts +93 -0
- package/src/release/binary.ts +181 -0
- package/src/release/catalog.test.ts +167 -0
- package/src/release/catalog.ts +238 -0
- package/src/release/checksums.test.ts +35 -0
- package/src/release/checksums.ts +43 -0
- package/src/release/declared-pins.ts +39 -0
- package/src/release/download.test.ts +138 -0
- package/src/release/download.ts +110 -0
- package/src/release/error-channel.test.ts +69 -0
- package/src/release/fake-deploy.ts +87 -0
- package/src/release/fake-engine.ts +50 -0
- package/src/release/fake-install.ts +59 -0
- package/src/release/fake-provider.ts +68 -0
- package/src/release/fake-release.ts +185 -0
- package/src/release/fixtures/openbao/checksums.txt +83 -0
- package/src/release/fixtures/openbao/checksums.txt.gpgsig +0 -0
- package/src/release/fixtures/openbao/darwin-assets.txt +12 -0
- package/src/release/fixtures/openbao/openbao-gpg-pub-20240618.asc +109 -0
- package/src/release/fixtures/victoria/darwin-arm64-assets.txt +22 -0
- package/src/release/fixtures/victoria/victoria-logs-darwin-arm64-v1.52.0_checksums.txt +2 -0
- package/src/release/fixtures/victoria/victoria-metrics-darwin-arm64-v1.151.0_checksums.txt +2 -0
- package/src/release/fixtures/victoria/victoria-traces-darwin-arm64-v0.10.0_checksums.txt +2 -0
- package/src/release/fixtures/victoria/vmutils-darwin-arm64-v1.151.0_checksums.txt +8 -0
- package/src/release/index.ts +44 -0
- package/src/release/openbao.test.ts +120 -0
- package/src/release/openbao.ts +86 -0
- package/src/release/plan.test.ts +189 -0
- package/src/release/provider-refusals.test.ts +48 -0
- package/src/release/providers.test.ts +175 -0
- package/src/release/providers.ts +24 -0
- package/src/release/recovery-read.test.ts +44 -0
- package/src/release/refused.ts +40 -0
- package/src/release/tar.test.ts +142 -0
- package/src/release/tar.ts +195 -0
- package/src/release/victoria.test.ts +108 -0
- package/src/release/victoria.ts +154 -0
- package/src/{proxmox → secrets}/write-only.ts +6 -0
- /package/dist/{proxmox → secrets}/write-only.d.ts +0 -0
- /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,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
|
+
});
|