New:Socket for Asana Is Now Available.Learn more
Get Started

once-kernel

Package Overview
Dependencies
Maintainers
1
Versions
5
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

once-kernel - npm Package Compare versions

Comparing version
0.4.0
to
0.5.0
+115
dist/ledger.d.ts
/**
* 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"}
/**
* 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",

@@ -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 @@