262 lines
16 KiB
JavaScript
262 lines
16 KiB
JavaScript
'use strict';
|
|
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const os = require('node:os');
|
|
const path = require('node:path');
|
|
const test = require('node:test');
|
|
const { A8_DISTINCT_CONTROL } = require('../../src/services/ps5-native-pkg-builder-provider');
|
|
const { createPs5FpkgDistinctControlPackageService } = require('../../src/services/ps5-fpkg-distinct-control-package-service');
|
|
const { assessPs5FpkgControlMetadata } = require('../../src/services/ps5-fpkg-control-metadata-assessment');
|
|
const { validatePs5NativePackageManifest } = require('../../src/services/ps5-native-package-registration-service');
|
|
|
|
test('repository distribution contains the exact frozen canonical artifact and Docker copies it', () => {
|
|
const artifact = path.join('assets', 'ps5-controls', 'a9', A8_DISTINCT_CONTROL.selectedArtifact.filename);
|
|
assert.equal(fs.statSync(artifact).size, A8_DISTINCT_CONTROL.selectedArtifact.size);
|
|
const sha256 = require('node:crypto').createHash('sha256').update(fs.readFileSync(artifact)).digest('hex');
|
|
assert.equal(sha256, A8_DISTINCT_CONTROL.selectedArtifact.sha256);
|
|
const docker = fs.readFileSync('Dockerfile', 'utf8');
|
|
assert.match(docker, /COPY --chown=chimera:chimera assets \.\/assets/);
|
|
assert.match(docker, new RegExp(A8_DISTINCT_CONTROL.selectedArtifact.sha256));
|
|
assert.match(docker, /chmod 0444 .*\/app\/assets\/ps5-controls\/a9/);
|
|
});
|
|
|
|
function fixture({ registrar, assessMetadata } = {}) {
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-a8-distinct-control-'));
|
|
const registrations = [];
|
|
let builds = 0;
|
|
let validations = 0;
|
|
const builder = {
|
|
async buildDistinctControl({ outputPath }) {
|
|
builds += 1;
|
|
fs.writeFileSync(outputPath, Buffer.from(`fixed-identity-randomized-artifact-${builds}`), { flag: 'wx' });
|
|
return { provider: { product: 'LibProsperoPkg', version: '2.6.0', revision: A8_DISTINCT_CONTROL.upstreamRevision, abi: 'chimera-prospero-bridge-v4', patched: false }, report: { fixedIdentity: true, licenseFree: true } };
|
|
},
|
|
async validatePackage() { validations += 1; return { accepted: true, checks: [{ name: 'Finalized image', status: 0 }] }; },
|
|
async inspectPackage() { return { type: 'FullDebug', contentId: A8_DISTINCT_CONTROL.contentId }; },
|
|
};
|
|
const service = createPs5FpkgDistinctControlPackageService({
|
|
outputRoot: path.join(root, 'vault'), stateFile: path.join(root, 'state.json'), builder,
|
|
assessMetadata: assessMetadata || (() => ({ qualified: true, status: 'metadata_qualified', issues: [] })),
|
|
registrar: registrar || (async manifest => { registrations.push(manifest); return { packageSetId: 71, titleId: 72 }; }),
|
|
});
|
|
return { root, registrations, service, builds: () => builds, validations: () => validations };
|
|
}
|
|
|
|
test('builds, validates, freezes and registers one exact distinct-title control artifact', async t => {
|
|
const f = fixture(); t.after(() => fs.rmSync(f.root, { recursive: true, force: true }));
|
|
const ready = await f.service.buildAndRegister({ sourcePath: path.join(f.root, 'source') });
|
|
assert.equal(ready.status, 'ready');
|
|
assert.equal(ready.titleId, A8_DISTINCT_CONTROL.titleId);
|
|
assert.equal(ready.contentId, A8_DISTINCT_CONTROL.contentId);
|
|
assert.equal(ready.artifactVerified, true);
|
|
assert.equal(ready.installAuthorized, false);
|
|
assert.equal(ready.installed, false);
|
|
assert.equal(ready.launchVerified, false);
|
|
assert.equal(ready.productionRestorable, false);
|
|
assert.equal(f.builds(), 1);
|
|
assert.equal(f.registrations[0].controlProfile, A8_DISTINCT_CONTROL.controlProfile);
|
|
assert.equal(f.registrations[0].packageFormat.licenseFree, true);
|
|
assert.equal(f.registrations[0].components[0].structuralValidation.status, 'accepted');
|
|
assert.equal(f.registrations[0].components[0].structuralValidation.upstream.accepted, true);
|
|
assert.equal(f.registrations[0].components[0].metadataQualification.status, 'accepted');
|
|
assert.deepEqual(f.registrations[0].components[0].structuralValidation.upstream.checks, [{ name: 'Finalized image', status: 0 }]);
|
|
assert.equal(f.registrations[0].evidence.immutableArtifact.sha256, ready.sha256);
|
|
assert.equal(f.registrations[0].evidence.immutableArtifact.rebuildSubstitutionAllowed, false);
|
|
assert.equal((await f.service.buildAndRegister({ sourcePath: 'ignored-after-ready' })).sha256, ready.sha256);
|
|
assert.equal(f.builds(), 1);
|
|
assert.equal(f.validations(), 1);
|
|
});
|
|
|
|
test('retains built_verified state when registration fails and retries without rebuilding', async t => {
|
|
let attempts = 0;
|
|
const f = fixture({ registrar: async () => {
|
|
attempts += 1;
|
|
if (attempts === 1) throw Object.assign(new Error('database unavailable'), { code: 'TEST_REGISTRATION' });
|
|
return { packageSetId: 81, titleId: 82 };
|
|
} });
|
|
t.after(() => fs.rmSync(f.root, { recursive: true, force: true }));
|
|
await assert.rejects(f.service.buildAndRegister({ sourcePath: path.join(f.root, 'source') }), /database unavailable/);
|
|
assert.equal(f.service.status().status, 'built_verified');
|
|
const ready = await f.service.buildAndRegister({ sourcePath: path.join(f.root, 'source') });
|
|
assert.equal(ready.status, 'ready');
|
|
assert.equal(f.builds(), 1);
|
|
assert.equal(attempts, 2);
|
|
assert.equal(f.validations(), 2);
|
|
});
|
|
|
|
test('fails closed when the selected artifact drifts or an unmanaged target already exists', async t => {
|
|
const f = fixture(); t.after(() => fs.rmSync(f.root, { recursive: true, force: true }));
|
|
await f.service.buildAndRegister({ sourcePath: path.join(f.root, 'source') });
|
|
fs.appendFileSync(f.service.targetPath(), 'drift');
|
|
assert.equal(f.service.status().status, 'artifact_drift');
|
|
await assert.rejects(f.service.buildAndRegister({ sourcePath: path.join(f.root, 'source') }), error => error.code === 'PS5_A8_CONTROL_ARTIFACT_DRIFT');
|
|
|
|
const other = fixture(); t.after(() => fs.rmSync(other.root, { recursive: true, force: true }));
|
|
fs.mkdirSync(path.dirname(other.service.targetPath()), { recursive: true });
|
|
fs.writeFileSync(other.service.targetPath(), 'unmanaged');
|
|
await assert.rejects(other.service.buildAndRegister({ sourcePath: path.join(other.root, 'source') }), error => error.code === 'PS5_A8_CONTROL_UNOWNED_ARTIFACT');
|
|
});
|
|
|
|
test('keeps an exact artifact distinct from a metadata assessment failure', async t => {
|
|
let parserFails = false;
|
|
const f = fixture({
|
|
assessMetadata: () => {
|
|
if (parserFails) throw Object.assign(new Error('host parser details'), { code: 'PS5_FPKG_METADATA_CONTAINER_INVALID' });
|
|
return { qualified: true, status: 'metadata_qualified', issues: [] };
|
|
},
|
|
});
|
|
t.after(() => fs.rmSync(f.root, { recursive: true, force: true }));
|
|
|
|
await f.service.buildAndRegister({ sourcePath: path.join(f.root, 'source') });
|
|
parserFails = true;
|
|
const state = f.service.status();
|
|
assert.equal(state.status, 'metadata_rejected');
|
|
assert.equal(state.artifactVerified, true);
|
|
assert.equal(state.metadataQualified, false);
|
|
await assert.rejects(
|
|
f.service.buildAndRegister({ sourcePath: path.join(f.root, 'source') }),
|
|
error => error.code === 'PS5_A8_CONTROL_METADATA_UNQUALIFIED'
|
|
&& error.details.status === 'metadata_rejected'
|
|
&& error.details.issues[0].actual === 'PS5_FPKG_METADATA_CONTAINER_INVALID'
|
|
&& !JSON.stringify(error.details).includes('host parser details'),
|
|
);
|
|
});
|
|
|
|
test('fails closed on corrupt durable state without replacing its evidence', async t => {
|
|
const f = fixture(); t.after(() => fs.rmSync(f.root, { recursive: true, force: true }));
|
|
const statePath = path.join(f.root, 'state.json');
|
|
fs.writeFileSync(statePath, '{invalid-json', 'utf8');
|
|
assert.throws(() => f.service.status(), error => error.code === 'PS5_A8_CONTROL_STATE_CORRUPT');
|
|
await assert.rejects(f.service.buildAndRegister({ sourcePath: path.join(f.root, 'source') }), error => error.code === 'PS5_A8_CONTROL_STATE_CORRUPT');
|
|
assert.equal(fs.readFileSync(statePath, 'utf8'), '{invalid-json');
|
|
assert.equal(f.builds(), 0);
|
|
});
|
|
|
|
test('rejects a concurrent materialization before a second build can start', async t => {
|
|
let releaseRegistration;
|
|
let registrationStarted;
|
|
const started = new Promise(resolve => { registrationStarted = resolve; });
|
|
const f = fixture({ registrar: async () => {
|
|
registrationStarted();
|
|
await new Promise(resolve => { releaseRegistration = resolve; });
|
|
return { packageSetId: 83, titleId: 84 };
|
|
} });
|
|
t.after(() => fs.rmSync(f.root, { recursive: true, force: true }));
|
|
const first = f.service.buildAndRegister({ sourcePath: path.join(f.root, 'source') });
|
|
await started;
|
|
await assert.rejects(f.service.buildAndRegister({ sourcePath: path.join(f.root, 'source') }), error => error.code === 'PS5_A8_CONTROL_BUSY');
|
|
assert.equal(f.builds(), 1);
|
|
releaseRegistration();
|
|
assert.equal((await first).status, 'ready');
|
|
});
|
|
|
|
test('production preparation copies and registers only the exact frozen distribution bytes', async t => {
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-a8-selected-control-'));
|
|
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
|
const selectedPath = path.join(root, 'selected.pkg');
|
|
const bytes = Buffer.from('frozen-distribution-control');
|
|
fs.writeFileSync(selectedPath, bytes);
|
|
const sha256 = require('node:crypto').createHash('sha256').update(bytes).digest('hex');
|
|
let builds = 0;
|
|
const service = createPs5FpkgDistinctControlPackageService({
|
|
outputRoot: path.join(root, 'vault'), stateFile: path.join(root, 'state.json'), selectedArtifactPath: selectedPath,
|
|
descriptor: { ...A8_DISTINCT_CONTROL, selectedArtifact: { filename: 'selected.pkg', size: bytes.length, sha256 } },
|
|
builder: {
|
|
async buildDistinctControl() { builds += 1; throw new Error('must not rebuild selected artifact'); },
|
|
async probe() { return { product: 'LibProsperoPkg', version: '2.6.0', revision: A8_DISTINCT_CONTROL.upstreamRevision, abi: 'chimera-prospero-bridge-v4', patched: false, pinned: true, clean: true, controlAvailable: true }; },
|
|
async validatePackage() { return { accepted: true }; },
|
|
async inspectPackage() { return { type: 'FullDebug', contentId: A8_DISTINCT_CONTROL.contentId }; },
|
|
},
|
|
assessMetadata: () => ({ qualified: true, status: 'metadata_qualified', issues: [] }),
|
|
registrar: async manifest => {
|
|
assert.equal(manifest.components[0].sha256, sha256);
|
|
return { packageSetId: 91, titleId: 92 };
|
|
},
|
|
});
|
|
const ready = await service.prepareSelectedAndRegister();
|
|
assert.equal(ready.status, 'ready');
|
|
assert.equal(ready.artifactSelection, 'frozen-distribution-artifact');
|
|
assert.equal(ready.sha256, sha256);
|
|
assert.deepEqual(fs.readFileSync(service.targetPath()), bytes);
|
|
assert.equal(builds, 0);
|
|
});
|
|
|
|
test('frozen canonical control carries structural and metadata qualification into registration', async t => {
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-a8-registration-boundary-'));
|
|
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
|
const outputRoot = path.join(root, 'vault');
|
|
const selectedArtifactPath = path.resolve('assets', 'ps5-controls', 'a9', A8_DISTINCT_CONTROL.selectedArtifact.filename);
|
|
let registeredManifest = null;
|
|
const service = createPs5FpkgDistinctControlPackageService({
|
|
outputRoot,
|
|
stateFile: path.join(root, 'state.json'),
|
|
selectedArtifactPath,
|
|
builder: {
|
|
async buildDistinctControl() { throw new Error('production must not rebuild the frozen artifact'); },
|
|
async probe() { return { product: 'LibProsperoPkg', version: '2.6.0', revision: A8_DISTINCT_CONTROL.upstreamRevision, abi: 'chimera-prospero-bridge-v4', patched: false, pinned: true, clean: true, controlAvailable: true }; },
|
|
async validatePackage() { return { accepted: true, checks: [{ name: 'Finalized image', status: 0 }] }; },
|
|
async inspectPackage() { return { type: 'FullDebug', contentId: A8_DISTINCT_CONTROL.contentId }; },
|
|
},
|
|
assessMetadata: sourcePath => assessPs5FpkgControlMetadata({
|
|
sourcePath,
|
|
expected: { titleId: A8_DISTINCT_CONTROL.titleId, contentId: A8_DISTINCT_CONTROL.contentId },
|
|
}),
|
|
registrar: async manifest => {
|
|
validatePs5NativePackageManifest(manifest, { outputRoot, verifyArtifacts: true });
|
|
registeredManifest = manifest;
|
|
return { packageSetId: 93, titleId: 94 };
|
|
},
|
|
});
|
|
const ready = await service.prepareSelectedAndRegister();
|
|
assert.equal(ready.status, 'ready');
|
|
assert.equal(ready.metadataQualified, true);
|
|
assert.equal(registeredManifest.controlProfile, A8_DISTINCT_CONTROL.controlProfile);
|
|
assert.equal(registeredManifest.components[0].structuralValidation.upstream.accepted, true);
|
|
assert.equal(registeredManifest.components[0].metadataQualification.assessment.status, 'metadata_qualified');
|
|
assert.equal(registeredManifest.components[0].metadataQualification.assessment.metadata.contentVersion, '01.000.000');
|
|
});
|
|
|
|
test('production registry keeps preparation separate from the single-use exact-confirmed control install gate', () => {
|
|
const registry = fs.readFileSync('src/server/create-registry.js', 'utf8');
|
|
assert.match(registry, /ps5-native-packages:a8-distinct-control-status[\s\S]*classification: 'read-only'/);
|
|
assert.match(registry, /ps5-native-packages:a8-distinct-control-prepare[\s\S]*classification: 'mutating'/);
|
|
assert.match(registry, /prepareSelectedAndRegister\(\)/);
|
|
assert.doesNotMatch(registry, /a8-distinct-control-build/);
|
|
assert.match(registry, /a8-distinct-control-install-plan[\s\S]*classification: 'mutating'/);
|
|
assert.match(registry, /CONFIRMATION: A8_CONTROL_INSTALL_CONFIRMATION/);
|
|
assert.match(registry, /a8-distinct-control-install[\s\S]*classification: 'destructive'[\s\S]*confirmation: A8_CONTROL_INSTALL_CONFIRMATION/);
|
|
assert.match(registry, /performConsoleScan\(A8_CONTROL_INSTALL_ROOTS\)/);
|
|
assert.match(registry, /ps5FpkgDistinctControlInstallGate\.plan\(context\.session\?\.id\)/);
|
|
assert.match(registry, /ps5FpkgDistinctControlInstallGate\.start\(input, context\.session\?\.id\)/);
|
|
assert.match(registry, /PS5_A8_CONTROL_INSTALL_GATE_REQUIRED/g);
|
|
});
|
|
|
|
test('server and desktop UI expose preflight, exact install, independent scan and launch qualification', () => {
|
|
const preload = fs.readFileSync('preload.js', 'utf8');
|
|
const web = fs.readFileSync('renderer/pork-web-shim.js', 'utf8');
|
|
const main = fs.readFileSync('main.js', 'utf8');
|
|
const modal = fs.readFileSync('renderer/modules/modal.js', 'utf8');
|
|
const surface = `${fs.readFileSync('renderer/index.html', 'utf8')}\n${modal}`;
|
|
for (const source of [preload, web]) assert.match(source, /ps5A8DistinctControl/);
|
|
assert.match(main, /PS5_A8_CONTROL_SERVER_REQUIRED/);
|
|
assert.match(surface, /ps5-a8-control-prepare/);
|
|
assert.match(modal, /ps5A8DistinctControl\('prepare'\)/);
|
|
assert.match(surface, /ps5-a8-control-preflight/);
|
|
assert.match(modal, /ps5A8DistinctControl\('install-plan'\)/);
|
|
assert.match(modal, /ps5A8DistinctControl\('install'/);
|
|
assert.match(modal, /ps5A8Control\('trust'\)\.removeAttribute\('data-i18n'\)/);
|
|
assert.match(modal, /ps5A8Control\('status'\)\.removeAttribute\('data-i18n'\)/);
|
|
assert.match(modal, /ps5NativePackageRestoreVerify\(\{ titleId: 'CHIM00002'/);
|
|
assert.match(modal, /ps5NativePackageRestoreLaunchPlan\(ps5A8ControlOperationId\)/);
|
|
assert.match(modal, /Number\(item\.packageSetId\) === Number\(a8Control\.packageSetId\)/);
|
|
assert.match(modal, /item\.components\[0\]\?\.sha256 === a8Control\.sha256/);
|
|
for (const locale of ['en', 'nl']) {
|
|
const catalog = JSON.parse(fs.readFileSync(`build/lang/${locale}.json`, 'utf8'));
|
|
assert.equal(typeof catalog.strings?.['ps5NativePackages.a8Control.ready'], 'string');
|
|
for (const key of ['preflight', 'install', 'verify', 'launch', 'confirm', 'boundary']) {
|
|
assert.equal(typeof catalog.strings?.[`ps5NativePackages.a8Control.${key}`], 'string');
|
|
}
|
|
}
|
|
});
|