diff --git a/skills/develop/develop-workflow/references/evidence-and-recovery.md b/skills/develop/develop-workflow/references/evidence-and-recovery.md index c27db8c..fd4ccb7 100644 --- a/skills/develop/develop-workflow/references/evidence-and-recovery.md +++ b/skills/develop/develop-workflow/references/evidence-and-recovery.md @@ -13,7 +13,7 @@ node skills/develop/develop-workflow/scripts/checkpoint-record.mjs \ --input --expected-sha256 ``` -Omit `--expected-sha256` only for first creation; require an absent target. Commit the initial record and every approved gate before dependent product mutation. A stale digest, lock, temporary artifact with no valid record, validation failure, or filesystem error blocks progress. Preserve artifacts for inspection; never hand-delete recovery evidence to make a command pass. +Omit `--expected-sha256` only for first creation; require an absent target. Commit the initial record and every approved gate before dependent product mutation. The exclusive lock serializes cooperating helper invocations; external mutation of its pathname is outside the contract. A stale digest, lock, temporary artifact with no valid record, validation failure, or filesystem error blocks progress. Preserve artifacts for inspection; never hand-delete recovery evidence to make a command pass. ## Assignment evidence diff --git a/skills/develop/develop-workflow/scripts/checkpoint-record.mjs b/skills/develop/develop-workflow/scripts/checkpoint-record.mjs index db753bd..6d1705f 100644 --- a/skills/develop/develop-workflow/scripts/checkpoint-record.mjs +++ b/skills/develop/develop-workflow/scripts/checkpoint-record.mjs @@ -38,7 +38,6 @@ const nodeIo = Object.freeze({ open: (...args) => fs.open(...args), readFile: (...args) => fs.readFile(...args), rename: (...args) => fs.rename(...args), - stat: (...args) => fs.stat(...args), unlink: (...args) => fs.unlink(...args), randomUUID: () => randomUUID(), }); @@ -173,10 +172,6 @@ async function unlinkQuietly(io, path) { } } -function sameFile(left, right) { - return left.dev === right.dev && left.ino === right.ino; -} - async function readExisting(io, target) { try { return { exists: true, bytes: await io.readFile(target) }; @@ -265,7 +260,6 @@ export async function checkpointRecord(request) { } let lockHandle = null; - let lockIdentity = null; let tempHandle = null; let tempPath = null; let primaryError; @@ -273,14 +267,13 @@ export async function checkpointRecord(request) { try { try { lockHandle = await io.open(lock, "wx", FILE_MODE); - lockIdentity = await lockHandle.stat(); } catch (error) { if (isAlreadyExists(error)) { throw new CheckpointError("LOCK_EXISTS", "checkpoint target is locked; refusing to recover or overwrite the lock", { cause: error, }); } - throw new CheckpointError("LOCK_FAILED", `could not acquire and identify checkpoint lock: ${messageFor(error)}`, { + throw new CheckpointError("LOCK_FAILED", `could not acquire checkpoint lock: ${messageFor(error)}`, { cause: error, }); } @@ -376,24 +369,12 @@ export async function checkpointRecord(request) { cause: lockCloseError, }); } - if (lockIdentity !== null) { - let currentLockIdentity; - try { - currentLockIdentity = await io.stat(lock); - } catch (error) { - if (!isMissing(error) && primaryError === undefined) { - primaryError = new CheckpointError("LOCK_CLEANUP_FAILED", `could not verify checkpoint lock ownership: ${messageFor(error)}`, { - cause: error, - }); - } - } - if (currentLockIdentity !== undefined && sameFile(lockIdentity, currentLockIdentity)) { - const lockUnlinkError = await unlinkQuietly(io, lock); - if (lockUnlinkError !== undefined && !isMissing(lockUnlinkError) && primaryError === undefined) { - primaryError = new CheckpointError("LOCK_CLEANUP_FAILED", `could not remove checkpoint lock: ${messageFor(lockUnlinkError)}`, { - cause: lockUnlinkError, - }); - } + if (lockHandle !== null) { + const lockUnlinkError = await unlinkQuietly(io, lock); + if (lockUnlinkError !== undefined && !isMissing(lockUnlinkError) && primaryError === undefined) { + primaryError = new CheckpointError("LOCK_CLEANUP_FAILED", `could not remove checkpoint lock: ${messageFor(lockUnlinkError)}`, { + cause: lockUnlinkError, + }); } } diff --git a/skills/develop/develop-workflow/tests/checkpoint-record.test.mjs b/skills/develop/develop-workflow/tests/checkpoint-record.test.mjs index 0b58ebb..fc6b87c 100644 --- a/skills/develop/develop-workflow/tests/checkpoint-record.test.mjs +++ b/skills/develop/develop-workflow/tests/checkpoint-record.test.mjs @@ -175,36 +175,6 @@ test("serializes concurrent updates with an exclusive per-target lock", async () assert.equal(await exists(`${target}.lock`), false); }); -test("does not remove a lock pathname replaced before cleanup", async () => { - const directory = await temporaryDirectory(); - const target = join(directory, "record.md"); - const lock = `${target}.lock`; - const replacement = "replacement lock"; - let replaced = false; - const io = { - ...defaultIo, - open: async (path, flags, mode) => { - const handle = await defaultIo.open(path, flags, mode); - if (path !== lock || flags !== "wx") return handle; - return { - stat: (...args) => handle.stat(...args), - close: async () => { - await handle.close(); - await fs.unlink(lock); - await fs.writeFile(lock, replacement, { mode: 0o600 }); - replaced = true; - }, - }; - }, - }; - - await checkpointRecord({ target, input: record(), io }); - - assert.equal(replaced, true); - assert.equal(await fs.readFile(lock, "utf8"), replacement); - assert.equal(await fs.readFile(target, "utf8"), record()); -}); - test("fails closed on a pre-existing stale lock and leaves it for recovery", async () => { const directory = await temporaryDirectory(); const target = join(directory, "record.md");