mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-09 03:46:16 -08:00
183 lines
8.6 KiB
JavaScript
183 lines
8.6 KiB
JavaScript
import assert from 'node:assert/strict'
|
|
import { Buffer } from 'node:buffer'
|
|
import { createHash } from 'node:crypto'
|
|
import fs from 'node:fs'
|
|
import path from 'node:path'
|
|
import process from 'node:process'
|
|
// eslint-disable-next-line test/no-import-node-test -- Provenance checks use the repository runner.
|
|
import test from 'node:test'
|
|
|
|
const args = process.argv.slice(2)
|
|
for (const value of args)
|
|
assert(['--network', '--cached-evidence'].includes(value) || value.startsWith('--provenance='), `unknown provenance argument: ${value}`)
|
|
const filename = args.find(value => value.startsWith('--provenance='))?.slice('--provenance='.length)
|
|
?? 'refactor/baselines/jassub-provenance.json'
|
|
const provenance = readJson(filename)
|
|
const cache = 'refactor/.cache'
|
|
const network = args.includes('--network')
|
|
const cachedEvidence = args.includes('--cached-evidence')
|
|
|
|
function readJson(file) {
|
|
return JSON.parse(fs.readFileSync(file, 'utf8'))
|
|
}
|
|
|
|
function digest(bytes, algorithm = 'sha256') {
|
|
return createHash(algorithm).update(bytes).digest('hex')
|
|
}
|
|
|
|
function gitBlob(bytes) {
|
|
return createHash('sha1').update(`blob ${bytes.length}\0`).update(bytes).digest('hex')
|
|
}
|
|
|
|
function verifyBytes(bytes, expected, label) {
|
|
assert.equal(bytes.length, expected.bytes, `${label}: bytes`)
|
|
assert.equal(digest(bytes), expected.sha256, `${label}: SHA-256`)
|
|
assert.equal(gitBlob(bytes), expected.gitBlob, `${label}: Git blob SHA-1`)
|
|
}
|
|
|
|
function verifyTreeEntry(tree, file, expected) {
|
|
assert.equal(tree.truncated, false, 'an incomplete tree cannot prove path identity')
|
|
const entry = tree.tree.find(item => item.path === file)
|
|
assert(entry, `upstream tree is missing ${file}`)
|
|
assert.equal(entry.type, 'blob', file)
|
|
assert.equal(entry.sha, expected.gitBlob, file)
|
|
assert.equal(entry.size, expected.bytes, file)
|
|
}
|
|
|
|
function verifyLineage(pagesCommit, nightlyCommit, pagesTree, sourceTree) {
|
|
const { pagesSnapshot, wasmNightly, build } = provenance.upstream
|
|
assert.equal(pagesCommit.sha, pagesSnapshot.commit)
|
|
assert.equal(pagesCommit.commit.message, pagesSnapshot.message)
|
|
assert(pagesCommit.commit.message.includes(`From ${pagesSnapshot.sourceCommit}`))
|
|
assert.deepEqual(pagesCommit.files.map(item => item.filename), pagesSnapshot.changedPaths)
|
|
assert.equal(nightlyCommit.sha, wasmNightly.commit)
|
|
assert.equal(nightlyCommit.commit.message, wasmNightly.message)
|
|
assert(nightlyCommit.commit.message.includes(`From ${build.sourceCommit}`))
|
|
assert.equal(wasmNightly.sourceCommit, build.sourceCommit, 'artifact source revision must match build source')
|
|
assert.equal(pagesTree.sha, pagesSnapshot.tree)
|
|
assert.equal(sourceTree.sha, build.tree)
|
|
assert.equal(sourceTree.truncated, false)
|
|
for (const item of provenance.assets) {
|
|
assert.equal(item.upstream.commit, pagesSnapshot.commit)
|
|
for (const file of item.upstream.paths) verifyTreeEntry(pagesTree, file, item)
|
|
if (item.kind === 'wasm') {
|
|
for (const file of item.upstream.paths) {
|
|
const changed = nightlyCommit.files.find(entry => entry.filename === file)
|
|
assert(changed, `nightly commit did not update ${file}`)
|
|
assert.equal(changed.sha, item.gitBlob, file)
|
|
}
|
|
}
|
|
}
|
|
for (const item of build.evidence) verifyTreeEntry(sourceTree, item.path, item)
|
|
for (const item of build.submodules) {
|
|
const entry = sourceTree.tree.find(row => row.path === item.path)
|
|
assert.equal(entry?.type, 'commit', item.path)
|
|
assert.equal(entry.sha, item.commit, item.path)
|
|
}
|
|
for (const notice of provenance.notices) {
|
|
const tree = notice.commit === build.sourceCommit ? sourceTree : pagesTree
|
|
verifyTreeEntry(tree, notice.path, notice)
|
|
}
|
|
}
|
|
|
|
test('JASSUB provenance checks all frozen local WASM/font bytes offline without making a remote claim', () => {
|
|
assert.equal(provenance.schemaVersion, 1)
|
|
assert.equal(provenance.kind, 'append-only-provenance-supplement')
|
|
assert.equal(provenance.assets.length, 13)
|
|
const baseline = readJson(provenance.baseline)
|
|
const expected = baseline.assets.filter(item => /\.(?:wasm|ttf|otf|woff2)$/i.test(item.file))
|
|
const files = provenance.assets.flatMap(item => [item.file, ...item.aliases])
|
|
assert.equal(new Set(files).size, files.length)
|
|
assert.deepEqual(files.toSorted(), expected.map(item => item.file).toSorted())
|
|
for (const item of provenance.assets) {
|
|
const original = fs.readFileSync(item.file)
|
|
for (const file of [item.file, ...item.aliases]) {
|
|
const frozen = expected.find(entry => entry.file === file)
|
|
assert.equal(item.sha256, frozen.sha256, `supplement must retain frozen ${file}`)
|
|
assert.equal(item.bytes, frozen.bytes, file)
|
|
const bytes = fs.readFileSync(file)
|
|
verifyBytes(bytes, item, file)
|
|
assert.deepEqual(bytes, original, file)
|
|
}
|
|
}
|
|
const vendor = readJson(provenance.vendorComparison)
|
|
for (const notice of provenance.notices) {
|
|
assert.equal(notice.npm.version, vendor.version)
|
|
assert.equal(notice.npm.sha256, vendor.files[notice.npm.member])
|
|
}
|
|
})
|
|
|
|
test('JASSUB provenance retains explicit build and licensing limits', () => {
|
|
const { licensing, upstream, validation } = provenance
|
|
assert.equal(upstream.build.reproduced, false)
|
|
assert.equal(licensing.redistributionApproval, false)
|
|
assert.equal(licensing.completeNoticeAudit, false)
|
|
assert.equal(validation.browserValidated, false)
|
|
const fonts = provenance.assets.filter(item => item.kind === 'font').map(item => path.basename(item.file))
|
|
const classified = [...licensing.openFontNoticeAssembly, ...licensing.unresolvedFontPermission]
|
|
assert.equal(new Set(classified).size, fonts.length)
|
|
assert.deepEqual(classified.toSorted(), fonts.toSorted())
|
|
})
|
|
|
|
if (cachedEvidence) {
|
|
test('JASSUB explicitly rechecks saved downloaded bytes and lineage without refreshing network evidence', () => {
|
|
const { pagesSnapshot, wasmNightly, build } = provenance.upstream
|
|
verifyLineage(
|
|
readJson(path.join(cache, pagesSnapshot.cachedCommit)),
|
|
readJson(path.join(cache, wasmNightly.cachedCommit)),
|
|
readJson(path.join(cache, pagesSnapshot.cachedTree)),
|
|
readJson(path.join(cache, build.cachedTree)),
|
|
)
|
|
for (const item of [...provenance.assets, ...build.evidence, ...provenance.notices]) {
|
|
const bytes = fs.readFileSync(path.join(cache, item.cachedDownload))
|
|
verifyBytes(bytes, item, item.cachedDownload)
|
|
if (item.file)
|
|
assert.deepEqual(bytes, fs.readFileSync(item.file), item.file)
|
|
if (item.npm) {
|
|
const npm = fs.readFileSync(path.join(cache, item.npm.cachedDownload))
|
|
assert.equal(npm.length, item.npm.bytes)
|
|
assert.equal(digest(npm), item.npm.sha256)
|
|
assert.notDeepEqual(bytes, npm)
|
|
assert.equal(bytes.toString().replaceAll('\r\n', '\n'), npm.toString().replaceAll('\r\n', '\n'))
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
if (network) {
|
|
test('JASSUB explicitly fetches immutable upstream commits, trees and bytes; unavailable evidence fails', async (context) => {
|
|
const { api, pagesSnapshot, wasmNightly, build } = provenance.upstream
|
|
assert.equal(api, 'https://api.github.com/repos/ThaUnknown/jassub')
|
|
let requests = 0
|
|
async function getJson(suffix) {
|
|
const response = await fetch(`${api}/${suffix}`, { signal: AbortSignal.timeout(20000) })
|
|
requests++
|
|
assert(response.ok, `upstream HTTP ${response.status}: ${suffix}`)
|
|
return response.json()
|
|
}
|
|
const [pagesCommit, nightlyCommit, pagesTree, sourceTree, nightlyTree] = await Promise.all([
|
|
getJson(`commits/${pagesSnapshot.commit}`),
|
|
getJson(`commits/${wasmNightly.commit}`),
|
|
getJson(`git/trees/${pagesSnapshot.commit}?recursive=1`),
|
|
getJson(`git/trees/${build.sourceCommit}?recursive=1`),
|
|
getJson(`git/trees/${wasmNightly.commit}?recursive=1`),
|
|
])
|
|
verifyLineage(pagesCommit, nightlyCommit, pagesTree, sourceTree)
|
|
for (const item of provenance.assets.filter(item => item.kind === 'wasm')) {
|
|
for (const file of item.upstream.paths) verifyTreeEntry(nightlyTree, file, item)
|
|
}
|
|
for (const notice of provenance.notices.filter(item => item.commit === wasmNightly.commit))
|
|
verifyTreeEntry(nightlyTree, notice.path, notice)
|
|
for (const item of [...provenance.assets, ...build.evidence, ...provenance.notices]) {
|
|
const remote = await getJson(`git/blobs/${item.gitBlob}`)
|
|
assert.equal(remote.encoding, 'base64')
|
|
assert.equal(remote.sha, item.gitBlob)
|
|
const bytes = Buffer.from(remote.content, 'base64')
|
|
verifyBytes(bytes, item, item.file ?? item.path)
|
|
if (item.file)
|
|
assert.deepEqual(bytes, fs.readFileSync(item.file), item.file)
|
|
}
|
|
context.diagnostic(`Fresh network verification finished at ${new Date().toISOString()}: ${requests} successful requests; no cache fallback; 13 downloaded asset comparisons.`)
|
|
})
|
|
}
|