'use strict'; const assert = require('node:assert/strict'); const crypto = require('node:crypto'); const fs = require('node:fs'); const os = require('node:os'); const path = require('node:path'); const test = require('node:test'); const { createPs5UploadPackageInstallerProvider, PINNED_PS5_PACKAGE_INSTALLER, } = require('../../src/services/ps5upload-package-installer-provider'); const { createPs5NativePackageInstallService } = require('../../src/services/ps5-native-package-install-service'); function fixture() { const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-ps5-fpkg-')); const filePath = path.join(root, 'EP9000-PPSA01234_00-CHIMERATEST00001.pkg'); const bytes = Buffer.concat([Buffer.from([0x7f, 0x46, 0x49, 0x48]), Buffer.alloc(64, 0x5a)]); fs.writeFileSync(filePath, bytes); return { root, filePath, component: { componentId: 'base', type: 'base', size: bytes.length, sha256: crypto.createHash('sha256').update(bytes).digest('hex'), contentId: 'EP9000-PPSA01234_00-CHIMERATEST00001', metadata: { absolutePath: filePath, titleId: { value: 'PPSA01234' } }, }, }; } function response(body, status = 200) { return { ok: status >= 200 && status < 300, status, json: async () => body, text: async () => typeof body === 'string' ? body : JSON.stringify(body) }; } function activeRuntimeProcesses() { return response({ processes: [ { pid: 107, name: 'kstuff.elf', comm: 'kstuff.elf', kind: 'payload' }, { pid: 108, name: 'etaHEN Utility Daemon', comm: 'etaHEN Utility Daemon', kind: 'system' }, ], truncated: false }); } function exactRuntimeIdentity() { return { observed: { engine: { revision: PINNED_PS5_PACKAGE_INSTALLER.revision, sha256: PINNED_PS5_PACKAGE_INSTALLER.artifacts.engineLinuxX64Sha256, evidence: 'running-process-bytes' }, payload: { revision: PINNED_PS5_PACKAGE_INSTALLER.revision, sha256: PINNED_PS5_PACKAGE_INSTALLER.artifacts.payloadSha256, evidence: 'loader-input-and-instance-transition' }, dpi: { active: false }, }, }; } test('PS5 FPKG provider pins 5.4.19, accepts only loopback and installs a hash-bound staged FIH .pkg', async t => { const item = fixture(); t.after(() => fs.rmSync(item.root, { recursive: true, force: true })); const calls = []; const fetch = async (url, options = {}) => { calls.push({ url: String(url), options, body: options.body ? JSON.parse(options.body) : null }); if (String(url).endsWith('/api/version')) return response({ version: '5.4.19' }); if (String(url).includes('/api/ps5/status')) return response({ version: '5.4.19', ucred_elevated: true }); if (String(url).includes('/api/ps5/readiness')) return response({ ready: true }); if (String(url).includes('/api/ps5/process/list')) return activeRuntimeProcesses(); return response({ session_id: '7846dc5a-415c-4d72-bf1b-97fd08a887b4', err_code: 0, package_type: 'PS5GD', register_path: 'bgft', launchable: null }); }; const provider = createPs5UploadPackageInstallerProvider({ engineOrigin: 'http://127.0.0.1:19113', consoleHost: 'ps5.local', packageRoot: item.root, getRuntimeIdentity: exactRuntimeIdentity, fetch }); const stagedPath = `/user/data/ps5upload/pkg_library/${item.component.contentId}.pkg`; const result = await provider.start({ component: item.component, stagedPath }); assert.equal(PINNED_PS5_PACKAGE_INSTALLER.version, '5.4.19'); assert.equal(result.installed, false); assert.equal(result.status, 'installing'); assert.deepEqual(calls.find(call => call.body?.package_type_override).body, { ps5_addr: 'ps5.local:9114', path: null, split_root: null, package_type_override: 'PS5GD', local_ps5_path: stagedPath, content_id: item.component.contentId, expected_size: item.component.size, package_fingerprint: null, delete_staging: false, serve_only: false, }); assert.equal(fs.existsSync(item.filePath), true); assert.throws(() => createPs5UploadPackageInstallerProvider({ engineOrigin: 'http://192.168.1.10:19113', consoleHost: 'ps5.local', packageRoot: item.root, fetch }), error => error.code === 'PS5_PKG_ENGINE_ORIGIN'); }); test('PS5 FPKG provider follows the pinned standalone-DPI fallback and restores the main payload', async t => { const item = fixture(); t.after(() => fs.rmSync(item.root, { recursive: true, force: true })); const calls = []; let dpiEnsures = 0; let restores = 0; const provider = createPs5UploadPackageInstallerProvider({ engineOrigin: 'http://127.0.0.1:19113', consoleHost: 'ps5.local', packageRoot: item.root, getRuntimeIdentity: exactRuntimeIdentity, ensureDpi: async () => { dpiEnsures += 1; return { ready: true }; }, restorePayload: async () => { restores += 1; return { managementReady: true }; }, fetch: async (url, options = {}) => { calls.push({ url: String(url), body: options.body ? JSON.parse(options.body) : null }); if (String(url).endsWith('/api/version')) return response({ version: '5.4.19' }); if (String(url).includes('/api/ps5/status')) return response({ version: '5.4.19', ucred_elevated: true }); if (String(url).includes('/api/ps5/readiness')) return response({ ready: true }); if (String(url).includes('/api/ps5/process/list')) return activeRuntimeProcesses(); if (String(url).endsWith('/api/pkg/install/start')) return response({ error: 'payload PKG_INSTALL failed: frame rejected' }, 502); if (String(url).endsWith('/api/pkg/dpi-install')) return response({ ok: true, rc: 0, init_failed: false }); return response({}); }, }); const result = await provider.start({ component: item.component, stagedPath: `/user/data/ps5upload/pkg_library/${item.component.contentId}.pkg` }); assert.equal(dpiEnsures, 1); assert.equal(restores, 1); assert.equal(result.status, 'accepted_unverified'); assert.equal(result.route, 'dpi-staged'); assert.equal(result.installed, false); assert.equal(result.postScanRequired, true); assert.deepEqual(calls.find(call => call.url.endsWith('/api/pkg/dpi-install')).body, { ps5_addr: 'ps5.local:9114', local_ps5_path: `/user/data/ps5upload/pkg_library/${item.component.contentId}.pkg` }); }); test('PS5 FPKG provider prefers a verified active etaHEN DPI v2 stream and skips AppInst plus payload swapping', async t => { const item = fixture(); t.after(() => fs.rmSync(item.root, { recursive: true, force: true })); let primaryStarts = 0; let dpiEnsures = 0; let restores = 0; let directInstalls = 0; const dpiV2Installer = { probe: async () => ({ available: true, protocol: 'etahen-dpi-v2-url-v1' }), install: async artifact => { directInstalls += 1; assert.equal(artifact.filePath, item.filePath); assert.equal(artifact.sha256, item.component.sha256); return { accepted: true, installed: false, postScanRequired: true, response: 'SUCCESS: queued', responseStatus: 200, responseSha256: 'b'.repeat(64) }; }, }; const provider = createPs5UploadPackageInstallerProvider({ engineOrigin: 'http://127.0.0.1:19113', consoleHost: 'ps5.local', packageRoot: item.root, getRuntimeIdentity: exactRuntimeIdentity, getEtaHenConfig: async () => ({ values: { PS5Debug: '1', DPI: '0', DPI_v2: '1' } }), dpiV2Installer, ensureDpi: async () => { dpiEnsures += 1; }, restorePayload: async () => { restores += 1; }, fetch: async url => { if (String(url).endsWith('/api/version')) return response({ version: '5.4.19' }); if (String(url).includes('/api/ps5/status')) return response({ version: '5.4.19', ucred_elevated: true }); if (String(url).includes('/api/ps5/readiness')) return response({ ready: true }); if (String(url).includes('/api/ps5/process/list')) return activeRuntimeProcesses(); if (String(url).endsWith('/api/pkg/install/start')) primaryStarts += 1; return response({ session_id: 'should-not-start', err_code: 0 }); }, }); const result = await provider.start({ component: item.component, stagedPath: `/user/data/ps5upload/pkg_library/${item.component.contentId}.pkg` }); assert.equal(result.status, 'accepted_unverified'); assert.equal(result.route, 'etahen-dpi-v2-url'); assert.equal(result.installed, false); assert.equal(result.postScanRequired, true); assert.equal(result.providerEvidence.response, 'SUCCESS: queued'); assert.equal(directInstalls, 1); assert.equal(primaryStarts, 0); assert.equal(dpiEnsures, 0); assert.equal(restores, 0); }); test('PS5 FPKG direct etaHEN DPI v2 preflight does not require PS5Upload engine or management payload', async t => { const item = fixture(); t.after(() => fs.rmSync(item.root, { recursive: true, force: true })); let managementCalls = 0; const dpiV2Installer = { probe: async () => ({ available: true, protocol: 'etahen-dpi-v2-url-v1' }), install: async () => ({ accepted: true, installed: false, postScanRequired: true }), }; const provider = createPs5UploadPackageInstallerProvider({ engineOrigin: 'http://127.0.0.1:19113', consoleHost: 'ps5.local', packageRoot: item.root, getEtaHenConfig: async () => ({ values: { PS5Debug: '1', DPI: '0', DPI_v2: '1' } }), dpiV2Installer, fetch: async url => { if (String(url).endsWith('/api/version')) return response({ error: 'engine offline' }, 503); managementCalls += 1; return response({ error: 'management offline' }, 503); }, }); const readiness = await provider.preflightInstall({ components: [item.component] }); assert.equal(readiness.ready, true); assert.equal(readiness.installStrategy, 'etahen-dpi-v2-url'); assert.equal(readiness.dpiV2.available, true); assert.deepEqual(readiness.capabilities, { artifactReady: true, etaHenDpiV2Configured: true, etaHenDpiV2IdentityReady: true, }); assert.equal(readiness.runtimeIdentity, null); assert.equal(readiness.runtimeIdentityRequired, false); assert.equal(managementCalls, 0); const result = await provider.start({ component: item.component, stagedPath: `/user/data/ps5upload/pkg_library/${item.component.contentId}.pkg` }); assert.equal(result.route, 'etahen-dpi-v2-url'); }); test('PS5 FPKG provider preserves the upstream error body when no DPI lifecycle is bound', async t => { const item = fixture(); t.after(() => fs.rmSync(item.root, { recursive: true, force: true })); const provider = createPs5UploadPackageInstallerProvider({ engineOrigin: 'http://127.0.0.1:19113', consoleHost: 'ps5.local', packageRoot: item.root, getRuntimeIdentity: exactRuntimeIdentity, fetch: async url => { if (String(url).endsWith('/api/version')) return response({ version: '5.4.19' }); if (String(url).includes('/api/ps5/status')) return response({ version: '5.4.19', ucred_elevated: true }); if (String(url).includes('/api/ps5/readiness')) return response({ ready: true }); if (String(url).includes('/api/ps5/process/list')) return activeRuntimeProcesses(); return response({ error: 'payload PKG_INSTALL failed: unknown frame' }, 502); }, }); await assert.rejects( () => provider.start({ component: item.component, stagedPath: `/user/data/ps5upload/pkg_library/${item.component.contentId}.pkg` }), error => error.code === 'PS5_PKG_PROVIDER_HTTP' && /unknown frame/.test(error.message) && /unknown frame/.test(error.details?.upstream || ''), ); }); test('PS5 FPKG provider rejects old pseudo formats, wrong hashes, traversal and unsupported updates', async t => { const item = fixture(); t.after(() => fs.rmSync(item.root, { recursive: true, force: true })); const provider = createPs5UploadPackageInstallerProvider({ engineOrigin: 'http://localhost:19113', consoleHost: 'ps5.local', packageRoot: item.root, fetch: async () => response({ version: '5.4.19' }) }); const wrongExtension = { ...item.component, metadata: { ...item.component.metadata, absolutePath: path.join(item.root, 'legacy.ffpkg') } }; fs.copyFileSync(item.filePath, wrongExtension.metadata.absolutePath); const stagedPath = `/user/data/ps5upload/pkg_library/${item.component.contentId}.pkg`; await assert.rejects(() => provider.start({ component: wrongExtension, stagedPath }), error => error.code === 'PS5_PKG_EXTENSION'); await assert.rejects(() => provider.start({ component: { ...item.component, sha256: '0'.repeat(64) }, stagedPath }), error => error.code === 'PS5_FPKG_ARTIFACT_HASH_DRIFT'); await assert.rejects(() => provider.start({ component: { ...item.component, type: 'update' }, stagedPath }), error => error.code === 'PS5_PKG_COMPONENT_TYPE'); const outside = path.join(os.tmpdir(), `outside-${crypto.randomUUID()}.pkg`); fs.copyFileSync(item.filePath, outside); t.after(() => fs.rmSync(outside, { force: true })); await assert.rejects(() => provider.start({ component: { ...item.component, metadata: { ...item.component.metadata, absolutePath: outside } }, stagedPath }), error => error.code === 'PS5_PKG_PATH'); await assert.rejects(() => provider.start({ component: item.component, stagedPath: '/data/arbitrary.pkg' }), error => error.code === 'PS5_PKG_STAGING_PATH'); }); test('PS5 FPKG provider maps DLC to PS5AC and never equates acceptance with installation', async t => { const item = fixture(); t.after(() => fs.rmSync(item.root, { recursive: true, force: true })); let statusBody = { session_id: 's1', phase: 'done', downloaded: 68, total: 68, err_code: 0, accepted_unverified: true, stalled: false, launchable: true }; const calls = []; const fetch = async (url, options = {}) => { calls.push({ url: String(url), body: options.body ? JSON.parse(options.body) : null }); if (String(url).endsWith('/api/version')) return response({ version: '5.4.19' }); if (String(url).includes('/api/ps5/status')) return response({ version: '5.4.19', ucred_elevated: true }); if (String(url).includes('/api/ps5/readiness')) return response({ ready: true }); if (String(url).includes('/api/ps5/process/list')) return activeRuntimeProcesses(); if (String(url).includes('/api/pkg/install/status')) return response(statusBody); return response({ session_id: 's1', err_code: 0, package_type: 'PS5AC' }); }; const provider = createPs5UploadPackageInstallerProvider({ engineOrigin: 'http://[::1]:19113', consoleHost: '10.0.0.20', packageRoot: item.root, getRuntimeIdentity: exactRuntimeIdentity, fetch }); const started = await provider.start({ component: { ...item.component, type: 'dlc' }, stagedPath: `/user/data/ps5upload/pkg_library/${item.component.contentId}.pkg` }); assert.equal(calls.find(call => call.body?.package_type_override).body.package_type_override, 'PS5AC'); assert.equal(started.installed, false); assert.equal((await provider.status({ sessionId: 's1' })).providerVerified, false); statusBody = { ...statusBody, accepted_unverified: false }; const complete = await provider.status({ sessionId: 's1' }); assert.equal(complete.providerVerified, true); assert.equal(complete.installed, false); assert.equal(complete.postScanRequired, true); }); test('PS5 FPKG provider fails fast on Sony AppInst rejection and preserves route evidence', async t => { const item = fixture(); t.after(() => fs.rmSync(item.root, { recursive: true, force: true })); const provider = createPs5UploadPackageInstallerProvider({ engineOrigin: 'http://127.0.0.1:19113', consoleHost: 'ps5.local', packageRoot: item.root, fetch: async url => { if (String(url).endsWith('/api/version')) return response({ version: '5.4.19' }); if (String(url).includes('/api/pkg/install/status')) return response({ session_id: 's-appinst', phase: 'install', downloaded: 0, total: item.component.size, err_code: 0, appinst_err: 0x80B2116F, shellui_err: 0, register_path: 'shellui-rpc', intdebug_avail: false, kernel_rw: true, installed_bytes: item.component.size - 32_188, accepted_unverified: false, stalled: false, launchable: null, }); return response({ ready: true }); }, }); const snapshot = await provider.status({ sessionId: 's-appinst' }); assert.equal(snapshot.phase, 'error'); assert.equal(snapshot.errCode, 0x80B2116F); assert.match(snapshot.errMessage, /0x80B2116F/); assert.equal(snapshot.terminalReason, 'sony-appinst-rejected'); assert.equal(snapshot.route, 'shellui-rpc'); assert.equal(snapshot.intdebugAvailable, false); assert.equal(snapshot.kernelRw, true); assert.equal(snapshot.appInstError, 0x80B2116F); assert.equal(snapshot.shellUiError, 0); assert.equal(snapshot.providerVerified, false); }); test('PS5 FPKG preflight records etaHEN PS5Debug as diagnostic evidence without treating it as package trust', async t => { const item = fixture(); t.after(() => fs.rmSync(item.root, { recursive: true, force: true })); let config = { PS5Debug: '0', DPI: '0', DPI_v2: '0' }; let mutations = 0; const provider = createPs5UploadPackageInstallerProvider({ engineOrigin: 'http://127.0.0.1:19113', consoleHost: 'ps5.local', packageRoot: item.root, getEtaHenConfig: async () => ({ values: config }), fetch: async (url, options = {}) => { if (options.method === 'POST') mutations += 1; if (String(url).endsWith('/api/version')) return response({ version: '5.4.19' }); if (String(url).includes('/api/ps5/status')) return response({ version: '5.4.19', ucred_elevated: true, instance_id: '7673539444651544000', started_at_unix: 1786635128, startup_reason: 1 }); if (String(url).includes('/api/ps5/readiness')) return response({ ready: true }); if (String(url).includes('/api/ps5/process/list')) return activeRuntimeProcesses(); return response({}); }, }); const diagnostic = await provider.preflight({ components: [item.component] }); assert.equal(diagnostic.ready, true); assert.equal(diagnostic.runtime.ps5Debug, false); assert.equal(diagnostic.runtime.required, false); assert.equal(diagnostic.runtime.reason, 'ps5debug-disabled-diagnostic-only'); assert.equal(diagnostic.console.instanceId, '7673539444651544000'); assert.equal(diagnostic.console.startedAtUnix, 1786635128); assert.equal(diagnostic.console.startupReason, 1); assert.equal(mutations, 0); config = { ...config, PS5Debug: '1' }; const ready = await provider.preflight({ components: [item.component] }); assert.equal(ready.ready, true); assert.equal(ready.runtime.ps5Debug, true); assert.equal(ready.runtime.verified, true); }); test('PS5 FPKG install job persists terminal provider diagnostics instead of waiting for generic done', async t => { const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-ps5-fpkg-terminal-')); t.after(() => fs.rmSync(root, { recursive: true, force: true })); const provider = { preflight: async () => ({ ready: true }), start: async () => ({ status: 'installing', sessionId: 's-terminal', errCode: 0 }), status: async () => ({ sessionId: 's-terminal', phase: 'error', downloaded: 0, total: 68, installedBytes: 36, errCode: 0x80B2116F, errMessage: 'Sony AppInst rejected the package (0x80B2116F).', terminalReason: 'sony-appinst-rejected', route: 'shellui-rpc', appInstError: 0x80B2116F, shellUiError: 0, intdebugAvailable: false, kernelRw: true, providerVerified: false, acceptedUnverified: false, stalled: false, launchable: null, }), cancel: async () => ({ cancelled: true }), }; const service = createPs5NativePackageInstallService({ provider, stager: async () => ({ stagedPath: '/user/data/ps5upload/pkg_library/test.pkg' }), stateFile: path.join(root, 'jobs.json'), pollMs: 5 }); const operation = await service.start({ titleId: 'PPSA01234', packageSet: { id: 7, components: [{ componentId: 'base', type: 'base', order: 1, sha256: 'a'.repeat(64), size: 68 }] } }); let current = operation; for (let attempt = 0; attempt < 30 && current.status !== 'blocked'; attempt += 1) { await new Promise(resolve => setTimeout(resolve, 10)); current = service.get(operation.id); } assert.equal(current.status, 'blocked'); assert.equal(current.error.code, 'PS5_PKG_INSTALL_REJECTED'); assert.equal(current.error.terminalReason, 'sony-appinst-rejected'); assert.equal(current.error.route, 'shellui-rpc'); assert.equal(current.error.appInstError, 0x80B2116F); assert.equal(current.error.intdebugAvailable, false); assert.equal(current.components[0].status, 'rejected'); assert.equal(current.installed, false); }); test('PS5 FPKG install job performs one deferred DPI fallback after a terminal AppInst rejection', async t => { const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-ps5-fpkg-deferred-dpi-')); t.after(() => fs.rmSync(root, { recursive: true, force: true })); let fallbackCalls = 0; const provider = { preflight: async () => ({ ready: true }), start: async () => ({ status: 'installing', sessionId: 's-primary', errCode: 0 }), status: async () => ({ sessionId: 's-primary', phase: 'error', reportedPhase: 'install', downloaded: 0, total: 68, installedBytes: 0, errCode: 0x80B2116F, errMessage: 'Sony AppInst rejected the package (0x80B2116F).', terminalReason: 'sony-appinst-rejected', route: 'shellui-rpc', appInstError: 0x80B2116F, shellUiError: 0, intdebugAvailable: false, kernelRw: true, providerVerified: false, acceptedUnverified: false, stalled: false, launchable: null, }), fallback: async ({ stagedPath, primaryEvidence }) => { fallbackCalls += 1; assert.equal(stagedPath, '/user/data/ps5upload/pkg_library/test.pkg'); assert.equal(primaryEvidence.appInstError, 0x80B2116F); return { status: 'accepted_unverified', sessionId: 'dpi-deferred', errCode: 0, route: 'dpi-staged', acceptedUnverified: true, providerVerified: false }; }, cancel: async () => ({ cancelled: true }), }; const service = createPs5NativePackageInstallService({ provider, stager: async () => ({ stagedPath: '/user/data/ps5upload/pkg_library/test.pkg' }), stateFile: path.join(root, 'jobs.json'), pollMs: 5 }); const operation = await service.start({ titleId: 'PPSA99099', packageSet: { id: 5, components: [{ componentId: 'base', type: 'base', order: 1, sha256: 'c'.repeat(64), size: 68 }] } }); let current = operation; for (let attempt = 0; attempt < 30 && current.status !== 'awaiting_post_scan'; attempt += 1) { await new Promise(resolve => setTimeout(resolve, 10)); current = service.get(operation.id); } assert.equal(current.status, 'awaiting_post_scan'); assert.equal(current.installed, false); assert.equal(current.components[0].dpiFallbackAttempted, true); assert.equal(current.components[0].primaryRejection.route, 'shellui-rpc'); assert.equal(current.components[0].route, 'dpi-staged'); assert.equal(fallbackCalls, 1); }); test('PS5 FPKG install job treats DPI acceptance as unproven until the independent post-scan', async t => { const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-ps5-fpkg-dpi-')); t.after(() => fs.rmSync(root, { recursive: true, force: true })); const provider = { preflight: async () => ({ ready: true }), start: async () => ({ status: 'accepted_unverified', sessionId: 'dpi-test', errCode: 0, route: 'dpi-staged', acceptedUnverified: true, providerVerified: false }), status: async () => { throw new Error('DPI acceptance must not use the main-payload status poll'); }, cancel: async () => ({ cancelled: true }), }; const service = createPs5NativePackageInstallService({ provider, stager: async () => ({ stagedPath: '/user/data/ps5upload/pkg_library/test.pkg' }), stateFile: path.join(root, 'jobs.json'), pollMs: 5 }); const operation = await service.start({ titleId: 'PPSA99099', packageSet: { id: 5, components: [{ componentId: 'base', type: 'base', order: 1, sha256: 'a'.repeat(64), size: 68 }] } }); let current = operation; for (let attempt = 0; attempt < 30 && current.status !== 'awaiting_post_scan'; attempt += 1) { await new Promise(resolve => setTimeout(resolve, 10)); current = service.get(operation.id); } assert.equal(current.status, 'awaiting_post_scan'); assert.equal(current.installed, false); assert.equal(current.components[0].status, 'accepted_unverified'); assert.equal(current.components[0].providerVerified, false); assert.equal(current.components[0].route, 'dpi-staged'); }); test('PS5 FPKG direct etaHEN URL handoff skips redundant FTP staging and persists bounded provider evidence', async t => { const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-ps5-fpkg-direct-url-')); t.after(() => fs.rmSync(root, { recursive: true, force: true })); let stages = 0; let stagedPathSeen = 'unset'; const provider = { preflight: async () => ({ ready: true, runtime: { dpiV2: true }, installStrategy: 'etahen-dpi-v2-url' }), start: async ({ stagedPath }) => { stagedPathSeen = stagedPath; return { status: 'accepted_unverified', sessionId: 'dpi-url', errCode: 0, route: 'etahen-dpi-v2-url', acceptedUnverified: true, providerVerified: false, providerEvidence: { responseStatus: 200, response: 'SUCCESS: queued', responseSha256: 'c'.repeat(64) } }; }, status: async () => { throw new Error('URL handoff must not poll the management payload'); }, cancel: async () => ({ cancelled: true }), }; const service = createPs5NativePackageInstallService({ provider, stager: async () => { stages += 1; return { stagedPath: '/should/not/be/used.pkg' }; }, stateFile: path.join(root, 'jobs.json') }); const operation = await service.start({ titleId: 'PPSA99099', packageSet: { id: 9, components: [{ componentId: 'base', type: 'base', order: 1, sha256: 'd'.repeat(64), size: 68 }] } }); let current = operation; for (let attempt = 0; attempt < 30 && current.status !== 'awaiting_post_scan'; attempt += 1) { await new Promise(resolve => setTimeout(resolve, 10)); current = service.get(operation.id); } assert.equal(current.status, 'awaiting_post_scan'); assert.equal(stages, 0); assert.equal(stagedPathSeen, null); assert.equal(current.components[0].providerEvidence.response, 'SUCCESS: queued'); assert.equal(current.installed, false); }); test('PS5 FPKG install job blocks an identical hash after proven AppInst rejection before staging', async t => { const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-ps5-fpkg-known-rejection-')); t.after(() => fs.rmSync(root, { recursive: true, force: true })); const stateFile = path.join(root, 'jobs.json'); const sha256 = 'eafb572e94b72b4b714f8f45a3316abb957fdca4a23ef5c44741c9deb5a16d0c'; const readiness = { ready: true, engine: { actualVersion: '5.2.1' }, runtime: { ps5Debug: false, dpi: false, dpiV2: false }, console: { runtimeVersion: '5.2.1', kernel: '9.60', ucredElevated: true, instanceId: '7673539444651544000', startedAtUnix: 1786635128, startupReason: 1 } }; const runtimeFingerprint = crypto.createHash('sha256').update(JSON.stringify({ engineVersion: '5.2.1', ps5Debug: false, dpi: false, dpiV2: false, managementVersion: '5.2.1', kernel: '9.60', ucredElevated: true, managementInstanceId: '7673539444651544000', managementStartedAtUnix: 1786635128, managementStartupReason: 1, installStrategy: 'primary-then-deferred-dpi-v1' })).digest('hex'); fs.writeFileSync(stateFile, JSON.stringify([{ id: 'previous-rejection', packageSetId: 4, titleId: 'PPSA17330', status: 'blocked', installed: false, createdAt: 1, updatedAt: 2, runtimeFingerprint, components: [{ componentId: 'base', type: 'base', order: 0, sha256, size: 1358617285, installed: false }], error: { code: 'PS5_PKG_INSTALL_REJECTED', terminalReason: 'sony-appinst-rejected', appInstError: 0x80B2116F, route: 'shellui-rpc' }, }])); let preflights = 0; let stages = 0; const provider = { preflight: async () => { preflights += 1; return readiness; }, start: async () => ({ status: 'installing', sessionId: 'should-not-start', errCode: 0 }), status: async () => ({ phase: 'install' }), cancel: async () => ({ cancelled: true }), }; const service = createPs5NativePackageInstallService({ provider, stager: async () => { stages += 1; return { stagedPath: '/user/data/ps5upload/pkg_library/test.pkg' }; }, stateFile }); await assert.rejects( () => service.start({ titleId: 'PPSA17330', packageSet: { id: 4, components: [{ componentId: 'base', type: 'base', order: 1, sha256, size: 1358617285 }] } }), error => error.code === 'PS5_PKG_INSTALL_KNOWN_REJECTED' && error.details?.previousOperationId === 'previous-rejection' && error.details?.appInstError === 0x80B2116F, ); assert.equal(preflights, 1); assert.equal(stages, 0); }); test('PS5 FPKG install job permits one retry after a new management instance and blocks a repeat in that instance', async t => { const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-ps5-fpkg-runtime-retry-')); t.after(() => fs.rmSync(root, { recursive: true, force: true })); const stateFile = path.join(root, 'jobs.json'); const sha256 = 'b'.repeat(64); const previousRuntimeFingerprint = crypto.createHash('sha256').update(JSON.stringify({ engineVersion: '5.2.1', ps5Debug: true, dpi: true, dpiV2: false, managementVersion: '5.2.1', kernel: '9.60', ucredElevated: true, managementInstanceId: '111', managementStartedAtUnix: 1786635000, managementStartupReason: 1, installStrategy: 'primary-then-deferred-dpi-v1' })).digest('hex'); fs.writeFileSync(stateFile, JSON.stringify([{ id: 'previous-instance-rejection', packageSetId: 5, titleId: 'PPSA99099', status: 'blocked', installed: false, createdAt: 1, updatedAt: 2, runtimeFingerprint: previousRuntimeFingerprint, components: [{ componentId: 'base', type: 'base', order: 0, sha256, size: 68, installed: false }], error: { code: 'PS5_PKG_INSTALL_REJECTED', terminalReason: 'sony-appinst-rejected', appInstError: 0x80B2116F, route: 'shellui-rpc' }, }])); let stages = 0; const provider = { preflight: async () => ({ ready: true, engine: { actualVersion: '5.2.1' }, runtime: { ps5Debug: true, dpi: true, dpiV2: false }, console: { runtimeVersion: '5.2.1', kernel: '9.60', ucredElevated: true, instanceId: '222', startedAtUnix: 1786635128, startupReason: 1 } }), start: async () => ({ status: 'rejected', sessionId: 'runtime-retry', errCode: 0x80B2116F, errMessage: 'rejected', terminalReason: 'sony-appinst-rejected', route: 'shellui-rpc', appInstError: 0x80B2116F }), status: async () => ({ phase: 'error' }), cancel: async () => ({ cancelled: true }), }; const service = createPs5NativePackageInstallService({ provider, stager: async () => { stages += 1; return { stagedPath: '/user/data/ps5upload/pkg_library/test.pkg' }; }, stateFile, pollMs: 5 }); const operation = await service.start({ titleId: 'PPSA99099', packageSet: { id: 5, components: [{ componentId: 'base', type: 'base', order: 1, sha256, size: 68 }] } }); assert.match(operation.runtimeFingerprint, /^[a-f0-9]{64}$/); let current = operation; for (let attempt = 0; attempt < 20 && current.status !== 'blocked'; attempt += 1) { await new Promise(resolve => setTimeout(resolve, 5)); current = service.get(operation.id); } assert.equal(current.status, 'blocked'); assert.equal(stages, 1); await assert.rejects( () => service.start({ titleId: 'PPSA99099', packageSet: { id: 5, components: [{ componentId: 'base', type: 'base', order: 1, sha256, size: 68 }] } }), error => error.code === 'PS5_PKG_INSTALL_KNOWN_REJECTED' && error.details?.previousOperationId === operation.id, ); assert.equal(stages, 1); }); test('PS5 FPKG provider blocks a non-pinned engine before mutation', async t => { const item = fixture(); t.after(() => fs.rmSync(item.root, { recursive: true, force: true })); let mutations = 0; const provider = createPs5UploadPackageInstallerProvider({ engineOrigin: 'http://127.0.0.1:19113', consoleHost: 'ps5.local', packageRoot: item.root, fetch: async (_url, options = {}) => { if (options.method === 'POST') mutations += 1; return response({ version: '5.1.0' }); } }); await assert.rejects(() => provider.start({ component: item.component, stagedPath: `/user/data/ps5upload/pkg_library/${item.component.contentId}.pkg` }), error => error.code === 'PS5_PKG_ENGINE_VERSION'); assert.equal(mutations, 0); }); test('PS5 FPKG provider requires live management readiness before install start', async t => { const item = fixture(); t.after(() => fs.rmSync(item.root, { recursive: true, force: true })); let installStarts = 0; const provider = createPs5UploadPackageInstallerProvider({ engineOrigin: 'http://127.0.0.1:19113', consoleHost: 'ps5.local', packageRoot: item.root, fetch: async (url, options = {}) => { if (String(url).endsWith('/api/version')) return response({ version: '5.4.19' }); if (String(url).includes('/api/ps5/readiness')) return response({ ready: false, detail: 'console busy' }); if (options.method === 'POST') installStarts += 1; return response({}); } }); await assert.rejects(() => provider.start({ component: item.component, stagedPath: `/user/data/ps5upload/pkg_library/${item.component.contentId}.pkg` }), error => error.code === 'PS5_PKG_CONSOLE_NOT_READY'); assert.equal(installStarts, 0); }); test('PS5 FPKG install job sequences base before DLC and stops at the independent post-scan gate', async t => { const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-ps5-fpkg-job-')); t.after(() => fs.rmSync(root, { recursive: true, force: true })); const started = []; const provider = { preflight: async ({ components }) => ({ ready: components.length === 2 }), start: async ({ component }) => { started.push(component.type); return { status: 'installing', sessionId: `session-${component.type}`, errCode: 0 }; }, status: async ({ sessionId }) => ({ sessionId, phase: 'done', downloaded: 100, total: 100, installedBytes: 100, errCode: 0, errMessage: '', detail: '', providerVerified: true, acceptedUnverified: false, stalled: false, launchable: true }), cancel: async () => ({ cancelled: true }), }; const service = createPs5NativePackageInstallService({ provider, stager: async ({ component }) => ({ stagedPath: `/user/data/ps5upload/pkg_library/${component.type}.pkg` }), stateFile: path.join(root, 'jobs.json'), pollMs: 5 }); const operation = await service.start({ titleId: 'PPSA01234', packageSet: { id: 7, components: [{ componentId: 'dlc', type: 'dlc', order: 20, sha256: 'b'.repeat(64), size: 2 }, { componentId: 'base', type: 'base', order: 10, sha256: 'a'.repeat(64), size: 1 }] } }); let current = operation; for (let attempt = 0; attempt < 30 && current.status !== 'awaiting_post_scan'; attempt += 1) { await new Promise(resolve => setTimeout(resolve, 10)); current = service.get(operation.id); } assert.deepEqual(started, ['base', 'dlc']); assert.equal(current.status, 'awaiting_post_scan'); assert.equal(current.installed, false); assert.equal(current.components.every(component => component.providerVerified && component.installed === false), true); const completed = service.markPostScan(operation.id, { source: 'post-install-console-scan' }); assert.equal(completed.status, 'installed_observed'); assert.equal(completed.installed, true); assert.equal(completed.installedContentVerified, false); assert.equal(completed.launchVerified, false); assert.equal(completed.productionRestorable, false); }); test('server, Electron, web and PS5 workspace expose the guarded .pkg install lifecycle', () => { const root = path.resolve(__dirname, '../..'); const registry = fs.readFileSync(path.join(root, 'src/server/create-registry.js'), 'utf8'); const preload = fs.readFileSync(path.join(root, 'preload.js'), 'utf8'); const web = fs.readFileSync(path.join(root, 'renderer/pork-web-shim.js'), 'utf8'); const modal = fs.readFileSync(path.join(root, 'renderer/modules/modal.js'), 'utf8'); const html = fs.readFileSync(path.join(root, 'renderer/index.html'), 'utf8'); const dockerfile = fs.readFileSync(path.join(root, 'Dockerfile'), 'utf8'); for (const command of ['ps5-native-packages:restore-plan', 'ps5-native-packages:restore', 'ps5-native-packages:restore-status', 'ps5-native-packages:restore-cancel', 'ps5-native-packages:restore-verify']) assert.match(registry, new RegExp(command)); assert.match(registry, /confirmation: 'INSTALL PS5 DEBUG PACKAGE'/); assert.match(registry, /\^\[A-Za-z0-9_-\]\{1,64\}\$/); assert.match(registry, /await hashFile\(localPath\) !== component\.sha256/); assert.match(registry, /PS5_FPKG_ARTIFACT_HASH_DRIFT/); assert.match(registry, /ci\.last_seen_at AS observed_at/); assert.doesNotMatch(registry, /ci\.observed_at/); assert.match(preload, /ps5NativePackageRestoreStatus/); assert.match(web, /ps5NativePackageRestoreStatus/); assert.match(web, /ps5NativePackageRestoreList/); assert.match(modal, /monitorPs5NativePackageInstall/); assert.match(modal, /item\.titleId === 'FAKE00000'/); assert.match(modal, /INSTALL PS5 DEBUG PACKAGE/); assert.match(modal, /operationId: ps5NativeInstallOperationId/); assert.match(modal, /ps5NativePackageSetOptionLabel/); assert.match(html, /id="ps5-native-package-cancel"[^>]*disabled/); assert.match(dockerfile, /LIBPROSPEROPKG_COMMIT=9084c0e88cd8d7cd9c0b353b8b6b89f7d1cc4275/); assert.match(dockerfile, /--self-contained true/); assert.match(dockerfile, /\/out\/chimera-prospero-bridge probe --json/); assert.match(registry, /ps5\.nativePackageBuilderPath', ''\) \|\| '\/opt\/chimera-prospero-bridge'/); });