@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 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
- ## **Logs narrate; IntentProof gives you proof.**
1
+ # intentproof-sdk-node
2
2
 
3
- [![CI](https://github.com/IntentProof/intentproof-sdk-node/actions/workflows/ci.yml/badge.svg)](https://github.com/IntentProof/intentproof-sdk-node/actions/workflows/ci.yml)
4
- [![npm version](https://img.shields.io/npm/v/@intentproof/sdk)](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
- **IntentProof** is **auditable execution records** for actions that must be defensible—**intent** tied to what actually ran.
5
+ ## Scope
8
6
 
9
- **Wrap** the calls that matter; each invocation emits one **verifiable** **`ExecutionEvent`**, structured so intent and outcome can be **reconciled** with reality—not only observed.
10
-
11
- Observability captures what happened. **IntentProof** tells you whether it matched what was **meant to happen**.
12
-
13
- Every **`ExecutionEvent`** contains:
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
- ```ts
59
- import { client } from "@intentproof/sdk";
60
-
61
- const refund = client.wrap(
62
- { intent: "Initiate refund", action: "stripe.refunds.create" },
63
- async (input) => stripe.refunds.create(input),
64
- );
65
- ```
66
-
67
- 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.
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.
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
- 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
-
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-2.0 (see **`LICENSE`** at the repository root).
52
+ Apache License 2.0 (`LICENSE`).
@@ -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
+ }
@@ -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
+ }