"use strict"; const test = require("node:test"); const assert = require("node:assert/strict"); const fs = require("node:fs"); const os = require("node:os"); const path = require("node:path"); const versions = require("../../lib/aas-v1/versions"); const { sha256, canonicalJson } = require("../../lib/aas-v1/canonical-json"); const { buildPlanEnvelope } = require("../../lib/aas-v1/stack"); const { appendRecord, acquireLock, applyPlan, buildRecoveryPlan, buildManagedState, createJournal, digestManagedEntries, doctor, journalPath, preflight, readManagedState, recover, releaseLock, recoveryIdFor, treeDigest, } = require("../../lib/aas-v1/transaction"); const { cleanupMaterializedLayout, inspectLayout, materializeLayout, remainingMaterializedLayoutPaths, } = require("../../lib/aas-v1/transaction/safety"); const DIGEST = (letter) => `sha256-${letter.repeat(64)}`; const CATALOG = { package: "agentic-awesome-skills", version: "15.0.0", integrity: DIGEST("a") }; const TARGET_ID = DIGEST("b"); function writeTree(directory, files) { fs.mkdirSync(directory, { recursive: true }); for (const [relative, content] of Object.entries(files)) { const destination = path.join(directory, relative); fs.mkdirSync(path.dirname(destination), { recursive: true }); fs.writeFileSync(destination, content); } } function fixture() { const sandbox = fs.mkdtempSync(path.join(os.tmpdir(), "aas-v1-transaction-")); const root = path.join(sandbox, "target"); const skillsDirectory = path.join(root, "skills"); const transactionDirectory = path.join(root, ".aas-transactions"); const stateFile = path.join(root, ".aas-managed-state.json"); const sources = path.join(sandbox, "sources"); fs.mkdirSync(skillsDirectory, { recursive: true }); fs.mkdirSync(transactionDirectory, { recursive: true }); fs.mkdirSync(sources, { recursive: true }); const adapter = { resolveTransactionLayout() { return { root, skillsDirectory, stateFile, transactionDirectory }; }, computeTargetIdentity() { return TARGET_ID; }, resolveSourceTree({ skillId }) { return path.join(sources, skillId); }, validateSourceTree(source) { return source.endsWith(`${path.sep}sources${path.sep}${path.basename(source)}`); }, }; return { sandbox, root, skillsDirectory, transactionDirectory, stateFile, sources, adapter }; } function buildInstallPlan(fx, skillId = "alpha", overrides = {}) { const source = path.join(fx.sources, skillId); if (!fs.existsSync(source)) writeTree(source, { "SKILL.md": "# Alpha\n", "nested/example.txt": "hello\n" }); const sourceDigest = treeDigest(source); const emptyDigest = digestManagedEntries([]); const nextDigest = digestManagedEntries([{ skillId, treeDigest: sourceDigest, catalogIntegrity: CATALOG.integrity }]); return buildPlanEnvelope({ manifest: { schemaVersion: 2, name: "transaction-test", catalog: CATALOG, targets: [{ host: "codex", scope: "project" }], profile: { goals: ["test"], languages: [], frameworks: [], constraints: [] }, skills: [{ id: skillId }], }, handshake: versions, catalog: CATALOG, runtime: { package: "agentic-awesome-skills", version: "15.0.0", integrity: "sha512-runtime", closureDigest: DIGEST("c") }, target: { host: "codex", scope: "project", adapterVersion: "1.0.0", identityDigest: TARGET_ID }, installedState: { digest: emptyDigest, entries: [] }, operations: [{ kind: "install", skillId, sourceTreeDigest: sourceDigest, expectedTreeDigest: null, resultTreeDigest: sourceDigest, backupRequired: false }], overrides: [], stateCommit: { previousDigest: emptyDigest, nextDigest, position: "final" }, ...overrides, }); } function buildReplacePlan(fx, { allowDrift }) { const state = readManagedState(fx.stateFile); const destination = path.join(fx.skillsDirectory, "alpha"); const currentDigest = treeDigest(destination); const source = path.join(fx.sources, "alpha-next"); writeTree(source, { "SKILL.md": "# Alpha v2\n" }); const resultDigest = treeDigest(source); const nextDigest = digestManagedEntries([{ skillId: "alpha", treeDigest: resultDigest, catalogIntegrity: CATALOG.integrity }]); const adapter = { ...fx.adapter, resolveSourceTree() { return source; } }; const plan = buildPlanEnvelope({ manifest: { schemaVersion: 2, name: "transaction-test", catalog: CATALOG, targets: [{ host: "codex", scope: "project" }], profile: { goals: ["test"], languages: [], frameworks: [], constraints: [] }, skills: [{ id: "alpha" }], }, handshake: versions, catalog: CATALOG, runtime: { package: "agentic-awesome-skills", version: "15.0.0", integrity: "sha512-runtime", closureDigest: DIGEST("c") }, target: { host: "codex", scope: "project", adapterVersion: "1.0.0", identityDigest: TARGET_ID }, installedState: { digest: state.stateDigest, entries: state.entries.map(({ skillId, treeDigest: digest, catalogIntegrity }) => ({ skillId, treeDigest: digest, catalogIntegrity })), }, operations: [{ kind: "replaceManaged", skillId: "alpha", sourceTreeDigest: resultDigest, expectedTreeDigest: currentDigest, resultTreeDigest: resultDigest, backupRequired: true }], overrides: allowDrift ? [{ kind: "managedDrift", skillId: "alpha", reasonCodes: ["approved-local-backup"] }] : [], stateCommit: { previousDigest: state.stateDigest, nextDigest, position: "final" }, }); return { adapter, plan, resultDigest }; } function snapshot(root) { const results = []; function visit(directory, relative = "") { for (const name of fs.readdirSync(directory).sort()) { const absolute = path.join(directory, name); const item = relative ? `${relative}/${name}` : name; const stat = fs.lstatSync(absolute); if (stat.isDirectory()) { results.push(`d:${item}`); visit(absolute, item); } else results.push(`f:${item}:${sha256(fs.readFileSync(absolute))}`); } } visit(root); return results; } test("tree digest is deterministic and rejects symlinks", () => { const fx = fixture(); const one = path.join(fx.sandbox, "one"); const two = path.join(fx.sandbox, "two"); writeTree(one, { "z.txt": "z", "a/x.txt": "x" }); writeTree(two, { "a/x.txt": "x", "z.txt": "z" }); assert.equal(treeDigest(one), treeDigest(two)); fs.symlinkSync(path.join(one, "z.txt"), path.join(one, "link")); assert.throws(() => treeDigest(one), { code: "AAS_TRANSACTION_TREE_SYMLINK" }); fs.rmSync(fx.sandbox, { recursive: true }); }); test("apply commits managed state last, preserves unmanaged bytes, and is idempotent", () => { const fx = fixture(); writeTree(path.join(fx.skillsDirectory, "unmanaged"), { "keep.txt": "do not change" }); const unmanagedBefore = treeDigest(path.join(fx.skillsDirectory, "unmanaged")); const plan = buildInstallPlan(fx); const boundaries = []; const result = applyPlan({ plan, adapter: fx.adapter, approvalDigest: plan.digest, onBoundary: (name) => boundaries.push(name) }); assert.equal(result.status, "applied"); assert.equal(treeDigest(path.join(fx.skillsDirectory, "unmanaged")), unmanagedBefore); assert.equal(treeDigest(path.join(fx.skillsDirectory, "alpha")), plan.payload.operations[0].resultTreeDigest); const state = readManagedState(fx.stateFile); assert.deepEqual(state.completedPlanDigests, [plan.digest]); assert.equal(boundaries.at(-1), "committed"); const before = snapshot(fx.root); let called = false; const again = applyPlan({ plan, adapter: fx.adapter, approvalDigest: plan.digest, onBoundary: () => { called = true; } }); assert.equal(again.status, "alreadyApplied"); assert.equal(called, false); assert.deepEqual(snapshot(fx.root), before); fs.rmSync(fx.sandbox, { recursive: true }); }); test("a post-commit cleanup failure is surfaced and completed by approved recovery", () => { const fx = fixture(); const plan = buildInstallPlan(fx); assert.throws(() => applyPlan({ plan, adapter: fx.adapter, approvalDigest: plan.digest, onBoundary(name) { if (name === "committed") throw new Error("cleanup boundary fault"); }, }), { code: "AAS_TRANSACTION_RECOVERY_REQUIRED" }); assert.equal(treeDigest(path.join(fx.skillsDirectory, "alpha")), plan.payload.operations[0].resultTreeDigest); const diagnosis = doctor({ target: plan.payload.target, adapter: fx.adapter }); assert.equal(diagnosis.status, "recoveryRequired"); const cleanup = buildRecoveryPlan({ plan, adapter: fx.adapter, recoveryId: diagnosis.recoveries[0].recoveryId, action: "cleanup" }); assert.equal(recover({ recoveryPlan: cleanup, plan, adapter: fx.adapter, approvalDigest: cleanup.digest }).status, "cleaned"); assert.equal(applyPlan({ plan, adapter: fx.adapter, approvalDigest: plan.digest }).status, "alreadyApplied"); fs.rmSync(fx.sandbox, { recursive: true }); }); test("bootstrap publication failure preserves plan-bound recovery evidence", () => { const fx = fixture(); const plan = buildInstallPlan(fx); assert.throws(() => applyPlan({ plan, adapter: fx.adapter, approvalDigest: plan.digest, onBoundary(name) { if (name === "bootstrapPublished") throw new Error("fault after bootstrap rename"); }, }), { code: "AAS_TRANSACTION_RECOVERY_REQUIRED" }); const lockFile = path.join(fx.root, ".aas-transaction.lock"); assert.equal(fs.existsSync(lockFile), true); let diagnosis = doctor({ target: plan.payload.target, adapter: fx.adapter }); assert.equal(diagnosis.status, "recoveryRequired"); assert.equal(diagnosis.recoveries[0].bootstrapOnly, true); // Simulate the failed CLI process having exited before the separately // approved recovery command starts. const lock = JSON.parse(fs.readFileSync(lockFile, "utf8")); lock.pid = 2147483647; fs.writeFileSync(lockFile, `${canonicalJson(lock)}\n`); const cleanup = buildRecoveryPlan({ plan, adapter: fx.adapter, recoveryId: diagnosis.recoveries[0].recoveryId, action: "cleanup", }); assert.equal(recover({ recoveryPlan: cleanup, plan, adapter: fx.adapter, approvalDigest: cleanup.digest }).status, "cleaned"); diagnosis = doctor({ target: plan.payload.target, adapter: fx.adapter }); assert.equal(diagnosis.status, "healthy"); fs.rmSync(fx.sandbox, { recursive: true }); }); test("cleanup recovery closes a WAL created before layout materialization", () => { const fx = fixture(); fs.rmSync(fx.transactionDirectory, { recursive: true }); writeTree(path.join(fx.skillsDirectory, "unmanaged"), { "keep.txt": "untouched\n" }); const unmanagedBefore = treeDigest(path.join(fx.skillsDirectory, "unmanaged")); const plan = buildInstallPlan(fx); const inspected = inspectLayout(fx.adapter, plan.payload.target); assert.equal(inspected.missingDirectories.length, 1); assert.equal(path.basename(inspected.missingDirectories[0]), path.basename(fx.transactionDirectory)); const applyLock = acquireLock(inspected, plan); const recoveryId = recoveryIdFor(plan.digest, TARGET_ID); const markerName = `.aas-layout-${recoveryId}`; const bootstrapBody = { directories: inspected.missingDirectories .map((directory) => path.relative(inspected.root, directory).split(path.sep).join("/")), markerName, markerToken: applyLock.token, planDigest: plan.digest, recoveryId, schemaVersion: 1, targetIdentityDigest: TARGET_ID, }; fs.writeFileSync(path.join(fx.root, `.aas-bootstrap-${recoveryId}.json`), `${canonicalJson({ ...bootstrapBody, recordDigest: sha256(canonicalJson(bootstrapBody)), })}\n`); createJournal(fx.root, recoveryId, plan.digest, TARGET_ID); fs.closeSync(applyLock.descriptor); fs.writeFileSync(applyLock.path, `${canonicalJson({ ...applyLock.record, pid: 2147483647 })}\n`); const diagnosis = doctor({ target: plan.payload.target, adapter: fx.adapter }); assert.equal(diagnosis.status, "recoveryRequired"); assert.equal(diagnosis.recoveries[0].bootstrapOnly, undefined); const cleanup = buildRecoveryPlan({ plan, adapter: fx.adapter, recoveryId, action: "cleanup" }); assert.equal(cleanup.payload.bootstrapOnly, false); assert.equal(recover({ recoveryPlan: cleanup, plan, adapter: fx.adapter, approvalDigest: cleanup.digest }).status, "cleaned"); assert.equal(fs.existsSync(fx.transactionDirectory), false); assert.equal(treeDigest(path.join(fx.skillsDirectory, "unmanaged")), unmanagedBefore); assert.equal(doctor({ target: plan.payload.target, adapter: fx.adapter }).status, "healthy"); fs.rmSync(fx.sandbox, { recursive: true }); }); test("cleanup recovery stays fail-closed when a marker-owned stage is not empty", () => { const fx = fixture(); fs.rmSync(fx.transactionDirectory, { recursive: true }); const plan = buildInstallPlan(fx); const inspected = inspectLayout(fx.adapter, plan.payload.target); const applyLock = acquireLock(inspected, plan); const recoveryId = recoveryIdFor(plan.digest, TARGET_ID); const markerName = `.aas-layout-${recoveryId}`; const bootstrapBody = { directories: inspected.missingDirectories .map((directory) => path.relative(inspected.root, directory).split(path.sep).join("/")), markerName, markerToken: applyLock.token, planDigest: plan.digest, recoveryId, schemaVersion: 1, targetIdentityDigest: TARGET_ID, }; fs.writeFileSync(path.join(fx.root, `.aas-bootstrap-${recoveryId}.json`), `${canonicalJson({ ...bootstrapBody, recordDigest: sha256(canonicalJson(bootstrapBody)), })}\n`); createJournal(fx.root, recoveryId, plan.digest, TARGET_ID); const missing = inspected.missingDirectories[0]; const stage = path.join(path.dirname(missing), `.aas-layout-stage-${applyLock.token}-${path.basename(missing)}`); fs.mkdirSync(stage, { mode: 0o700 }); fs.writeFileSync(path.join(stage, markerName), `${applyLock.token}\n`, { mode: 0o600 }); fs.writeFileSync(path.join(stage, "unexpected.txt"), "unmanaged\n", { mode: 0o600 }); fs.closeSync(applyLock.descriptor); fs.writeFileSync(applyLock.path, `${canonicalJson({ ...applyLock.record, pid: 2147483647 })}\n`); const cleanup = buildRecoveryPlan({ plan, adapter: fx.adapter, recoveryId, action: "cleanup" }); assert.throws(() => recover({ recoveryPlan: cleanup, plan, adapter: fx.adapter, approvalDigest: cleanup.digest, }), { code: "AAS_TRANSACTION_LAYOUT_CLEANUP_INCOMPLETE" }); assert.equal(fs.existsSync(stage), true); assert.equal(fs.readFileSync(path.join(stage, "unexpected.txt"), "utf8"), "unmanaged\n"); assert.equal(fs.existsSync(path.join(fx.root, `.aas-bootstrap-${recoveryId}.json`)), true); assert.equal(doctor({ target: plan.payload.target, adapter: fx.adapter }).status, "recoveryRequired"); fs.unlinkSync(path.join(stage, "unexpected.txt")); assert.equal(recover({ recoveryPlan: cleanup, plan, adapter: fx.adapter, approvalDigest: cleanup.digest, }).status, "cleaned"); assert.equal(fs.existsSync(stage), false); assert.equal(doctor({ target: plan.payload.target, adapter: fx.adapter }).status, "healthy"); fs.rmSync(fx.sandbox, { recursive: true }); }); test("layout publication failure is tracked before fsync and leaves no partial artifact", () => { const fx = fixture(); fs.rmSync(fx.skillsDirectory, { recursive: true }); fs.rmSync(fx.transactionDirectory, { recursive: true }); const plan = buildInstallPlan(fx); assert.throws(() => applyPlan({ plan, adapter: fx.adapter, approvalDigest: plan.digest, onBoundary(name) { if (name === "layoutDirectoryPublished") throw new Error("fault after layout rename"); }, }), { code: "AAS_TRANSACTION_LAYOUT_CREATE_FAILED" }); assert.equal(fs.existsSync(fx.skillsDirectory), false); assert.equal(fs.existsSync(fx.transactionDirectory), false); assert.equal(fs.readdirSync(fx.root).some((name) => name.startsWith(".aas-")), false); assert.equal(doctor({ target: plan.payload.target, adapter: fx.adapter }).status, "healthy"); fs.rmSync(fx.sandbox, { recursive: true }); }); test("layout tombstone cleanup resumes after its rename durability boundary", () => { const fx = fixture(); fs.rmSync(fx.skillsDirectory, { recursive: true }); fs.rmSync(fx.transactionDirectory, { recursive: true }); const inspected = inspectLayout(fx.adapter, { host: "codex", scope: "project", identityDigest: TARGET_ID }); const markerToken = "c".repeat(48); const markerName = `.aas-layout-recovery-${"d".repeat(32)}`; const created = []; materializeLayout(inspected, { markerName, markerToken, createdDirectories: created }); assert.throws(() => cleanupMaterializedLayout(inspected, created, { markerName, markerToken, onBoundary(name) { if (name === "layoutDirectoryTombstoned") throw new Error("fault after tombstone rename"); }, }), /fault after tombstone rename/); assert.ok(fs.readdirSync(fx.root).some((name) => name.startsWith(`.aas-layout-remove-${markerToken}-`))); cleanupMaterializedLayout(inspected, created, { markerName, markerToken }); assert.equal(fs.existsSync(fx.skillsDirectory), false); assert.equal(fs.existsSync(fx.transactionDirectory), false); assert.equal(fs.readdirSync(fx.root).some((name) => name.includes(markerToken)), false); fs.rmSync(fx.sandbox, { recursive: true }); }); test("layout cleanup removes only exact marker-owned stages left before publication", () => { const fx = fixture(); fs.writeFileSync(path.join(fx.root, "unmanaged.txt"), "mine\n"); fs.rmSync(fx.skillsDirectory, { recursive: true }); fs.rmSync(fx.transactionDirectory, { recursive: true }); const inspected = inspectLayout(fx.adapter, { host: "codex", scope: "project", identityDigest: TARGET_ID }); const markerToken = "e".repeat(48); const markerName = `.aas-layout-recovery-${"f".repeat(32)}`; for (const directory of inspected.missingDirectories) { const stage = path.join(path.dirname(directory), `.aas-layout-stage-${markerToken}-${path.basename(directory)}`); fs.mkdirSync(stage, { mode: 0o700 }); fs.writeFileSync(path.join(stage, markerName), `${markerToken}\n`, { mode: 0o600 }); } assert.ok(remainingMaterializedLayoutPaths(inspected, inspected.missingDirectories, { markerName, markerToken }).length > 0); cleanupMaterializedLayout(inspected, inspected.missingDirectories, { markerName, markerToken }); assert.deepEqual(remainingMaterializedLayoutPaths(inspected, inspected.missingDirectories, { markerName, markerToken }), []); assert.equal(fs.readdirSync(fx.root).some((name) => name.includes(markerToken)), false); assert.equal(fs.readFileSync(path.join(fx.root, "unmanaged.txt"), "utf8"), "mine\n"); assert.equal(fs.existsSync(fx.skillsDirectory), false); assert.equal(fs.existsSync(fx.transactionDirectory), false); fs.rmSync(fx.sandbox, { recursive: true }); }); test("layout cleanup postcondition detects a dangling symlink artifact", (t) => { const fx = fixture(); fs.rmSync(fx.transactionDirectory, { recursive: true }); const inspected = inspectLayout(fx.adapter, { host: "codex", scope: "project", identityDigest: TARGET_ID }); const markerToken = "9".repeat(48); const markerName = `.aas-layout-recovery-${"8".repeat(32)}`; const directory = inspected.missingDirectories[0]; const stage = path.join(path.dirname(directory), `.aas-layout-stage-${markerToken}-${path.basename(directory)}`); fs.mkdirSync(path.dirname(stage), { recursive: true }); try { fs.symlinkSync(path.join(fx.root, "missing-target"), stage, "dir"); } catch (error) { if (["EPERM", "EACCES", "ENOSYS"].includes(error.code)) { fs.rmSync(fx.sandbox, { recursive: true }); t.skip(`symlink unavailable: ${error.code}`); return; } throw error; } assert.deepEqual(remainingMaterializedLayoutPaths(inspected, [directory], { markerName, markerToken }), [ path.relative(inspected.root, stage).split(path.sep).join("/"), ]); fs.unlinkSync(stage); assert.deepEqual(remainingMaterializedLayoutPaths(inspected, [directory], { markerName, markerToken }), []); fs.rmSync(fx.sandbox, { recursive: true }); }); test("alreadyApplied verifies final bytes and refuses a completed state with drift or recovery artifacts", () => { const fx = fixture(); const plan = buildInstallPlan(fx); applyPlan({ plan, adapter: fx.adapter, approvalDigest: plan.digest }); fs.writeFileSync(path.join(fx.skillsDirectory, "alpha", "SKILL.md"), "tampered\n"); assert.throws(() => applyPlan({ plan, adapter: fx.adapter, approvalDigest: plan.digest }), { code: "AAS_TRANSACTION_FINAL_STATE_DRIFT" }); fs.writeFileSync(path.join(fx.skillsDirectory, "alpha", "SKILL.md"), "# Alpha\n"); const recoveryId = recoveryIdFor(plan.digest, TARGET_ID); createJournal(fx.root, recoveryId, plan.digest, TARGET_ID); assert.throws(() => applyPlan({ plan, adapter: fx.adapter, approvalDigest: plan.digest }), { code: "AAS_TRANSACTION_RECOVERY_REQUIRED" }); fs.rmSync(fx.sandbox, { recursive: true }); }); test("unmanaged collision and target identity drift fail before writes", () => { const fx = fixture(); writeTree(path.join(fx.skillsDirectory, "alpha"), { "personal.txt": "mine" }); const plan = buildInstallPlan(fx); const before = snapshot(fx.root); assert.throws(() => preflight({ plan, adapter: fx.adapter }), { code: "AAS_TRANSACTION_UNMANAGED_COLLISION" }); assert.deepEqual(snapshot(fx.root), before); const drifting = { ...fx.adapter, computeTargetIdentity() { return DIGEST("f"); } }; assert.throws(() => preflight({ plan, adapter: drifting }), { code: "AAS_TRANSACTION_TARGET_DRIFT" }); fs.rmSync(fx.sandbox, { recursive: true }); }); test("target lock is held before materialization and preflight, preventing concurrent stale plans", () => { const fx = fixture(); const first = buildInstallPlan(fx, "alpha"); const second = buildInstallPlan(fx, "beta"); let concurrentCode = null; const result = applyPlan({ plan: first, adapter: fx.adapter, approvalDigest: first.digest, onBoundary(name) { if (name !== "lockAcquired" || concurrentCode) return; try { applyPlan({ plan: second, adapter: fx.adapter, approvalDigest: second.digest }); } catch (error) { concurrentCode = error.code; } }, }); assert.equal(result.status, "applied"); assert.equal(concurrentCode, "AAS_TRANSACTION_LOCKED"); const state = readManagedState(fx.stateFile); assert.deepEqual(state.entries.map((entry) => entry.skillId), ["alpha"]); assert.equal(fs.existsSync(path.join(fx.skillsDirectory, "beta")), false); fs.rmSync(fx.sandbox, { recursive: true }); }); test("managed local edits block unless the immutable plan carries an explicit backed-up override", () => { const fx = fixture(); const initial = buildInstallPlan(fx); applyPlan({ plan: initial, adapter: fx.adapter, approvalDigest: initial.digest }); fs.writeFileSync(path.join(fx.skillsDirectory, "alpha", "SKILL.md"), "# locally edited\n"); const blocked = buildReplacePlan(fx, { allowDrift: false }); assert.throws(() => preflight({ plan: blocked.plan, adapter: blocked.adapter }), { code: "AAS_TRANSACTION_MANAGED_DRIFT" }); const approved = buildReplacePlan(fx, { allowDrift: true }); assert.equal(applyPlan({ plan: approved.plan, adapter: approved.adapter, approvalDigest: approved.plan.digest }).status, "applied"); assert.equal(treeDigest(path.join(fx.skillsDirectory, "alpha")), approved.resultDigest); fs.rmSync(fx.sandbox, { recursive: true }); }); test("fault after promotion rolls back bytes and requires approved cleanup before reuse", () => { const fx = fixture(); const plan = buildInstallPlan(fx); let injected = false; assert.throws(() => applyPlan({ plan, adapter: fx.adapter, approvalDigest: plan.digest, onBoundary(name) { if (name === "destinationPromoted" && !injected) { injected = true; throw new Error("fault"); } }, }), { code: "AAS_TRANSACTION_APPLY_ROLLED_BACK" }); assert.equal(fs.existsSync(path.join(fx.skillsDirectory, "alpha")), false); assert.equal(fs.existsSync(fx.stateFile), false); assert.throws(() => applyPlan({ plan, adapter: fx.adapter, approvalDigest: plan.digest }), { code: "AAS_TRANSACTION_RECOVERY_REQUIRED" }); const diagnosisBefore = snapshot(fx.root); const diagnosis = doctor({ target: plan.payload.target, adapter: fx.adapter }); assert.equal(diagnosis.status, "recoveryRequired"); assert.deepEqual(snapshot(fx.root), diagnosisBefore, "doctor must be read-only"); const cleanup = buildRecoveryPlan({ plan, adapter: fx.adapter, recoveryId: diagnosis.recoveries[0].recoveryId, action: "cleanup" }); assert.throws(() => recover({ recoveryPlan: cleanup, plan, adapter: fx.adapter, approvalDigest: DIGEST("0") }), { code: "AAS_RECOVERY_APPROVAL_MISMATCH" }); assert.equal(recover({ recoveryPlan: cleanup, plan, adapter: fx.adapter, approvalDigest: cleanup.digest }).status, "cleaned"); assert.equal(applyPlan({ plan, adapter: fx.adapter, approvalDigest: plan.digest }).status, "applied"); fs.rmSync(fx.sandbox, { recursive: true }); }); test("rollback restores a pre-existing empty managed-state file byte-for-byte", () => { const fx = fixture(); const plan = buildInstallPlan(fx); const emptyState = buildManagedState({ target: plan.payload.target, catalog: CATALOG, entries: [], completedPlanDigests: [], }); const previousBytes = `${canonicalJson(emptyState)}\n`; fs.writeFileSync(fx.stateFile, previousBytes); assert.throws(() => applyPlan({ plan, adapter: fx.adapter, approvalDigest: plan.digest, onBoundary(name) { if (name === "managedStateCommitted") throw new Error("fault after state promote"); }, }), { code: "AAS_TRANSACTION_APPLY_ROLLED_BACK" }); assert.equal(fs.readFileSync(fx.stateFile, "utf8"), previousBytes); assert.equal(fs.existsSync(path.join(fx.skillsDirectory, "alpha")), false); const diagnosis = doctor({ target: plan.payload.target, adapter: fx.adapter }); const cleanup = buildRecoveryPlan({ plan, adapter: fx.adapter, recoveryId: diagnosis.recoveries[0].recoveryId, action: "cleanup" }); assert.equal(recover({ recoveryPlan: cleanup, plan, adapter: fx.adapter, approvalDigest: cleanup.digest }).status, "cleaned"); fs.rmSync(fx.sandbox, { recursive: true }); }); test("explicit rollback is digest-bound and refuses destination drift", () => { const fx = fixture(); const plan = buildInstallPlan(fx); const recoveryId = recoveryIdFor(plan.digest, TARGET_ID); const root = path.join(fx.transactionDirectory, recoveryId); fs.mkdirSync(root, { mode: 0o700 }); const journal = createJournal(fx.root, recoveryId, plan.digest, TARGET_ID); fs.renameSync(path.join(fx.sources, "alpha"), path.join(fx.skillsDirectory, "alpha")); appendRecord(journal, "destinationPromoted", { skillId: "alpha", treeDigest: plan.payload.operations[0].resultTreeDigest }); const recoveryPlan = buildRecoveryPlan({ plan, adapter: fx.adapter, recoveryId, action: "rollback" }); const misleading = structuredClone(recoveryPlan); misleading.payload.operations = []; misleading.digest = sha256(canonicalJson(misleading.payload)); assert.throws(() => recover({ recoveryPlan: misleading, plan, adapter: fx.adapter, approvalDigest: misleading.digest }), { code: "AAS_RECOVERY_OPERATIONS_MISMATCH" }); fs.writeFileSync(path.join(fx.skillsDirectory, "alpha", "SKILL.md"), "tampered\n"); assert.throws(() => recover({ recoveryPlan, plan, adapter: fx.adapter, approvalDigest: recoveryPlan.digest }), { code: "AAS_RECOVERY_PRECONDITION_DRIFT" }); fs.writeFileSync(path.join(fx.skillsDirectory, "alpha", "SKILL.md"), "# Alpha\n"); assert.equal(recover({ recoveryPlan, plan, adapter: fx.adapter, approvalDigest: recoveryPlan.digest }).status, "rolledBack"); assert.equal(fs.existsSync(path.join(fx.skillsDirectory, "alpha")), false); const cleanup = buildRecoveryPlan({ plan, adapter: fx.adapter, recoveryId, action: "cleanup" }); assert.equal(recover({ recoveryPlan: cleanup, plan, adapter: fx.adapter, approvalDigest: cleanup.digest }).status, "cleaned"); fs.rmSync(fx.sandbox, { recursive: true }); }); test("recovery never retires an apply lock owned by a live process", () => { const fx = fixture(); const plan = buildInstallPlan(fx); const ready = preflight({ plan, adapter: fx.adapter }); const applyLock = acquireLock(ready.layout, plan); const recoveryId = recoveryIdFor(plan.digest, TARGET_ID); createJournal(fx.root, recoveryId, plan.digest, TARGET_ID); const recoveryPlan = buildRecoveryPlan({ plan, adapter: fx.adapter, recoveryId, action: "cleanup" }); assert.throws(() => recover({ recoveryPlan, plan, adapter: fx.adapter, approvalDigest: recoveryPlan.digest }), { code: "AAS_TRANSACTION_LOCK_LIVE_OR_UNKNOWN" }); assert.equal(fs.existsSync(applyLock.path), true); releaseLock(applyLock); fs.rmSync(fx.sandbox, { recursive: true }); }); test("doctor exposes and recovery retires a dead standalone recovery guard", () => { const fx = fixture(); const plan = buildInstallPlan(fx); const ready = preflight({ plan, adapter: fx.adapter }); const guard = acquireLock(ready.layout, plan, "recovery-guard"); fs.closeSync(guard.descriptor); const record = { ...guard.record, pid: 2147483647 }; fs.writeFileSync(guard.path, `${canonicalJson(record)}\n`); const diagnosis = doctor({ target: plan.payload.target, adapter: fx.adapter }); assert.equal(diagnosis.status, "recoveryRequired"); assert.equal(diagnosis.recoveries.length, 1); const cleanup = buildRecoveryPlan({ plan, adapter: fx.adapter, recoveryId: diagnosis.recoveries[0].recoveryId, action: "cleanup" }); assert.equal(recover({ recoveryPlan: cleanup, plan, adapter: fx.adapter, approvalDigest: cleanup.digest }).status, "cleaned"); assert.equal(fs.readdirSync(fx.root).some((name) => name.includes("transaction-recovery.lock")), false); fs.rmSync(fx.sandbox, { recursive: true }); }); test("a partial bootstrap record is recoverable only before a root WAL exists", () => { const fx = fixture(); const plan = buildInstallPlan(fx); const ready = preflight({ plan, adapter: fx.adapter }); const applyLock = acquireLock(ready.layout, plan); fs.closeSync(applyLock.descriptor); fs.writeFileSync(applyLock.path, `${canonicalJson({ ...applyLock.record, pid: 2147483647 })}\n`); const recoveryId = recoveryIdFor(plan.digest, TARGET_ID); const bootstrap = path.join(fx.root, `.aas-bootstrap-${recoveryId}.json`); fs.writeFileSync(bootstrap, '{"partial":'); const pendingWal = `${journalPath(fx.root, recoveryId)}.pending-${applyLock.token}`; fs.writeFileSync(pendingWal, '{"partialWal":'); const diagnosis = doctor({ target: plan.payload.target, adapter: fx.adapter }); assert.equal(diagnosis.status, "recoveryRequired"); assert.equal(diagnosis.recoveries[0].recoveryId, recoveryId); const cleanup = buildRecoveryPlan({ plan, adapter: fx.adapter, recoveryId, action: "cleanup" }); assert.equal(recover({ recoveryPlan: cleanup, plan, adapter: fx.adapter, approvalDigest: cleanup.digest }).status, "cleaned"); assert.equal(fs.existsSync(bootstrap), false); assert.equal(fs.existsSync(pendingWal), false); fs.rmSync(fx.sandbox, { recursive: true }); }); test("recovery repairs only an approved torn WAL tail", () => { const fx = fixture(); const plan = buildInstallPlan(fx); assert.throws(() => applyPlan({ plan, adapter: fx.adapter, approvalDigest: plan.digest, onBoundary(name) { if (name === "destinationPromoted") throw new Error("fault"); }, }), { code: "AAS_TRANSACTION_APPLY_ROLLED_BACK" }); const recoveryId = recoveryIdFor(plan.digest, TARGET_ID); const wal = journalPath(fx.root, recoveryId); fs.appendFileSync(wal, '{"partial":'); const diagnosis = doctor({ target: plan.payload.target, adapter: fx.adapter }); assert.equal(diagnosis.status, "recoveryRequired"); const cleanup = buildRecoveryPlan({ plan, adapter: fx.adapter, recoveryId, action: "cleanup" }); assert.match(cleanup.payload.checkpoint.tornTailDigest, /^sha256-[a-f0-9]{64}$/); assert.equal(recover({ recoveryPlan: cleanup, plan, adapter: fx.adapter, approvalDigest: cleanup.digest }).status, "cleaned"); assert.equal(fs.existsSync(wal), false); fs.rmSync(fx.sandbox, { recursive: true }); }); test("the same approved recovery plan resumes after a crash between mutation and WAL completion", () => { const fx = fixture(); const plan = buildInstallPlan(fx); const recoveryId = recoveryIdFor(plan.digest, TARGET_ID); const root = path.join(fx.transactionDirectory, recoveryId); fs.mkdirSync(root, { mode: 0o700 }); const journal = createJournal(fx.root, recoveryId, plan.digest, TARGET_ID); fs.renameSync(path.join(fx.sources, "alpha"), path.join(fx.skillsDirectory, "alpha")); appendRecord(journal, "mutationIntent", { actionId: "apply:promote:alpha", phase: "apply", kind: "rename", logicalId: "alpha", beforeDigest: null, afterDigest: plan.payload.operations[0].resultTreeDigest, }); const recoveryPlan = buildRecoveryPlan({ plan, adapter: fx.adapter, recoveryId, action: "rollback" }); let killed = false; assert.throws(() => recover({ recoveryPlan, plan, adapter: fx.adapter, approvalDigest: recoveryPlan.digest, onBoundary(name) { if (name === "rollback:discard-new:alpha:mutated" && !killed) { killed = true; throw new Error("simulated hard stop"); } }, }), /simulated hard stop/); assert.equal(fs.existsSync(path.join(fx.skillsDirectory, "alpha")), false); fs.appendFileSync(journalPath(fx.root, recoveryId), '{"partialRecovery":'); assert.equal(recover({ recoveryPlan, plan, adapter: fx.adapter, approvalDigest: recoveryPlan.digest }).status, "rolledBack"); const cleanup = buildRecoveryPlan({ plan, adapter: fx.adapter, recoveryId, action: "cleanup" }); assert.equal(recover({ recoveryPlan: cleanup, plan, adapter: fx.adapter, approvalDigest: cleanup.digest }).status, "cleaned"); fs.rmSync(fx.sandbox, { recursive: true }); }); test("plan physical paths are ignored and adapter containment is enforced", () => { const fx = fixture(); const plan = buildInstallPlan(fx); assert.equal(canonicalJson(plan).includes(fx.root), false); const escaping = { ...fx.adapter, resolveTransactionLayout() { return { root: fx.root, skillsDirectory: fx.skillsDirectory, stateFile: path.join(fx.sandbox, "outside.json"), transactionDirectory: fx.transactionDirectory }; }, }; assert.throws(() => preflight({ plan, adapter: escaping }), { code: "AAS_TRANSACTION_PATH_OUTSIDE_TARGET" }); fs.rmSync(fx.sandbox, { recursive: true }); });