import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { writeFile } from "node:fs/promises";
import { MemoryStorage, WorkspaceJournal } from "./journal.mjs";
import { createCheckpoint } from "./recovery.mjs";
import { ReplayTimeline, canonicalJson, fnv1a64 } from "../worker/replay.mjs";
import {
applyRollbackPackage,
createOfflinePackage,
createRollbackPackage,
createWorkspacePackage,
migrateWorkspacePackage,
verifyOfflinePackage,
verifyWorkspacePackage,
} from "./migration.mjs";
const contextId = "linuxcnc-2.10.0-pre0-wasm-m1-py-c1";
function timelineAt(events) {
const timeline = new ReplayTimeline();
timeline.appendEvent(1, 0, "SESSION_READY", {});
timeline.checkpoint(1, 0, { tick: 0, position: 0 });
if (events) {
timeline.appendEvent(2, 4, "SIM_STEP", { cycles: 4 });
timeline.checkpoint(2, 4, { tick: 4, position: 4 }, timeline.snapshots.latest().snapshot_id);
}
return timeline;
}
const storage = new MemoryStorage();
const journal = new WorkspaceJournal(storage, { projectId: "demo" });
journal.prepare("op-1", [{ path: "config/main.ini", data: "[EMC]\n" }]);
journal.commit("op-1");
const journalRecords = journal.records();
journal.prepare("op-2", [{ path: "programs/main.ngc", data: "G0 X1\n" }]);
journal.commit("op-2");
const currentJournalRecords = journal.records();
const committedTimeline = timelineAt(false);
const committedCheckpoint = createCheckpoint({
contextId,
timeline: committedTimeline,
state: { tick: 0, position: 0 },
workspaceHash: fnv1a64(journalRecords),
});
const currentTimeline = timelineAt(true);
const currentCheckpoint = createCheckpoint({
contextId,
timeline: currentTimeline,
state: { tick: 4, position: 4 },
workspaceHash: fnv1a64(currentJournalRecords),
});
const committed = createWorkspacePackage({ contextId, timeline: committedTimeline, checkpoint: committedCheckpoint, journal: journalRecords, committedSeq: 1 });
const current = createWorkspacePackage({ contextId, timeline: currentTimeline, checkpoint: currentCheckpoint, journal: currentJournalRecords, committedSeq: 2 });
assert.equal(verifyWorkspacePackage(committed, { contextId }).package_hash, committed.package_hash);
assert.equal(verifyWorkspacePackage(current, { contextId }).package_hash, current.package_hash);
assert.deepEqual(createWorkspacePackage({ contextId, timeline: currentTimeline, checkpoint: currentCheckpoint, journal: currentJournalRecords, committedSeq: 2 }), current);
const migrated = migrateWorkspacePackage(current, 2);
assert.equal(migrated.format_version, 2);
assert.equal(migrated.migration.lossless, true);
assert.equal(verifyWorkspacePackage(migrated, { contextId, expectedVersion: 2 }).format_version, 2);
const roundTrip = migrateWorkspacePackage(migrated, 1);
assert.deepEqual(roundTrip, current);
assert.throws(() => migrateWorkspacePackage(current, 3), /package_version_unsupported/);
assert.throws(() => verifyWorkspacePackage(current, { contextId: "other-context" }), /package_context_mismatch/);
const tampered = structuredClone(migrated);
tampered.timeline.events.events[1].payload.cycles = 99;
assert.throws(() => verifyWorkspacePackage(tampered), /(?:package_(?:content_)?hash_mismatch|event_payload_hash_mismatch)/);
const rollback = createRollbackPackage({ current, committed });
assert.equal(rollback.nearest_committed_seq, 1);
assert.throws(() => applyRollbackPackage(rollback, { currentPackageHash: current.package_hash, failureInjection: "before_commit" }), /rollback_injected_failure/);
const rolledBack = applyRollbackPackage(rollback, { currentPackageHash: current.package_hash });
assert.deepEqual(rolledBack, committed);
assert.equal(current.package_hash, current.package_hash);
const badRollback = structuredClone(rollback);
badRollback.target_package_hash = "fnv1a64:0000000000000000";
assert.throws(() => applyRollbackPackage(badRollback, { currentPackageHash: current.package_hash }), /rollback_hash_mismatch/);
const offline = createOfflinePackage({
workspacePackage: migrated,
resources: [
{ path: "ui/index.html", data: "LinuxCNC" },
{ path: "wasm/sim.wasm", data: "wasm-bytes" },
],
});
assert.deepEqual(verifyOfflinePackage(offline, { contextId, workspacePackage: migrated, available: [{ path: "ui/index.html", data: "LinuxCNC" }, { path: "wasm/sim.wasm", data: "wasm-bytes" }] }), { status: "ready", missing: [], corrupted: [], offline: true });
assert.throws(() => verifyOfflinePackage(offline, { contextId, available: ["ui/index.html"], online: false }), /offline_resource_invalid/);
assert.deepEqual(verifyOfflinePackage(offline, { contextId, available: ["ui/index.html"], online: true }), { status: "network_required", missing: ["wasm/sim.wasm"], corrupted: [], offline: false });
const report = {
schema: "workspace-migration-report.v1",
context_id: contextId,
status: "passed",
implementation_status: "verified",
format: {
schema: "workspace-package.v1",
supported_versions: [1, 2],
payload_schemas_unchanged: ["event-log.v1", "snapshot.v1", "simulation-checkpoint.v1", "workspace-journal.v1"],
old_package_immutable: true,
},
migration: {
status: "passed",
source_version: current.format_version,
target_version: migrated.format_version,
source_hash: current.package_hash,
migrated_hash: migrated.package_hash,
round_trip_hash: roundTrip.package_hash,
round_trip_equal: roundTrip.package_hash === current.package_hash,
deterministic: migrated.package_hash === migrateWorkspacePackage(current, 2).package_hash,
},
rollback: {
status: "passed",
schema: rollback.schema,
current_hash: current.package_hash,
target_hash: committed.package_hash,
nearest_committed_seq: rollback.nearest_committed_seq,
rollback_hash: rollback.rollback_hash,
failure_injection: "before_commit",
failure_result: "rollback_injected_failure",
recovery_status: "recovered",
recovery_hash: rolledBack.package_hash,
},
offline: {
status: "passed",
schema: offline.schema,
bundle_hash: offline.bundle_hash,
required_paths: offline.required_paths,
offline_start: "ready",
missing_resource_result: "offline_resource_invalid",
online_degraded_result: "network_required",
package_path: "coverage/offline-cache.package.json",
},
rollback_package_path: "coverage/workspace-rollback.package.json",
recovery_endpoint: {
status: "verified",
endpoint: "nearest_committed_package",
state_hash: committedCheckpoint.state_hash,
no_silent_downgrade: true,
},
oracle: {
status: "blocked",
reason: "official migration and browser offline oracle is absent from the locked source",
blocked_samples: ["native/WASM package migration", "browser Service Worker offline startup", "OPFS rollback after crash"],
},
};
report.report_sha256 = createHash("sha256").update(canonicalJson(report), "utf8").digest("hex");
await writeFile(new URL("../../coverage/workspace-rollback.package.json", import.meta.url), `${JSON.stringify(rollback, null, 2)}\n`);
await writeFile(new URL("../../coverage/offline-cache.package.json", import.meta.url), `${JSON.stringify(offline, null, 2)}\n`);
await writeFile(new URL("../../coverage/workspace-migration.report.json", import.meta.url), `${JSON.stringify(report, null, 2)}\n`);
console.log(`migration=passed source=${current.package_hash} target=${migrated.package_hash} rollback=${rollback.rollback_hash}`);