// K601–K604 are local library steps, NOT K605 product/guest acceptance. import assert from 'node:assert/strict'; import { createHash } from 'node:crypto'; import { readFileSync, readdirSync } from 'node:fs'; import { join } from 'node:path'; import { spawnSync } from 'node:child_process'; import { REVIEW_ARTIFACTS } from '../check-security-review.mjs'; export const PACKAGE = 'reports/k559-k589-prep/turkos-k559-inceleme-20261007.tar.gz'; export const PACKAGE_SHA = '3820027b4d26d33c9a50714b7f87a6f4d4177bf330aa61988b0fb4d4c736abe6'; export const INDEX = 'site/release-index.json'; export const INDEX_SHA = '080c53db3922ded41bd6472c4ebb09bb16d21b68463e57e3c021c5716e58b221'; export const IMAGE = 'scripts/release-evidence/turkos-kernel-image.bin'; export const IMAGE_SHA = 'da56d1a47260ca7648bf68db85932d17e20fea3790afcbf9012b6076cd8d9f3e'; export const NEW_SOURCES = Object.freeze([ 'os-runtime/Cargo.toml', 'os-runtime/Cargo.lock', 'os-runtime/src/lib.rs', 'os-runtime/src/boot_contract.rs', 'os-runtime/tests/boot_contract.rs', 'os-runtime/tests/common/mod.rs', 'os-runtime/examples/boot-contract-fixtures.rs', 'scripts/os-acceptance/boot_contract.mjs', 'scripts/os-acceptance/boot-contract-report.mjs', 'scripts/os-acceptance/test-boot-contract-report.mjs', ]); export const MUTANTS = Object.freeze([ { id: 'uefi-falls-back-to-direct', needle: 'Some(decode_header(located.bytes)?)', replacement: 'decode_header(located.bytes).ok()', test: 'os_boot_contract_origin_negative' }, { id: 'direct-ignores-payload', needle: 'return Err(BootError::UnexpectedBootInfo);', replacement: '// MUTANT: ignore contradictory payload', test: 'origin_negative_zero_x1_with_payload_is_not_silently_direct' }, { id: 'ignores-header-address', needle: 'return Err(BootError::BootInfoAddressMismatch);', replacement: '// MUTANT: trust wrongly labelled header', test: 'origin_negative_wrong_address_labels_are_rejected' }, { id: 'ignores-image-dtb-overlap', needle: 'return Err(BootError::ImageDtbOverlap);', replacement: '// MUTANT: ignore image/DTB overlap', test: 'origin_negative_image_dtb_overlap_is_rejected_on_both_paths' }, ]); export const sha256 = bytes => createHash('sha256').update(bytes).digest('hex'); export const hashAt = (root, path) => sha256(readFileSync(join(root, path))); function treeFiles(root, directory) { const files = []; for (const entry of readdirSync(join(root, directory), { withFileTypes: true })) { const path = `${directory}/${entry.name}`; if (entry.isDirectory()) files.push(...treeFiles(root, path)); else if (entry.isFile()) files.push(path); else assert.fail(`source symlink/special file not allowed: ${path}`); } return files; } export function sourceSnapshot(root) { // Compile dependencies are read-only but must still invalidate local evidence // when changed. Cargo.lock pins registry dependencies; this lists local bytes. const paths = [...NEW_SOURCES, 'kernel/Cargo.toml', 'kernel/Cargo.lock', 'bootinfo/Cargo.toml', 'bootinfo/Cargo.lock', '.cargo/config.toml', ...treeFiles(root, 'kernel/src'), ...treeFiles(root, 'bootinfo/src')].sort(); return paths.map(path => ({ path, sha256: hashAt(root, path) })); } export function frozenSnapshot(root) { assert.equal(hashAt(root, PACKAGE), PACKAGE_SHA, 'K559 package changed'); const extracted = spawnSync('tar', ['-xOf', join(root, PACKAGE), 'turkos-k559-inceleme-20261007/kaynak-manifesti.sha256'], { encoding: 'utf8', maxBuffer: 1024 * 1024 }); assert.equal(extracted.status, 0, 'cannot read review manifest'); const manifest = new Map(extracted.stdout.trim().split('\n').map(line => { const match = line.match(/^([a-f0-9]{64}) \.\/(.+)$/); assert.ok(match, 'bad manifest row'); return [match[2], match[1]]; })); const inputs = REVIEW_ARTIFACTS.map(path => { const actual = hashAt(root, path); assert.equal(actual, manifest.get(path), `K559 input changed: ${path}`); return { path, sha256: actual }; }); assert.equal(hashAt(root, INDEX), INDEX_SHA, 'paused K589 site index changed'); assert.equal(hashAt(root, IMAGE), IMAGE_SHA, 'reviewed product Image changed'); return { packageSha256: PACKAGE_SHA, inputs, siteIndexSha256: INDEX_SHA, productImageSha256: IMAGE_SHA }; } function rustTestSummary(log, expected, passed, failed, needle = null) { assert.match(log, new RegExp(`running ${expected} tests?\\b`), 'empty or wrong test selection'); assert.match(log, new RegExp(`test result: ${failed ? 'FAILED' : 'ok'}\\. ${passed} passed; ${failed} failed; 0 ignored; 0 measured;`)); assert.doesNotMatch(log, /error\[E\d+\]|could not compile/, 'compile error is not a failing regression'); if (needle) assert.match(log, new RegExp(`test ${needle} \\.\\.\\. FAILED`)); } export function inspectBootContractReport(report, root) { assert.equal(report.schema, 'turkos-os-local-execution/v1'); assert.equal(report.feature, 'boot_contract.origin'); assert.deepEqual(report.counts, { locallyValidated: 4, guestAccepted: 0, blocked: 1, notStarted: 1895 }); assert.equal(report.scope, 'host-library-only'); assert.deepEqual(report.policy, { release: 'experimental', productionWrite: 'blocked', published: false, defaultImageChanged: false, productIntegration: false, firmwareMapOwnershipProven: false }); assert.deepEqual(report.sources, sourceSnapshot(root), 'local evidence is stale'); assert.deepEqual(report.protectedBefore, frozenSnapshot(root)); assert.deepEqual(report.protectedAfter, report.protectedBefore, 'protected inputs changed during run'); assert.match(report.runDirectory, /^reports\/os-implementation\/K601-K604-[A-Za-z0-9_-]+$/); const readEvidence = entry => { assert.ok(entry.path.startsWith(`${report.runDirectory}/`), 'evidence outside this run'); assert.ok(!entry.path.split('/').some(part => part === '..' || part === '.'), 'unsafe path'); assert.equal(hashAt(root, entry.path), entry.sha256, `evidence hash: ${entry.path}`); return readFileSync(join(root, entry.path)); }; assert.deepEqual(report.commands.map(command => command.id), ['fmt', 'host', 'positive', 'negative', 'arm64', 'clippy', 'fixtures']); const logs = new Map(); for (const command of report.commands) { assert.equal(command.exitCode, 0, `command failed: ${command.id}`); assert.ok(command.argv.includes('--locked') || command.id === 'fmt'); const log = readEvidence(command.log).toString('utf8'); logs.set(command.id, log); } rustTestSummary(logs.get('host'), 18, 18, 0); rustTestSummary(logs.get('positive'), 4, 4, 0); rustTestSummary(logs.get('negative'), 14, 14, 0); for (const mib of [256, 512, 1024]) { assert.ok(logs.get('positive').includes(`K603 profile=${mib}MiB direct-image/uefi-bootinfo-v1 RAM=equal; host-only`)); } assert.ok(report.commands.find(command => command.id === 'arm64').argv.includes('aarch64-unknown-none')); assert.match(logs.get('arm64'), /Finished `dev` profile/); assert.ok(report.arm64Library.path.endsWith('libturkos_os_runtime.rlib')); assert.ok(readEvidence(report.arm64Library).length > 1024, 'no ARM64 library artifact'); assert.equal(report.fixtures.length, 6); for (const [index, mib] of [256, 512, 1024].entries()) { const dtb = readEvidence(report.fixtures[index * 2]); assert.ok(report.fixtures[index * 2].path.endsWith(`ram-${mib}.dtb`)); assert.equal(dtb.readUInt32BE(0), 0xd00dfeed); assert.equal(dtb.readUInt32BE(4), dtb.length); const header = readEvidence(report.fixtures[index * 2 + 1]); assert.ok(report.fixtures[index * 2 + 1].path.endsWith(`uefi-${mib}.header`)); assert.equal(header.length, 72); assert.equal(header.subarray(0, 8).toString(), 'TURKOSBI'); assert.equal(header.readUInt32LE(8), 1); assert.equal(header.readUInt32LE(12), 72); } assert.equal(report.mutations.length, MUTANTS.length); for (const [index, mutation] of report.mutations.entries()) { const expected = MUTANTS[index]; assert.equal(mutation.id, expected.id); assert.equal(mutation.test, expected.test); assert.equal(mutation.exitCode, 101, 'mutant must fail an assertion, not a timeout'); const bytes = readEvidence(mutation.source); const original = readFileSync(join(root, 'os-runtime/src/boot_contract.rs'), 'utf8'); assert.equal(original.split(expected.needle).length, 2, 'nonunique mutation'); assert.equal(bytes.toString(), original.replace(expected.needle, expected.replacement)); rustTestSummary(readEvidence(mutation.log).toString(), 1, 0, 1, expected.test); } assert.deepEqual(report.moves.map(move => [move.id, move.stage, move.status]), [ ['K601', 'contract', 'LOCAL_VALIDATED'], ['K602', 'implementation', 'LOCAL_VALIDATED'], ['K603', 'positive', 'LOCAL_VALIDATED'], ['K604', 'negative', 'LOCAL_VALIDATED'], ['K605', 'integration', 'BLOCKED_REVIEW_FREEZE'], ]); for (const move of report.moves) { assert.equal(move.productAccepted, false); assert.equal(move.actualSource, 'os-runtime/src/boot_contract.rs'); assert.equal(move.plannedSource, 'kernel/src/runtime/boot_contract.rs'); for (const field of ['reason', 'test', 'counterexample', 'boundary']) assert.ok(move[field]?.length > 20, `move ${move.id}: ${field}`); } assert.equal(report.guestRuns, 0); assert.equal(report.k605MarkerObserved, false); return { localSteps: 4, uniqueHostTests: 18, mutationsDetected: 4, guestAccepted: 0, protectedFiles: REVIEW_ARTIFACTS.length }; }