'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 { createPs5NativePackageInstallService } = require('../../src/services/ps5-native-package-install-service'); function fixture(t, overrides = {}) { const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-p15-a11-evidence-')); t.after(() => fs.rmSync(root, { recursive: true, force: true })); const stateFile = path.join(root, 'operations.json'); const diagnostic = { schema: 'ps5upload.shellui-launch-observation.v1', diagnosticIdentity: 'chimera-p15-shellui-transition-observe-v1', shellUi: { pidBefore: 110, pidAfter: 110, sameProcess: true, initRc: 0, ready: true }, attempts: [{ index: 1, callEntered: true, sonyReturnObserved: true, rawSigned32: 0, wrapperRc: 0 }], terminalAttemptIndex: 1, terminalWrapperRc: 0, providerAccepted: true, providerNormalizedResult: 'accepted', applicationTransition: { schema: 'chimera.ps5-shellui-application-transition.v1', preRunningBigAppId: -1, samples: [0, 50, 100, 250, 500, 1000, 2000].map(offset => ({ scheduledOffsetMs: offset, observedAfterMs: offset, runningBigAppId: -1, shellUiPid: 110, processInventoryComplete: true, targetPid: null })), sampleOffsetsMs: [0, 50, 100, 250, 500, 1000, 2000], cleanBaseline: false, shellUiStable: true, inventoryComplete: true, timingQualified: true, bigAppTransitionObserved: false, firstPositiveOffsetMs: null, exactTargetProcessObserved: false, outcomeQualified: false, classification: 'outcome_unknown', launchVerified: false, productionRestorable: false, }, }; const operation = { id: 'a10-operation-0001', titleId: 'CHIM00003', packageSetId: 10, status: 'launch_failed', launchOutcome: 'failed', launchVerified: false, productionRestorable: false, installed: true, installedContentVerified: false, packageIdentitySha256: '5'.repeat(64), createdAt: 100, updatedAt: 200, launchStartedAt: 120, launchFailedAt: 180, components: [{ type: 'base', componentId: 'base', sha256: 'a'.repeat(64), size: 68, source: { metadata: { absolutePath: '/private/staging/control.pkg' } } }], error: { code: 'PS5_FPKG_SHELLUI_POST_LNC_FAILED', message: 'Failed before stable execution.' }, launchEvidence: { schema: 'chimera.ps5-fpkg-launch-evidence.v2', titleId: 'CHIM00003', packageIdentitySha256: '5'.repeat(64), runtimeFingerprint: 'f'.repeat(64), runtime: { ready: true, capabilities: { foregroundClean: true } }, logWatermark: { sha256: '0'.repeat(64), bytes: 0 }, registration: { required: false, registered: true }, request: { accepted: true, diagnostic }, requestedAt: 130, preflightCompletedAt: 125, firstObservedAt: null, observationCount: 0, process: null, rawEvidence: [{ observedAt: 150, summary: { observed: false, process: null, failureBoundary: 'ShellUI launch flow', failureCode: 'PS5_FPKG_SHELLUI_LAUNCH_FLOW_FAILED', normalizedEvents: [], processListTruncated: false, syslog: { continuity: 'exact-watermark', currentSha256: 'b'.repeat(64), currentWindowBytes: 29, currentWindowSha256: 'c'.repeat(64) }, }, evidence: { truncated: true, sha256: 'd'.repeat(64), preview: '{"currentWindow":"LaunchFlowError.CHIM00003"}' }, }], }, ...overrides, }; fs.writeFileSync(stateFile, JSON.stringify([operation], null, 2)); let providerCalls = 0; const provider = { preflightInstall: async () => { providerCalls += 1; return {}; }, install: async () => { providerCalls += 1; return {}; }, installStatus: async () => { providerCalls += 1; return {}; }, preflightLaunch: async () => { providerCalls += 1; return {}; }, launch: async () => { providerCalls += 1; return {}; }, observeLaunch: async () => { providerCalls += 1; return {}; }, }; const service = createPs5NativePackageInstallService({ provider, stateFile }); return { root, stateFile, operation, service, providerCalls: () => providerCalls }; } test('retained A10 evidence export is lossless for the bounded durable record and classifies the post-LNC boundary', t => { const f = fixture(t); const before = fs.readFileSync(f.stateFile); const beforeStat = fs.statSync(f.stateFile); const result = f.service.retainedLaunchEvidence('a10-operation-0001'); assert.equal(result.schema, 'chimera.ps5-fpkg-retained-launch-export.v1'); assert.equal(result.operation.titleId, 'CHIM00003'); assert.equal(result.operation.launchVerified, false); assert.equal(result.operation.productionRestorable, false); assert.equal(result.analysis.requestAccepted, true); assert.equal(result.analysis.terminalSonyResultAccepted, true); assert.equal(result.analysis.exactTitleProcessObservationCount, 0); assert.equal(result.analysis.stableExactTitleProcess, false); assert.equal(result.analysis.firstUnprovenBoundary, 'after-accepted-shellui-lnc-before-big-app-allocation'); assert.equal(result.analysis.applicationTransition.retainedClassification, 'outcome_unknown'); assert.equal(result.analysis.applicationTransition.retrospectiveClassification, 'accepted_lnc_without_big_app_transition'); assert.equal(result.analysis.applicationTransition.classificationChanged, true); assert.equal(result.analysis.applicationTransition.baselineIdleSentinel, -1); assert.equal(result.analysis.applicationTransition.outcomeQualified, true); assert.equal(result.analysis.applicationTransition.launchVerified, false); assert.equal(result.analysis.applicationTransition.productionRestorable, false); assert.equal(result.analysis.applicationTransition.sourceRecordMutated, false); assert.equal(result.retention.rawSourceComplete, false); assert.equal(result.retention.losslessForRetainedRecord, true); assert.equal(result.retention.observationsRetained, 1); assert.deepEqual(result.launchEvidence, f.operation.launchEvidence); assert.equal(result.launchEvidence.request.diagnostic.attempts[0].rawSigned32, 0); assert.equal(result.launchEvidence.request.diagnostic.attempts[0].wrapperRc, 0); assert.equal(result.launchEvidence.rawEvidence[0].evidence.preview, '{"currentWindow":"LaunchFlowError.CHIM00003"}'); assert.equal(result.integrity.algorithm, 'sha256'); assert.equal(result.integrity.retainedLaunchEvidenceSha256, crypto.createHash('sha256').update(JSON.stringify(f.operation.launchEvidence)).digest('hex')); assert.equal(result.integrity.retainedBytes, Buffer.byteLength(JSON.stringify(f.operation.launchEvidence))); assert.equal(JSON.stringify(result).includes('/private/staging/control.pkg'), false); assert.equal(f.providerCalls(), 0); assert.deepEqual(fs.readFileSync(f.stateFile), before); assert.equal(fs.statSync(f.stateFile).mtimeMs, beforeStat.mtimeMs); }); test('retrospective transition analysis is absent for legacy evidence and explicit for malformed retained evidence', t => { const legacy = fixture(t, { launchEvidence: { schema: 'chimera.ps5-fpkg-launch-evidence.v2', request: { accepted: true, diagnostic: { providerAccepted: true } }, rawEvidence: [], } }); assert.equal(legacy.service.retainedLaunchEvidence('a10-operation-0001').analysis.applicationTransition, null); const malformed = fixture(t, { launchEvidence: { schema: 'chimera.ps5-fpkg-launch-evidence.v2', request: { accepted: true, diagnostic: { providerAccepted: true, applicationTransition: { schema: 'unknown', samples: [] }, } }, rawEvidence: [], } }); const result = malformed.service.retainedLaunchEvidence('a10-operation-0001').analysis.applicationTransition; assert.equal(result.retrospectiveClassification, 'invalid_retained_transition'); assert.equal(result.classificationChanged, false); assert.equal(result.launchVerified, false); assert.equal(result.productionRestorable, false); assert.equal(result.sourceRecordMutated, false); assert.equal(result.errorCode, 'PS5_FPKG_TRANSITION_OBSERVATION_INVALID'); const nonMonotone = fixture(t); const persisted = JSON.parse(fs.readFileSync(nonMonotone.stateFile, 'utf8')); persisted[0].launchEvidence.request.diagnostic.applicationTransition.samples[0].observedAfterMs = 1000; fs.writeFileSync(nonMonotone.stateFile, JSON.stringify(persisted, null, 2)); const restarted = createPs5NativePackageInstallService({ stateFile: nonMonotone.stateFile, provider: { preflightInstall: async () => ({}), install: async () => ({}), installStatus: async () => ({}) }, }); assert.equal(restarted.retainedLaunchEvidence('a10-operation-0001').analysis.applicationTransition.retrospectiveClassification, 'invalid_retained_transition'); }); test('retained evidence export is a deep clone and survives service restart without replay', t => { const f = fixture(t); const first = f.service.retainedLaunchEvidence('a10-operation-0001'); first.launchEvidence.request.diagnostic.attempts[0].wrapperRc = 99; first.analysis.firstUnprovenBoundary = 'tampered'; assert.equal(f.service.get('a10-operation-0001').launchEvidence.request.diagnostic.attempts[0].wrapperRc, 0); const restarted = createPs5NativePackageInstallService({ stateFile: f.stateFile, provider: { preflightInstall: async () => ({}), install: async () => ({}), installStatus: async () => ({}) }, }); const second = restarted.retainedLaunchEvidence('a10-operation-0001'); assert.equal(second.launchEvidence.request.diagnostic.attempts[0].wrapperRc, 0); assert.equal(second.integrity.retainedLaunchEvidenceSha256, first.integrity.retainedLaunchEvidenceSha256); assert.equal(f.providerCalls(), 0); }); test('retained evidence export redacts secret-bearing legacy fields and discloses the loss', t => { const f = fixture(t, { launchEvidence: { schema: 'chimera.ps5-fpkg-launch-evidence.v2', titleId: 'CHIM00003', request: { accepted: false, authorization: 'Bearer super-secret', diagnostic: { token: 'query-secret' } }, rawEvidence: [{ observedAt: 150, summary: { observed: false }, evidence: { log: 'Authorization: Bearer header-secret https://user:pass@example.invalid/path?token=url-secret' } }], } }); const result = f.service.retainedLaunchEvidence('a10-operation-0001'); const serialized = JSON.stringify(result); assert.doesNotMatch(serialized, /super-secret|query-secret|header-secret|user:pass|url-secret/); assert.equal(result.retention.losslessForRetainedRecord, false); assert.deepEqual(result.retention.redactedPaths.sort(), ['launchEvidence.rawEvidence.0.evidence.log', 'launchEvidence.request.authorization', 'launchEvidence.request.diagnostic.token']); assert.match(result.integrity.retainedLaunchEvidenceSha256, /^[a-f0-9]{64}$/); }); test('retained evidence export fails typed for unavailable and oversized durable evidence', t => { const noEvidence = fixture(t, { status: 'installed_observed', launchEvidence: null }); assert.throws(() => noEvidence.service.retainedLaunchEvidence('missing'), error => error.code === 'PS5_PKG_INSTALL_NOT_FOUND'); assert.throws(() => noEvidence.service.retainedLaunchEvidence('a10-operation-0001'), error => error.code === 'PS5_FPKG_LAUNCH_EVIDENCE_UNAVAILABLE'); const oversized = fixture(t, { launchEvidence: { schema: 'legacy', rawEvidence: [{ evidence: { preview: 'x'.repeat(600 * 1024) } }] } }); assert.throws(() => oversized.service.retainedLaunchEvidence('a10-operation-0001'), error => error.code === 'PS5_FPKG_LAUNCH_EVIDENCE_OVERSIZE'); }); test('A11 evidence bridge is read-only, exact-operation scoped and never polls launch status', () => { const registry = fs.readFileSync(path.resolve(__dirname, '../../src/server/create-registry.js'), 'utf8'); const preload = fs.readFileSync(path.resolve(__dirname, '../../preload.js'), 'utf8'); const main = fs.readFileSync(path.resolve(__dirname, '../../main.js'), 'utf8'); const web = fs.readFileSync(path.resolve(__dirname, '../../renderer/pork-web-shim.js'), 'utf8'); const evidenceBridge = fs.readFileSync(path.resolve(__dirname, '../../renderer/pork-evidence-bridge.js'), 'utf8'); const modal = fs.readFileSync(path.resolve(__dirname, '../../renderer/modules/modal.js'), 'utf8'); assert.match(registry, /name: 'ps5-native-packages:retained-launch-evidence', classification: 'read-only'/); assert.match(registry, /retainedLaunchEvidence\(input\.operationId\)/); for (const source of [preload, main, `${web}\n${evidenceBridge}`]) assert.match(source, /ps5-native-packages(?::|%3A)retained-launch-evidence/); assert.match(preload, /URL\.revokeObjectURL\(url\)/); assert.match(modal, /ps5NativePackageRetainedLaunchEvidence\(ps5A10ControlOperationId\)/); assert.match(modal, /window\.pork\.download \|\| window\.xavatarAPI\?\.download/); assert.match(modal, /op === 'interrupted' \? 'launchInterrupted'/); assert.match(modal, /s\.dataset\.operationId !== id/); assert.match(modal, /chimera:language-changed/); assert.doesNotMatch(modal, /status\.textContent = ps5A10Text\('evidenceDownloaded'\)/); assert.doesNotMatch(modal, /retainedLaunchEvidence[\s\S]{0,200}RestoreLaunchStatus/); });