@nexart/ai-execution 0.1.0 → 0.3.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/README.md +146 -232
- package/dist/archive.d.ts +4 -0
- package/dist/archive.d.ts.map +1 -0
- package/dist/archive.js +28 -0
- package/dist/archive.js.map +1 -0
- package/dist/attest.d.ts +3 -0
- package/dist/attest.d.ts.map +1 -0
- package/dist/attest.js +79 -0
- package/dist/attest.js.map +1 -0
- package/dist/certify.d.ts +3 -0
- package/dist/certify.d.ts.map +1 -0
- package/dist/certify.js +27 -0
- package/dist/certify.js.map +1 -0
- package/dist/errors.d.ts +11 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +21 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/providers/anthropic.d.ts +22 -0
- package/dist/providers/anthropic.d.ts.map +1 -0
- package/dist/providers/anthropic.js +61 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/wrap.d.ts +7 -0
- package/dist/providers/wrap.d.ts.map +1 -0
- package/dist/providers/wrap.js +28 -0
- package/dist/providers/wrap.js.map +1 -0
- package/dist/run.d.ts +14 -0
- package/dist/run.d.ts.map +1 -0
- package/dist/run.js +62 -0
- package/dist/run.js.map +1 -0
- package/dist/snapshot.d.ts.map +1 -1
- package/dist/snapshot.js +15 -2
- package/dist/snapshot.js.map +1 -1
- package/dist/types.d.ts +100 -0
- package/dist/types.d.ts.map +1 -1
- package/fixtures/golden/golden-001.json +29 -0
- package/fixtures/golden/golden-002.json +38 -0
- package/fixtures/golden/golden-003.json +36 -0
- package/fixtures/golden/golden-004.json +29 -0
- package/fixtures/golden/golden-005.json +29 -0
- package/fixtures/vectors/vector-002.chain.json +101 -0
- package/package.json +15 -3
- package/dist/__tests__/fixtures.test.d.ts +0 -2
- package/dist/__tests__/fixtures.test.d.ts.map +0 -1
- package/dist/__tests__/fixtures.test.js +0 -37
- package/dist/__tests__/fixtures.test.js.map +0 -1
- package/dist/__tests__/vectors.test.d.ts +0 -2
- package/dist/__tests__/vectors.test.d.ts.map +0 -1
- package/dist/__tests__/vectors.test.js +0 -261
- package/dist/__tests__/vectors.test.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
# @nexart/ai-execution v0.
|
|
1
|
+
# @nexart/ai-execution v0.3.0
|
|
2
2
|
|
|
3
3
|
Tamper-evident records and Certified Execution Records (CER) for AI operations.
|
|
4
4
|
|
|
5
|
+
## Why Not Just Store Logs?
|
|
6
|
+
|
|
7
|
+
Logs tell you what happened. CERs prove it. A log entry can be edited, truncated, or fabricated after the fact with no way to detect it. A CER bundle is cryptographically sealed: any modification — to the input, output, parameters, or ordering — invalidates the certificate hash. If you need to demonstrate to an auditor, regulator, or downstream system that a specific AI model produced a specific output for a specific input with specific parameters, logs are insufficient. CERs provide the tamper-evident chain of custody that logs cannot.
|
|
8
|
+
|
|
5
9
|
## What This Does
|
|
6
10
|
|
|
7
11
|
This package creates integrity records for AI executions. Every time you call an AI model, it captures:
|
|
@@ -11,10 +15,17 @@ This package creates integrity records for AI executions. Every time you call an
|
|
|
11
15
|
- The exact parameters used (temperature, model, etc.)
|
|
12
16
|
- SHA-256 hashes of everything for tamper detection
|
|
13
17
|
|
|
14
|
-
These records can be verified
|
|
18
|
+
These records can be verified offline to prove the execution happened as recorded.
|
|
15
19
|
|
|
16
20
|
**Important:** This does NOT promise that an AI model will produce the same output twice. LLMs are not deterministic. This package provides **integrity and auditability** — proof that a specific input produced a specific output at a specific time with specific parameters.
|
|
17
21
|
|
|
22
|
+
## Compatibility Guarantees
|
|
23
|
+
|
|
24
|
+
- **v0.1.0 and v0.2.0 bundles verify forever.** Any CER bundle produced by any prior version will pass `verify()` in v0.3.0 and all future versions.
|
|
25
|
+
- **Hashing rules are frozen for `cer.ai.execution.v1`.** The canonicalization, SHA-256 computation, and certificate hash inputs (bundleType, version, createdAt, snapshot) are unchanged.
|
|
26
|
+
- **New optional snapshot fields** (runId, stepId, stepIndex, etc.) default to undefined and are excluded from legacy snapshots. They participate in the certificate hash only when present.
|
|
27
|
+
- **Canonicalization is frozen for v1.** Number-to-string conversion uses `JSON.stringify()`, which is consistent across JavaScript engines but does not implement RFC 8785 (JCS) for edge cases like `-0`. If stricter canonicalization is required, it will ship as a new bundle type (`cer.ai.execution.v2`), never as a modification to v1.
|
|
28
|
+
|
|
18
29
|
## Installation
|
|
19
30
|
|
|
20
31
|
```bash
|
|
@@ -23,87 +34,92 @@ npm install @nexart/ai-execution
|
|
|
23
34
|
|
|
24
35
|
## Quick Start
|
|
25
36
|
|
|
26
|
-
###
|
|
37
|
+
### Single Decision (3 lines)
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { certifyDecision } from '@nexart/ai-execution';
|
|
41
|
+
|
|
42
|
+
const cer = certifyDecision({
|
|
43
|
+
provider: 'openai',
|
|
44
|
+
model: 'gpt-4o',
|
|
45
|
+
prompt: 'Summarize.',
|
|
46
|
+
input: userQuery,
|
|
47
|
+
output: llmResponse,
|
|
48
|
+
parameters: { temperature: 0.7, maxTokens: 1024, topP: null, seed: null },
|
|
49
|
+
});
|
|
50
|
+
console.log(cer.certificateHash); // "sha256:..."
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Manual Snapshot + Seal
|
|
27
54
|
|
|
28
55
|
```typescript
|
|
29
|
-
import { createSnapshot,
|
|
56
|
+
import { createSnapshot, sealCer, verify } from '@nexart/ai-execution';
|
|
30
57
|
|
|
31
|
-
// Create a snapshot of an AI execution
|
|
32
58
|
const snapshot = createSnapshot({
|
|
33
59
|
executionId: 'exec-001',
|
|
34
60
|
provider: 'openai',
|
|
35
61
|
model: 'gpt-4o',
|
|
36
|
-
modelVersion: '2026-01-01',
|
|
37
62
|
prompt: 'You are a helpful assistant.',
|
|
38
63
|
input: 'What is 2+2?',
|
|
39
|
-
parameters: {
|
|
40
|
-
temperature: 0.7,
|
|
41
|
-
maxTokens: 1024,
|
|
42
|
-
topP: null,
|
|
43
|
-
seed: null,
|
|
44
|
-
},
|
|
64
|
+
parameters: { temperature: 0.7, maxTokens: 1024, topP: null, seed: null },
|
|
45
65
|
output: 'The answer is 4.',
|
|
46
66
|
});
|
|
47
67
|
|
|
48
|
-
// Verify the snapshot hashes are correct
|
|
49
|
-
const result = verifySnapshot(snapshot);
|
|
50
|
-
console.log(result.ok); // true
|
|
51
|
-
|
|
52
|
-
// Seal into a Certified Execution Record
|
|
53
68
|
const bundle = sealCer(snapshot);
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
// Verify the entire bundle
|
|
57
|
-
const cerResult = verifyCer(bundle);
|
|
58
|
-
console.log(cerResult.ok); // true
|
|
69
|
+
const result = verify(bundle);
|
|
70
|
+
console.log(result.ok); // true
|
|
59
71
|
```
|
|
60
72
|
|
|
61
|
-
###
|
|
73
|
+
### Agentic Multi-Step Workflow
|
|
62
74
|
|
|
63
75
|
```typescript
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
seed: 42,
|
|
75
|
-
},
|
|
76
|
-
output: { entities: ['John', 'Paris'], count: 2 },
|
|
76
|
+
import { RunBuilder } from '@nexart/ai-execution';
|
|
77
|
+
|
|
78
|
+
const run = new RunBuilder({ runId: 'analysis-run', workflowId: 'data-pipeline' });
|
|
79
|
+
|
|
80
|
+
run.step({
|
|
81
|
+
provider: 'openai', model: 'gpt-4o',
|
|
82
|
+
prompt: 'Plan the analysis.',
|
|
83
|
+
input: 'Analyze Q1 sales data.',
|
|
84
|
+
output: 'I will: 1) load data, 2) compute totals, 3) summarize.',
|
|
85
|
+
parameters: { temperature: 0.3, maxTokens: 512, topP: null, seed: null },
|
|
77
86
|
});
|
|
78
|
-
```
|
|
79
87
|
|
|
80
|
-
|
|
88
|
+
run.step({
|
|
89
|
+
provider: 'openai', model: 'gpt-4o',
|
|
90
|
+
prompt: 'Execute step 1.',
|
|
91
|
+
input: 'Load and total Q1 data.',
|
|
92
|
+
output: 'Total revenue: $1.2M.',
|
|
93
|
+
parameters: { temperature: 0.3, maxTokens: 512, topP: null, seed: null },
|
|
94
|
+
});
|
|
81
95
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
96
|
+
const summary = run.finalize();
|
|
97
|
+
// { runId, stepCount: 2, steps: [...], finalStepHash: "sha256:..." }
|
|
98
|
+
```
|
|
85
99
|
|
|
86
|
-
|
|
100
|
+
### Attest to Canonical Node
|
|
87
101
|
|
|
88
102
|
```typescript
|
|
89
|
-
import {
|
|
103
|
+
import { certifyDecision, attest } from '@nexart/ai-execution';
|
|
90
104
|
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
parameters: {
|
|
96
|
-
temperature: 0.7,
|
|
97
|
-
maxTokens: 256,
|
|
98
|
-
},
|
|
105
|
+
const cer = certifyDecision({ /* ... */ });
|
|
106
|
+
const proof = await attest(cer, {
|
|
107
|
+
nodeUrl: 'https://node.nexart.io',
|
|
108
|
+
apiKey: process.env.NEXART_API_KEY!,
|
|
99
109
|
});
|
|
100
|
-
|
|
101
|
-
console.log(result.output); // "The capital of France is Paris."
|
|
102
|
-
console.log(result.snapshot.inputHash); // "sha256:..."
|
|
103
|
-
console.log(result.bundle.certificateHash); // "sha256:..."
|
|
110
|
+
console.log(proof.attestationId);
|
|
104
111
|
```
|
|
105
112
|
|
|
106
|
-
|
|
113
|
+
Attestation verifies internal integrity only. It does not re-run the model. The node confirms the bundle's hashes are consistent and records it in the proof ledger.
|
|
114
|
+
|
|
115
|
+
### Archive (Export / Import)
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
import { exportCer, importCer } from '@nexart/ai-execution';
|
|
119
|
+
|
|
120
|
+
const json = exportCer(bundle); // canonical JSON string
|
|
121
|
+
const restored = importCer(json); // parse + verify (throws on tamper)
|
|
122
|
+
```
|
|
107
123
|
|
|
108
124
|
## Snapshot Format (ai.execution.v1)
|
|
109
125
|
|
|
@@ -123,46 +139,16 @@ Requires `OPENAI_API_KEY` environment variable or `apiKey` parameter.
|
|
|
123
139
|
| `modelVersion` | Optional | `string \| null` | Defaults to `null` |
|
|
124
140
|
| `parameters.topP` | Optional | `number \| null` | Defaults to `null` |
|
|
125
141
|
| `parameters.seed` | Optional | `number \| null` | Defaults to `null` |
|
|
126
|
-
| `sdkVersion` | Optional | `string \| null` | Defaults to
|
|
142
|
+
| `sdkVersion` | Optional | `string \| null` | Defaults to `"0.3.0"` |
|
|
127
143
|
| `appId` | Optional | `string \| null` | Defaults to `null` |
|
|
144
|
+
| `runId` | Optional | `string \| null` | Workflow run ID |
|
|
145
|
+
| `stepId` | Optional | `string \| null` | Step identifier within a run |
|
|
146
|
+
| `stepIndex` | Optional | `number \| null` | 0-based step position |
|
|
147
|
+
| `workflowId` | Optional | `string \| null` | Workflow template ID |
|
|
148
|
+
| `conversationId` | Optional | `string \| null` | Conversation/session ID |
|
|
149
|
+
| `prevStepHash` | Optional | `string \| null` | certificateHash of previous step |
|
|
128
150
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
| Field | Value |
|
|
132
|
-
|---|---|
|
|
133
|
-
| `type` | `"ai.execution.v1"` |
|
|
134
|
-
| `protocolVersion` | `"1.2.0"` |
|
|
135
|
-
| `executionSurface` | `"ai"` |
|
|
136
|
-
| `inputHash` | SHA-256 of input |
|
|
137
|
-
| `outputHash` | SHA-256 of output |
|
|
138
|
-
|
|
139
|
-
### Example Snapshot
|
|
140
|
-
|
|
141
|
-
```json
|
|
142
|
-
{
|
|
143
|
-
"type": "ai.execution.v1",
|
|
144
|
-
"protocolVersion": "1.2.0",
|
|
145
|
-
"executionSurface": "ai",
|
|
146
|
-
"executionId": "exec-001",
|
|
147
|
-
"timestamp": "2026-02-12T00:00:00.000Z",
|
|
148
|
-
"provider": "openai",
|
|
149
|
-
"model": "gpt-4o",
|
|
150
|
-
"modelVersion": "2026-01-01",
|
|
151
|
-
"prompt": "You are a helpful assistant.",
|
|
152
|
-
"input": "What is 2+2?",
|
|
153
|
-
"inputHash": "sha256:...",
|
|
154
|
-
"parameters": {
|
|
155
|
-
"temperature": 0.7,
|
|
156
|
-
"maxTokens": 1024,
|
|
157
|
-
"topP": null,
|
|
158
|
-
"seed": null
|
|
159
|
-
},
|
|
160
|
-
"output": "The answer is 4.",
|
|
161
|
-
"outputHash": "sha256:...",
|
|
162
|
-
"sdkVersion": "0.1.0",
|
|
163
|
-
"appId": null
|
|
164
|
-
}
|
|
165
|
-
```
|
|
151
|
+
Auto-generated fields (set by `createSnapshot`, do not set manually): `type`, `protocolVersion`, `executionSurface`, `inputHash`, `outputHash`.
|
|
166
152
|
|
|
167
153
|
## CER Bundle Format
|
|
168
154
|
|
|
@@ -173,181 +159,109 @@ The following fields are **auto-generated** by `createSnapshot` and must not be
|
|
|
173
159
|
"createdAt": "2026-02-12T00:00:00.000Z",
|
|
174
160
|
"version": "0.1",
|
|
175
161
|
"snapshot": { ... },
|
|
176
|
-
"meta": {
|
|
177
|
-
"source": "my-app",
|
|
178
|
-
"tags": ["production"]
|
|
179
|
-
}
|
|
162
|
+
"meta": { "source": "my-app", "tags": ["production"] }
|
|
180
163
|
}
|
|
181
164
|
```
|
|
182
165
|
|
|
183
166
|
### Certificate Hash Computation
|
|
184
167
|
|
|
185
|
-
The `certificateHash` is SHA-256 of the UTF-8 bytes of the canonical JSON of
|
|
168
|
+
The `certificateHash` is SHA-256 of the UTF-8 bytes of the canonical JSON of exactly: `{ bundleType, version, createdAt, snapshot }`. `meta` is excluded. Key-ordering is recursive. This computation is identical across all SDK versions.
|
|
186
169
|
|
|
187
|
-
|
|
188
|
-
{ bundleType, version, createdAt, snapshot }
|
|
189
|
-
```
|
|
170
|
+
## Attestation
|
|
190
171
|
|
|
191
|
-
|
|
192
|
-
- Key-ordering rules apply **recursively** — every nested object within `snapshot` is also sorted lexicographically by key.
|
|
193
|
-
- The bytes being hashed are the UTF-8 encoding of the canonical JSON string (no BOM, no trailing newline).
|
|
172
|
+
Endpoint: `POST {nodeUrl}/api/attest`
|
|
194
173
|
|
|
195
|
-
|
|
174
|
+
- Authorization: `Bearer {apiKey}`
|
|
175
|
+
- Body: the full CER bundle as JSON
|
|
176
|
+
- Returns: `AttestationResult` with `attestationId`, `nodeRuntimeHash`, `certificateHash`, `protocolVersion`
|
|
177
|
+
- Default timeout: 10 seconds (configurable via `timeoutMs`)
|
|
178
|
+
- Validates: response `certificateHash` matches submitted bundle; all hashes in `sha256:<64hex>` format
|
|
179
|
+
- Throws: `CerAttestationError` on mismatch, network error, timeout, or HTTP error
|
|
196
180
|
|
|
197
|
-
|
|
198
|
-
- **Object/Array values**: SHA-256 of UTF-8 bytes of canonical JSON (sorted keys, no whitespace, stable array order)
|
|
199
|
-
- All hashes use the format `sha256:<hex>` (lowercase hex, 64 characters)
|
|
181
|
+
Attestation verifies internal integrity only. It does not re-run the model or validate the correctness of the AI output.
|
|
200
182
|
|
|
201
183
|
## Canonical JSON Constraints
|
|
202
184
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
1. **Object keys** are sorted lexicographically (Unicode codepoint order) at every nesting level.
|
|
206
|
-
2. **No whitespace** — no spaces, tabs, or newlines between tokens.
|
|
207
|
-
3. **Array order is preserved** — arrays are never re-sorted.
|
|
208
|
-
4. **`null`** is valid and serialized as `null`.
|
|
209
|
-
5. **Numbers** must be finite. `NaN`, `Infinity`, and `-Infinity` are **rejected** (throw).
|
|
210
|
-
6. **`undefined`** values in object properties are **omitted** (the key is dropped).
|
|
211
|
-
7. **`BigInt`**, **functions**, and **`Symbol`** are **not valid** JSON types and are **rejected** (throw).
|
|
212
|
-
8. Strings are JSON-escaped (e.g. `"` → `\"`).
|
|
213
|
-
|
|
214
|
-
These constraints ensure that the same logical value always produces the same byte sequence, which is essential for hash stability across implementations and languages.
|
|
215
|
-
|
|
216
|
-
## Interoperability (Test Vectors)
|
|
217
|
-
|
|
218
|
-
Cross-language implementations **must** match the test vectors exactly to be considered compatible. Vectors are committed as JSON fixtures at:
|
|
219
|
-
|
|
220
|
-
```
|
|
221
|
-
packages/ai-execution/fixtures/vectors/
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
### Vector 001
|
|
225
|
-
|
|
226
|
-
**Input snapshot** (`vector-001.snapshot.json`):
|
|
227
|
-
|
|
228
|
-
```json
|
|
229
|
-
{
|
|
230
|
-
"type": "ai.execution.v1",
|
|
231
|
-
"protocolVersion": "1.2.0",
|
|
232
|
-
"executionSurface": "ai",
|
|
233
|
-
"executionId": "vec-001",
|
|
234
|
-
"timestamp": "2026-02-12T00:00:00.000Z",
|
|
235
|
-
"provider": "openai",
|
|
236
|
-
"model": "gpt-4o",
|
|
237
|
-
"modelVersion": "2026-01-01",
|
|
238
|
-
"prompt": "You are a helpful assistant.",
|
|
239
|
-
"input": "What is 2+2?",
|
|
240
|
-
"inputHash": "sha256:52cb6b5e4a038af1756708f98afb718a08c75b87b2f03dbee4dd9c8139c15c5e",
|
|
241
|
-
"parameters": {
|
|
242
|
-
"temperature": 0.7,
|
|
243
|
-
"maxTokens": 1024,
|
|
244
|
-
"topP": null,
|
|
245
|
-
"seed": null
|
|
246
|
-
},
|
|
247
|
-
"output": "The answer is 4.",
|
|
248
|
-
"outputHash": "sha256:ae758477f843049bd252ceb5498aa33f190326589ee92cbe5a1ab563f54bc05b",
|
|
249
|
-
"sdkVersion": "0.1.0",
|
|
250
|
-
"appId": "vector-test"
|
|
251
|
-
}
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
**Expected hashes** (`vector-001.expected.json`):
|
|
255
|
-
|
|
256
|
-
| Hash | Value |
|
|
257
|
-
|---|---|
|
|
258
|
-
| `inputHash` | `sha256:52cb6b5e4a038af1756708f98afb718a08c75b87b2f03dbee4dd9c8139c15c5e` |
|
|
259
|
-
| `outputHash` | `sha256:ae758477f843049bd252ceb5498aa33f190326589ee92cbe5a1ab563f54bc05b` |
|
|
260
|
-
| `certificateHash` | `sha256:86275d60d088483eefaf0bd31d79629b11342315816f3a1da26980e4a05352f4` |
|
|
261
|
-
|
|
262
|
-
The `certificateHash` is computed with `cerCreatedAt: "2026-02-12T00:00:00.000Z"`.
|
|
263
|
-
|
|
264
|
-
**Canonicalization rules for verification:**
|
|
265
|
-
1. Object keys sorted lexicographically at every level.
|
|
185
|
+
1. Object keys sorted lexicographically (Unicode codepoint order) at every nesting level.
|
|
266
186
|
2. No whitespace between tokens.
|
|
267
|
-
3.
|
|
268
|
-
4.
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
3. Recompute `outputHash` from `snapshot.output` and assert it matches `expected.outputHash`.
|
|
274
|
-
4. Construct a CER payload `{ bundleType: "cer.ai.execution.v1", version: "0.1", createdAt: "2026-02-12T00:00:00.000Z", snapshot }`, canonicalize it, hash it, and assert it matches `expected.certificateHash`.
|
|
275
|
-
|
|
276
|
-
## Threat Model
|
|
277
|
-
|
|
278
|
-
This package provides **integrity and auditability** for AI execution records. It is an auditing tool, not a security boundary.
|
|
279
|
-
|
|
280
|
-
### What it prevents
|
|
187
|
+
3. Array order preserved.
|
|
188
|
+
4. `null` serialized as `null`.
|
|
189
|
+
5. Numbers must be finite. `NaN`, `Infinity`, `-Infinity` rejected (throw).
|
|
190
|
+
6. `undefined` values in object properties omitted (key dropped).
|
|
191
|
+
7. `BigInt`, functions, `Symbol` rejected (throw).
|
|
192
|
+
8. Strings JSON-escaped.
|
|
281
193
|
|
|
282
|
-
|
|
283
|
-
- **Parameter laundering** — changing temperature, model, seed, or other parameters after the fact is detectable because they are included in the certificate hash.
|
|
284
|
-
- **Silent record edits** — any modification to a sealed CER bundle is detectable via `verifyCer()`.
|
|
194
|
+
**Canonicalization is frozen for v1.** Number formatting uses `JSON.stringify()` (V8-consistent). This does not normalize `-0` to `0` and does not implement RFC 8785 exponential notation rules. These are documented known behaviors, not bugs. Any future stricter canonicalization will ship under a new bundle type.
|
|
285
195
|
|
|
286
|
-
|
|
196
|
+
## Error Types
|
|
287
197
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
198
|
+
| Error | When thrown | Structured data |
|
|
199
|
+
|---|---|---|
|
|
200
|
+
| `CerVerificationError` | `importCer()` on invalid/tampered data | `.errors: string[]` |
|
|
201
|
+
| `CerAttestationError` | `attest()` on failure | `.statusCode`, `.responseBody`, `.details: string[]` |
|
|
291
202
|
|
|
292
|
-
##
|
|
203
|
+
## Interoperability (Test Vectors)
|
|
293
204
|
|
|
294
|
-
|
|
205
|
+
Fixtures at `fixtures/vectors/` and `fixtures/golden/`. Cross-language implementations must match committed hash values exactly. Golden fixtures (v0.1.0 semantics) must verify with every future version.
|
|
295
206
|
|
|
296
|
-
|
|
297
|
-
2. Call `sealCer(snapshot)` to produce the CER bundle.
|
|
298
|
-
3. Call `verifyCer(bundle)` **before** storing or transmitting the bundle — this catches any in-memory corruption.
|
|
299
|
-
4. Store the full bundle as an audit artifact. Treat it as immutable.
|
|
300
|
-
5. On retrieval, call `verifyCer(bundle)` again to confirm integrity.
|
|
207
|
+
## API Reference
|
|
301
208
|
|
|
302
|
-
|
|
303
|
-
const snapshot = createSnapshot({ ... });
|
|
304
|
-
const bundle = sealCer(snapshot);
|
|
209
|
+
### Core
|
|
305
210
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
211
|
+
| Function | Description |
|
|
212
|
+
|---|---|
|
|
213
|
+
| `createSnapshot(params)` | Create snapshot with computed hashes |
|
|
214
|
+
| `verifySnapshot(snapshot)` | Verify snapshot hashes and structure |
|
|
215
|
+
| `sealCer(snapshot, options?)` | Seal snapshot into CER bundle |
|
|
216
|
+
| `verify(bundle)` / `verifyCer(bundle)` | Verify CER bundle |
|
|
217
|
+
| `certifyDecision(params)` | One-call: createSnapshot + sealCer |
|
|
310
218
|
|
|
311
|
-
|
|
312
|
-
```
|
|
219
|
+
### Workflow
|
|
313
220
|
|
|
314
|
-
|
|
221
|
+
| Export | Description |
|
|
222
|
+
|---|---|
|
|
223
|
+
| `RunBuilder` | Multi-step workflow builder with prevStepHash chaining |
|
|
315
224
|
|
|
316
|
-
|
|
225
|
+
### Attestation & Archive
|
|
317
226
|
|
|
318
|
-
|
|
319
|
-
|
|
227
|
+
| Function | Description |
|
|
228
|
+
|---|---|
|
|
229
|
+
| `attest(bundle, options)` | Post CER to canonical node |
|
|
230
|
+
| `exportCer(bundle)` | Serialize to canonical JSON string |
|
|
231
|
+
| `importCer(json)` | Parse + verify from JSON string |
|
|
320
232
|
|
|
321
|
-
|
|
233
|
+
### Providers (sub-exports)
|
|
322
234
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
235
|
+
| Function | Export path |
|
|
236
|
+
|---|---|
|
|
237
|
+
| `runOpenAIChatExecution` | `@nexart/ai-execution/providers/openai` |
|
|
238
|
+
| `runAnthropicExecution` | `@nexart/ai-execution/providers/anthropic` |
|
|
239
|
+
| `wrapProvider` | `@nexart/ai-execution/providers/wrap` |
|
|
326
240
|
|
|
327
|
-
##
|
|
241
|
+
## Version History
|
|
328
242
|
|
|
329
|
-
|
|
|
330
|
-
|
|
331
|
-
|
|
|
332
|
-
|
|
|
333
|
-
|
|
|
334
|
-
|
|
|
335
|
-
| `computeInputHash(input)` | Compute hash for input (string or object) |
|
|
336
|
-
| `computeOutputHash(output)` | Compute hash for output (string or object) |
|
|
337
|
-
| `toCanonicalJson(value)` | Deterministic JSON serialization |
|
|
338
|
-
| `sha256Hex(data)` | Raw SHA-256 hex digest |
|
|
339
|
-
| `hashUtf8(value)` | Hash a UTF-8 string |
|
|
340
|
-
| `hashCanonicalJson(value)` | Hash canonical JSON of a value |
|
|
243
|
+
| Version | Description |
|
|
244
|
+
|---|---|
|
|
245
|
+
| v0.1.0 | Core snapshot + CER + verify + OpenAI adapter |
|
|
246
|
+
| v0.2.0 | certifyDecision, RunBuilder, attest, archive, Anthropic, wrapProvider, typed errors, workflow fields |
|
|
247
|
+
| **v0.3.0** | Attestation hardening (hash validation, timeout), `verify` alias, `CerAttestationError.details`, release hygiene |
|
|
248
|
+
| v1.0.0 | Planned: API stabilization, freeze public API surface |
|
|
341
249
|
|
|
342
|
-
##
|
|
250
|
+
## Releasing
|
|
343
251
|
|
|
344
252
|
```bash
|
|
345
253
|
cd packages/ai-execution
|
|
346
254
|
npm run build
|
|
347
|
-
npm
|
|
255
|
+
npm test
|
|
348
256
|
npm publish --access public
|
|
349
257
|
```
|
|
350
258
|
|
|
259
|
+
The `release` script automates build, test, version bump, and publish:
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
npm run release
|
|
263
|
+
```
|
|
264
|
+
|
|
351
265
|
## License
|
|
352
266
|
|
|
353
267
|
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../src/archive.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAKvD,wBAAgB,SAAS,CAAC,MAAM,EAAE,oBAAoB,GAAG,MAAM,CAE9D;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,oBAAoB,CAwB5D"}
|
package/dist/archive.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { toCanonicalJson } from './canonicalJson.js';
|
|
2
|
+
import { verifyCer } from './cer.js';
|
|
3
|
+
import { CerVerificationError } from './errors.js';
|
|
4
|
+
export function exportCer(bundle) {
|
|
5
|
+
return toCanonicalJson(bundle);
|
|
6
|
+
}
|
|
7
|
+
export function importCer(json) {
|
|
8
|
+
let parsed;
|
|
9
|
+
try {
|
|
10
|
+
parsed = JSON.parse(json);
|
|
11
|
+
}
|
|
12
|
+
catch (err) {
|
|
13
|
+
throw new CerVerificationError([`Invalid JSON: ${err.message}`]);
|
|
14
|
+
}
|
|
15
|
+
const bundle = parsed;
|
|
16
|
+
if (!bundle || typeof bundle !== 'object') {
|
|
17
|
+
throw new CerVerificationError(['Parsed value is not an object']);
|
|
18
|
+
}
|
|
19
|
+
if (bundle.bundleType !== 'cer.ai.execution.v1') {
|
|
20
|
+
throw new CerVerificationError([`Expected bundleType "cer.ai.execution.v1", got "${bundle.bundleType}"`]);
|
|
21
|
+
}
|
|
22
|
+
const result = verifyCer(bundle);
|
|
23
|
+
if (!result.ok) {
|
|
24
|
+
throw new CerVerificationError(result.errors);
|
|
25
|
+
}
|
|
26
|
+
return bundle;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=archive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archive.js","sourceRoot":"","sources":["../src/archive.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEnD,MAAM,UAAU,SAAS,CAAC,MAA4B;IACpD,OAAO,eAAe,CAAC,MAAM,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,oBAAoB,CAAC,CAAC,iBAAkB,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IAC9E,CAAC;IAED,MAAM,MAAM,GAAG,MAA8B,CAAC;IAE9C,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC1C,MAAM,IAAI,oBAAoB,CAAC,CAAC,+BAA+B,CAAC,CAAC,CAAC;IACpE,CAAC;IAED,IAAI,MAAM,CAAC,UAAU,KAAK,qBAAqB,EAAE,CAAC;QAChD,MAAM,IAAI,oBAAoB,CAAC,CAAC,mDAAmD,MAAM,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC;IAC5G,CAAC;IAED,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;IACjC,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QACf,MAAM,IAAI,oBAAoB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAChD,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
package/dist/attest.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attest.d.ts","sourceRoot":"","sources":["../src/attest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAczF,wBAAsB,MAAM,CAC1B,MAAM,EAAE,oBAAoB,EAC5B,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,iBAAiB,CAAC,CA0F5B"}
|
package/dist/attest.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { CerAttestationError } from './errors.js';
|
|
2
|
+
const SHA256_PATTERN = /^sha256:[0-9a-f]{64}$/;
|
|
3
|
+
const DEFAULT_TIMEOUT_MS = 10_000;
|
|
4
|
+
function validateHashFormat(value, fieldName) {
|
|
5
|
+
if (typeof value !== 'string')
|
|
6
|
+
return null;
|
|
7
|
+
if (!SHA256_PATTERN.test(value)) {
|
|
8
|
+
return `${fieldName} is not in sha256:<64hex> format: "${value}"`;
|
|
9
|
+
}
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
export async function attest(bundle, options) {
|
|
13
|
+
const url = `${options.nodeUrl.replace(/\/+$/, '')}/api/attest`;
|
|
14
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
15
|
+
const controller = new AbortController();
|
|
16
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
17
|
+
let response;
|
|
18
|
+
try {
|
|
19
|
+
response = await fetch(url, {
|
|
20
|
+
method: 'POST',
|
|
21
|
+
headers: {
|
|
22
|
+
'Content-Type': 'application/json',
|
|
23
|
+
'Authorization': `Bearer ${options.apiKey}`,
|
|
24
|
+
},
|
|
25
|
+
body: JSON.stringify(bundle),
|
|
26
|
+
signal: controller.signal,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
clearTimeout(timer);
|
|
31
|
+
const error = err;
|
|
32
|
+
if (error.name === 'AbortError') {
|
|
33
|
+
throw new CerAttestationError(`Attestation request timed out after ${timeoutMs}ms`);
|
|
34
|
+
}
|
|
35
|
+
throw new CerAttestationError(`Network error contacting attestation node: ${error.message}`);
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
clearTimeout(timer);
|
|
39
|
+
}
|
|
40
|
+
let body;
|
|
41
|
+
try {
|
|
42
|
+
body = await response.json();
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
const text = await response.text().catch(() => '');
|
|
46
|
+
throw new CerAttestationError(`Attestation node returned non-JSON response (${response.status}): ${text}`, response.status);
|
|
47
|
+
}
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
const result = body;
|
|
50
|
+
const msg = typeof result.error === 'string'
|
|
51
|
+
? result.error
|
|
52
|
+
: `HTTP ${response.status}`;
|
|
53
|
+
const details = Array.isArray(result.details) ? result.details : undefined;
|
|
54
|
+
throw new CerAttestationError(`Attestation failed: ${msg}`, response.status, body, details);
|
|
55
|
+
}
|
|
56
|
+
const result = body;
|
|
57
|
+
const errors = [];
|
|
58
|
+
if (typeof result.certificateHash === 'string' && result.certificateHash !== bundle.certificateHash) {
|
|
59
|
+
errors.push(`Node returned certificateHash "${result.certificateHash}" but bundle has "${bundle.certificateHash}"`);
|
|
60
|
+
}
|
|
61
|
+
const certHashErr = validateHashFormat(result.certificateHash, 'response.certificateHash');
|
|
62
|
+
if (certHashErr)
|
|
63
|
+
errors.push(certHashErr);
|
|
64
|
+
const runtimeHashErr = validateHashFormat(result.nodeRuntimeHash, 'response.nodeRuntimeHash');
|
|
65
|
+
if (runtimeHashErr)
|
|
66
|
+
errors.push(runtimeHashErr);
|
|
67
|
+
if (errors.length > 0) {
|
|
68
|
+
throw new CerAttestationError(`Attestation response validation failed: ${errors.join('; ')}`, response.status, body, errors);
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
ok: true,
|
|
72
|
+
attestationId: typeof result.attestationId === 'string' ? result.attestationId : undefined,
|
|
73
|
+
nodeRuntimeHash: typeof result.nodeRuntimeHash === 'string' ? result.nodeRuntimeHash : undefined,
|
|
74
|
+
certificateHash: typeof result.certificateHash === 'string' ? result.certificateHash : undefined,
|
|
75
|
+
protocolVersion: typeof result.protocolVersion === 'string' ? result.protocolVersion : undefined,
|
|
76
|
+
raw: body,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=attest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attest.js","sourceRoot":"","sources":["../src/attest.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"certify.d.ts","sourceRoot":"","sources":["../src/certify.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAI9E,wBAAgB,eAAe,CAAC,MAAM,EAAE,qBAAqB,GAAG,oBAAoB,CAwBnF"}
|