@intentproof/sdk 0.1.4 → 0.2.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/LICENSE +21 -201
- package/README.md +37 -391
- package/dist/canon.d.ts +11 -0
- package/dist/canon.js +17 -0
- package/dist/client.d.ts +21 -0
- package/dist/client.js +114 -0
- package/dist/exporter.d.ts +12 -0
- package/dist/exporter.js +68 -0
- package/dist/index.d.ts +3 -524
- package/dist/index.js +22 -1119
- package/dist/instrumentation.d.ts +6 -0
- package/dist/instrumentation.js +129 -0
- package/dist/outbox.d.ts +11 -0
- package/dist/outbox.js +53 -0
- package/dist/signing.d.ts +6 -0
- package/dist/signing.js +55 -0
- package/package.json +38 -71
- package/dist/index.cjs +0 -1172
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -524
- package/dist/index.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,420 +1,66 @@
|
|
|
1
|
-
|
|
1
|
+
# intentproof-sdk-node
|
|
2
2
|
|
|
3
3
|
[](https://github.com/IntentProof/intentproof-sdk-node/actions/workflows/ci.yml)
|
|
4
|
-
[](https://www.npmjs.com/package/@intentproof/sdk)
|
|
5
|
-
<a href="https://github.com/IntentProof/intentproof-sdk-node/raw/main/conformance-certificate.json" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/badge/conformance_certificate-view-0366d6" alt="Conformance Certificate" /></a>
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
Node.js SDK for signing IntentProof `ExecutionEvent` records locally.
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
## Use
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
- **`intent`**: what this invocation was meant to prove
|
|
16
|
-
- **`action`**: the stable operation id for this step
|
|
17
|
-
- **`status`**: success or error
|
|
18
|
-
- **`inputs`** and **`output`**: what the runtime saw going in and coming out
|
|
19
|
-
|
|
20
|
-
## Why this matters
|
|
21
|
-
|
|
22
|
-
Modern systems—especially AI agents—do not only compute; they act:
|
|
23
|
-
issuing refunds, sending emails, updating databases.
|
|
24
|
-
|
|
25
|
-
When something goes wrong, logs tell you what ran.
|
|
26
|
-
They don't tell you:
|
|
27
|
-
|
|
28
|
-
- what was supposed to happen
|
|
29
|
-
- whether all steps completed
|
|
30
|
-
- whether systems ended up in a consistent state
|
|
31
|
-
|
|
32
|
-
**IntentProof** exists to bridge that gap.
|
|
33
|
-
|
|
34
|
-
It records intent alongside execution so systems can be verified, not just observed.
|
|
35
|
-
|
|
36
|
-
### Picture this:
|
|
37
|
-
|
|
38
|
-
It's 4:47 on a Friday. A customer insists the critical action never happened. Support sees scattered traces; engineering sees green checks; finance asks for **one** clean chain: what was **supposed** to occur, what **did** occur, and whether the outcome is **complete**.
|
|
39
|
-
|
|
40
|
-
Ordinary telemetry shows that *something ran*. It rarely ships an **auditable story** you can hand to someone who doesn't read your codebase. **IntentProof** exists for when the question stops being "what was logged?" and starts being **"prove it."**
|
|
41
|
-
|
|
42
|
-
## Requirements
|
|
43
|
-
|
|
44
|
-
- **Node.js** 22 or newer
|
|
9
|
+
- `wrap(intent, action, fn)` instrumentation
|
|
10
|
+
- Ed25519 signing and JCS canonicalization
|
|
11
|
+
- SQLite outbox for durable local capture
|
|
12
|
+
- Export events to your app or bundle pipeline
|
|
45
13
|
|
|
46
14
|
## Install
|
|
47
15
|
|
|
48
|
-
**Package:** `@intentproof/sdk`.
|
|
49
|
-
|
|
50
|
-
Replace **`x.y.z`** with the package version you intend to pin.
|
|
51
|
-
|
|
52
16
|
```bash
|
|
53
|
-
npm install @intentproof/sdk
|
|
17
|
+
npm install @intentproof/sdk
|
|
54
18
|
```
|
|
55
19
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
```ts
|
|
59
|
-
import { client } from "@intentproof/sdk";
|
|
20
|
+
Development in this repo:
|
|
60
21
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
22
|
+
```bash
|
|
23
|
+
npm install
|
|
24
|
+
npm run build
|
|
25
|
+
npm test
|
|
65
26
|
```
|
|
66
27
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
## Reference
|
|
70
|
-
|
|
71
|
-
Detailed tables for the client API, emitted events, configuration, and related exports.
|
|
72
|
-
|
|
73
|
-
### `IntentProofClient` API
|
|
74
|
-
|
|
75
|
-
| Member | Description |
|
|
76
|
-
| ------ | ----------- |
|
|
77
|
-
| **`constructor(config?)`** | Creates a client. Default exporters: a single **`MemoryExporter`** if you omit **`config.exporters`**. |
|
|
78
|
-
| **`configure(config)`** | Re-applies **`IntentProofConfig`** fields (exporters, error hook, defaults, stack policy). |
|
|
79
|
-
| **`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.). |
|
|
80
|
-
| **`flush()`** | Awaits **`flush()`** on every **`Exporter`** that implements it, in parallel. |
|
|
81
|
-
| **`shutdown()`** | For each **`Exporter`**, awaits **`shutdown()`** if implemented, otherwise **`flush()`** if implemented. |
|
|
82
|
-
| **`getCorrelationId()`** | Returns the correlation ID from **`AsyncLocalStorage`**, if any. |
|
|
83
|
-
| **`withCorrelation(fn)`** | Runs **`fn`** with a **fresh UUID** as correlation ID for nested wraps. |
|
|
84
|
-
| **`withCorrelation(id, fn)`** | Runs **`fn`** with **`id`** trimmed; blank / whitespace-only **`id`** falls back to a UUID. |
|
|
85
|
-
|
|
86
|
-
#### Module-level helpers (same module as the client)
|
|
87
|
-
|
|
88
|
-
These use the same async correlation store as **`IntentProofClient`** instances:
|
|
89
|
-
|
|
90
|
-
| Export | Description |
|
|
91
|
-
| ------ | ----------- |
|
|
92
|
-
| **`createIntentProofClient(config?)`** | New isolated client (tests, workers, multi-tenant). |
|
|
93
|
-
| **`getIntentProofClient()`** | Lazy singleton used by **`client`**. |
|
|
94
|
-
| **`client`** | Default singleton instance. |
|
|
95
|
-
| **`getCorrelationId()`** | Same behavior as the instance method. |
|
|
96
|
-
| **`runWithCorrelationId(id, fn)`** | Requires a **non-empty** correlation ID after trim; throws if invalid. |
|
|
97
|
-
| **`assertCorrelationId(id)`** | Runtime assertion for correlation ID shape. |
|
|
98
|
-
| **`assertWrapOptionsShape(options)`** | Runtime validation for **`WrapOptions`**. |
|
|
99
|
-
|
|
100
|
-
### `ExecutionEvent` fields
|
|
101
|
-
|
|
102
|
-
| Field | Description |
|
|
103
|
-
| ----- | ----------- |
|
|
104
|
-
| **`id`** | Unique event id (UUID). |
|
|
105
|
-
| **`correlationId`** | Request or trace correlation ID when present—usually from context or **`WrapOptions`**. |
|
|
106
|
-
| **`intent`** | Human-readable label for what this invocation is meant to prove (outcome, policy goal, or domain). |
|
|
107
|
-
| **`action`** | Stable operation id for this step (often dotted or namespaced). |
|
|
108
|
-
| **`inputs`** | JSON-safe snapshot of call arguments (default) or **`captureInput`** result. |
|
|
109
|
-
| **`output`** | JSON-safe return value or **`captureOutput`** result on success. When **`status`** is **`"error"`**, set only if **`captureError`** returned a value. |
|
|
110
|
-
| **`error`** | On failure: **`name`**, **`message`**, and optional **`stack`** (see **`includeErrorStack`**). |
|
|
111
|
-
| **`status`** | **`"ok"`** if the wrapped call completed normally; **`"error"`** if it threw. |
|
|
112
|
-
| **`startedAt`** | Start time (ISO 8601). |
|
|
113
|
-
| **`completedAt`** | Completion time (ISO 8601). |
|
|
114
|
-
| **`durationMs`** | Wall time between start and completion, in milliseconds. |
|
|
115
|
-
| **`attributes`** | Optional plain record (string / number / boolean values only), merged from client defaults and wrap options. |
|
|
116
|
-
|
|
117
|
-
### `WrapOptions` and `IntentProofConfig`
|
|
118
|
-
|
|
119
|
-
#### `WrapOptions` (passed to **`wrap`**)
|
|
120
|
-
|
|
121
|
-
| Field | Description |
|
|
122
|
-
| ----- | ----------- |
|
|
123
|
-
| **`intent`**, **`action`** | Required, non-empty after trim. |
|
|
124
|
-
| **`correlationId`** | Optional; when set, non-empty after trim. Otherwise the active correlation ID from context is used, if any. |
|
|
125
|
-
| **`attributes`** | Per-invocation dimensions merged over **`defaultAttributes`**. |
|
|
126
|
-
| **`captureInput`**, **`captureOutput`**, **`captureError`** | Optional hooks to replace default **`snapshot`** behavior for inputs, success output, or error-side extra **`output`**. |
|
|
127
|
-
| **`includeErrorStack`** | When `false`, omit **`error.stack`** for this wrap (overrides client default). |
|
|
128
|
-
| **`maxDepth`**, **`maxKeys`**, **`redactKeys`**, **`maxStringLength`** | Forwarded to **`snapshot`** for inputs and outputs (see **`SerializeOptions`** in types). |
|
|
129
|
-
|
|
130
|
-
#### `IntentProofConfig` (constructor / **`configure`**)
|
|
131
|
-
|
|
132
|
-
| Field | Description |
|
|
133
|
-
| ----- | ----------- |
|
|
134
|
-
| **`exporters`** | Ordered list of **`Exporter`** instances; each receives every **`ExecutionEvent`**. |
|
|
135
|
-
| **`onExporterError`** | Called when any exporter’s **`export()`** throws or returns a rejected promise. Defaults to **`console.error`**. |
|
|
136
|
-
| **`defaultAttributes`** | Merged into every event’s **`attributes`** (wrap-specific attributes win on key collision). |
|
|
137
|
-
| **`includeErrorStack`** | Default `true`; set `false` in production if stacks must not leave the trust zone. |
|
|
138
|
-
|
|
139
|
-
### Related exports
|
|
140
|
-
|
|
141
|
-
- **`MemoryExporter`**, **`HttpExporter`**, **`BoundedQueueExporter`** — Delivery implementations; each implements **`Exporter`**.
|
|
142
|
-
- **`snapshot`** — Same JSON-safe serializer the client uses internally, if you build custom tooling.
|
|
143
|
-
- **`VERSION`** — Package version string injected at build time.
|
|
144
|
-
|
|
145
|
-
---
|
|
146
|
-
|
|
147
|
-
## Examples
|
|
148
|
-
|
|
149
|
-
### 1 — Refund and customer receipt
|
|
150
|
-
|
|
151
|
-
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.
|
|
28
|
+
Conformance vectors live in
|
|
29
|
+
[`intentproof-spec`](https://github.com/IntentProof/intentproof-spec).
|
|
152
30
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
JSON on the wire uses **camelCase**; TypeScript **`WrapOptions`** use the same camelCase names (e.g. **`captureInput`**).
|
|
156
|
-
|
|
157
|
-
```ts
|
|
158
|
-
const createRefund = client.wrap(
|
|
159
|
-
{
|
|
160
|
-
intent: "Return captured funds to the customer's original card network",
|
|
161
|
-
action: "stripe.refund.create",
|
|
162
|
-
attributes: { vendor: "stripe", step: "refund_money" },
|
|
163
|
-
captureInput: (args) => {
|
|
164
|
-
const [input] = args as [
|
|
165
|
-
{
|
|
166
|
-
paymentIntentId: string;
|
|
167
|
-
amountCents: number;
|
|
168
|
-
reason?: "requested_by_customer" | "duplicate";
|
|
169
|
-
},
|
|
170
|
-
];
|
|
171
|
-
return {
|
|
172
|
-
paymentIntentId: input.paymentIntentId,
|
|
173
|
-
amountCents: input.amountCents,
|
|
174
|
-
reason: input.reason,
|
|
175
|
-
};
|
|
176
|
-
},
|
|
177
|
-
captureOutput: (result) => {
|
|
178
|
-
const r = result as {
|
|
179
|
-
id: string;
|
|
180
|
-
status: "succeeded";
|
|
181
|
-
amountCents: number;
|
|
182
|
-
};
|
|
183
|
-
return {
|
|
184
|
-
refundId: r.id,
|
|
185
|
-
status: r.status,
|
|
186
|
-
amountCents: r.amountCents,
|
|
187
|
-
};
|
|
188
|
-
},
|
|
189
|
-
},
|
|
190
|
-
(input: {
|
|
191
|
-
paymentIntentId: string;
|
|
192
|
-
amountCents: number;
|
|
193
|
-
reason?: "requested_by_customer" | "duplicate";
|
|
194
|
-
}) => ({
|
|
195
|
-
id: "re_3SAMPLEabcdefghijklmnop",
|
|
196
|
-
status: "succeeded" as const,
|
|
197
|
-
amountCents: input.amountCents,
|
|
198
|
-
}),
|
|
199
|
-
);
|
|
31
|
+
## Quick start
|
|
200
32
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
intent: "Deliver a customer-visible refund confirmation for the ledger entry",
|
|
204
|
-
action: "email.customer.refund_receipt",
|
|
205
|
-
attributes: { channel: "email", step: "notify_customer" },
|
|
206
|
-
captureInput: (args) => {
|
|
207
|
-
const [p] = args as [
|
|
208
|
-
{
|
|
209
|
-
customerId: string;
|
|
210
|
-
orderId: string;
|
|
211
|
-
refundId: string;
|
|
212
|
-
amountCents: number;
|
|
213
|
-
},
|
|
214
|
-
];
|
|
215
|
-
return {
|
|
216
|
-
customerId: p.customerId,
|
|
217
|
-
orderId: p.orderId,
|
|
218
|
-
refundId: p.refundId,
|
|
219
|
-
amountCents: p.amountCents,
|
|
220
|
-
};
|
|
221
|
-
},
|
|
222
|
-
captureOutput: (result) => {
|
|
223
|
-
const r = result as { messageId: string; status: "queued" };
|
|
224
|
-
return { messageId: r.messageId, status: r.status };
|
|
225
|
-
},
|
|
226
|
-
},
|
|
227
|
-
(p: {
|
|
228
|
-
customerId: string;
|
|
229
|
-
orderId: string;
|
|
230
|
-
refundId: string;
|
|
231
|
-
amountCents: number;
|
|
232
|
-
}) => ({ messageId: "msg_49401_sample", status: "queued" as const }),
|
|
233
|
-
);
|
|
33
|
+
```typescript
|
|
34
|
+
import { configure, wrap, flush } from '@intentproof/sdk';
|
|
234
35
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
amountCents: 4999,
|
|
239
|
-
reason: "requested_by_customer",
|
|
240
|
-
});
|
|
241
|
-
await Promise.resolve(
|
|
242
|
-
sendRefundReceipt({
|
|
243
|
-
customerId: "cus_SAMPLEabcdefghijkl",
|
|
244
|
-
orderId: "ORD-1042",
|
|
245
|
-
refundId: refund.id,
|
|
246
|
-
amountCents: refund.amountCents,
|
|
247
|
-
}),
|
|
248
|
-
);
|
|
36
|
+
configure({
|
|
37
|
+
dbPath: './intentproof-outbox.db',
|
|
38
|
+
dataDir: './.intentproof-sdk',
|
|
249
39
|
});
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
Emitted **`ExecutionEvent`** values (same **`correlationId`** on each; distinct **`intent`** per step; **`id`** / timestamps omitted):
|
|
253
|
-
|
|
254
|
-
```json
|
|
255
|
-
[
|
|
256
|
-
{
|
|
257
|
-
"correlationId": "req_refund_ord_1042",
|
|
258
|
-
"intent": "Return captured funds to the customer's original card network",
|
|
259
|
-
"action": "stripe.refund.create",
|
|
260
|
-
"inputs": {
|
|
261
|
-
"paymentIntentId": "pi_3SAMPLEabcdefghijklmnop",
|
|
262
|
-
"amountCents": 4999,
|
|
263
|
-
"reason": "requested_by_customer"
|
|
264
|
-
},
|
|
265
|
-
"status": "ok",
|
|
266
|
-
"output": {
|
|
267
|
-
"refundId": "re_3SAMPLEabcdefghijklmnop",
|
|
268
|
-
"status": "succeeded",
|
|
269
|
-
"amountCents": 4999
|
|
270
|
-
},
|
|
271
|
-
"attributes": {
|
|
272
|
-
"service": "billing-api",
|
|
273
|
-
"env": "test",
|
|
274
|
-
"vendor": "stripe",
|
|
275
|
-
"step": "refund_money"
|
|
276
|
-
}
|
|
277
|
-
},
|
|
278
|
-
{
|
|
279
|
-
"correlationId": "req_refund_ord_1042",
|
|
280
|
-
"intent": "Deliver a customer-visible refund confirmation for the ledger entry",
|
|
281
|
-
"action": "email.customer.refund_receipt",
|
|
282
|
-
"inputs": {
|
|
283
|
-
"customerId": "cus_SAMPLEabcdefghijkl",
|
|
284
|
-
"orderId": "ORD-1042",
|
|
285
|
-
"refundId": "re_3SAMPLEabcdefghijklmnop",
|
|
286
|
-
"amountCents": 4999
|
|
287
|
-
},
|
|
288
|
-
"status": "ok",
|
|
289
|
-
"output": { "messageId": "msg_49401_sample", "status": "queued" },
|
|
290
|
-
"attributes": {
|
|
291
|
-
"service": "billing-api",
|
|
292
|
-
"env": "test",
|
|
293
|
-
"channel": "email",
|
|
294
|
-
"step": "notify_customer"
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
]
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
### 2 — Payment failure with operator metadata (`captureError`)
|
|
301
40
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
{
|
|
307
|
-
intent: "Capture authorized funds",
|
|
308
|
-
action: "stripe.payment_intent.capture",
|
|
309
|
-
captureInput: (args) => {
|
|
310
|
-
const [{ paymentIntentId }] = args as [{ paymentIntentId: string }];
|
|
311
|
-
return { paymentIntentId };
|
|
312
|
-
},
|
|
313
|
-
captureError: () => ({ code: "card_declined", retryable: false }),
|
|
314
|
-
},
|
|
315
|
-
async (_input: { paymentIntentId: string }) => {
|
|
316
|
-
throw new Error("Your card was declined.");
|
|
317
|
-
},
|
|
41
|
+
const refund = wrap(
|
|
42
|
+
'Return funds to the customer',
|
|
43
|
+
'payments.refund.execute',
|
|
44
|
+
async (input) => ({ id: 're_123' }),
|
|
318
45
|
);
|
|
319
46
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
} catch {
|
|
323
|
-
/* card declined — expected */
|
|
324
|
-
}
|
|
325
|
-
```
|
|
326
|
-
|
|
327
|
-
```json
|
|
328
|
-
{
|
|
329
|
-
"intent": "Capture authorized funds",
|
|
330
|
-
"action": "stripe.payment_intent.capture",
|
|
331
|
-
"inputs": { "paymentIntentId": "pi_3SAMPLEabcdefghijklmnop" },
|
|
332
|
-
"status": "error",
|
|
333
|
-
"error": {
|
|
334
|
-
"name": "Error",
|
|
335
|
-
"message": "Your card was declined."
|
|
336
|
-
},
|
|
337
|
-
"output": { "code": "card_declined", "retryable": false }
|
|
338
|
-
}
|
|
339
|
-
```
|
|
340
|
-
|
|
341
|
-
### 3 — Proof delivery over HTTP (same **`ExecutionEvent`** shape)
|
|
342
|
-
|
|
343
|
-
**`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 omits ambient credentials; the body is **`{ "intentproof": "1", "event": … }`** (see exporter implementation). For authenticated collectors, pass **`headers`** (e.g. **`Authorization`**, API keys) — see the Security section above.
|
|
344
|
-
|
|
345
|
-
```ts
|
|
346
|
-
const runProbe = client.wrap({ intent: "HTTP test", action: "test.http" }, () => 42);
|
|
347
|
-
runProbe();
|
|
47
|
+
await refund({ amount_cents: 4999 });
|
|
48
|
+
await flush();
|
|
348
49
|
```
|
|
349
50
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
"intent": "HTTP test",
|
|
353
|
-
"action": "test.http",
|
|
354
|
-
"inputs": [],
|
|
355
|
-
"status": "ok",
|
|
356
|
-
"output": 42
|
|
357
|
-
}
|
|
358
|
-
```
|
|
359
|
-
|
|
360
|
-
---
|
|
361
|
-
|
|
362
|
-
## Security
|
|
51
|
+
Signing keys default to `~/.intentproof/sdk-node/keypair.json` when `dataDir`
|
|
52
|
+
is omitted. Delete that directory to reset the local identity.
|
|
363
53
|
|
|
364
|
-
|
|
54
|
+
Optional: run `intentproof local` from
|
|
55
|
+
[`intentproof-tools`](https://github.com/IntentProof/intentproof-tools) for a
|
|
56
|
+
loopback dev ingest — not required for offline verification.
|
|
365
57
|
|
|
366
|
-
|
|
58
|
+
## Support
|
|
367
59
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
- **`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.
|
|
372
|
-
- **Runtime surface:** This package targets **Node**; if you wrap code in a browser, treat the ingest endpoint and headers as you would any cross-origin credential (CORS, CSP, token storage policies are your app’s responsibility).
|
|
373
|
-
- **Delivery semantics:** Exporter failures invoke **`onExporterError`** and do **not** roll back the wrapped function’s side effects—design compensating controls if you need strict “delivered exactly once” guarantees.
|
|
374
|
-
|
|
375
|
-
Custom **`body`** serializers: if **`body(event)`** throws, **`HttpExporter`** notifies **`onError`** and falls back to the same **JSON envelope** path as the default serializer (full event, then a partial envelope, then a minimal `eventSerializeFailed` payload) so **`export()`** still completes and **`fetch`** runs when possible.
|
|
376
|
-
|
|
377
|
-
---
|
|
378
|
-
|
|
379
|
-
## Canonical specification (`intentproof-spec`)
|
|
380
|
-
|
|
381
|
-
**Shared pins and terminology** (`INTENTPROOF_SPEC_ROOT`, **`intentproofSpecCommit`**, script names) are documented in the **`intentproof-spec`** repository (`CONTRIBUTING.md`, Terminology).
|
|
382
|
-
|
|
383
|
-
**`intentproof-spec`** holds normative schemas, golden **`execution_event_cases.jsonl`**, and the canonical **`spec-conformance.sh`** toolchain (Vitest/Jest-style harness code used by the spec lives there too).
|
|
384
|
-
|
|
385
|
-
- **Version pin:** **`intentproofSpecVersion`** and **`intentproofSpecCommit`** in the root **`package.json`** and **`packages/sdk/package.json`** match **`spec.json`** and the spec **`HEAD`** checkout; **`scripts/check-sdk-spec-pin.sh`** enforces this before conformance.
|
|
386
|
-
|
|
387
|
-
- **CI:** every push/PR checks out this SDK plus **`intentproof-spec`** and runs **`scripts/spec-conformance.sh`** (pin check + full oracle; see `.github/workflows/ci.yml`). The **`sdk`** job sets **`INTENTPROOF_SPEC_ROOT`** so **`packages/sdk`** Vitest imports the spec **`sdk_test_harness`**—golden **`execution_event_cases.jsonl`** plus **`MemoryExporter`** **`validateExecutionEvent`** smoke (`spec_conformance.integration.test.ts`).
|
|
388
|
-
- **Repo-root certificates:** each run uploads **`conformance-report.json`** and **`conformance-certificate.json`** as workflow artifacts; after a green default-branch push, the conformance GitHub App commits the same files at the repo root when they differ from **`main`**.
|
|
389
|
-
- **Local:** clone `intentproof-spec` **next to** this repository (`../intentproof-spec`), then:
|
|
390
|
-
|
|
391
|
-
```bash
|
|
392
|
-
npm run spec:conformance
|
|
393
|
-
```
|
|
394
|
-
|
|
395
|
-
Or set `INTENTPROOF_SPEC_ROOT` to your spec checkout and run `bash scripts/spec-conformance.sh`.
|
|
396
|
-
|
|
397
|
-
- **Generated fingerprint metadata:** schema codegen writes **`packages/sdk/src/generated/spec_fingerprint.json`** (spec version, generator version, per-schema SHA-256, aggregate hash). Validate/update generated artifacts with:
|
|
398
|
-
|
|
399
|
-
```bash
|
|
400
|
-
bash scripts/verify-generated-types.sh
|
|
401
|
-
```
|
|
402
|
-
|
|
403
|
-
- **No handwritten model types:** **`scripts/check-no-handwritten-model-types.sh`** delegates to the shared **`intentproof-spec`** checker. It is wired into **`npm run ci`**, CI, and release, and fails if schema model/type declarations appear outside **`packages/sdk/src/generated`** or if the bridge aliases in **`packages/sdk/src/types.ts`** stop mapping to generated types.
|
|
404
|
-
|
|
405
|
-
---
|
|
406
|
-
|
|
407
|
-
## Project development
|
|
408
|
-
|
|
409
|
-
Contributing and shared **`intentproof-spec`** terminology: see **`CONTRIBUTING.md`**.
|
|
410
|
-
|
|
411
|
-
Layout: **npm workspace** (`package.json` **`workspaces`**, publishable package **`packages/sdk`**). Requires **Node.js** 22 or newer (see `.nvmrc` and workspace **`engines`**). Release history: **`CHANGELOG.md`**.
|
|
412
|
-
|
|
413
|
-
```bash
|
|
414
|
-
npm ci
|
|
415
|
-
npm run ci
|
|
416
|
-
```
|
|
60
|
+
[GitHub Issues](https://github.com/IntentProof/intentproof-sdk-node/issues) —
|
|
61
|
+
see [CONTRIBUTING.md](CONTRIBUTING.md). Security reports:
|
|
62
|
+
`security@intentproof.io` or a private GitHub Security Advisory.
|
|
417
63
|
|
|
418
64
|
## License
|
|
419
65
|
|
|
420
|
-
|
|
66
|
+
MIT — see [LICENSE](LICENSE).
|
package/dist/canon.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonicalize returns the RFC 8785 (JCS) canonical JSON string for the
|
|
3
|
+
* given value. This is the canonical form used by all IntentProof signing
|
|
4
|
+
* and verifying paths so that bytes hashed or signed are deterministic
|
|
5
|
+
* across language implementations (Node, Go, Python).
|
|
6
|
+
*
|
|
7
|
+
* The output is stable for objects, arrays, strings, numbers, booleans,
|
|
8
|
+
* and null, with object keys sorted by UTF-16 code unit order and numbers
|
|
9
|
+
* formatted per ES6 Number.prototype.toString.
|
|
10
|
+
*/
|
|
11
|
+
export declare function canonicalizeIntentProof(value: unknown): string;
|
package/dist/canon.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.canonicalizeIntentProof = canonicalizeIntentProof;
|
|
4
|
+
const json_canonicalize_1 = require("json-canonicalize");
|
|
5
|
+
/**
|
|
6
|
+
* Canonicalize returns the RFC 8785 (JCS) canonical JSON string for the
|
|
7
|
+
* given value. This is the canonical form used by all IntentProof signing
|
|
8
|
+
* and verifying paths so that bytes hashed or signed are deterministic
|
|
9
|
+
* across language implementations (Node, Go, Python).
|
|
10
|
+
*
|
|
11
|
+
* The output is stable for objects, arrays, strings, numbers, booleans,
|
|
12
|
+
* and null, with object keys sorted by UTF-16 code unit order and numbers
|
|
13
|
+
* formatted per ES6 Number.prototype.toString.
|
|
14
|
+
*/
|
|
15
|
+
function canonicalizeIntentProof(value) {
|
|
16
|
+
return (0, json_canonicalize_1.canonicalize)(value);
|
|
17
|
+
}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SDK runtime configuration and shared state.
|
|
3
|
+
*/
|
|
4
|
+
import { HttpExporter } from './exporter';
|
|
5
|
+
import { Outbox } from './outbox';
|
|
6
|
+
export declare const SDK_VERSION = "node@0.2.0";
|
|
7
|
+
export declare function configure(options: {
|
|
8
|
+
dbPath: string;
|
|
9
|
+
tenantId?: string;
|
|
10
|
+
dataDir?: string;
|
|
11
|
+
ingestUrl?: string;
|
|
12
|
+
redactKeys?: string[];
|
|
13
|
+
}): void;
|
|
14
|
+
export declare function flush(): Promise<void>;
|
|
15
|
+
export declare function getOutbox(): Outbox;
|
|
16
|
+
export declare function getInstanceId(): string;
|
|
17
|
+
export declare function getTenantId(): string;
|
|
18
|
+
export declare function getPublicKey(): Promise<Uint8Array>;
|
|
19
|
+
export declare function getPrivateKey(): Uint8Array;
|
|
20
|
+
export declare function getExporter(): HttpExporter | null;
|
|
21
|
+
export declare function getRedactKeys(): string[];
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SDK runtime configuration and shared state.
|
|
4
|
+
*/
|
|
5
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
6
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.SDK_VERSION = void 0;
|
|
10
|
+
exports.configure = configure;
|
|
11
|
+
exports.flush = flush;
|
|
12
|
+
exports.getOutbox = getOutbox;
|
|
13
|
+
exports.getInstanceId = getInstanceId;
|
|
14
|
+
exports.getTenantId = getTenantId;
|
|
15
|
+
exports.getPublicKey = getPublicKey;
|
|
16
|
+
exports.getPrivateKey = getPrivateKey;
|
|
17
|
+
exports.getExporter = getExporter;
|
|
18
|
+
exports.getRedactKeys = getRedactKeys;
|
|
19
|
+
const fs_1 = __importDefault(require("fs"));
|
|
20
|
+
const path_1 = __importDefault(require("path"));
|
|
21
|
+
const ulid_1 = require("ulid");
|
|
22
|
+
const exporter_1 = require("./exporter");
|
|
23
|
+
const outbox_1 = require("./outbox");
|
|
24
|
+
const signing_1 = require("./signing");
|
|
25
|
+
exports.SDK_VERSION = 'node@0.2.0';
|
|
26
|
+
let instancePrivateKey;
|
|
27
|
+
let instanceId;
|
|
28
|
+
let tenantId;
|
|
29
|
+
let outbox;
|
|
30
|
+
let exporter = null;
|
|
31
|
+
let dataDir;
|
|
32
|
+
let redactKeys = [];
|
|
33
|
+
function ensureDir(dir) {
|
|
34
|
+
if (!fs_1.default.existsSync(dir)) {
|
|
35
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function loadOrCreateKeypair(dir) {
|
|
39
|
+
const keyPath = path_1.default.join(dir, 'keypair.json');
|
|
40
|
+
if (fs_1.default.existsSync(keyPath)) {
|
|
41
|
+
const raw = fs_1.default.readFileSync(keyPath, 'utf-8');
|
|
42
|
+
fs_1.default.chmodSync(keyPath, 0o600);
|
|
43
|
+
return JSON.parse(raw);
|
|
44
|
+
}
|
|
45
|
+
const privateKey = require('crypto').randomBytes(32);
|
|
46
|
+
const kp = {
|
|
47
|
+
privateKey: Buffer.from(privateKey).toString('base64'),
|
|
48
|
+
instanceId: 'inst_' + (0, ulid_1.ulid)(),
|
|
49
|
+
};
|
|
50
|
+
fs_1.default.writeFileSync(keyPath, JSON.stringify(kp, null, 2), { mode: 0o600 });
|
|
51
|
+
return kp;
|
|
52
|
+
}
|
|
53
|
+
function configure(options) {
|
|
54
|
+
if (exporter) {
|
|
55
|
+
void exporter.flush();
|
|
56
|
+
}
|
|
57
|
+
dataDir =
|
|
58
|
+
options.dataDir ||
|
|
59
|
+
path_1.default.join(require('os').homedir(), '.intentproof', 'sdk-node');
|
|
60
|
+
ensureDir(dataDir);
|
|
61
|
+
const kp = loadOrCreateKeypair(dataDir);
|
|
62
|
+
instancePrivateKey = (0, signing_1.loadPrivateKey)(kp.privateKey);
|
|
63
|
+
instanceId = kp.instanceId;
|
|
64
|
+
tenantId =
|
|
65
|
+
options.tenantId ||
|
|
66
|
+
process.env.INTENTPROOF_TENANT_ID?.trim() ||
|
|
67
|
+
'tnt_default';
|
|
68
|
+
redactKeys = options.redactKeys ?? [];
|
|
69
|
+
outbox = new outbox_1.Outbox(options.dbPath);
|
|
70
|
+
const ingestURL = (0, exporter_1.resolveIngestURL)(options.ingestUrl);
|
|
71
|
+
exporter = ingestURL ? new exporter_1.HttpExporter(ingestURL) : null;
|
|
72
|
+
}
|
|
73
|
+
async function flush() {
|
|
74
|
+
if (exporter) {
|
|
75
|
+
await exporter.flush();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function getOutbox() {
|
|
79
|
+
if (!outbox) {
|
|
80
|
+
throw new Error('SDK not configured: call configure() before getOutbox()');
|
|
81
|
+
}
|
|
82
|
+
return outbox;
|
|
83
|
+
}
|
|
84
|
+
function getInstanceId() {
|
|
85
|
+
if (!instanceId) {
|
|
86
|
+
throw new Error('SDK not configured: call configure() before getInstanceId()');
|
|
87
|
+
}
|
|
88
|
+
return instanceId;
|
|
89
|
+
}
|
|
90
|
+
function getTenantId() {
|
|
91
|
+
if (!tenantId) {
|
|
92
|
+
throw new Error('SDK not configured: call configure() before getTenantId()');
|
|
93
|
+
}
|
|
94
|
+
return tenantId;
|
|
95
|
+
}
|
|
96
|
+
function getPublicKey() {
|
|
97
|
+
if (!instancePrivateKey) {
|
|
98
|
+
return Promise.reject(new Error('SDK not configured: call configure() before getPublicKey()'));
|
|
99
|
+
}
|
|
100
|
+
const { getPublicKeyAsync } = require('@noble/ed25519');
|
|
101
|
+
return getPublicKeyAsync(instancePrivateKey);
|
|
102
|
+
}
|
|
103
|
+
function getPrivateKey() {
|
|
104
|
+
if (!instancePrivateKey) {
|
|
105
|
+
throw new Error('SDK not configured: call configure() before getPrivateKey()');
|
|
106
|
+
}
|
|
107
|
+
return instancePrivateKey;
|
|
108
|
+
}
|
|
109
|
+
function getExporter() {
|
|
110
|
+
return exporter;
|
|
111
|
+
}
|
|
112
|
+
function getRedactKeys() {
|
|
113
|
+
return [...redactKeys];
|
|
114
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Posts signed ExecutionEvents to a configured ingest URL when set.
|
|
3
|
+
*/
|
|
4
|
+
export declare function resolveIngestURL(explicit?: string): string | null;
|
|
5
|
+
export declare function ingestRequestHeaders(): Record<string, string>;
|
|
6
|
+
export declare class HttpExporter {
|
|
7
|
+
private readonly ingestURL;
|
|
8
|
+
private readonly pending;
|
|
9
|
+
constructor(ingestURL: string);
|
|
10
|
+
enqueue(event: Record<string, unknown>): void;
|
|
11
|
+
flush(): Promise<void>;
|
|
12
|
+
}
|