@intentproof/sdk 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,16 +1,30 @@
1
- # @intentproof/sdk
1
+ ## **Logs narrate; IntentProof gives you proof.**
2
2
 
3
- **IntentProof** turns function calls into **verifiable records** of what your system **meant** to do and what **actually happened**—**intent**, **action**, and **outcome** on the same wire. That is not another log stream: logs narrate; **IntentProof** gives you **proof** you can reconcile, attest to, or feed a verifier, because every record is tied to a real invocation with structured inputs, a stable label pair, and a completed result or error snapshot.
3
+ Turn your function calls into **verifiable** execution records designed to be reconciled.
4
+ Observability captures what happened. **IntentProof** tells you whether it matched what was **meant to happen**.
4
5
 
5
- This package is the Node.js / TypeScript SDK: **`IntentProofClient.wrap`** is the bridge from your code to those records. You keep your functions; each call through the wrapper emits one canonical **`ExecutionEvent`** for export.
6
+ Every wrapped call emits one **`ExecutionEvent`** containing:
6
7
 
7
- ## What this SDK does
8
+ - **`intent`**: what this invocation was meant to prove
9
+ - **`action`**: the stable operation id for this step
10
+ - **`status`**: success or error
11
+ - **`inputs`** and **`output`**: what the runtime saw going in and coming out
8
12
 
9
- Each such invocation produces one **`ExecutionEvent`**: **`intent`** / **`action`** (what you declared this call represents), JSON-safe **`inputs`** and **`output`** (or an **`error`** snapshot for what occurred), **`startedAt`** / **`completedAt`**, **`durationMs`**, a unique **`id`**, optional **`correlationId`** (async context or wrap options), and merged **`attributes`**. Serialization is configurable (`snapshot`, redaction, custom capture hooks) so the proof stays bounded and policy-aware.
13
+ ## Why this matters
10
14
 
11
- Events are delivered to every configured **`Exporter`** (`export(event)`). Built-ins cover an in-memory ring buffer, HTTP ingest, and a bounded async queue. If an exporter throws or rejects, **`onExporterError`** is notified; the wrapped function’s return value or thrown error is unchangedyour runtime behavior stays authoritative; the record is best-effort delivery.
15
+ Modern systems—especially AI agentsdo not only compute; they act:
16
+ issuing refunds, sending emails, updating databases.
12
17
 
13
- The wire shape stays small and canonical so verifiers and ingest paths reason about **intent / action / outcome**, not ad hoc log lines or internal object graphs.
18
+ When something goes wrong, logs tell you what ran.
19
+ They don't tell you:
20
+
21
+ - what was supposed to happen
22
+ - whether all steps completed
23
+ - whether systems ended up in a consistent state
24
+
25
+ **IntentProof** exists to bridge that gap.
26
+
27
+ It records intent alongside execution so systems can be verified, not just observed.
14
28
 
15
29
  ## Requirements
16
30
 
@@ -24,124 +38,101 @@ npm install @intentproof/sdk
24
38
 
25
39
  ## Quick start
26
40
 
27
- Below, the object printed from **`getEvents()`** is the **proof artifact** for a single call: intent and action you chose at wrap time, inputs and output the runtime saw, plus timing and id.
28
-
29
- Use a **`MemoryExporter`** so you can inspect that record immediately (the default singleton client also uses memory, but you need your own instance to call **`getEvents()`**).
30
-
31
41
  ```ts
32
- import { createIntentProofClient, MemoryExporter } from "@intentproof/sdk";
33
-
34
- const memory = new MemoryExporter({ maxEvents: 50 });
35
- const client = createIntentProofClient({ exporters: [memory] });
42
+ import { client } from "@intentproof/sdk";
36
43
 
37
- const add = client.wrap(
38
- { intent: "demo", action: "math.add" },
39
- (a: number, b: number) => a + b,
44
+ const refund = client.wrap(
45
+ { intent: "Initiate refund", action: "stripe.refunds.create" },
46
+ async (input) => stripe.refunds.create(input),
40
47
  );
41
-
42
- const sum = add(2, 3);
43
- console.assert(sum === 5);
44
-
45
- const [event] = memory.getEvents();
46
- console.log(event);
47
48
  ```
48
49
 
49
- Calling **`add(2, 3)`** runs your original function and, after it returns, emits one event per exporter. A typical emitted object looks like this (values vary at runtime):
50
-
51
- ```json
52
- {
53
- "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
54
- "intent": "demo",
55
- "action": "math.add",
56
- "inputs": [2, 3],
57
- "status": "ok",
58
- "output": 5,
59
- "startedAt": "2026-05-02T12:00:00.000Z",
60
- "completedAt": "2026-05-02T12:00:00.003Z",
61
- "durationMs": 3
62
- }
63
- ```
64
-
65
- - **`id`** — Random UUID for this invocation.
66
- - **`inputs` / `output`** — Produced via **`snapshot()`** (see `SerializeOptions` / `WrapOptions`) unless you override with **`captureInput`** / **`captureOutput`**.
67
- - **`correlationId`** — Omitted here; present when async context or wrap options supply one (see examples below).
68
- - **`attributes`** — Omitted when empty; otherwise merged from client **`defaultAttributes`** and per-wrap **`attributes`**.
50
+ Each refund call emits one **`ExecutionEvent`** with the **`intent`** and **`action`** you chose, the **`inputs`** and **`output`** (or **`error`** + **`status: "error"`**), and timing fields—an execution record you can inspect, export, or verify later.
69
51
 
70
52
  ## `IntentProofClient` API
71
53
 
54
+
72
55
  | Member | Description |
73
56
  | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
74
57
  | **`constructor(config?)`** | Creates a client. Default exporters: a single **`MemoryExporter`** if you omit **`config.exporters`**. |
75
58
  | **`configure(config)`** | Re-applies **`IntentProofConfig`** fields (exporters, error hook, defaults, stack policy). |
76
59
  | **`wrap(options, fn)`** | Returns a function that records one **`ExecutionEvent`** per call (sync or async). **`options`** must satisfy **`assertWrapOptionsShape`** (`intent` / `action` non-empty strings, etc.). |
77
- | **`flush()`** | Awaits optional **`Exporter.flush`** on all exporters in parallel. |
78
- | **`shutdown()`** | Awaits **`Exporter.shutdown`** when defined, otherwise **`flush`**. |
79
- | **`getCorrelationId()`** | Returns the correlation id from **`AsyncLocalStorage`**, if any. |
80
- | **`withCorrelation(fn)`** | Runs **`fn`** with a **fresh UUID** as correlation id for nested wraps. |
60
+ | **`flush()`** | Awaits **`flush()`** on every **`Exporter`** that implements it, in parallel. |
61
+ | **`shutdown()`** | For each **`Exporter`**, awaits **`shutdown()`** if implemented, otherwise **`flush()`** if implemented. |
62
+ | **`getCorrelationId()`** | Returns the correlation ID from **`AsyncLocalStorage`**, if any. |
63
+ | **`withCorrelation(fn)`** | Runs **`fn`** with a **fresh UUID** as correlation ID for nested wraps. |
81
64
  | **`withCorrelation(id, fn)`** | Runs **`fn`** with **`id`** trimmed; blank / whitespace-only **`id`** falls back to a UUID. |
82
65
 
66
+
83
67
  ### Module-level helpers (same module as the client)
84
68
 
85
69
  These use the same async correlation store as **`IntentProofClient`** instances:
86
70
 
71
+
87
72
  | Export | Description |
88
73
  | -------------------------------------- | ---------------------------------------------------------------------- |
89
74
  | **`createIntentProofClient(config?)`** | New isolated client (tests, workers, multi-tenant). |
90
75
  | **`getIntentProofClient()`** | Lazy singleton used by **`client`**. |
91
76
  | **`client`** | Default singleton instance. |
92
77
  | **`getCorrelationId()`** | Same behavior as the instance method. |
93
- | **`runWithCorrelationId(id, fn)`** | Requires a **non-empty** correlation id after trim; throws if invalid. |
94
- | **`assertCorrelationId(id)`** | Runtime assertion for correlation id shape. |
78
+ | **`runWithCorrelationId(id, fn)`** | Requires a **non-empty** correlation ID after trim; throws if invalid. |
79
+ | **`assertCorrelationId(id)`** | Runtime assertion for correlation ID shape. |
95
80
  | **`assertWrapOptionsShape(options)`** | Runtime validation for **`WrapOptions`**. |
96
81
 
82
+
97
83
  ## `ExecutionEvent` fields
98
84
 
99
- | Field | Type | When present | Meaning |
100
- | ------------------- | ------------------------ | ------------ | -------------------------------------------------------------------------------------------------------------------------------- |
101
- | **`id`** | `string` | always | Unique event id (UUID). |
102
- | **`correlationId`** | `string` | optional | Request / trace id from context or wrap options. |
103
- | **`intent`** | `string` | always | Human-readable label for what this invocation is meant to prove (outcome, policy goal, or domain). |
104
- | **`action`** | `string` | always | Stable operation id for this step (often dotted or namespaced). |
105
- | **`inputs`** | `unknown` | always | JSON-safe snapshot of call arguments (default) or **`captureInput`** result. |
106
- | **`output`** | `unknown` | success | JSON-safe return value or **`captureOutput`** result. On **`status: "error"`**, set only if **`captureError`** returned a value. |
107
- | **`error`** | `ExecutionErrorSnapshot` | failure | **`name`**, **`message`**, optional **`stack`** (see **`includeErrorStack`**). |
108
- | **`status`** | `"ok" \| "error"` | always | Outcome of the wrapped invocation. |
109
- | **`startedAt`** | ISO string | always | Start timestamp. |
110
- | **`completedAt`** | ISO string | always | Completion timestamp. |
111
- | **`durationMs`** | `number` | always | Wall time between start and completion. |
112
- | **`attributes`** | plain record | optional | String / number / boolean values only; merged from client defaults and wrap options. |
85
+ | Field | Description |
86
+ | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
87
+ | **`id`** | Unique event id (UUID). |
88
+ | **`correlationId`** | Request or trace correlation ID when present—usually from context or **`WrapOptions`**. |
89
+ | **`intent`** | Human-readable label for what this invocation is meant to prove (outcome, policy goal, or domain). |
90
+ | **`action`** | Stable operation id for this step (often dotted or namespaced). |
91
+ | **`inputs`** | JSON-safe snapshot of call arguments (default) or **`captureInput`** result. |
92
+ | **`output`** | JSON-safe return value or **`captureOutput`** result on success. When **`status`** is **`"error"`**, set only if **`captureError`** returned a value. |
93
+ | **`error`** | On failure: **`name`**, **`message`**, and optional **`stack`** (see **`includeErrorStack`**). |
94
+ | **`status`** | **`"ok"`** if the wrapped call completed normally; **`"error"`** if it threw. |
95
+ | **`startedAt`** | Start time (ISO 8601). |
96
+ | **`completedAt`** | Completion time (ISO 8601). |
97
+ | **`durationMs`** | Wall time between start and completion, in milliseconds. |
98
+ | **`attributes`** | Optional plain record (string / number / boolean values only), merged from client defaults and wrap options. |
99
+
113
100
 
114
101
  ## `WrapOptions` and `IntentProofConfig`
115
102
 
116
103
  ### `WrapOptions` (passed to **`wrap`**)
117
104
 
105
+
118
106
  | Field | Description |
119
107
  | ---------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
120
108
  | **`intent`**, **`action`** | Required, non-empty after trim. |
121
- | **`correlationId`** | Optional; when set, non-empty after trim. Otherwise the active context id is used if any. |
109
+ | **`correlationId`** | Optional; when set, non-empty after trim. Otherwise the active correlation ID from context is used, if any. |
122
110
  | **`attributes`** | Per-invocation dimensions merged over **`defaultAttributes`**. |
123
111
  | **`captureInput`**, **`captureOutput`**, **`captureError`** | Optional hooks to replace default **`snapshot`** behavior for inputs, success output, or error-side extra **`output`**. |
124
112
  | **`includeErrorStack`** | When `false`, omit **`error.stack`** for this wrap (overrides client default). |
125
113
  | **`maxDepth`**, **`maxKeys`**, **`redactKeys`**, **`maxStringLength`** | Forwarded to **`snapshot`** for inputs and outputs (see **`SerializeOptions`** in types). |
126
114
 
115
+
127
116
  ### `IntentProofConfig` (constructor / **`configure`**)
128
117
 
129
- | Field | Description |
130
- | ----------------------- | ----------------------------------------------------------------------------------------------- |
131
- | **`exporters`** | Ordered list of **`Exporter`** instances; each receives every event. |
132
- | **`onExporterError`** | Called when **`export`** throws or returns a rejected promise. Defaults to **`console.error`**. |
133
- | **`defaultAttributes`** | Merged into every event’s **`attributes`** (wrap-specific attributes win on key collision). |
134
- | **`includeErrorStack`** | Default `true`; set `false` in production if stacks must not leave the trust zone. |
118
+
119
+ | Field | Description |
120
+ | ----------------------- | ---------------------------------------------------------------------------------------------------------------- |
121
+ | **`exporters`** | Ordered list of **`Exporter`** instances; each receives every **`ExecutionEvent`**. |
122
+ | **`onExporterError`** | Called when any exporter’s **`export()`** throws or returns a rejected promise. Defaults to **`console.error`**. |
123
+ | **`defaultAttributes`** | Merged into every event’s **`attributes`** (wrap-specific attributes win on key collision). |
124
+ | **`includeErrorStack`** | Default `true`; set `false` in production if stacks must not leave the trust zone. |
125
+
135
126
 
136
127
  ---
137
128
 
138
129
  ## Examples
139
130
 
140
- ### 1 — Refund saga: money back, customer notified, ops alerted (one correlation id)
131
+ ### 1 — Refund and customer receipt
141
132
 
142
- Support approves **order `ORD-1042`**. Your service runs one cohesive workflow: create the **Stripe refund**, email the customer a receipt, post to **Slack** so billing ops see it. **`runWithCorrelationId`** ties all three calls to **`req_refund_ord_1042`**. Each wrap has its own **`intent`** (the outcome you are proving for that step) and **`action`** (how it is done); **`correlationId`** is what stitches the saga together.
133
+ Support approves **order `ORD-1042`**. Your service creates the **Stripe refund**, then emails the customer a receipt. **`runWithCorrelationId`** ties both calls to **`req_refund_ord_1042`**. Each **`wrap`** defines its own **`intent`** (the outcome you are proving for that step) and **`action`** (how it is done); **`correlationId`** is what stitches them together.
143
134
 
144
- **`captureInput` / `captureOutput`** trim each record to the fields you want in proof (refund id, amounts, message id, Slack metadata)—not full vendor payloads.
135
+ **`captureInput`** / **`captureOutput`** trim each record to the fields you want in proof (refund id, amounts, message id)—not full vendor payloads.
145
136
 
146
137
  ```ts
147
138
  const createRefund = client.wrap(
@@ -221,35 +212,6 @@ const sendRefundReceipt = client.wrap(
221
212
  }) => ({ messageId: "msg_49401_sample", status: "queued" as const }),
222
213
  );
223
214
 
224
- const notifyOpsRefund = client.wrap(
225
- {
226
- intent: "Surface the completed refund to billing operations for review",
227
- action: "slack.operations.refund_posted",
228
- attributes: { channel: "slack", step: "notify_ops" },
229
- captureInput: (args) => {
230
- const [p] = args as [{ refundId: string; orderId: string; amountCents: number }];
231
- return {
232
- refundId: p.refundId,
233
- orderId: p.orderId,
234
- amountCents: p.amountCents,
235
- };
236
- },
237
- captureOutput: (result) => {
238
- const r = result as {
239
- ok: boolean;
240
- channel: string;
241
- ts: string;
242
- };
243
- return { ok: r.ok, channel: r.channel, ts: r.ts };
244
- },
245
- },
246
- (p: { refundId: string; orderId: string; amountCents: number }) => ({
247
- ok: true,
248
- channel: "#billing-alerts",
249
- ts: "1714648800.000100",
250
- }),
251
- );
252
-
253
215
  await runWithCorrelationId("req_refund_ord_1042", async () => {
254
216
  const refund = createRefund({
255
217
  paymentIntentId: "pi_3SAMPLEabcdefghijklmnop",
@@ -264,17 +226,10 @@ await runWithCorrelationId("req_refund_ord_1042", async () => {
264
226
  amountCents: refund.amountCents,
265
227
  }),
266
228
  );
267
- await Promise.resolve(
268
- notifyOpsRefund({
269
- refundId: refund.id,
270
- orderId: "ORD-1042",
271
- amountCents: refund.amountCents,
272
- }),
273
- );
274
229
  });
275
230
  ```
276
231
 
277
- Emitted events (same **`correlationId`** on each; distinct **`intent`** per step; **`id`** / timestamps omitted):
232
+ Emitted **`ExecutionEvent`** values (same **`correlationId`** on each; distinct **`intent`** per step; **`id`** / timestamps omitted):
278
233
 
279
234
  ```json
280
235
  [
@@ -318,28 +273,6 @@ Emitted events (same **`correlationId`** on each; distinct **`intent`** per step
318
273
  "channel": "email",
319
274
  "step": "notify_customer"
320
275
  }
321
- },
322
- {
323
- "correlationId": "req_refund_ord_1042",
324
- "intent": "Surface the completed refund to billing operations for review",
325
- "action": "slack.operations.refund_posted",
326
- "inputs": {
327
- "refundId": "re_3SAMPLEabcdefghijklmnop",
328
- "orderId": "ORD-1042",
329
- "amountCents": 4999
330
- },
331
- "status": "ok",
332
- "output": {
333
- "ok": true,
334
- "channel": "#billing-alerts",
335
- "ts": "1714648800.000100"
336
- },
337
- "attributes": {
338
- "service": "billing-api",
339
- "env": "test",
340
- "channel": "slack",
341
- "step": "notify_ops"
342
- }
343
276
  }
344
277
  ]
345
278
  ```
@@ -385,7 +318,7 @@ try {
385
318
  }
386
319
  ```
387
320
 
388
- ### 3 — Proof delivery over HTTP (same event shape)
321
+ ### 3 — Proof delivery over HTTP (same **`ExecutionEvent`** shape)
389
322
 
390
323
  **`HttpExporter`** POSTs the same **`ExecutionEvent`** your verifiers see in memory—here alongside **`MemoryExporter`** so tests can assert the wire without a real collector. The request uses **`credentials: "omit"`**; the body is **`{ intentproof: "1", event: … }`** (see exporter implementation). For authenticated collectors, pass **`headers`** (e.g. **`Authorization`**, API keys) — see [Security](#security).
391
324
 
@@ -408,9 +341,9 @@ runProbe();
408
341
 
409
342
  ## Security
410
343
 
411
- **IntentProof events are data you ship off-process.** Treat **`ExecutionEvent`** like structured logs or audit payloads: they can include PII, secrets, stack traces, and business identifiers depending on your **`snapshot`** / **`capture*`** hooks.
344
+ Every **`ExecutionEvent`** you emit is data you may ship off-process. Treat them like audit-grade execution records: they can include PII, secrets, stack traces, and business identifiers depending on your **`snapshot`** / **`capture*`** hooks.
412
345
 
413
- - **Minimize payload:** Use **`redactKeys`**, **`maxDepth` / `maxKeys` / `maxStringLength`**, and narrow **`captureInput` / `captureOutput` / `captureError`** so proof records contain only what verifiers need.
346
+ - **Minimize payload:** Use **`redactKeys`**, **`maxDepth`** / **`maxKeys`** / **`maxStringLength`**, and narrow **`captureInput`** / **`captureOutput`** / **`captureError`** so proof records contain only what verifiers need.
414
347
  - **Stacks:** Set **`includeErrorStack: false`** on the client (or per wrap) when traces must not leave your trust zone.
415
348
  - **HTTP ingest:** Keep collector **`url`** and any redirect behavior under **trusted configuration** (avoid SSRF if URLs were ever influenced by untrusted input). Prefer **HTTPS** and **short-lived credentials** end-to-end.
416
349
  - **`HttpExporter` auth:** Pass credentials in **`headers`** (for example **`Authorization: Bearer …`**, **`x-api-key`**, or whatever your collector expects). The SDK does **not** log header values; use short-lived tokens and scope them to ingest only.
@@ -431,7 +364,7 @@ Custom **`body`** serializers: if **`body(event)`** throws, **`HttpExporter`** n
431
364
 
432
365
  This repository is an npm workspace; the publishable package is [`packages/sdk`](packages/sdk).
433
366
 
434
- Requires **Node.js 22+** (see `.nvmrc` and workspace `engines`).
367
+ Requires **Node.js** 22 or newer (see `.nvmrc` and workspace `engines`).
435
368
 
436
369
  ```bash
437
370
  npm ci
@@ -440,4 +373,4 @@ npm run ci
440
373
 
441
374
  ## License
442
375
 
443
- Apache-2.0 (see `LICENSE` at the repository root and in the published npm package).
376
+ Apache-2.0 (see `LICENSE` at the repository root and in the published npm package).
package/dist/index.cjs CHANGED
@@ -759,7 +759,7 @@ var BoundedQueueExporter = class {
759
759
  };
760
760
 
761
761
  // src/index.ts
762
- var VERSION = "0.1.0";
762
+ var VERSION = "0.1.1";
763
763
  var client = getIntentProofClient();
764
764
  function createIntentProofClient(config) {
765
765
  return new IntentProofClient(config);
package/dist/index.js CHANGED
@@ -721,7 +721,7 @@ var BoundedQueueExporter = class {
721
721
  };
722
722
 
723
723
  // src/index.ts
724
- var VERSION = "0.1.0";
724
+ var VERSION = "0.1.1";
725
725
  var client = getIntentProofClient();
726
726
  function createIntentProofClient(config) {
727
727
  return new IntentProofClient(config);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentproof/sdk",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "TypeScript SDK for IntentProof",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {