@intentproof/sdk 0.1.4 → 0.2.0
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 +0 -25
- package/NOTICE +16 -0
- package/README.md +40 -408
- 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 +40 -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/LICENSE
CHANGED
|
@@ -174,28 +174,3 @@
|
|
|
174
174
|
of your accepting any such warranty or additional liability.
|
|
175
175
|
|
|
176
176
|
END OF TERMS AND CONDITIONS
|
|
177
|
-
|
|
178
|
-
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
-
|
|
180
|
-
To apply the Apache License to your work, attach the following
|
|
181
|
-
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
-
replaced with your own identifying information. (Don't include
|
|
183
|
-
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
-
comment syntax for the file format. We also recommend that a
|
|
185
|
-
file or class name and description of purpose be included on the
|
|
186
|
-
same "printed page" as the copyright notice for easier
|
|
187
|
-
identification within third-party archives.
|
|
188
|
-
|
|
189
|
-
Copyright 2026 The IntentProof Authors
|
|
190
|
-
|
|
191
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
-
you may not use this file except in compliance with the License.
|
|
193
|
-
You may obtain a copy of the License at
|
|
194
|
-
|
|
195
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
-
|
|
197
|
-
Unless required by applicable law or agreed to in writing, software
|
|
198
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
-
See the License for the specific language governing permissions and
|
|
201
|
-
limitations under the License.
|
package/NOTICE
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
IntentProof Node.js SDK
|
|
2
|
+
Copyright 2026 IntentProof, Inc.
|
|
3
|
+
|
|
4
|
+
This product includes software developed at IntentProof, Inc.
|
|
5
|
+
|
|
6
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
you may not use this file except in compliance with the License.
|
|
8
|
+
You may obtain a copy of the License at
|
|
9
|
+
|
|
10
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
|
|
12
|
+
Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
See the License for the specific language governing permissions and
|
|
16
|
+
limitations under the License.
|
package/README.md
CHANGED
|
@@ -1,420 +1,52 @@
|
|
|
1
|
-
|
|
1
|
+
# intentproof-sdk-node
|
|
2
2
|
|
|
3
|
-
|
|
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>
|
|
3
|
+
Node.js SDK for emitting signed `ExecutionEvent` records to IntentProof.
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
## Scope
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
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
|
|
45
|
-
|
|
46
|
-
## Install
|
|
47
|
-
|
|
48
|
-
**Package:** `@intentproof/sdk`.
|
|
49
|
-
|
|
50
|
-
Replace **`x.y.z`** with the package version you intend to pin.
|
|
51
|
-
|
|
52
|
-
```bash
|
|
53
|
-
npm install @intentproof/sdk@x.y.z
|
|
54
|
-
```
|
|
7
|
+
- `wrap(intent, action, fn)` instrumentation helper
|
|
8
|
+
- Correlation-id helpers
|
|
9
|
+
- Event signing and canonical serialization
|
|
10
|
+
- Local outbox support
|
|
11
|
+
- HTTP export to ingest when `INTENTPROOF_INGEST_URL` is set
|
|
55
12
|
|
|
56
13
|
## Quick start
|
|
57
14
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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.
|
|
152
|
-
|
|
153
|
-
**`captureInput`** / **`captureOutput`** trim each record to the fields you want in proof (refund id, amounts, message id)—not full vendor payloads.
|
|
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
|
-
);
|
|
200
|
-
|
|
201
|
-
const sendRefundReceipt = client.wrap(
|
|
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
|
-
);
|
|
234
|
-
|
|
235
|
-
await runWithCorrelationId("req_refund_ord_1042", async () => {
|
|
236
|
-
const refund = createRefund({
|
|
237
|
-
paymentIntentId: "pi_3SAMPLEabcdefghijklmnop",
|
|
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
|
-
);
|
|
15
|
+
1. Install deps: `npm install`
|
|
16
|
+
2. Build/test with project scripts.
|
|
17
|
+
3. Start local ingest (`intentproof local`) or set hosted ingest URL:
|
|
18
|
+
- `INTENTPROOF_INGEST_URL=http://127.0.0.1:9787` (appends `/v1/events`)
|
|
19
|
+
- or `INTENTPROOF_USE_LOCAL_INGEST=1` for the default local URL
|
|
20
|
+
4. For **hosted** ingest that requires bearer auth, set
|
|
21
|
+
`INTENTPROOF_INGEST_TOKEN` to your tenant ingest token (sent as
|
|
22
|
+
`Authorization: Bearer …` on export). Local loop ingest does not use this.
|
|
23
|
+
5. Call `flush()` before process exit to await in-flight exports.
|
|
24
|
+
|
|
25
|
+
## Local key and data directory
|
|
26
|
+
|
|
27
|
+
`configure()` requires an outbox database path through `dbPath`. That outbox
|
|
28
|
+
location is application-controlled and is not defaulted by the SDK.
|
|
29
|
+
|
|
30
|
+
If `configure()` is called without `dataDir`, the SDK stores its local signing
|
|
31
|
+
keypair at `~/.intentproof/sdk-node/keypair.json`. The keypair is reused across
|
|
32
|
+
process restarts so the same local SDK instance can continue signing a stable
|
|
33
|
+
event chain. Delete `~/.intentproof/sdk-node` to reset the default local SDK
|
|
34
|
+
identity.
|
|
35
|
+
|
|
36
|
+
Pass an explicit `dataDir` to isolate tests, demos, or applications that should
|
|
37
|
+
not use the default `~/.intentproof` tree:
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
configure({
|
|
41
|
+
dbPath: './intentproof-outbox.db',
|
|
42
|
+
dataDir: './.intentproof-sdk',
|
|
249
43
|
});
|
|
250
44
|
```
|
|
251
45
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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
|
-
|
|
302
|
-
When a capture **throws**, the record still carries **`status: "error"`** and **`error.message`** for proof of failure. **`captureError`** adds a small, JSON-safe **`output`** for dashboards (e.g. decline code) without pretending the business call succeeded.
|
|
303
|
-
|
|
304
|
-
```ts
|
|
305
|
-
const capturePayment = client.wrap(
|
|
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
|
-
},
|
|
318
|
-
);
|
|
319
|
-
|
|
320
|
-
try {
|
|
321
|
-
await capturePayment({ paymentIntentId: "pi_3SAMPLEabcdefghijklmnop" });
|
|
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();
|
|
348
|
-
```
|
|
349
|
-
|
|
350
|
-
```json
|
|
351
|
-
{
|
|
352
|
-
"intent": "HTTP test",
|
|
353
|
-
"action": "test.http",
|
|
354
|
-
"inputs": [],
|
|
355
|
-
"status": "ok",
|
|
356
|
-
"output": 42
|
|
357
|
-
}
|
|
358
|
-
```
|
|
359
|
-
|
|
360
|
-
---
|
|
361
|
-
|
|
362
|
-
## Security
|
|
363
|
-
|
|
364
|
-
For **vulnerability reporting**, use this repository’s Security tab (private advisories).
|
|
365
|
-
|
|
366
|
-
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.
|
|
367
|
-
|
|
368
|
-
- **Minimize payload:** Use **`redactKeys`**, **`maxDepth`** / **`maxKeys`** / **`maxStringLength`**, and narrow **`captureInput`** / **`captureOutput`** / **`captureError`** so proof records contain only what verifiers need.
|
|
369
|
-
- **Stacks:** Set **`includeErrorStack: false`** on the client (or per wrap) when traces must not leave your trust zone.
|
|
370
|
-
- **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.
|
|
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
|
-
```
|
|
46
|
+
When `intentproof local` is running, it imports
|
|
47
|
+
`~/.intentproof/sdk-node/keypair.json` if present so locally exported events can
|
|
48
|
+
verify without extra key-registration steps.
|
|
417
49
|
|
|
418
50
|
## License
|
|
419
51
|
|
|
420
|
-
Apache
|
|
52
|
+
Apache License 2.0 (`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
|
+
}
|