once-kernel
Advanced tools
| /** | ||
| * World confirmation — asking the provider whether the effect actually exists. | ||
| * | ||
| * Everything else in this library reasons about our own records. That is the | ||
| * cheap half of the problem. The expensive half is the question an operator | ||
| * asks at 3am: *the call timed out — did it land?* Our database cannot answer | ||
| * that. It only knows what we did, not what arrived. | ||
| * | ||
| * A confirmer asks the system of record. Three outcomes, and the difference | ||
| * between them is the whole point: | ||
| * | ||
| * - `confirmed` — the provider has it. Replay; never re-run. | ||
| * - `absent` — the provider does not have it, asked properly. Safe to run. | ||
| * - `unknown` — we could not find out (network down, no permission, the | ||
| * provider has no way to look it up). **This is not | ||
| * `absent`.** Treating "I don't know" as "it didn't happen" | ||
| * is precisely how a retry becomes a double charge, and it | ||
| * is the most common mistake in code that tries to do this. | ||
| * | ||
| * Everything here is built to make that third case impossible to lose by | ||
| * accident: it is a distinct value, it carries a reason, and nothing collapses | ||
| * it into a boolean. | ||
| */ | ||
| /** What the world says about one effect. */ | ||
| export type WorldState = "confirmed" | "absent" | "unknown"; | ||
| export interface Confirmation { | ||
| state: WorldState; | ||
| /** Provider's own id for the effect, when it has one. */ | ||
| ref?: string; | ||
| /** Provider-specific status string, verbatim — never interpreted here. */ | ||
| providerStatus?: string; | ||
| /** Why we could not tell. Present only when state is "unknown". */ | ||
| reason?: string; | ||
| } | ||
| /** | ||
| * A provider-specific confirmer. | ||
| * | ||
| * `lookup` receives whatever handle the caller stored at execution time (a | ||
| * message id, a payment intent id, an idempotency key) and reports what the | ||
| * provider says about it. | ||
| */ | ||
| export interface Confirmer { | ||
| /** Provider name, for receipts and the atlas. */ | ||
| readonly provider: string; | ||
| lookup(ref: string): Promise<Confirmation>; | ||
| } | ||
| export interface HttpConfirmerOptions { | ||
| provider: string; | ||
| /** Build the lookup URL for a given ref. */ | ||
| url: (ref: string) => string; | ||
| headers?: Record<string, string>; | ||
| /** Milliseconds before we give up and answer "unknown". */ | ||
| timeoutMs?: number; | ||
| fetchImpl?: typeof fetch; | ||
| /** | ||
| * Decide what a 200 body means. Return `undefined` to accept the default: | ||
| * a 200 means confirmed. Providers that return 200 with a "deleted" or | ||
| * "failed" status need this hook. | ||
| */ | ||
| interpret?: (body: unknown) => Confirmation | undefined; | ||
| } | ||
| /** | ||
| * The general shape: GET the resource, and map the response honestly. | ||
| * | ||
| * 404 is the only status treated as `absent`, and only because it is the one | ||
| * status that means "this identifier is not known here". A 401, 403, 429 or | ||
| * 500 tells us about our access or their health, nothing about the effect — | ||
| * those are `unknown`, loudly. | ||
| */ | ||
| export declare function httpConfirmer(opts: HttpConfirmerOptions): Confirmer; | ||
| /** | ||
| * Resend — the first adapter, because it is the path we run in production and | ||
| * the one that burned us: our own mailer once reported 16 emails as sent when | ||
| * none had verifiably left, because it trusted its own status field. | ||
| * | ||
| * `GET /emails/{id}` is a read, permitted on a sending-scoped key (unlike | ||
| * /domains, which 403s — a different permission scope, learned the hard way). | ||
| * | ||
| * MEASURED BEHAVIOUR: see docs/PROVIDER-ATLAS.md. Anything not measured is | ||
| * marked as claimed-not-measured there rather than asserted here. | ||
| */ | ||
| export declare function resendConfirmer(apiKey: string, fetchImpl?: typeof fetch): Confirmer; | ||
| /** | ||
| * Stripe — payment intents, looked up by id. | ||
| * | ||
| * Stripe additionally supports idempotency keys on creation, which is a | ||
| * different mechanism from confirmation: the key prevents a duplicate, the | ||
| * lookup tells you whether one exists. We use both, and never assume the first | ||
| * makes the second unnecessary. | ||
| */ | ||
| export declare function stripeConfirmer(apiKey: string, fetchImpl?: typeof fetch): Confirmer; | ||
| export interface ResolveOptions { | ||
| confirmer: Confirmer; | ||
| /** Provider handle recorded when the effect was executed. */ | ||
| ref: string; | ||
| } | ||
| export type Resolution = { | ||
| action: "replay"; | ||
| confirmation: Confirmation; | ||
| } | { | ||
| action: "execute"; | ||
| confirmation: Confirmation; | ||
| } | { | ||
| action: "escalate"; | ||
| confirmation: Confirmation; | ||
| why: string; | ||
| }; | ||
| /** | ||
| * The timeout resolver: given a handle, say what to do next. | ||
| * | ||
| * This is deliberately three-valued. Most implementations of this idea return | ||
| * a boolean and quietly turn `unknown` into `execute`, which is the double-fire | ||
| * bug wearing a helpful face. Here, not knowing escalates to a human — the only | ||
| * honest answer when the effect is irreversible. | ||
| */ | ||
| export declare function resolveAfterTimeout(opts: ResolveOptions): Promise<Resolution>; | ||
| //# sourceMappingURL=confirm.d.ts.map |
| {"version":3,"file":"confirm.d.ts","sourceRoot":"","sources":["../src/confirm.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,4CAA4C;AAC5C,MAAM,MAAM,UAAU,GAAG,WAAW,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE5D,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,UAAU,CAAC;IAClB,yDAAyD;IACzD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,SAAS;IACxB,iDAAiD;IACjD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;CAC5C;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,4CAA4C;IAC5C,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,2DAA2D;IAC3D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB;;;;OAIG;IACH,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,YAAY,GAAG,SAAS,CAAC;CACzD;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,oBAAoB,GAAG,SAAS,CA+CnE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,OAAO,KAAK,GAAG,SAAS,CAiBnF;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,OAAO,KAAK,GAAG,SAAS,CAcnF;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,SAAS,CAAC;IACrB,6DAA6D;IAC7D,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,MAAM,UAAU,GAClB;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,YAAY,EAAE,YAAY,CAAA;CAAE,GAChD;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,YAAY,EAAE,YAAY,CAAA;CAAE,GACjD;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,YAAY,EAAE,YAAY,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpE;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC,CAYnF"} |
+156
| /** | ||
| * World confirmation — asking the provider whether the effect actually exists. | ||
| * | ||
| * Everything else in this library reasons about our own records. That is the | ||
| * cheap half of the problem. The expensive half is the question an operator | ||
| * asks at 3am: *the call timed out — did it land?* Our database cannot answer | ||
| * that. It only knows what we did, not what arrived. | ||
| * | ||
| * A confirmer asks the system of record. Three outcomes, and the difference | ||
| * between them is the whole point: | ||
| * | ||
| * - `confirmed` — the provider has it. Replay; never re-run. | ||
| * - `absent` — the provider does not have it, asked properly. Safe to run. | ||
| * - `unknown` — we could not find out (network down, no permission, the | ||
| * provider has no way to look it up). **This is not | ||
| * `absent`.** Treating "I don't know" as "it didn't happen" | ||
| * is precisely how a retry becomes a double charge, and it | ||
| * is the most common mistake in code that tries to do this. | ||
| * | ||
| * Everything here is built to make that third case impossible to lose by | ||
| * accident: it is a distinct value, it carries a reason, and nothing collapses | ||
| * it into a boolean. | ||
| */ | ||
| /** | ||
| * The general shape: GET the resource, and map the response honestly. | ||
| * | ||
| * 404 is the only status treated as `absent`, and only because it is the one | ||
| * status that means "this identifier is not known here". A 401, 403, 429 or | ||
| * 500 tells us about our access or their health, nothing about the effect — | ||
| * those are `unknown`, loudly. | ||
| */ | ||
| export function httpConfirmer(opts) { | ||
| const doFetch = opts.fetchImpl ?? fetch; | ||
| const timeoutMs = opts.timeoutMs ?? 10_000; | ||
| return { | ||
| provider: opts.provider, | ||
| async lookup(ref) { | ||
| if (!ref) { | ||
| return { state: "unknown", reason: "no reference to look up — nothing was recorded at execution time" }; | ||
| } | ||
| let res; | ||
| try { | ||
| res = await doFetch(opts.url(ref), { | ||
| method: "GET", | ||
| headers: opts.headers ?? {}, | ||
| signal: AbortSignal.timeout(timeoutMs), | ||
| }); | ||
| } | ||
| catch (err) { | ||
| // A failed lookup is not a zero. We genuinely do not know. | ||
| const msg = err instanceof Error ? err.message : String(err); | ||
| return { state: "unknown", reason: `lookup failed: ${msg}` }; | ||
| } | ||
| if (res.status === 404) { | ||
| return { state: "absent", providerStatus: "404" }; | ||
| } | ||
| if (!res.ok) { | ||
| return { | ||
| state: "unknown", | ||
| providerStatus: String(res.status), | ||
| reason: `provider answered ${res.status} — this says nothing about whether the effect exists`, | ||
| }; | ||
| } | ||
| let body; | ||
| try { | ||
| body = await res.json(); | ||
| } | ||
| catch { | ||
| // A 200 we cannot parse still tells us the id resolved. | ||
| return { state: "confirmed", ref, providerStatus: "200" }; | ||
| } | ||
| const custom = opts.interpret?.(body); | ||
| if (custom) | ||
| return custom; | ||
| return { state: "confirmed", ref, providerStatus: "200" }; | ||
| }, | ||
| }; | ||
| } | ||
| /** | ||
| * Resend — the first adapter, because it is the path we run in production and | ||
| * the one that burned us: our own mailer once reported 16 emails as sent when | ||
| * none had verifiably left, because it trusted its own status field. | ||
| * | ||
| * `GET /emails/{id}` is a read, permitted on a sending-scoped key (unlike | ||
| * /domains, which 403s — a different permission scope, learned the hard way). | ||
| * | ||
| * MEASURED BEHAVIOUR: see docs/PROVIDER-ATLAS.md. Anything not measured is | ||
| * marked as claimed-not-measured there rather than asserted here. | ||
| */ | ||
| export function resendConfirmer(apiKey, fetchImpl) { | ||
| return httpConfirmer({ | ||
| provider: "resend", | ||
| url: (id) => `https://api.resend.com/emails/${encodeURIComponent(id)}`, | ||
| headers: { Authorization: `Bearer ${apiKey}` }, | ||
| fetchImpl, | ||
| interpret: (body) => { | ||
| const b = body; | ||
| const status = (b?.last_event ?? b?.status); | ||
| if (!status) | ||
| return undefined; | ||
| // Resend reports a lifecycle, not a boolean. "bounced" still means the | ||
| // send HAPPENED — the effect exists, it simply failed downstream. That | ||
| // distinction matters: re-sending a bounced message is a decision for a | ||
| // human, not something a retry should do silently. | ||
| return { state: "confirmed", ref: String(b?.id ?? ""), providerStatus: status }; | ||
| }, | ||
| }); | ||
| } | ||
| /** | ||
| * Stripe — payment intents, looked up by id. | ||
| * | ||
| * Stripe additionally supports idempotency keys on creation, which is a | ||
| * different mechanism from confirmation: the key prevents a duplicate, the | ||
| * lookup tells you whether one exists. We use both, and never assume the first | ||
| * makes the second unnecessary. | ||
| */ | ||
| export function stripeConfirmer(apiKey, fetchImpl) { | ||
| return httpConfirmer({ | ||
| provider: "stripe", | ||
| url: (id) => `https://api.stripe.com/v1/payment_intents/${encodeURIComponent(id)}`, | ||
| headers: { Authorization: `Bearer ${apiKey}` }, | ||
| fetchImpl, | ||
| interpret: (body) => { | ||
| const b = body; | ||
| const status = b?.status; | ||
| if (!status) | ||
| return undefined; | ||
| // canceled means the intent EXISTS and was cancelled — still confirmed. | ||
| return { state: "confirmed", ref: String(b?.id ?? ""), providerStatus: status }; | ||
| }, | ||
| }); | ||
| } | ||
| /** | ||
| * The timeout resolver: given a handle, say what to do next. | ||
| * | ||
| * This is deliberately three-valued. Most implementations of this idea return | ||
| * a boolean and quietly turn `unknown` into `execute`, which is the double-fire | ||
| * bug wearing a helpful face. Here, not knowing escalates to a human — the only | ||
| * honest answer when the effect is irreversible. | ||
| */ | ||
| export async function resolveAfterTimeout(opts) { | ||
| const c = await opts.confirmer.lookup(opts.ref); | ||
| if (c.state === "confirmed") | ||
| return { action: "replay", confirmation: c }; | ||
| if (c.state === "absent") | ||
| return { action: "execute", confirmation: c }; | ||
| // The reason explains why we cannot tell; the consequence explains what is | ||
| // at stake. A human deciding at 3am needs both, so both are always present. | ||
| const cause = c.reason ?? "the provider could not tell us whether this effect exists"; | ||
| return { | ||
| action: "escalate", | ||
| confirmation: c, | ||
| why: `${cause} — re-running may duplicate it, so this needs a human decision`, | ||
| }; | ||
| } | ||
| //# sourceMappingURL=confirm.js.map |
| {"version":3,"file":"confirm.js","sourceRoot":"","sources":["../src/confirm.ts"],"names":[],"mappings":"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"} |
+6
-2
| { | ||
| "name": "once-kernel", | ||
| "version": "0.5.0", | ||
| "version": "0.6.0", | ||
| "description": "Idempotency kernel for side-effecting operations. 1,000 racing callers, exactly one execution — proven, not asserted.", | ||
@@ -58,2 +58,6 @@ "keywords": [ | ||
| "default": "./dist/ledger.js" | ||
| }, | ||
| "./confirm": { | ||
| "types": "./dist/confirm.d.ts", | ||
| "default": "./dist/confirm.js" | ||
| } | ||
@@ -72,3 +76,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/ledger.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/confirm.test.ts", | ||
| "test:storm": "node --experimental-strip-types --no-warnings --test test/storm.test.ts", | ||
@@ -75,0 +79,0 @@ "test:all": "npm run test && npm run test:storm", |
+32
-0
@@ -166,2 +166,34 @@ # once-kernel | ||
| ### Ask the world, and keep the third answer | ||
| Your database knows what you did, not what arrived. After a timeout, only the | ||
| provider can say whether the effect exists. | ||
| ```ts | ||
| import { resendConfirmer, resolveAfterTimeout } from "once-kernel/confirm"; | ||
| const r = await resolveAfterTimeout({ | ||
| confirmer: resendConfirmer(process.env.RESEND_API_KEY!), | ||
| ref: storedMessageId, | ||
| }); | ||
| switch (r.action) { | ||
| case "replay": return stored; // provider has it — never re-run | ||
| case "execute": return send(); // provider asked, genuinely absent | ||
| case "escalate": alertHuman(r.why); // we could not find out | ||
| } | ||
| ``` | ||
| Three answers, not two. `unknown` — a 500, a timeout, a revoked key — is **not** | ||
| `absent`. Most implementations return a boolean and quietly turn "I could not | ||
| find out" into "it didn't happen", which is the double-charge bug wearing a | ||
| helpful face. Here it escalates instead, because re-running an irreversible | ||
| effect on a guess is the thing we are trying to prevent. | ||
| Only 404 means absent. A `bounced` email is **confirmed** — the send happened, | ||
| delivery failed afterwards; re-sending it is a human's decision, not a retry's. | ||
| Measured provider behaviour, with claims and measurements kept strictly apart: | ||
| [docs/PROVIDER-ATLAS.md](docs/PROVIDER-ATLAS.md). | ||
| ### Notarised receipts — evidence an outsider can check | ||
@@ -168,0 +200,0 @@ |
Network access
Supply chain riskThis module accesses the network.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
190224
10.54%40
11.11%2448
12.45%389
8.96%3
50%4
Infinity%