@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/README.md CHANGED
@@ -1,420 +1,66 @@
1
- ## **Logs narrate; IntentProof gives you proof.**
1
+ # intentproof-sdk-node
2
2
 
3
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>
6
4
 
7
- **IntentProof** is **auditable execution records** for actions that must be defensible—**intent** tied to what actually ran.
5
+ Node.js SDK for signing IntentProof `ExecutionEvent` records locally.
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.
7
+ ## Use
10
8
 
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
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@x.y.z
17
+ npm install @intentproof/sdk
54
18
  ```
55
19
 
56
- ## Quick start
57
-
58
- ```ts
59
- import { client } from "@intentproof/sdk";
20
+ Development in this repo:
60
21
 
61
- const refund = client.wrap(
62
- { intent: "Initiate refund", action: "stripe.refunds.create" },
63
- async (input) => stripe.refunds.create(input),
64
- );
22
+ ```bash
23
+ npm install
24
+ npm run build
25
+ npm test
65
26
  ```
66
27
 
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.
28
+ Conformance vectors live in
29
+ [`intentproof-spec`](https://github.com/IntentProof/intentproof-spec).
152
30
 
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
- );
31
+ ## Quick start
200
32
 
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
- );
33
+ ```typescript
34
+ import { configure, wrap, flush } from '@intentproof/sdk';
234
35
 
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
- );
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
- 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
- },
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
- 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();
47
+ await refund({ amount_cents: 4999 });
48
+ await flush();
348
49
  ```
349
50
 
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
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
- For **vulnerability reporting**, use this repository’s Security tab (private advisories).
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
- 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.
58
+ ## Support
367
59
 
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
- ```
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
- Apache-2.0 (see **`LICENSE`** at the repository root).
66
+ MIT see [LICENSE](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
+ }
@@ -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
+ }