fix(continuity): hash exact remote receipt bytes
This commit is contained in:
parent
6a05fe6ba2
commit
e2887513b2
3 changed files with 18 additions and 8 deletions
|
|
@ -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": {
|
||||||
|
|
|
||||||
|
|
@ -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}`);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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")));
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue