once-kernel
Advanced tools
+115
| /** | ||
| * Notarised receipts — evidence an outsider can check. | ||
| * | ||
| * `receipt()` answers "did this run once?" from the operator's own records. | ||
| * That is enough for the operator and worth nothing to anyone else: a log you | ||
| * keep about yourself is a diary entry. An auditor, a regulator, or a customer | ||
| * disputing a charge has no reason to believe a line you could have written | ||
| * this morning. | ||
| * | ||
| * This module closes that gap with two properties: | ||
| * | ||
| * 1. **Append-only by construction.** Each entry carries the hash of the | ||
| * entry before it. Change, insert or delete anything and every hash after | ||
| * it stops matching — so tampering is detectable by arithmetic rather than | ||
| * by trust. This is the same idea as a certificate-transparency log. | ||
| * | ||
| * 2. **Verifiable without the writer.** `verifyChain()` needs only the | ||
| * entries. Not our servers, not the operator's database, not us. Hand the | ||
| * file to anyone; they can check it themselves. | ||
| * | ||
| * What this deliberately does NOT claim: | ||
| * | ||
| * - **It is not a signature.** A hash chain proves *internal consistency* — | ||
| * that this sequence has not been edited since it was written. It does not | ||
| * prove *who* wrote it, and an operator who controls the whole file can | ||
| * rewrite the entire chain from scratch. Detached signatures (a key we hold | ||
| * and they do not) are what turns "self-consistent" into "third-party | ||
| * attested", and that is a later step, not this one. Claiming otherwise | ||
| * would be exactly the overclaim this project exists to refuse. | ||
| * - **It is not proof the effect happened in the world.** It records what the | ||
| * world adapter reported, and marks the difference plainly: `confirmed` | ||
| * means a provider was asked and said yes; `unconfirmed` means nobody asked | ||
| * or nobody answered. Those are not the same and must never be printed the | ||
| * same. | ||
| */ | ||
| import type { Receipt } from "./kernel.ts"; | ||
| /** The empty-chain sentinel — the hash a first entry links to. */ | ||
| export declare const GENESIS: string; | ||
| export type WorldStatus = "confirmed" | "unconfirmed" | "not_applicable"; | ||
| export interface LedgerEntry { | ||
| /** Position in the chain, starting at 0. */ | ||
| seq: number; | ||
| /** Idempotency key this entry is about. */ | ||
| key: string; | ||
| /** Terminal state recorded for that key. */ | ||
| status: Receipt["status"]; | ||
| /** RFC 8785 hash of the payload — proves WHAT ran, not merely that it did. */ | ||
| payloadHash: string; | ||
| /** >1 means a worker died mid-flight and another took over. */ | ||
| generation: number; | ||
| /** Was the effect confirmed by the world, or only by us? Never conflated. */ | ||
| world: WorldStatus; | ||
| /** Provider's own identifier for the effect, when there is one. */ | ||
| worldRef?: string; | ||
| /** ISO-8601. Supplied by the caller: the clock is the operator's, not ours. */ | ||
| at: string; | ||
| /** Hash of the previous entry, or GENESIS for the first. */ | ||
| prevHash: string; | ||
| /** Hash over every field above. Changing anything changes this. */ | ||
| hash: string; | ||
| } | ||
| export interface AppendInput { | ||
| receipt: Receipt; | ||
| /** Defaults to "unconfirmed" — the honest default when nobody asked. */ | ||
| world?: WorldStatus; | ||
| worldRef?: string; | ||
| /** ISO-8601 timestamp. Defaults to the receipt's own settledAt. */ | ||
| at?: string; | ||
| } | ||
| /** | ||
| * A hash-chained, append-only receipt log. | ||
| * | ||
| * Storage is the caller's problem on purpose: `toJSON()` and `fromJSON()` are | ||
| * the whole interface. A ledger that insisted on its own database would be one | ||
| * more thing to run, and this has to be cheap enough that keeping it is never | ||
| * the reason someone doesn't. | ||
| */ | ||
| export declare class ReceiptLedger { | ||
| private entries; | ||
| static fromJSON(json: string | LedgerEntry[]): ReceiptLedger; | ||
| get length(): number; | ||
| /** The hash a next entry would link to. */ | ||
| head(): string; | ||
| all(): readonly LedgerEntry[]; | ||
| /** Every entry recorded for one key — the "prove this ran once" query. */ | ||
| forKey(key: string): LedgerEntry[]; | ||
| append(input: AppendInput): LedgerEntry; | ||
| toJSON(): string; | ||
| } | ||
| export type VerifyResult = { | ||
| ok: true; | ||
| entries: number; | ||
| } | { | ||
| ok: false; | ||
| entries: number; | ||
| failedAt: number; | ||
| reason: string; | ||
| }; | ||
| /** | ||
| * Check a chain end to end. Needs nothing but the entries themselves — no | ||
| * network, no database, no us. That independence is the entire point. | ||
| * | ||
| * Reports the FIRST break and stops: after a broken link every later hash is | ||
| * suspect anyway, and a list of forty consequent failures buries the one that | ||
| * matters. | ||
| */ | ||
| export declare function verifyChain(entries: readonly LedgerEntry[]): VerifyResult; | ||
| /** | ||
| * A plain-English audit answer for one key. | ||
| * | ||
| * Written for the person asking "prove this refund went out exactly once", | ||
| * who is usually not the person who wrote the code. | ||
| */ | ||
| export declare function auditKey(entries: readonly LedgerEntry[], key: string): string; | ||
| //# sourceMappingURL=ledger.d.ts.map |
| {"version":3,"file":"ledger.d.ts","sourceRoot":"","sources":["../src/ledger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAIH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,kEAAkE;AAClE,eAAO,MAAM,OAAO,QAAiB,CAAC;AAEtC,MAAM,MAAM,WAAW,GAAG,WAAW,GAAG,aAAa,GAAG,gBAAgB,CAAC;AAEzE,MAAM,WAAW,WAAW;IAC1B,4CAA4C;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,2CAA2C;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,4CAA4C;IAC5C,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC1B,8EAA8E;IAC9E,WAAW,EAAE,MAAM,CAAC;IACpB,+DAA+D;IAC/D,UAAU,EAAE,MAAM,CAAC;IACnB,6EAA6E;IAC7E,KAAK,EAAE,WAAW,CAAC;IACnB,mEAAmE;IACnE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,EAAE,EAAE,MAAM,CAAC;IACX,4DAA4D;IAC5D,QAAQ,EAAE,MAAM,CAAC;IACjB,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAC;CACd;AAqBD,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,wEAAwE;IACxE,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mEAAmE;IACnE,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;GAOG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,OAAO,CAAqB;IAEpC,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,GAAG,aAAa;IAQ5D,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,2CAA2C;IAC3C,IAAI,IAAI,MAAM;IAId,GAAG,IAAI,SAAS,WAAW,EAAE;IAI7B,0EAA0E;IAC1E,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,EAAE;IAIlC,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,WAAW;IAkBvC,MAAM,IAAI,MAAM;CAGjB;AAED,MAAM,MAAM,YAAY,GACpB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAC7B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAErE;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,GAAG,YAAY,CAiBzE;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CA8B7E"} |
+173
| /** | ||
| * Notarised receipts — evidence an outsider can check. | ||
| * | ||
| * `receipt()` answers "did this run once?" from the operator's own records. | ||
| * That is enough for the operator and worth nothing to anyone else: a log you | ||
| * keep about yourself is a diary entry. An auditor, a regulator, or a customer | ||
| * disputing a charge has no reason to believe a line you could have written | ||
| * this morning. | ||
| * | ||
| * This module closes that gap with two properties: | ||
| * | ||
| * 1. **Append-only by construction.** Each entry carries the hash of the | ||
| * entry before it. Change, insert or delete anything and every hash after | ||
| * it stops matching — so tampering is detectable by arithmetic rather than | ||
| * by trust. This is the same idea as a certificate-transparency log. | ||
| * | ||
| * 2. **Verifiable without the writer.** `verifyChain()` needs only the | ||
| * entries. Not our servers, not the operator's database, not us. Hand the | ||
| * file to anyone; they can check it themselves. | ||
| * | ||
| * What this deliberately does NOT claim: | ||
| * | ||
| * - **It is not a signature.** A hash chain proves *internal consistency* — | ||
| * that this sequence has not been edited since it was written. It does not | ||
| * prove *who* wrote it, and an operator who controls the whole file can | ||
| * rewrite the entire chain from scratch. Detached signatures (a key we hold | ||
| * and they do not) are what turns "self-consistent" into "third-party | ||
| * attested", and that is a later step, not this one. Claiming otherwise | ||
| * would be exactly the overclaim this project exists to refuse. | ||
| * - **It is not proof the effect happened in the world.** It records what the | ||
| * world adapter reported, and marks the difference plainly: `confirmed` | ||
| * means a provider was asked and said yes; `unconfirmed` means nobody asked | ||
| * or nobody answered. Those are not the same and must never be printed the | ||
| * same. | ||
| */ | ||
| import { createHash } from "node:crypto"; | ||
| import { canonicalBytes } from "./canonical.js"; | ||
| /** The empty-chain sentinel — the hash a first entry links to. */ | ||
| export const GENESIS = "0".repeat(64); | ||
| /** The fields that are hashed, in a fixed shape. RFC 8785 handles key order. */ | ||
| function entryBody(e) { | ||
| return { | ||
| seq: e.seq, | ||
| key: e.key, | ||
| status: e.status, | ||
| payloadHash: e.payloadHash, | ||
| generation: e.generation, | ||
| world: e.world, | ||
| worldRef: e.worldRef ?? "", | ||
| at: e.at, | ||
| prevHash: e.prevHash, | ||
| }; | ||
| } | ||
| function hashEntry(e) { | ||
| return createHash("sha256").update(canonicalBytes(entryBody(e))).digest("hex"); | ||
| } | ||
| /** | ||
| * A hash-chained, append-only receipt log. | ||
| * | ||
| * Storage is the caller's problem on purpose: `toJSON()` and `fromJSON()` are | ||
| * the whole interface. A ledger that insisted on its own database would be one | ||
| * more thing to run, and this has to be cheap enough that keeping it is never | ||
| * the reason someone doesn't. | ||
| */ | ||
| export class ReceiptLedger { | ||
| entries = []; | ||
| static fromJSON(json) { | ||
| const parsed = typeof json === "string" ? JSON.parse(json) : json; | ||
| if (!Array.isArray(parsed)) | ||
| throw new Error("ledger: expected an array of entries"); | ||
| const l = new ReceiptLedger(); | ||
| l.entries = parsed; | ||
| return l; | ||
| } | ||
| get length() { | ||
| return this.entries.length; | ||
| } | ||
| /** The hash a next entry would link to. */ | ||
| head() { | ||
| return this.entries.length ? this.entries[this.entries.length - 1].hash : GENESIS; | ||
| } | ||
| all() { | ||
| return this.entries; | ||
| } | ||
| /** Every entry recorded for one key — the "prove this ran once" query. */ | ||
| forKey(key) { | ||
| return this.entries.filter((e) => e.key === key); | ||
| } | ||
| append(input) { | ||
| const r = input.receipt; | ||
| const body = { | ||
| seq: this.entries.length, | ||
| key: r.key, | ||
| status: r.status, | ||
| payloadHash: r.payloadHash, | ||
| generation: r.generation, | ||
| world: input.world ?? "unconfirmed", | ||
| worldRef: input.worldRef, | ||
| at: input.at ?? r.settledAt, | ||
| prevHash: this.head(), | ||
| }; | ||
| const entry = { ...body, hash: hashEntry(body) }; | ||
| this.entries.push(entry); | ||
| return entry; | ||
| } | ||
| toJSON() { | ||
| return JSON.stringify(this.entries, null, 2); | ||
| } | ||
| } | ||
| /** | ||
| * Check a chain end to end. Needs nothing but the entries themselves — no | ||
| * network, no database, no us. That independence is the entire point. | ||
| * | ||
| * Reports the FIRST break and stops: after a broken link every later hash is | ||
| * suspect anyway, and a list of forty consequent failures buries the one that | ||
| * matters. | ||
| */ | ||
| export function verifyChain(entries) { | ||
| let prev = GENESIS; | ||
| for (let i = 0; i < entries.length; i++) { | ||
| const e = entries[i]; | ||
| if (e.seq !== i) { | ||
| return { ok: false, entries: entries.length, failedAt: i, reason: `entry ${i} claims seq ${e.seq} — an entry was inserted or removed` }; | ||
| } | ||
| if (e.prevHash !== prev) { | ||
| return { ok: false, entries: entries.length, failedAt: i, reason: `entry ${i} does not link to the entry before it — the chain was cut or reordered` }; | ||
| } | ||
| const { hash, ...body } = e; | ||
| if (hashEntry(body) !== hash) { | ||
| return { ok: false, entries: entries.length, failedAt: i, reason: `entry ${i} has been altered since it was written` }; | ||
| } | ||
| prev = e.hash; | ||
| } | ||
| return { ok: true, entries: entries.length }; | ||
| } | ||
| /** | ||
| * A plain-English audit answer for one key. | ||
| * | ||
| * Written for the person asking "prove this refund went out exactly once", | ||
| * who is usually not the person who wrote the code. | ||
| */ | ||
| export function auditKey(entries, key) { | ||
| const chain = verifyChain(entries); | ||
| if (!chain.ok) { | ||
| return `CHAIN BROKEN at entry ${chain.failedAt}: ${chain.reason}\nNo claim about "${key}" can be trusted from this file.`; | ||
| } | ||
| const mine = entries.filter((e) => e.key === key); | ||
| if (mine.length === 0) | ||
| return `No record of "${key}" in ${entries.length} verified entries.`; | ||
| const completed = mine.filter((e) => e.status === "completed"); | ||
| const confirmed = completed.filter((e) => e.world === "confirmed"); | ||
| const lines = [ | ||
| `Chain verified: ${chain.entries} entries, unbroken.`, | ||
| `"${key}": ${mine.length} record(s), ${completed.length} completed.`, | ||
| ]; | ||
| if (completed.length > 1) { | ||
| lines.push(`⚠ ${completed.length} completions for one key — this is the duplicate you are looking for.`); | ||
| } | ||
| else if (completed.length === 1) { | ||
| const c = completed[0]; | ||
| lines.push(`Executed once, at ${c.at}. Payload hash ${c.payloadHash.slice(0, 16)}…`); | ||
| if (c.generation > 1) | ||
| lines.push(`Generation ${c.generation}: a worker died mid-flight and another finished the work.`); | ||
| lines.push(c.world === "confirmed" | ||
| ? `Confirmed by the provider${c.worldRef ? ` (${c.worldRef})` : ""}.` | ||
| : `NOT confirmed by any provider — this records what we did, not what the world received.`); | ||
| } | ||
| if (confirmed.length === 0 && completed.length > 0) { | ||
| lines.push(`Note: no world confirmation on record. Absence of confirmation is not evidence of failure — it means nobody asked.`); | ||
| } | ||
| return lines.join("\n"); | ||
| } | ||
| //# sourceMappingURL=ledger.js.map |
| {"version":3,"file":"ledger.js","sourceRoot":"","sources":["../src/ledger.ts"],"names":[],"mappings":"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"} |
+6
-2
| { | ||
| "name": "once-kernel", | ||
| "version": "0.4.0", | ||
| "version": "0.5.0", | ||
| "description": "Idempotency kernel for side-effecting operations. 1,000 racing callers, exactly one execution — proven, not asserted.", | ||
@@ -54,2 +54,6 @@ "keywords": [ | ||
| "default": "./dist/budget.js" | ||
| }, | ||
| "./ledger": { | ||
| "types": "./dist/ledger.d.ts", | ||
| "default": "./dist/ledger.js" | ||
| } | ||
@@ -68,3 +72,3 @@ }, | ||
| "clean": "rm -rf dist", | ||
| "test": "node --experimental-strip-types --no-warnings --test test/canonical.test.ts test/kernel.test.ts test/store-conformance.test.ts test/guard.test.ts test/capabilities.test.ts test/readme.test.ts test/once-audit-cli.test.ts test/door.test.ts", | ||
| "test": "node --experimental-strip-types --no-warnings --test test/canonical.test.ts test/kernel.test.ts test/store-conformance.test.ts test/guard.test.ts test/capabilities.test.ts test/readme.test.ts test/once-audit-cli.test.ts test/door.test.ts test/ledger.test.ts", | ||
| "test:storm": "node --experimental-strip-types --no-warnings --test test/storm.test.ts", | ||
@@ -71,0 +75,0 @@ "test:all": "npm run test && npm run test:storm", |
+36
-0
@@ -166,2 +166,38 @@ # once-kernel | ||
| ### Notarised receipts — evidence an outsider can check | ||
| `receipt()` answers "did this run once?" from your own records. That is worth | ||
| everything to you and nothing to an auditor: a log you keep about yourself is a | ||
| diary entry. `ReceiptLedger` hash-chains those receipts so that editing, | ||
| deleting or reordering any entry breaks every hash after it — tampering becomes | ||
| detectable by arithmetic instead of trust. | ||
| ```ts | ||
| import { ReceiptLedger, verifyChain, auditKey } from "once-kernel/ledger"; | ||
| const ledger = ReceiptLedger.fromJSON(await fs.readFile("receipts.json", "utf8")); | ||
| ledger.append({ | ||
| receipt: await once.receipt(`refund:${orderId}`), | ||
| world: "confirmed", // a provider was asked and said yes | ||
| worldRef: paymentIntentId, | ||
| }); | ||
| verifyChain(ledger.all()); // → { ok: true, entries: 1284 } | ||
| console.log(auditKey(ledger.all(), `refund:${orderId}`)); | ||
| // Chain verified: 1284 entries, unbroken. | ||
| // "refund:4471": 1 record(s), 1 completed. | ||
| // Executed once, at 2026-08-11T04:12:09.884Z. Payload hash 9f2a… | ||
| // Confirmed by the provider (pi_3Qx…). | ||
| ``` | ||
| `verifyChain` needs only the entries — no network, no database, not us. Hand the | ||
| file to anyone and they can check it themselves. | ||
| **What it does not claim:** a hash chain proves the sequence has not been edited | ||
| since it was written. It does not prove *who* wrote it — an operator holding the | ||
| whole file can rewrite the chain from scratch. Detached signatures are what turn | ||
| self-consistent into third-party attested, and that is a later step. It also | ||
| never conflates `confirmed` (a provider said yes) with `unconfirmed` (nobody | ||
| asked); the audit output says which, every time. | ||
| ### Warn before an unguarded effect | ||
@@ -168,0 +204,0 @@ |
172091
13.57%36
12.5%2177
15.12%357
11.21%