198 lines
8.0 KiB
JavaScript
198 lines
8.0 KiB
JavaScript
'use strict';
|
|
|
|
const assert = require('node:assert/strict');
|
|
const test = require('node:test');
|
|
const {
|
|
qualifyA8ControlOperation,
|
|
qualifyA8ControlEvidence,
|
|
verifyA8ControlQualification,
|
|
} = require('../../src/services/ps5-fpkg-a8-control-qualification');
|
|
|
|
function verifiedOperation(overrides = {}) {
|
|
return {
|
|
id: 'qualified-control-operation',
|
|
titleId: 'PPSA01234',
|
|
status: 'launch_verified',
|
|
launchVerified: true,
|
|
productionRestorable: true,
|
|
launchEvidence: {
|
|
schema: 'chimera.ps5-fpkg-launch-evidence.v2',
|
|
runtimeFingerprint: '7'.repeat(64),
|
|
preflightCompletedAt: 1_000,
|
|
requestedAt: 2_000,
|
|
runtime: {
|
|
ready: true,
|
|
capabilities: {
|
|
debugPackageRuntime: true, fpkgEnableActive: true, kernelPatchReady: true,
|
|
packageInstallerReady: true, launchServiceReady: true,
|
|
processObservationReady: true, syslogObservationReady: true, foregroundClean: true,
|
|
},
|
|
runtime: { foregroundGameProcesses: [] },
|
|
},
|
|
request: {
|
|
accepted: true,
|
|
titleId: 'PPSA01234',
|
|
route: '/api/ps5/app/launch',
|
|
clientAutomaticRetry: false,
|
|
},
|
|
boundaries: { verifyImage: 'passed', pfsMount: 'passed', executable: 'reached', process: 'stable', basis: 'stable-exact-title-process' },
|
|
rawEvidence: [
|
|
{ observedAt: 2_100, summary: { observed: true, processListTruncated: false, process: { titleId: 'PPSA01234', kind: 'app', pid: 321 } } },
|
|
{ observedAt: 12_100, summary: { observed: true, processListTruncated: false, process: { titleId: 'PPSA01234', kind: 'app', pid: 321 } } },
|
|
],
|
|
},
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function fullFpkgEvidence(overrides = {}) {
|
|
return {
|
|
titleId: 'PPSA01234',
|
|
packageClass: 'fpkg',
|
|
sourceEvidenceSha256: 'a'.repeat(64),
|
|
route: '/api/ps5/app/launch',
|
|
preflight: {
|
|
observedAtMs: 1_000,
|
|
coverageComplete: true,
|
|
truncated: false,
|
|
exactTitleAbsent: true,
|
|
},
|
|
request: {
|
|
observedAtMs: 2_000,
|
|
operationId: 'qualified-control-operation',
|
|
titleId: 'PPSA01234',
|
|
clientRequests: 1,
|
|
retryPerformed: false,
|
|
},
|
|
processSamples: [
|
|
{ observedAtMs: 2_100, coverageComplete: true, truncated: false, titleId: 'PPSA01234', kind: 'app', pid: 321 },
|
|
{ observedAtMs: 12_100, coverageComplete: true, truncated: false, titleId: 'PPSA01234', kind: 'app', pid: 321 },
|
|
],
|
|
packageEvidence: {
|
|
fakePackage: true,
|
|
artifactSha256: 'b'.repeat(64),
|
|
installedEvidenceSha256: 'c'.repeat(64),
|
|
},
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
test('A8 qualifies only a request-bound stable exact-title fPKG process', () => {
|
|
const result = qualifyA8ControlEvidence(fullFpkgEvidence());
|
|
assert.equal(result.schema, 'chimera.p15-a8-control-qualification.v1');
|
|
assert.equal(result.titleId, 'PPSA01234');
|
|
assert.equal(result.packageClass, 'fpkg');
|
|
assert.equal(result.qualificationScope, 'full_fpkg_route');
|
|
assert.equal(result.sameApiRoute, '/api/ps5/app/launch');
|
|
assert.equal(result.stableDurationMs, 10_000);
|
|
assert.equal(result.stableExactTitleProcess, true);
|
|
assert.match(result.qualificationDigest, /^[a-f0-9]{64}$/);
|
|
assert.equal(verifyA8ControlQualification(result), result);
|
|
assert.ok(Object.isFrozen(result));
|
|
});
|
|
|
|
test('an unrelated already-running title cannot become an A8 control', () => {
|
|
assert.throws(() => qualifyA8ControlEvidence(fullFpkgEvidence({
|
|
titleId: 'PPSA02774',
|
|
preflight: {
|
|
observedAtMs: 1_000,
|
|
coverageComplete: true,
|
|
truncated: false,
|
|
exactTitleAbsent: false,
|
|
},
|
|
request: {
|
|
observedAtMs: 2_000,
|
|
operationId: 'launching-a-different-title',
|
|
titleId: 'PPSA99099',
|
|
clientRequests: 1,
|
|
retryPerformed: false,
|
|
},
|
|
processSamples: [
|
|
{ observedAtMs: 2_100, coverageComplete: true, truncated: false, titleId: 'PPSA02774', kind: 'app', pid: 121 },
|
|
{ observedAtMs: 12_100, coverageComplete: true, truncated: false, titleId: 'PPSA02774', kind: 'app', pid: 121 },
|
|
],
|
|
})), error => error.code === 'P15_A8_CONTROL_NOT_REQUEST_BOUND');
|
|
});
|
|
|
|
test('A8 rejects incomplete, unstable and too-short process observations', () => {
|
|
assert.throws(() => qualifyA8ControlEvidence(fullFpkgEvidence({
|
|
processSamples: [
|
|
{ observedAtMs: 2_100, coverageComplete: true, truncated: false, titleId: 'PPSA01234', kind: 'app', pid: 321 },
|
|
{ observedAtMs: 12_100, coverageComplete: false, truncated: true, titleId: 'PPSA01234', kind: 'app', pid: 321 },
|
|
],
|
|
})), error => error.code === 'P15_A8_CONTROL_PROCESS_EVIDENCE_INCOMPLETE');
|
|
assert.throws(() => qualifyA8ControlEvidence(fullFpkgEvidence({
|
|
processSamples: [
|
|
{ observedAtMs: 2_100, coverageComplete: true, truncated: false, titleId: 'PPSA01234', kind: 'app', pid: 321 },
|
|
{ observedAtMs: 12_100, coverageComplete: true, truncated: false, titleId: 'PPSA01234', kind: 'app', pid: 322 },
|
|
],
|
|
})), error => error.code === 'P15_A8_CONTROL_PROCESS_NOT_STABLE');
|
|
assert.throws(() => qualifyA8ControlEvidence(fullFpkgEvidence({
|
|
processSamples: [
|
|
{ observedAtMs: 2_100, coverageComplete: true, truncated: false, titleId: 'PPSA01234', kind: 'app', pid: 321 },
|
|
{ observedAtMs: 12_099, coverageComplete: true, truncated: false, titleId: 'PPSA01234', kind: 'app', pid: 321 },
|
|
],
|
|
})), error => error.code === 'P15_A8_CONTROL_PROCESS_NOT_STABLE');
|
|
});
|
|
|
|
test('signed retail evidence can qualify ShellUI only, never the full fPKG route', () => {
|
|
const evidence = fullFpkgEvidence({
|
|
packageClass: 'signed_retail',
|
|
packageEvidence: {
|
|
fakePackage: false,
|
|
artifactSha256: 'b'.repeat(64),
|
|
installedEvidenceSha256: 'c'.repeat(64),
|
|
},
|
|
});
|
|
const result = qualifyA8ControlEvidence(evidence);
|
|
assert.equal(result.qualificationScope, 'shellui_route_only');
|
|
});
|
|
|
|
test('qualification integrity rejects semantic tampering', () => {
|
|
const result = qualifyA8ControlEvidence(fullFpkgEvidence());
|
|
assert.throws(
|
|
() => verifyA8ControlQualification({ ...result, titleId: 'PPSA99999' }),
|
|
error => error.code === 'P15_A8_CONTROL_QUALIFICATION_CORRUPT',
|
|
);
|
|
});
|
|
|
|
test('A8 derives qualification from an existing durable launch-verified operation', () => {
|
|
const result = qualifyA8ControlOperation({
|
|
operation: verifiedOperation(),
|
|
sourceEvidenceSha256: 'd'.repeat(64),
|
|
packageClass: 'fpkg',
|
|
packageEvidence: { fakePackage: true, artifactSha256: 'e'.repeat(64), installedEvidenceSha256: 'f'.repeat(64) },
|
|
});
|
|
assert.equal(result.titleId, 'PPSA01234');
|
|
assert.equal(result.operationId, 'qualified-control-operation');
|
|
assert.equal(result.qualificationScope, 'full_fpkg_route');
|
|
});
|
|
|
|
test('success flags cannot hide route drift, truncated coverage or an unrelated process', () => {
|
|
const input = {
|
|
sourceEvidenceSha256: 'd'.repeat(64), packageClass: 'fpkg',
|
|
packageEvidence: { fakePackage: true, artifactSha256: 'e'.repeat(64), installedEvidenceSha256: 'f'.repeat(64) },
|
|
};
|
|
assert.throws(() => qualifyA8ControlOperation({ ...input, operation: verifiedOperation({
|
|
launchEvidence: { ...verifiedOperation().launchEvidence, request: { ...verifiedOperation().launchEvidence.request, route: '/api/ps5/app/open' } },
|
|
}) }), error => error.code === 'P15_A8_CONTROL_ROUTE_MISMATCH');
|
|
const truncated = verifiedOperation();
|
|
truncated.launchEvidence.rawEvidence[1].summary.processListTruncated = true;
|
|
assert.throws(
|
|
() => qualifyA8ControlOperation({ ...input, operation: truncated }),
|
|
error => error.code === 'P15_A8_CONTROL_PROCESS_EVIDENCE_INCOMPLETE',
|
|
);
|
|
const unrelated = verifiedOperation();
|
|
unrelated.launchEvidence.rawEvidence[0].summary.process.titleId = 'PPSA02774';
|
|
assert.throws(
|
|
() => qualifyA8ControlOperation({ ...input, operation: unrelated }),
|
|
error => error.code === 'P15_A8_CONTROL_PROCESS_NOT_STABLE',
|
|
);
|
|
const missingBoundary = verifiedOperation();
|
|
delete missingBoundary.launchEvidence.boundaries;
|
|
assert.throws(
|
|
() => qualifyA8ControlOperation({ ...input, operation: missingBoundary }),
|
|
error => error.code === 'P15_A8_CONTROL_OPERATION_UNQUALIFIED',
|
|
);
|
|
});
|