Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions bin/aas.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1424,18 +1424,17 @@ export function writeAtomicFile(
const temporary = `${target}.${process.pid}.${randomUUID()}.tmp`;
let written = false;
try {
writeFile(temporary, data, { encoding: "utf8", flag: "wx" });
const fd = openSync(temporary, "wx");
written = true;
try { writeFile(fd, data, { encoding: "utf8" }); }
finally { closeSync(fd); }
if (replace) rename(temporary, target);
else link(temporary, target);
} catch (error) {
throw error;
} finally {
if (written) {
// Best-effort cleanup. Only attempt unlink when we know the
// temporary was created; if the write itself failed, the file
// does not exist and unlink would just throw ENOENT we have to
// swallow. Rethrow the original error above either way.
// Clean up only the temporary we created, including partial writes.
try {
unlink(temporary);
} catch (cleanupError) {
Expand Down
4 changes: 2 additions & 2 deletions docs/release-readiness.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ Local checks may install dependencies and write caches or temporary files; see
- [ ] Review the final raw tree and changed-file list.
- [ ] Confirm `stack-lock.json` still contains the approved public URLs and commits:
- Constitutional Agent Testbench: `16b2faa71b0f92b9afa15b13afad8c48da8132f4`
- Consequence Rail: `9f60ab3223970c22371c20d3584e8330674d997c` ([PR #67](https://github.com/EauDoon/consequence-rail/pull/67))
- MandateBound: `b51fe137958afe26eee052c5a129e5481ccae560` ([PR #102](https://github.com/EauDoon/mandatebound/pull/102))
- Consequence Rail: `c430383c0a0931f0dcf17845d6f0e8ccf328615a` ([PR #68](https://github.com/EauDoon/consequence-rail/pull/68))
- MandateBound: `708256d4e48babeb13079fac7589d172920a5c95` ([PR #103](https://github.com/EauDoon/mandatebound/pull/103))
- [ ] Run bootstrap from a clean workspace and verify detached, clean, exact dependency checkouts.
- [ ] Confirm no private repository, credential, or production endpoint is referenced.

Expand Down
7 changes: 7 additions & 0 deletions docs/stack-lock.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ synthetic evidence whose currency contradicts the proposal. The historical
MandateBound `e526c4c`; it pins replay compatibility for an ordinary synthetic
refund. Its public demonstration signatures establish no real-world provenance.

The current Rail pin also binds a receipt's close time to its terminal event,
so a clock advancing between reads still produces evidence that survives the
same-case handoff and replay. The MandateBound pin rejects weak Ed25519 keys in
caller-pinned CasePack checkpoint trust snapshots using its existing strict key
validator. Ordinary valid evidence retains the same format; neither update
rewrites old artifacts. The testbench runtime pin is unchanged.

An older artifact with numeric-looking object keys may contain signatures made
with the former Rail canonical ordering. The current verifier does not try that
obsolete ordering after verification fails. Preserve the original artifact and
Expand Down
4 changes: 2 additions & 2 deletions stack-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
{
"name": "consequence-rail",
"repository": "https://github.com/EauDoon/consequence-rail.git",
"commit": "9f60ab3223970c22371c20d3584e8330674d997c",
"commit": "c430383c0a0931f0dcf17845d6f0e8ccf328615a",
"expected_entrypoints": [
"package.json",
"cmd/crctl.js"
Expand All @@ -22,7 +22,7 @@
{
"name": "mandatebound",
"repository": "https://github.com/EauDoon/mandatebound.git",
"commit": "b51fe137958afe26eee052c5a129e5481ccae560",
"commit": "708256d4e48babeb13079fac7589d172920a5c95",
"expected_entrypoints": [
"package.json",
"package-lock.json",
Expand Down
19 changes: 18 additions & 1 deletion test/component-compatibility.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { readFileSync } from "node:fs";
import test from "node:test";
import { replayBundle, runProveRail } from "../bin/aas.mjs";
import { canonicalJson, digest } from "../deps/consequence-rail/src/canonical.js";
import { createDemoRuntime, buildRefundProposal, runRefundDemo } from "../deps/consequence-rail/src/demo.js";
import { createDemoRuntime, buildRefundProposal, prepareRefund, runRefundDemo } from "../deps/consequence-rail/src/demo.js";
import { validateSettlementBundle } from "../deps/consequence-rail/src/bundle-validation.js";
import { MemoryEventStore } from "../deps/consequence-rail/src/event-store.js";
import { createDemoSigner, signArtifact } from "../deps/consequence-rail/src/signing.js";
Expand Down Expand Up @@ -63,3 +63,20 @@ test("ordinary synthetic case exported by the previous pins still replays", () =
assert.equal(result.ok, true, JSON.stringify(result));
assert.ok(result.checks.every((check) => check.passed));
});

test("moving-clock rail evidence survives the same-case handoff and replay", async () => {
let tick = Date.parse("2035-01-01T00:00:00.000Z");
const runtime = createDemoRuntime({ clock: { now: () => new Date(tick++).toISOString() } });
const { actionId } = prepareRefund(runtime);
await runtime.rail.execute(actionId, { fault: "duplicate" });
await runtime.rail.verifyOutcome(actionId);
await runtime.rail.remediate(actionId);
const bundle = runtime.rail.exportBundle(actionId, { profile: "audit" });
const proved = runProveRail(bundle);
assert.equal(proved.ok, true);
const replayed = replayBundle({
report: { run_id: "moving-clock" },
stages: { act: { action_id: actionId, rail_bundle: bundle }, prove: proved.raw },
});
assert.equal(replayed.ok, true, JSON.stringify(replayed));
});
14 changes: 10 additions & 4 deletions test/stack.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ const PROVENANCE = [
{
name: "consequence-rail",
repository: "https://github.com/EauDoon/consequence-rail.git",
commit: "9f60ab3223970c22371c20d3584e8330674d997c",
commit: "c430383c0a0931f0dcf17845d6f0e8ccf328615a",
origin: "https://github.com/EauDoon/consequence-rail.git",
detached: true,
clean: true,
Expand All @@ -69,7 +69,7 @@ const PROVENANCE = [
{
name: "mandatebound",
repository: "https://github.com/EauDoon/mandatebound.git",
commit: "b51fe137958afe26eee052c5a129e5481ccae560",
commit: "708256d4e48babeb13079fac7589d172920a5c95",
origin: "https://github.com/EauDoon/mandatebound.git",
detached: true,
clean: true,
Expand Down Expand Up @@ -783,10 +783,16 @@ test("act and prove child-process errors keep the run isolated", async () => {
test("atomic writes leave no partial target or temporary file after a write failure", () => {
const outputRoot = tempRoot();
const target = join(outputRoot, "atomic", "manifest.json");
mkdirSync(join(outputRoot, "atomic"));
writeFileSync(target, "original evidence");
assert.throws(() => writeAtomicFile(target, "payload", {
writeFile: () => { throw new Error("simulated partial write"); },
writeFile: (fd) => {
writeFileSync(fd, "partial evidence");
throw new Error("simulated partial write");
},
}), /partial write/);
assert.equal(readdirSync(join(outputRoot, "atomic")).length, 0);
assert.equal(readFileSync(target, "utf8"), "original evidence");
assert.deepEqual(readdirSync(join(outputRoot, "atomic")), ["manifest.json"]);
});

test("persistRunBundle refuses a final-directory collision", () => {
Expand Down
Loading