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"
],
"source_sha256": {
"finalizer": "a1f51515cc3c175f83a487f499ddd26ec13aec17d063d4416efe714780a02517",
"tests": "39e70be546c54c1e315bd5d8447947b79fb1a0b67416cc2acd0f0a7027075bd0"
"finalizer": "7216ec32244d4b5c6956908b8f72a6c3cd207cddcc4f527ca90272e9de1a672d",
"tests": "9e9297d2ec0b72aadd54c0781d537d986afd8d384ef739b1aac9c1714dfb2f57"
},
"runtime_sha256": {
"bootstrap": "cbf5be03e5156a11f3ae11d0baf2f9daa2c8b4e58c8a405de22bcadff024c282",
@ -39,7 +39,7 @@
"publish_policy": "10bc53c439479c67f5b59685a29e221e0db2baab48ceb26c8369ca75c1427330"
},
"verification": {
"finalizer_node_tests": "PASS_100_9_OF_9",
"finalizer_node_tests": "PASS_100_10_OF_10",
"continuity_guard_self_test": "PASS_100",
"source_and_runtime_syntax": "PASS_100",
"environment_audit": {

View file

@ -138,18 +138,23 @@ export function isJsonRecordName(name) {
}
function run(file, args, options = {}) {
return execFileSync(file, args, {
const output = execFileSync(file, args, {
cwd: options.cwd,
encoding: "utf8",
stdio: options.stdio || ["ignore", "pipe", "pipe"],
maxBuffer: 16 * 1024 * 1024,
}).trim();
});
return options.trim === false ? output : output.trim();
}
function git(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) {
const output = run(process.execPath, [guardScript, ...args]);
return output ? JSON.parse(output) : null;
@ -159,7 +164,7 @@ function readJson(filePath) {
return JSON.parse(fs.readFileSync(filePath, "utf8"));
}
function sha256(value) {
export function sha256(value) {
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 receiptEvidence = receipts.map((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}`);
return {
path: relative,
@ -321,7 +326,7 @@ function pushAndReadBack(worktree, repository, branch, head, receipts) {
if (cloneHead !== head) throw new Error(`FRESH_CLONE_HEAD_MISMATCH:${cloneHead}`);
git(clone, ["fsck", "--full"]);
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) {
throw new Error(`REMOTE_RECEIPT_MISMATCH:${receipt.path}`);
}

View file

@ -11,6 +11,7 @@ import {
parseArgs,
safeCacheRelative,
selfTest,
sha256,
} from "./finalize-development.mjs";
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.txt"), false);
});
test("receipt hashes preserve exact trailing bytes", () => {
assert.notEqual(sha256(Buffer.from("{}")), sha256(Buffer.from("{}\n")));
});