fix(continuity): hash exact remote receipt bytes

This commit is contained in:
冰朔 2026-08-05 21:13:01 +08:00
commit e2887513b2
3 changed files with 18 additions and 8 deletions

View file

@ -30,8 +30,8 @@
"any failed gate leaves the development lane unfinished" "any failed gate leaves the development lane unfinished"
], ],
"source_sha256": { "source_sha256": {
"finalizer": "a1f51515cc3c175f83a487f499ddd26ec13aec17d063d4416efe714780a02517", "finalizer": "7216ec32244d4b5c6956908b8f72a6c3cd207cddcc4f527ca90272e9de1a672d",
"tests": "39e70be546c54c1e315bd5d8447947b79fb1a0b67416cc2acd0f0a7027075bd0" "tests": "9e9297d2ec0b72aadd54c0781d537d986afd8d384ef739b1aac9c1714dfb2f57"
}, },
"runtime_sha256": { "runtime_sha256": {
"bootstrap": "cbf5be03e5156a11f3ae11d0baf2f9daa2c8b4e58c8a405de22bcadff024c282", "bootstrap": "cbf5be03e5156a11f3ae11d0baf2f9daa2c8b4e58c8a405de22bcadff024c282",
@ -39,7 +39,7 @@
"publish_policy": "10bc53c439479c67f5b59685a29e221e0db2baab48ceb26c8369ca75c1427330" "publish_policy": "10bc53c439479c67f5b59685a29e221e0db2baab48ceb26c8369ca75c1427330"
}, },
"verification": { "verification": {
"finalizer_node_tests": "PASS_100_9_OF_9", "finalizer_node_tests": "PASS_100_10_OF_10",
"continuity_guard_self_test": "PASS_100", "continuity_guard_self_test": "PASS_100",
"source_and_runtime_syntax": "PASS_100", "source_and_runtime_syntax": "PASS_100",
"environment_audit": { "environment_audit": {

View file

@ -138,18 +138,23 @@ export function isJsonRecordName(name) {
} }
function run(file, args, options = {}) { function run(file, args, options = {}) {
return execFileSync(file, args, { const output = execFileSync(file, args, {
cwd: options.cwd, cwd: options.cwd,
encoding: "utf8", encoding: "utf8",
stdio: options.stdio || ["ignore", "pipe", "pipe"], stdio: options.stdio || ["ignore", "pipe", "pipe"],
maxBuffer: 16 * 1024 * 1024, maxBuffer: 16 * 1024 * 1024,
}).trim(); });
return options.trim === false ? output : output.trim();
} }
function git(worktree, args) { function git(worktree, args) {
return run("/usr/bin/git", ["-C", worktree, ...args]); return run("/usr/bin/git", ["-C", worktree, ...args]);
} }
function gitRaw(worktree, args) {
return run("/usr/bin/git", ["-C", worktree, ...args], { trim: false });
}
function guard(guardScript, args) { function guard(guardScript, args) {
const output = run(process.execPath, [guardScript, ...args]); const output = run(process.execPath, [guardScript, ...args]);
return output ? JSON.parse(output) : null; return output ? JSON.parse(output) : null;
@ -159,7 +164,7 @@ function readJson(filePath) {
return JSON.parse(fs.readFileSync(filePath, "utf8")); return JSON.parse(fs.readFileSync(filePath, "utf8"));
} }
function sha256(value) { export function sha256(value) {
return crypto.createHash("sha256").update(value).digest("hex"); return crypto.createHash("sha256").update(value).digest("hex");
} }
@ -214,7 +219,7 @@ function verifyWorktree(worktree, receipts) {
const branch = normalizeBranch(git(root, ["branch", "--show-current"])); const branch = normalizeBranch(git(root, ["branch", "--show-current"]));
const receiptEvidence = receipts.map((receipt) => { const receiptEvidence = receipts.map((receipt) => {
const relative = normalizeReceipt(receipt); const relative = normalizeReceipt(receipt);
const content = git(root, ["show", `HEAD:${relative}`]); const content = gitRaw(root, ["show", `HEAD:${relative}`]);
if (!content) throw new Error(`RECEIPT_NOT_COMMITTED:${relative}`); if (!content) throw new Error(`RECEIPT_NOT_COMMITTED:${relative}`);
return { return {
path: relative, path: relative,
@ -321,7 +326,7 @@ function pushAndReadBack(worktree, repository, branch, head, receipts) {
if (cloneHead !== head) throw new Error(`FRESH_CLONE_HEAD_MISMATCH:${cloneHead}`); if (cloneHead !== head) throw new Error(`FRESH_CLONE_HEAD_MISMATCH:${cloneHead}`);
git(clone, ["fsck", "--full"]); git(clone, ["fsck", "--full"]);
for (const receipt of receipts) { for (const receipt of receipts) {
const remoteContent = git(clone, ["show", `HEAD:${receipt.path}`]); const remoteContent = gitRaw(clone, ["show", `HEAD:${receipt.path}`]);
if (sha256(Buffer.from(remoteContent)) !== receipt.sha256) { if (sha256(Buffer.from(remoteContent)) !== receipt.sha256) {
throw new Error(`REMOTE_RECEIPT_MISMATCH:${receipt.path}`); throw new Error(`REMOTE_RECEIPT_MISMATCH:${receipt.path}`);
} }

View file

@ -11,6 +11,7 @@ import {
parseArgs, parseArgs,
safeCacheRelative, safeCacheRelative,
selfTest, selfTest,
sha256,
} from "./finalize-development.mjs"; } from "./finalize-development.mjs";
test("the finalizer accepts only the registered Fifth Domain code channel", () => { test("the finalizer accepts only the registered Fifth Domain code channel", () => {
@ -83,3 +84,7 @@ test("macOS AppleDouble sidecars are never parsed as JSON records", () => {
assert.equal(isJsonRecordName("._lease.json"), false); assert.equal(isJsonRecordName("._lease.json"), false);
assert.equal(isJsonRecordName("lease.txt"), false); assert.equal(isJsonRecordName("lease.txt"), false);
}); });
test("receipt hashes preserve exact trailing bytes", () => {
assert.notEqual(sha256(Buffer.from("{}")), sha256(Buffer.from("{}\n")));
});