184 lines
7.1 KiB
JavaScript
184 lines
7.1 KiB
JavaScript
'use strict';
|
|
|
|
const assert = require('node:assert/strict');
|
|
const test = require('node:test');
|
|
const {
|
|
createPs5FpkgCompletionTrace,
|
|
parseKstuffCompletionSnapshot,
|
|
} = require('../../src/services/ps5-fpkg-completion-trace');
|
|
|
|
const OPERATION_ID = '123e4567-e89b-42d3-a456-426614174000';
|
|
const FINGERPRINT = 'a'.repeat(64);
|
|
|
|
function runtime(overrides = {}) {
|
|
return {
|
|
product: 'EchoStretch/kstuff-lite',
|
|
version: '1.10',
|
|
revision: '3b2ab4a16ba43c8ab38daf7ec83cc297014e0e47',
|
|
payloadSha256: '1'.repeat(64),
|
|
loaderSha256: '2'.repeat(64),
|
|
readerSha256: '3'.repeat(64),
|
|
observabilityPatchSha256: '4'.repeat(64),
|
|
observabilityProfile: 'chimera-p15-a8-call-bound-completion-v1',
|
|
identityStatus: 'exact',
|
|
traceEligible: true,
|
|
firmware: '9.60',
|
|
fingerprint: FINGERPRINT,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function metrics(overrides = {}) {
|
|
return {
|
|
nmountDispatch: 0,
|
|
taggedArmAttempts: 0,
|
|
taggedReadFailures: 0,
|
|
taggedPcbPointerFailures: 0,
|
|
taggedPcbStateFailures: 0,
|
|
taggedStackPushFailures: 0,
|
|
taggedPcbSetFailures: 0,
|
|
taggedWriteFailures: 0,
|
|
taggedArmSuccesses: 0,
|
|
taggedRollbackRestoreFailures: 0,
|
|
taggedFinishEntries: 0,
|
|
taggedFinishPeekFailures: 0,
|
|
taggedFinishRestoreFailures: 0,
|
|
taggedFinishSuccesses: 0,
|
|
mailboxTotal: 0,
|
|
mailboxFpkg: 0,
|
|
mailboxUnhandled: 0,
|
|
verifySuperBlockMailbox: 0,
|
|
verifySuperBlockEmulated: 0,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function snapshot(sequence, values) {
|
|
return { sequence, sampledAt: 1000 + sequence, runtimeFingerprint: FINGERPRINT, metrics: metrics(values) };
|
|
}
|
|
|
|
function trace(postMetrics, overrides = {}) {
|
|
const post = snapshot(11, postMetrics);
|
|
return createPs5FpkgCompletionTrace({
|
|
operationId: OPERATION_ID,
|
|
titleId: 'FAKE00000',
|
|
runtime: runtime(),
|
|
pre: snapshot(10, metrics()),
|
|
post,
|
|
postStable: snapshot(12, postMetrics),
|
|
postStableConfirm: snapshot(13, postMetrics),
|
|
launchRequests: 1,
|
|
retryPerformed: false,
|
|
...overrides,
|
|
});
|
|
}
|
|
|
|
test('A8 strict parser accepts the exact semantic completion schema', () => {
|
|
const parsed = parseKstuffCompletionSnapshot([
|
|
'schema=chimera.p15-a8-completion-metrics.v1',
|
|
'status=attached',
|
|
'sample=8',
|
|
'route nmount_dispatch=7',
|
|
'completion tag=nmount attempt=7 read_fail=0 pcb_ptr_fail=0 pcb_state_fail=0 stack_fail=0 pcb_set_fail=0 write_fail=0 armed=7 rollback_restore_fail=0 finish_entry=7 finish_peek_fail=1 finish_restore_fail=0 finish=6',
|
|
'mailbox total=4 fpkg=3 unhandled=1',
|
|
'fpkg verify=3 verify_emu=3',
|
|
].join('\n'), { sequence: 8, sampledAt: 1008, runtimeFingerprint: FINGERPRINT });
|
|
assert.equal(parsed.metrics.taggedArmSuccesses, 7);
|
|
assert.equal(parsed.metrics.taggedFinishEntries, 7);
|
|
assert.equal(parsed.metrics.taggedFinishPeekFailures, 1);
|
|
});
|
|
|
|
test('A8 parser rejects raw data, unknown tags, duplicate fields and sample drift', () => {
|
|
const base = [
|
|
'schema=chimera.p15-a8-completion-metrics.v1', 'status=attached', 'sample=8',
|
|
'route nmount_dispatch=1',
|
|
'completion tag=nmount attempt=1 read_fail=0 pcb_ptr_fail=0 pcb_state_fail=0 stack_fail=0 pcb_set_fail=0 write_fail=0 armed=1 rollback_restore_fail=0 finish_entry=1 finish_peek_fail=0 finish_restore_fail=0 finish=1',
|
|
'mailbox total=0 fpkg=0 unhandled=0', 'fpkg verify=0 verify_emu=0',
|
|
];
|
|
for (const unsafe of [
|
|
[...base, 'request_ptr=0xffff800012345678'],
|
|
base.map(line => line.startsWith('completion ') ? line.replace('tag=nmount', 'tag=ioctl') : line),
|
|
base.map(line => line.startsWith('completion ') ? `${line} finish=1` : line),
|
|
base.map(line => line === 'sample=8' ? 'sample=9' : line),
|
|
]) {
|
|
assert.throws(
|
|
() => parseKstuffCompletionSnapshot(unsafe.join('\n'), { sequence: 8, sampledAt: 1008, runtimeFingerprint: FINGERPRINT }),
|
|
error => error.code === 'PS5_FPKG_COMPLETION_TRACE_UNSAFE_LINE',
|
|
);
|
|
}
|
|
});
|
|
|
|
test('A8 proves a balanced call-bound arm and successful finalization', () => {
|
|
const result = trace(metrics({
|
|
nmountDispatch: 1, taggedArmAttempts: 1, taggedArmSuccesses: 1,
|
|
taggedFinishEntries: 1, taggedFinishSuccesses: 1,
|
|
}));
|
|
assert.equal(result.status, 'observed');
|
|
assert.equal(result.lifecycle.attribution, 'call-bound-nmount');
|
|
assert.equal(result.lifecycle.unresolvedArms, 0);
|
|
assert.equal(result.recovery.debugRegistersRestored, true);
|
|
assert.equal(result.normalizedFailure, 'PS5_FPKG_MAILBOX_TARGET_NOT_REACHED');
|
|
});
|
|
|
|
test('A8 makes a post-arm stack peek failure terminal and visible', () => {
|
|
const result = trace(metrics({
|
|
nmountDispatch: 1, taggedArmAttempts: 1, taggedArmSuccesses: 1,
|
|
taggedFinishEntries: 1, taggedFinishPeekFailures: 1,
|
|
}));
|
|
assert.equal(result.status, 'observed');
|
|
assert.equal(result.firstFailedStage, 'debug_register_finish_stack_peek_failed');
|
|
assert.equal(result.normalizedFailure, 'PS5_FPKG_DEBUG_REGISTER_FINISH_PEEK_FAILED');
|
|
assert.equal(result.lifecycle.unresolvedArms, 0);
|
|
assert.equal(result.recovery.debugRegistersRestored, false);
|
|
});
|
|
|
|
test('A8 qualifies a completion that settles after the immediate post sample', () => {
|
|
const complete = metrics({
|
|
nmountDispatch: 1, taggedArmAttempts: 1, taggedArmSuccesses: 1,
|
|
taggedFinishEntries: 1, taggedFinishSuccesses: 1,
|
|
});
|
|
const result = trace(complete, {
|
|
post: snapshot(11, metrics({ nmountDispatch: 1, taggedArmAttempts: 1, taggedArmSuccesses: 1 })),
|
|
postStable: snapshot(12, complete),
|
|
postStableConfirm: snapshot(13, complete),
|
|
});
|
|
assert.equal(result.status, 'observed');
|
|
assert.equal(result.postArmSettled, true);
|
|
assert.equal(result.lifecycle.unresolvedArms, 0);
|
|
});
|
|
|
|
test('A8 fails closed on missing or contradictory call-bound terminal events', () => {
|
|
const missing = trace(metrics({ nmountDispatch: 1, taggedArmAttempts: 1, taggedArmSuccesses: 1 }));
|
|
assert.equal(missing.status, 'outcome_unknown');
|
|
assert.equal(missing.outcomeReason, 'call_bound_finalization_missing');
|
|
assert.equal(missing.lifecycle.unresolvedArms, 1);
|
|
|
|
const contradictory = trace(metrics({
|
|
nmountDispatch: 1, taggedArmAttempts: 1, taggedArmSuccesses: 1,
|
|
taggedFinishEntries: 1, taggedFinishPeekFailures: 1, taggedFinishSuccesses: 1,
|
|
}));
|
|
assert.equal(contradictory.status, 'outcome_unknown');
|
|
assert.equal(contradictory.outcomeReason, 'finish_accounting_invariant_failed');
|
|
});
|
|
|
|
test('A8 never upgrades the immutable A7 five-count mismatch into a kernel failure claim', () => {
|
|
const result = trace(metrics({
|
|
nmountDispatch: 85, taggedArmAttempts: 85, taggedArmSuccesses: 85,
|
|
taggedFinishEntries: 80, taggedFinishSuccesses: 80,
|
|
}));
|
|
assert.equal(result.status, 'outcome_unknown');
|
|
assert.equal(result.outcomeReason, 'launch_scope_ambiguous');
|
|
assert.equal(result.legacyA7InferenceAllowed, false);
|
|
});
|
|
|
|
test('A8 does not attribute unrelated global mailbox activity to the nmount', () => {
|
|
const result = trace(metrics({
|
|
nmountDispatch: 1, taggedArmAttempts: 1, taggedArmSuccesses: 1,
|
|
taggedFinishEntries: 1, taggedFinishSuccesses: 1,
|
|
mailboxTotal: 1, mailboxUnhandled: 1,
|
|
}));
|
|
assert.equal(result.status, 'outcome_unknown');
|
|
assert.equal(result.outcomeReason, 'mailbox_scope_ambiguous');
|
|
assert.equal(result.normalizedFailure, null);
|
|
});
|