ci: cache verified engine payloads

- cache target-scoped engine payloads with exact lock and toolchain keys\n- validate manifest provenance, checksums, and paths before reuse\n- restrict cache writes to trusted main pushes while allowing release restores\n- fall back to source provisioning when a cache is absent or invalid\n- harden release checksum generation against pipeline races
This commit is contained in:
NimBold
2026-09-06 20:55:06 +03:30
parent 164f9f32a1
commit d8a6440998
9 changed files with 483 additions and 46 deletions
+42 -1
View File
@@ -141,8 +141,39 @@ jobs:
mingw-w64-x86_64-openssl mingw-w64-x86_64-libssh2
mingw-w64-x86_64-c-ares mingw-w64-x86_64-expat
mingw-w64-x86_64-sqlite3 mingw-w64-x86_64-zlib
- name: Provision locked engines
- name: Fingerprint engine toolchain
id: engine-toolchain
if: runner.os != 'macOS'
env:
FIRELINK_MSYS2_ROOT: ${{ steps.aria2-msys.outputs.msys2-location }}
FIRELINK_TARGET_TRIPLE: ${{ matrix.target }}
run: node scripts/engine-toolchain-fingerprint.js
- name: Restore verified engine payload cache
id: engine-cache
if: runner.os != 'macOS'
# v4.2.0 pinned to an immutable commit; this cache is an optimization,
# and a miss always falls back to source provisioning below.
uses: actions/cache/restore@1bd1e32a3bdc45362d1e726936510720a7c30a57
with:
path: src-tauri/provisioned-engines/${{ matrix.target }}
# The target, toolchain fingerprint, lockfiles, provisioning code,
# payload validators, and runner package lists all invalidate the key.
key: firelink-engine-payload-v1-${{ matrix.target }}-${{ steps.engine-toolchain.outputs.fingerprint }}-${{ hashFiles('engine-sources.lock.json', 'engines.lock.json', 'scripts/aria2/**', 'scripts/engine-*.js', 'scripts/provision-engines.js', 'scripts/stage-engines.js', 'scripts/verify-binaries.js', 'scripts/aria2-route-contract.js', '.github/workflows/ci.yml', '.github/workflows/release.yml') }}
- name: Validate restored engine payload
id: engine-cache-validation
if: runner.os != 'macOS' && steps.engine-cache.outputs.cache-hit == 'true'
continue-on-error: true
env:
FIRELINK_TARGET_TRIPLE: ${{ matrix.target }}
FIRELINK_ENGINE_OUTPUT_ROOT: ${{ runner.temp }}/firelink-engine-cache-validation/${{ matrix.target }}
run: |
node scripts/stage-engines.js
node scripts/verify-binaries.js --staged
- name: Provision locked engines
if: >-
runner.os != 'macOS' &&
(steps.engine-cache.outputs.cache-hit != 'true' ||
steps.engine-cache-validation.outcome != 'success')
env:
FIRELINK_MSYS2_ROOT: ${{ steps.aria2-msys.outputs.msys2-location }}
run: node scripts/provision-engines.js --target ${{ matrix.target }}
@@ -164,3 +195,13 @@ jobs:
env:
FIRELINK_ENGINE_OUTPUT_ROOT: ${{ runner.temp }}/firelink-engine-workspace/${{ matrix.target }}/engine-dist
run: node scripts/smoke-aria2-transfers.js
- name: Save verified engine payload cache
if: >-
runner.os != 'macOS' &&
github.event_name == 'push' &&
github.ref == 'refs/heads/main' &&
steps.engine-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@1bd1e32a3bdc45362d1e726936510720a7c30a57
with:
path: src-tauri/provisioned-engines/${{ matrix.target }}
key: firelink-engine-payload-v1-${{ matrix.target }}-${{ steps.engine-toolchain.outputs.fingerprint }}-${{ hashFiles('engine-sources.lock.json', 'engines.lock.json', 'scripts/aria2/**', 'scripts/engine-*.js', 'scripts/provision-engines.js', 'scripts/stage-engines.js', 'scripts/verify-binaries.js', 'scripts/aria2-route-contract.js', '.github/workflows/ci.yml', '.github/workflows/release.yml') }}
+35 -2
View File
@@ -115,8 +115,37 @@ jobs:
mingw-w64-x86_64-openssl mingw-w64-x86_64-libssh2
mingw-w64-x86_64-c-ares mingw-w64-x86_64-expat
mingw-w64-x86_64-sqlite3 mingw-w64-x86_64-zlib
- name: Provision locked engines
- name: Fingerprint engine toolchain
id: engine-toolchain
if: runner.os != 'macOS'
env:
FIRELINK_MSYS2_ROOT: ${{ steps.aria2-msys.outputs.msys2-location }}
FIRELINK_TARGET_TRIPLE: ${{ matrix.target }}
run: node scripts/engine-toolchain-fingerprint.js
- name: Restore verified engine payload cache
id: engine-cache
if: runner.os != 'macOS'
# v4.2.0 pinned to an immutable commit; release jobs never write cache
# entries, so only trusted CI pushes can populate the shared payload.
uses: actions/cache/restore@1bd1e32a3bdc45362d1e726936510720a7c30a57
with:
path: src-tauri/provisioned-engines/${{ matrix.target }}
key: firelink-engine-payload-v1-${{ matrix.target }}-${{ steps.engine-toolchain.outputs.fingerprint }}-${{ hashFiles('engine-sources.lock.json', 'engines.lock.json', 'scripts/aria2/**', 'scripts/engine-*.js', 'scripts/provision-engines.js', 'scripts/stage-engines.js', 'scripts/verify-binaries.js', 'scripts/aria2-route-contract.js', '.github/workflows/ci.yml', '.github/workflows/release.yml') }}
- name: Validate restored engine payload
id: engine-cache-validation
if: runner.os != 'macOS' && steps.engine-cache.outputs.cache-hit == 'true'
continue-on-error: true
env:
FIRELINK_TARGET_TRIPLE: ${{ matrix.target }}
FIRELINK_ENGINE_OUTPUT_ROOT: ${{ runner.temp }}/firelink-engine-cache-validation/${{ matrix.target }}
run: |
node scripts/stage-engines.js
node scripts/verify-binaries.js --staged
- name: Provision locked engines
if: >-
runner.os != 'macOS' &&
(steps.engine-cache.outputs.cache-hit != 'true' ||
steps.engine-cache-validation.outcome != 'success')
env:
FIRELINK_MSYS2_ROOT: ${{ steps.aria2-msys.outputs.msys2-location }}
run: node scripts/provision-engines.js --target ${{ matrix.target }}
@@ -338,8 +367,12 @@ jobs:
rename_asset '*.zip' "Firelink_${VERSION}_Windows-x64-portable.zip"
- name: Generate checksums
run: |
set -euo pipefail
cd release-assets
find . -type f ! -name SHA256SUMS -print0 | sort -z | xargs -0 sha256sum > SHA256SUMS
checksum_tmp="$RUNNER_TEMP/Firelink-SHA256SUMS"
trap 'rm -f "$checksum_tmp"' EXIT
find . -type f ! -name SHA256SUMS -print0 | sort -z | xargs -0 sha256sum > "$checksum_tmp"
mv "$checksum_tmp" SHA256SUMS
- uses: softprops/action-gh-release@v3
with:
files: release-assets/**
@@ -0,0 +1,45 @@
import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
import test from 'node:test';
const repositoryRoot = path.resolve(import.meta.dirname, '..');
const ciWorkflow = fs.readFileSync(
path.join(repositoryRoot, '.github', 'workflows', 'ci.yml'),
'utf8',
);
const releaseWorkflow = fs.readFileSync(
path.join(repositoryRoot, '.github', 'workflows', 'release.yml'),
'utf8',
);
const cacheActionSha = '1bd1e32a3bdc45362d1e726936510720a7c30a57';
function assertSafeEngineCacheWorkflow(workflow) {
assert.match(workflow, new RegExp(`actions/cache/restore@${cacheActionSha}`));
assert.match(workflow, /key: firelink-engine-payload-v1-\$\{\{ matrix\.target \}\}-\$\{\{ steps\.engine-toolchain\.outputs\.fingerprint \}\}/);
assert.match(workflow, /engine-sources\.lock\.json/);
assert.match(workflow, /scripts\/aria2\/\*\*/);
assert.match(workflow, /scripts\/engine-\*\.js/);
assert.match(workflow, /scripts\/verify-binaries\.js/);
assert.doesNotMatch(workflow, /restore-keys:/);
const restore = workflow.indexOf('actions/cache/restore@');
const validation = workflow.indexOf('id: engine-cache-validation');
const provision = workflow.indexOf('node scripts/provision-engines.js');
assert.ok(restore >= 0 && restore < validation && validation < provision);
assert.match(workflow, /continue-on-error: true/);
assert.match(workflow, /FIRELINK_TARGET_TRIPLE: \$\{\{ matrix\.target \}\}/);
}
test('CI and release restore only exact, validated engine payload caches', () => {
assertSafeEngineCacheWorkflow(ciWorkflow);
assertSafeEngineCacheWorkflow(releaseWorkflow);
});
test('only trusted main pushes save the shared engine cache', () => {
assert.match(ciWorkflow, new RegExp(`actions/cache/save@${cacheActionSha}`));
const save = ciWorkflow.slice(ciWorkflow.indexOf('- name: Save verified engine payload cache'));
assert.match(save, /github\.event_name == 'push'/);
assert.match(save, /github\.ref == 'refs\/heads\/main'/);
assert.doesNotMatch(releaseWorkflow, /actions\/cache\/save@/);
});
+118
View File
@@ -0,0 +1,118 @@
import fs from 'node:fs';
import path from 'node:path';
import { collectRegularFiles, sha256 } from './engine-payload-integrity.js';
function canonicalize(value) {
if (Array.isArray(value)) return value.map(canonicalize);
if (value && typeof value === 'object') {
return Object.fromEntries(
Object.keys(value)
.sort()
.map(key => [key, canonicalize(value[key])]),
);
}
return value;
}
export function buildPayloadProvenance(targetSources) {
if (!targetSources || typeof targetSources !== 'object') {
throw new Error('Engine source lock is missing the target provenance.');
}
return Object.fromEntries(
Object.entries(targetSources).map(([name, source]) => [
name,
{
version: source.version,
url: source.url || source.sourceUrl,
sha256: source.sha256 || source.sourceSha256,
...(source.buildFromSource === true
? {
patchSha256: source.patchSha256,
allocationTelemetry: source.allocationTelemetry === true,
}
: {}),
...(name === 'aria2c' && source.firelinkRouteContract
? { firelinkRouteContract: source.firelinkRouteContract }
: {}),
},
]),
);
}
export function assertPayloadManifestProvenance(manifest, targetSources, target) {
if (manifest?.schemaVersion !== 1) {
throw new Error(`Unsupported engine payload manifest schema for ${target}.`);
}
if (manifest.target !== target) {
throw new Error(`Engine payload manifest target mismatch for ${target}.`);
}
const expected = buildPayloadProvenance(targetSources);
if (JSON.stringify(canonicalize(manifest.generatedFrom))
!== JSON.stringify(canonicalize(expected))) {
throw new Error(`Engine payload manifest provenance mismatch for ${target}.`);
}
}
function resolveManifestFile(root, relative) {
if (typeof relative !== 'string' || relative.length === 0) {
throw new Error('Engine payload manifest contains an invalid file path.');
}
const resolvedRoot = path.resolve(root);
const candidate = path.resolve(resolvedRoot, relative);
const relativeToRoot = path.relative(resolvedRoot, candidate);
if (
relativeToRoot === '..'
|| relativeToRoot.startsWith(`..${path.sep}`)
|| path.isAbsolute(relativeToRoot)
) {
throw new Error(`Engine payload manifest escapes its root: ${relative}.`);
}
return candidate;
}
export function readAndValidatePayloadManifest(root, targetSources, target) {
const manifestPath = path.join(root, 'payload-manifest.json');
if (!fs.existsSync(manifestPath)) {
throw new Error(`Engine payload manifest is missing for ${target}.`);
}
let manifest;
try {
manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
} catch (error) {
throw new Error(`Engine payload manifest is invalid for ${target}: ${error.message}`);
}
assertPayloadManifestProvenance(manifest, targetSources, target);
if (!manifest.files || typeof manifest.files !== 'object' || Array.isArray(manifest.files)) {
throw new Error(`Engine payload manifest files are invalid for ${target}.`);
}
const expectedFiles = Object.keys(manifest.files).sort();
const resolvedFiles = new Map(
expectedFiles.map(relative => [relative, resolveManifestFile(root, relative)]),
);
const actualFiles = collectRegularFiles(root, {
ignoredNames: ['payload-manifest.json'],
}).map(file => path.relative(root, file).split(path.sep).join('/')).sort();
if (JSON.stringify(actualFiles) !== JSON.stringify(expectedFiles)) {
throw new Error(`Engine payload files do not match the manifest for ${target}.`);
}
for (const relative of expectedFiles) {
const expected = manifest.files[relative];
if (!/^[a-f0-9]{64}$/.test(expected)) {
throw new Error(`Engine payload manifest checksum is invalid: ${relative}.`);
}
const file = resolvedFiles.get(relative);
if (!fs.statSync(file).isFile() || sha256(file) !== expected) {
throw new Error(`Engine payload manifest checksum mismatch: ${relative}.`);
}
}
return manifest;
}
@@ -0,0 +1,97 @@
import assert from 'node:assert/strict';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import test from 'node:test';
import {
assertPayloadManifestProvenance,
buildPayloadProvenance,
readAndValidatePayloadManifest,
} from './engine-payload-manifest.js';
import { sha256 } from './engine-payload-integrity.js';
const TARGET = 'x86_64-unknown-linux-gnu';
const SOURCES = {
'yt-dlp': {
version: '2026.08.19',
url: 'https://example.invalid/yt-dlp.zip',
sha256: 'a'.repeat(64),
},
deno: {
version: '2.9.6',
url: 'https://example.invalid/deno.zip',
sha256: 'b'.repeat(64),
},
ffmpeg: {
version: '9.0.1',
url: 'https://example.invalid/ffmpeg.tar.xz',
sha256: 'c'.repeat(64),
},
aria2c: {
version: '1.37.0-firelink-native-dns-v1',
url: 'https://example.invalid/aria2.tar.xz',
sha256: 'd'.repeat(64),
buildFromSource: true,
patchSha256: 'e'.repeat(64),
allocationTelemetry: true,
firelinkRouteContract: {
revision: 'firelink-native-dns-v1',
dnsResolver: 'native-async',
networkTargetPolicy: 'firelink-v1',
networkTargetPolicyDigest: 'sha256:test',
},
},
};
function createPayload() {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'firelink-engine-manifest-'));
const file = path.join(root, 'aria2c');
fs.writeFileSync(file, 'verified engine');
const manifest = {
schemaVersion: 1,
target: TARGET,
generatedFrom: buildPayloadProvenance(SOURCES),
files: { aria2c: sha256(file) },
};
fs.writeFileSync(path.join(root, 'payload-manifest.json'), `${JSON.stringify(manifest)}\n`);
return { root, manifest };
}
test('payload manifest validation binds files and source provenance', () => {
const { root, manifest } = createPayload();
try {
assert.deepEqual(readAndValidatePayloadManifest(root, SOURCES, TARGET), manifest);
assert.doesNotThrow(() => assertPayloadManifestProvenance(manifest, SOURCES, TARGET));
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});
test('payload manifest validation rejects changed provenance and path traversal', () => {
const { root, manifest } = createPayload();
try {
const changed = { ...manifest, generatedFrom: { ...manifest.generatedFrom } };
changed.generatedFrom.aria2c = {
...changed.generatedFrom.aria2c,
patchSha256: 'f'.repeat(64),
};
fs.writeFileSync(path.join(root, 'payload-manifest.json'), JSON.stringify(changed));
assert.throws(
() => readAndValidatePayloadManifest(root, SOURCES, TARGET),
/provenance mismatch/,
);
const traversal = {
...manifest,
files: { '../outside': '0'.repeat(64) },
};
fs.writeFileSync(path.join(root, 'payload-manifest.json'), JSON.stringify(traversal));
assert.throws(
() => readAndValidatePayloadManifest(root, SOURCES, TARGET),
/escapes its root|files do not match/,
);
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});
+109
View File
@@ -0,0 +1,109 @@
#!/usr/bin/env node
import crypto from 'node:crypto';
import fs from 'node:fs';
import path from 'node:path';
import { execFileSync } from 'node:child_process';
const WINDOWS_PACKAGES = [
'autoconf',
'automake',
'libtool',
'gettext-devel',
'pkgconf',
'make',
'patch',
'binutils',
'mingw-w64-x86_64-gcc',
'mingw-w64-x86_64-binutils',
'mingw-w64-x86_64-pkgconf',
'mingw-w64-x86_64-openssl',
'mingw-w64-x86_64-libssh2',
'mingw-w64-x86_64-c-ares',
'mingw-w64-x86_64-expat',
'mingw-w64-x86_64-sqlite3',
'mingw-w64-x86_64-zlib',
];
const LINUX_PACKAGES = [
'gcc',
'g++',
'make',
'patch',
'binutils',
'autoconf',
'automake',
'libtool',
'gettext',
'autopoint',
'pkg-config',
'libssl-dev',
'libssh2-1-dev',
'libgcrypt20-dev',
'libc-ares-dev',
'libexpat1-dev',
'libsqlite3-dev',
'zlib1g-dev',
];
function run(command, args) {
try {
return execFileSync(command, args, {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe'],
}).replaceAll('\r\n', '\n').trim();
} catch (error) {
const detail = error.stderr?.toString().trim() || error.message;
throw new Error(`Could not fingerprint the engine toolchain with ${command}: ${detail}`);
}
}
function writeOutput(name, value) {
const line = `${name}=${value}\n`;
if (process.env.GITHUB_OUTPUT) {
fs.appendFileSync(process.env.GITHUB_OUTPUT, line);
} else {
process.stdout.write(line);
}
}
function main() {
const target = process.env.FIRELINK_TARGET_TRIPLE;
if (!target) throw new Error('FIRELINK_TARGET_TRIPLE is required.');
const records = [`target=${target}`];
if (process.platform === 'win32') {
const msysRoot = process.env.FIRELINK_MSYS2_ROOT;
if (!msysRoot) throw new Error('FIRELINK_MSYS2_ROOT is required on Windows.');
const bash = path.join(msysRoot, 'usr', 'bin', 'bash.exe');
const packages = WINDOWS_PACKAGES.join(' ');
records.push(`msys2-packages=${run(bash, ['-lc', `pacman -Q ${packages}`])}`);
} else if (process.platform === 'linux') {
records.push(`debian-packages=${run('dpkg-query', [
'-W',
'-f=${binary:Package}=${Version}\\n',
...LINUX_PACKAGES,
])}`);
for (const [command, args] of [
['gcc', ['--version']],
['make', ['--version']],
['autoconf', ['--version']],
['automake', ['--version']],
['pkg-config', ['--version']],
]) {
records.push(`${command}=${run(command, args).split('\n', 1)[0]}`);
}
} else {
throw new Error(`Unsupported engine toolchain host: ${process.platform}`);
}
const fingerprint = crypto.createHash('sha256').update(records.join('\n')).digest('hex');
writeOutput('fingerprint', fingerprint);
}
try {
main();
} catch (error) {
console.error(error.message);
process.exitCode = 1;
}
+2 -14
View File
@@ -5,6 +5,7 @@ import { execFile } from 'node:child_process';
import { fileURLToPath } from 'node:url';
import { promisify } from 'node:util';
import { collectRegularFiles, sha256 } from './engine-payload-integrity.js';
import { buildPayloadProvenance } from './engine-payload-manifest.js';
import { downloadEngineArchive } from './engine-download.js';
import {
promoteDirectory,
@@ -138,20 +139,7 @@ function writePayloadManifest() {
const manifest = {
schemaVersion: 1,
target,
generatedFrom: Object.fromEntries(
Object.entries(targetSources).map(([name, source]) => [
name,
{
version: source.version,
url: source.url || source.sourceUrl,
sha256: source.sha256 || source.sourceSha256,
...(source.buildFromSource ? { patchSha256: source.patchSha256, allocationTelemetry: true } : {}),
...(name === 'aria2c' && source.firelinkRouteContract
? { firelinkRouteContract: source.firelinkRouteContract }
: {})
}
])
),
generatedFrom: buildPayloadProvenance(targetSources),
files: Object.fromEntries(
files.map(file => [
path.relative(payloadDestination, file).split(path.sep).join('/'),
+11 -29
View File
@@ -2,7 +2,8 @@
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { collectRegularFiles, sha256, treeDigest } from './engine-payload-integrity.js';
import { sha256, treeDigest } from './engine-payload-integrity.js';
import { readAndValidatePayloadManifest } from './engine-payload-manifest.js';
import { promoteDirectory, removePathWithRetry } from './engine-payload-promotion.js';
import {
assertSafeOutputRoot,
@@ -15,6 +16,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
const repoRoot = path.resolve(__dirname, '..');
const binariesRoot = path.join(repoRoot, 'src-tauri', 'binaries');
const lock = JSON.parse(fs.readFileSync(path.join(repoRoot, 'engines.lock.json'), 'utf8'));
const sourceLock = JSON.parse(fs.readFileSync(path.join(repoRoot, 'engine-sources.lock.json'), 'utf8'));
const target = resolveTargetTriple();
const outputRoot = assertSafeOutputRoot(resolveOutputRoot(), [
@@ -78,38 +80,18 @@ if (targetLock) {
}
}
} else {
const manifestPath = path.join(source, 'payload-manifest.json');
if (!fs.existsSync(manifestPath)) {
console.error(`No committed lock or payload manifest exists for ${target}.`);
const sourceTargetLock = sourceLock.targets?.[target];
if (!sourceTargetLock) {
console.error(`No source lock exists for the provisioned engine target ${target}.`);
process.exit(1);
}
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
if (manifest.target !== target) {
console.error(`Payload manifest target mismatch: ${manifest.target}`);
process.exit(1);
}
if (manifest.generatedFrom?.aria2c?.firelinkRouteContract) {
try {
try {
const manifest = readAndValidatePayloadManifest(source, sourceTargetLock, target);
if (manifest.generatedFrom?.aria2c?.firelinkRouteContract) {
assertAria2RouteSource(manifest.generatedFrom.aria2c, target);
} catch (error) {
console.error(error.message);
process.exit(1);
}
}
for (const [relative, expected] of Object.entries(manifest.files || {})) {
const file = path.join(source, relative);
if (!fs.existsSync(file) || sha256(file) !== expected) {
console.error(`Payload manifest mismatch: ${relative}`);
process.exit(1);
}
}
const actualFiles = collectRegularFiles(source, {
ignoredNames: ['payload-manifest.json'],
}).map(file => path.relative(source, file).split(path.sep).join('/'));
const expectedFiles = Object.keys(manifest.files || {}).sort();
actualFiles.sort();
if (JSON.stringify(actualFiles) !== JSON.stringify(expectedFiles)) {
console.error(`Payload contains files not covered by manifest for ${target}.`);
} catch (error) {
console.error(error.message);
process.exit(1);
}
}
+24
View File
@@ -13,7 +13,9 @@ import {
ARIA2_SYSTEM_RESOLVER_DAEMON_ARGS,
assertAria2Baseline,
assertAria2AllocationCapabilities,
assertAria2RouteSource,
} from './aria2-route-contract.js';
import { readAndValidatePayloadManifest } from './engine-payload-manifest.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@@ -48,6 +50,10 @@ const ext = isWindows ? '.exe' : '';
const suffix = `-${targetTriple}${ext}`;
const scriptsDir = __dirname;
const repoRoot = path.resolve(__dirname, '..');
const sourceLock = JSON.parse(
fs.readFileSync(path.join(repoRoot, 'engine-sources.lock.json'), 'utf8')
);
const searchRoot = argValue('--search-root');
function findEngineRoot(root) {
const expected = `yt-dlp-${targetTriple}${ext}`;
@@ -97,6 +103,7 @@ const binariesDir = configuredRoot
: path.join(scriptsDir, '..', 'src-tauri', 'binaries');
const requiredEngines = ['yt-dlp', 'aria2c', 'ffmpeg', 'deno'];
const stagedVerification = process.argv.includes('--staged');
const sourceTargetLock = sourceLock.targets?.[targetTriple];
const FORBIDDEN_OTOOL_PATHS = ['/opt/homebrew', '/usr/local/Cellar'];
const FORBIDDEN_STDERR = [
@@ -117,6 +124,23 @@ function ok(msg) {
console.log(`[OK] ${msg}`);
}
if (sourceTargetLock) {
try {
const manifest = readAndValidatePayloadManifest(binariesDir, sourceTargetLock, targetTriple);
if (manifest.generatedFrom?.aria2c?.firelinkRouteContract) {
assertAria2RouteSource(manifest.generatedFrom.aria2c, targetTriple);
}
ok('Payload manifest provenance and checksums');
} catch (error) {
fail(error.message);
}
}
if (exitCode !== 0) {
console.error('\nAborting: engine payload integrity checks failed.');
process.exit(1);
}
function rejectSymlinks(root, label) {
if (!fs.existsSync(root)) {
return;