@nexart/ai-execution 0.1.0 → 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/README.md +270 -112
- package/dist/__tests__/v020.test.d.ts +2 -0
- package/dist/__tests__/v020.test.d.ts.map +1 -0
- package/dist/__tests__/v020.test.js +408 -0
- package/dist/__tests__/v020.test.js.map +1 -0
- 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 +42 -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 +10 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +19 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- 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 +99 -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 +13 -3
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @nexart/ai-execution v0.
|
|
1
|
+
# @nexart/ai-execution v0.2.0
|
|
2
2
|
|
|
3
3
|
Tamper-evident records and Certified Execution Records (CER) for AI operations.
|
|
4
4
|
|
|
@@ -15,6 +15,13 @@ These records can be verified later to prove the execution happened as recorded.
|
|
|
15
15
|
|
|
16
16
|
**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
17
|
|
|
18
|
+
## Compatibility Guarantees
|
|
19
|
+
|
|
20
|
+
- **v0.1.0 bundles verify forever.** Any CER bundle produced by v0.1.0 will pass `verifyCer()` in v0.2.0 and all future versions.
|
|
21
|
+
- **Hashing rules are frozen for `cer.ai.execution.v1`.** The canonicalization, SHA-256 computation, and certificate hash inputs (bundleType, version, createdAt, snapshot) are unchanged.
|
|
22
|
+
- **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.
|
|
23
|
+
- **If stricter canonicalization is needed in the future**, it will ship as a new bundle type (e.g. `cer.ai.execution.v2`), not as a change to v1.
|
|
24
|
+
|
|
18
25
|
## Installation
|
|
19
26
|
|
|
20
27
|
```bash
|
|
@@ -23,12 +30,27 @@ npm install @nexart/ai-execution
|
|
|
23
30
|
|
|
24
31
|
## Quick Start
|
|
25
32
|
|
|
26
|
-
###
|
|
33
|
+
### Single Decision (3 lines)
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { certifyDecision } from '@nexart/ai-execution';
|
|
37
|
+
|
|
38
|
+
const cer = certifyDecision({
|
|
39
|
+
provider: 'openai',
|
|
40
|
+
model: 'gpt-4o',
|
|
41
|
+
prompt: 'Summarize.',
|
|
42
|
+
input: userQuery,
|
|
43
|
+
output: llmResponse,
|
|
44
|
+
parameters: { temperature: 0.7, maxTokens: 1024, topP: null, seed: null },
|
|
45
|
+
});
|
|
46
|
+
console.log(cer.certificateHash); // "sha256:..."
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Manual Snapshot + Seal (v0.1.0 style, still supported)
|
|
27
50
|
|
|
28
51
|
```typescript
|
|
29
52
|
import { createSnapshot, verifySnapshot, sealCer, verifyCer } from '@nexart/ai-execution';
|
|
30
53
|
|
|
31
|
-
// Create a snapshot of an AI execution
|
|
32
54
|
const snapshot = createSnapshot({
|
|
33
55
|
executionId: 'exec-001',
|
|
34
56
|
provider: 'openai',
|
|
@@ -36,28 +58,79 @@ const snapshot = createSnapshot({
|
|
|
36
58
|
modelVersion: '2026-01-01',
|
|
37
59
|
prompt: 'You are a helpful assistant.',
|
|
38
60
|
input: 'What is 2+2?',
|
|
39
|
-
parameters: {
|
|
40
|
-
temperature: 0.7,
|
|
41
|
-
maxTokens: 1024,
|
|
42
|
-
topP: null,
|
|
43
|
-
seed: null,
|
|
44
|
-
},
|
|
61
|
+
parameters: { temperature: 0.7, maxTokens: 1024, topP: null, seed: null },
|
|
45
62
|
output: 'The answer is 4.',
|
|
46
63
|
});
|
|
47
64
|
|
|
48
|
-
// Verify the snapshot hashes are correct
|
|
49
65
|
const result = verifySnapshot(snapshot);
|
|
50
66
|
console.log(result.ok); // true
|
|
51
67
|
|
|
52
|
-
// Seal into a Certified Execution Record
|
|
53
68
|
const bundle = sealCer(snapshot);
|
|
54
69
|
console.log(bundle.certificateHash); // "sha256:..."
|
|
55
70
|
|
|
56
|
-
// Verify the entire bundle
|
|
57
71
|
const cerResult = verifyCer(bundle);
|
|
58
72
|
console.log(cerResult.ok); // true
|
|
59
73
|
```
|
|
60
74
|
|
|
75
|
+
### Agentic Multi-Step Workflow
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import { RunBuilder } from '@nexart/ai-execution';
|
|
79
|
+
|
|
80
|
+
const run = new RunBuilder({ runId: 'analysis-run', workflowId: 'data-pipeline' });
|
|
81
|
+
|
|
82
|
+
const step0 = run.step({
|
|
83
|
+
provider: 'openai',
|
|
84
|
+
model: 'gpt-4o',
|
|
85
|
+
prompt: 'Plan the analysis.',
|
|
86
|
+
input: 'Analyze Q1 sales data.',
|
|
87
|
+
output: 'I will: 1) load data, 2) compute totals, 3) summarize.',
|
|
88
|
+
parameters: { temperature: 0.3, maxTokens: 512, topP: null, seed: null },
|
|
89
|
+
});
|
|
90
|
+
// step0.snapshot.stepIndex === 0, prevStepHash === null
|
|
91
|
+
|
|
92
|
+
const step1 = run.step({
|
|
93
|
+
provider: 'openai',
|
|
94
|
+
model: 'gpt-4o',
|
|
95
|
+
prompt: 'Execute step 1.',
|
|
96
|
+
input: 'Load and total Q1 data.',
|
|
97
|
+
output: 'Total revenue: $1.2M.',
|
|
98
|
+
parameters: { temperature: 0.3, maxTokens: 512, topP: null, seed: null },
|
|
99
|
+
});
|
|
100
|
+
// step1.snapshot.stepIndex === 1, prevStepHash === step0.certificateHash
|
|
101
|
+
|
|
102
|
+
const summary = run.finalize();
|
|
103
|
+
// { runId, stepCount: 2, steps: [...], finalStepHash: step1.certificateHash }
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Attest to Canonical Node
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
import { certifyDecision, attest } from '@nexart/ai-execution';
|
|
110
|
+
|
|
111
|
+
const cer = certifyDecision({ /* ... */ });
|
|
112
|
+
const proof = await attest(cer, {
|
|
113
|
+
nodeUrl: 'https://node.nexart.io',
|
|
114
|
+
apiKey: process.env.NEXART_API_KEY!,
|
|
115
|
+
});
|
|
116
|
+
console.log(proof.attestationId); // ledger proof reference
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Archive (Export / Import)
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
import { exportCer, importCer, sealCer, createSnapshot } from '@nexart/ai-execution';
|
|
123
|
+
|
|
124
|
+
const bundle = sealCer(createSnapshot({ /* ... */ }));
|
|
125
|
+
|
|
126
|
+
// Export as canonical JSON string (deterministic, archive-safe)
|
|
127
|
+
const json = exportCer(bundle);
|
|
128
|
+
await fs.writeFile('audit/cer-001.json', json);
|
|
129
|
+
|
|
130
|
+
// Import and verify in one step (throws CerVerificationError if invalid)
|
|
131
|
+
const restored = importCer(await fs.readFile('audit/cer-001.json', 'utf-8'));
|
|
132
|
+
```
|
|
133
|
+
|
|
61
134
|
### With JSON Input/Output
|
|
62
135
|
|
|
63
136
|
```typescript
|
|
@@ -67,12 +140,7 @@ const snapshot = createSnapshot({
|
|
|
67
140
|
model: 'gpt-4o',
|
|
68
141
|
prompt: 'Extract entities',
|
|
69
142
|
input: { text: 'John lives in Paris', lang: 'en' },
|
|
70
|
-
parameters: {
|
|
71
|
-
temperature: 0,
|
|
72
|
-
maxTokens: 512,
|
|
73
|
-
topP: null,
|
|
74
|
-
seed: 42,
|
|
75
|
-
},
|
|
143
|
+
parameters: { temperature: 0, maxTokens: 512, topP: null, seed: 42 },
|
|
76
144
|
output: { entities: ['John', 'Paris'], count: 2 },
|
|
77
145
|
});
|
|
78
146
|
```
|
|
@@ -83,8 +151,6 @@ Convenience adapter only; the core value of this package is the snapshot + hashi
|
|
|
83
151
|
The OpenAI adapter wraps a single `chat/completions` call and returns a sealed CER bundle.
|
|
84
152
|
**Provider determinism is not guaranteed** — identical inputs may produce different outputs across calls.
|
|
85
153
|
|
|
86
|
-
If you have an OpenAI API key, the package includes a convenience adapter:
|
|
87
|
-
|
|
88
154
|
```typescript
|
|
89
155
|
import { runOpenAIChatExecution } from '@nexart/ai-execution/providers/openai';
|
|
90
156
|
|
|
@@ -92,19 +158,65 @@ const result = await runOpenAIChatExecution({
|
|
|
92
158
|
prompt: 'You are a helpful assistant.',
|
|
93
159
|
input: 'What is the capital of France?',
|
|
94
160
|
model: 'gpt-4o',
|
|
95
|
-
parameters: {
|
|
96
|
-
temperature: 0.7,
|
|
97
|
-
maxTokens: 256,
|
|
98
|
-
},
|
|
161
|
+
parameters: { temperature: 0.7, maxTokens: 256 },
|
|
99
162
|
});
|
|
100
163
|
|
|
101
|
-
console.log(result.output);
|
|
102
|
-
console.log(result.snapshot.inputHash);
|
|
164
|
+
console.log(result.output); // "The capital of France is Paris."
|
|
165
|
+
console.log(result.snapshot.inputHash); // "sha256:..."
|
|
103
166
|
console.log(result.bundle.certificateHash); // "sha256:..."
|
|
104
167
|
```
|
|
105
168
|
|
|
106
169
|
Requires `OPENAI_API_KEY` environment variable or `apiKey` parameter.
|
|
107
170
|
|
|
171
|
+
### Anthropic Provider (Optional)
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
import { runAnthropicExecution } from '@nexart/ai-execution/providers/anthropic';
|
|
175
|
+
|
|
176
|
+
const result = await runAnthropicExecution({
|
|
177
|
+
prompt: 'You are a helpful assistant.',
|
|
178
|
+
input: 'Explain quantum computing briefly.',
|
|
179
|
+
model: 'claude-sonnet-4-20250514',
|
|
180
|
+
parameters: { temperature: 0.5, maxTokens: 512 },
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Requires `ANTHROPIC_API_KEY` environment variable or `apiKey` parameter.
|
|
185
|
+
|
|
186
|
+
### Generic Provider Wrapper
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import { wrapProvider } from '@nexart/ai-execution/providers/wrap';
|
|
190
|
+
|
|
191
|
+
const myProvider = wrapProvider({
|
|
192
|
+
provider: 'my-llm',
|
|
193
|
+
callFn: async (input) => await myLlmClient.chat(input),
|
|
194
|
+
extractOutput: (raw) => raw.message,
|
|
195
|
+
extractModelVersion: (raw) => raw.modelVersion,
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const result = await myProvider.execute({
|
|
199
|
+
providerInput: { messages: [{ role: 'user', content: 'Hello' }] },
|
|
200
|
+
prompt: 'System prompt',
|
|
201
|
+
input: 'Hello',
|
|
202
|
+
model: 'my-model-v2',
|
|
203
|
+
parameters: { temperature: 0.7, maxTokens: 1024, topP: null, seed: null },
|
|
204
|
+
});
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## v0.2.0 Additions
|
|
208
|
+
|
|
209
|
+
| Feature | Description |
|
|
210
|
+
|---|---|
|
|
211
|
+
| `certifyDecision()` | One-call convenience: createSnapshot + sealCer combined |
|
|
212
|
+
| `RunBuilder` | Agentic workflow support with step chaining and prevStepHash |
|
|
213
|
+
| `attest()` | Post CER bundle to canonical node for ledger attestation |
|
|
214
|
+
| `exportCer()` / `importCer()` | Archive-safe canonical JSON serialization with verification |
|
|
215
|
+
| `wrapProvider()` | Generic provider wrapper factory |
|
|
216
|
+
| Anthropic adapter | `@nexart/ai-execution/providers/anthropic` |
|
|
217
|
+
| Typed errors | `CerVerificationError`, `CerAttestationError` |
|
|
218
|
+
| Workflow fields | `runId`, `stepId`, `stepIndex`, `workflowId`, `conversationId`, `prevStepHash` |
|
|
219
|
+
|
|
108
220
|
## Snapshot Format (ai.execution.v1)
|
|
109
221
|
|
|
110
222
|
### Required vs Optional Fields
|
|
@@ -123,8 +235,14 @@ Requires `OPENAI_API_KEY` environment variable or `apiKey` parameter.
|
|
|
123
235
|
| `modelVersion` | Optional | `string \| null` | Defaults to `null` |
|
|
124
236
|
| `parameters.topP` | Optional | `number \| null` | Defaults to `null` |
|
|
125
237
|
| `parameters.seed` | Optional | `number \| null` | Defaults to `null` |
|
|
126
|
-
| `sdkVersion` | Optional | `string \| null` | Defaults to package version (`"0.
|
|
238
|
+
| `sdkVersion` | Optional | `string \| null` | Defaults to package version (`"0.2.0"`) |
|
|
127
239
|
| `appId` | Optional | `string \| null` | Defaults to `null` |
|
|
240
|
+
| `runId` | Optional | `string \| null` | Workflow run ID (v0.2.0+) |
|
|
241
|
+
| `stepId` | Optional | `string \| null` | Step identifier within a run (v0.2.0+) |
|
|
242
|
+
| `stepIndex` | Optional | `number \| null` | 0-based step position (v0.2.0+) |
|
|
243
|
+
| `workflowId` | Optional | `string \| null` | Workflow template ID (v0.2.0+) |
|
|
244
|
+
| `conversationId` | Optional | `string \| null` | Conversation/session ID (v0.2.0+) |
|
|
245
|
+
| `prevStepHash` | Optional | `string \| null` | certificateHash of previous step (v0.2.0+) |
|
|
128
246
|
|
|
129
247
|
The following fields are **auto-generated** by `createSnapshot` and must not be set manually:
|
|
130
248
|
|
|
@@ -136,34 +254,6 @@ The following fields are **auto-generated** by `createSnapshot` and must not be
|
|
|
136
254
|
| `inputHash` | SHA-256 of input |
|
|
137
255
|
| `outputHash` | SHA-256 of output |
|
|
138
256
|
|
|
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
|
-
```
|
|
166
|
-
|
|
167
257
|
## CER Bundle Format
|
|
168
258
|
|
|
169
259
|
```json
|
|
@@ -191,6 +281,7 @@ The `certificateHash` is SHA-256 of the UTF-8 bytes of the canonical JSON of **e
|
|
|
191
281
|
- `meta` is **excluded** from the certificate hash. It is an informational envelope field that does not affect integrity verification.
|
|
192
282
|
- Key-ordering rules apply **recursively** — every nested object within `snapshot` is also sorted lexicographically by key.
|
|
193
283
|
- The bytes being hashed are the UTF-8 encoding of the canonical JSON string (no BOM, no trailing newline).
|
|
284
|
+
- **This computation is identical in v0.1.0 and v0.2.0.** New optional snapshot fields (runId, stepIndex, etc.) participate in the hash only when present in the snapshot object.
|
|
194
285
|
|
|
195
286
|
## Hashing Rules
|
|
196
287
|
|
|
@@ -213,102 +304,117 @@ Canonical JSON is a deterministic subset of JSON. The following rules apply:
|
|
|
213
304
|
|
|
214
305
|
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
306
|
|
|
307
|
+
**Note on number formatting:** v1 bundles use `JSON.stringify()` for number-to-string conversion, which is consistent across JavaScript engines but does not implement RFC 8785 (JCS) number formatting for edge cases like `-0`. If stricter canonicalization is required in the future, it will be introduced via a new bundle type (`cer.ai.execution.v2`), not by modifying v1 behavior.
|
|
308
|
+
|
|
309
|
+
## RunBuilder: Agentic Workflow Chaining
|
|
310
|
+
|
|
311
|
+
The `RunBuilder` class enables multi-step AI workflows with cryptographic chaining:
|
|
312
|
+
|
|
313
|
+
- **Auto-incrementing `stepIndex`**: starts at 0, increments with each `.step()` call.
|
|
314
|
+
- **`prevStepHash` chaining**: each step's `prevStepHash` is set to the `certificateHash` of the previous step (null for step 0).
|
|
315
|
+
- **`executionId` generation**: automatically set to `{runId}-step-{stepIndex}`.
|
|
316
|
+
- Each step produces a full, independently verifiable CER bundle.
|
|
317
|
+
- Call `.finalize()` to get a `RunSummary` with all step hashes and chain metadata.
|
|
318
|
+
|
|
319
|
+
## Attestation
|
|
320
|
+
|
|
321
|
+
The `attest()` function posts a sealed CER bundle to a NexArt canonical node:
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
const proof = await attest(bundle, {
|
|
325
|
+
nodeUrl: 'https://node.nexart.io',
|
|
326
|
+
apiKey: 'your-api-key',
|
|
327
|
+
});
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
- Endpoint: `POST {nodeUrl}/api/attest`
|
|
331
|
+
- Authorization: `Bearer {apiKey}`
|
|
332
|
+
- Body: the full CER bundle as JSON
|
|
333
|
+
- Returns: `AttestationResult` with `attestationId`, `nodeRuntimeHash`, `certificateHash`, `protocolVersion`
|
|
334
|
+
- Throws: `CerAttestationError` on network or HTTP errors
|
|
335
|
+
|
|
336
|
+
## Error Types
|
|
337
|
+
|
|
338
|
+
| Error | When thrown |
|
|
339
|
+
|---|---|
|
|
340
|
+
| `CerVerificationError` | `importCer()` receives invalid or tampered JSON |
|
|
341
|
+
| `CerAttestationError` | `attest()` fails (network error, HTTP error, invalid response) |
|
|
342
|
+
|
|
343
|
+
Both extend `Error` and include structured data:
|
|
344
|
+
- `CerVerificationError.errors`: `string[]` of specific verification failures
|
|
345
|
+
- `CerAttestationError.statusCode`: HTTP status code (if available)
|
|
346
|
+
- `CerAttestationError.responseBody`: raw response body (if available)
|
|
347
|
+
|
|
216
348
|
## Interoperability (Test Vectors)
|
|
217
349
|
|
|
218
|
-
Cross-language implementations **must** match the test vectors exactly
|
|
350
|
+
Cross-language implementations **must** match the test vectors exactly. Vectors are committed as JSON fixtures at:
|
|
219
351
|
|
|
220
352
|
```
|
|
221
353
|
packages/ai-execution/fixtures/vectors/
|
|
222
354
|
```
|
|
223
355
|
|
|
224
|
-
### Vector 001
|
|
356
|
+
### Vector 001 (single decision)
|
|
225
357
|
|
|
226
|
-
|
|
358
|
+
See `vector-001.snapshot.json` and `vector-001.expected.json` for a single text-in/text-out CER with committed hash values.
|
|
227
359
|
|
|
228
|
-
|
|
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
|
-
```
|
|
360
|
+
### Vector 002 (3-step agentic chain)
|
|
253
361
|
|
|
254
|
-
|
|
362
|
+
See `vector-002.chain.json` for a 3-step workflow chain with:
|
|
363
|
+
- Step 0: `prevStepHash: null`, `stepIndex: 0`
|
|
364
|
+
- Step 1: `prevStepHash: step0.certificateHash`, `stepIndex: 1`
|
|
365
|
+
- Step 2: `prevStepHash: step1.certificateHash`, `stepIndex: 2`
|
|
255
366
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
| `outputHash` | `sha256:ae758477f843049bd252ceb5498aa33f190326589ee92cbe5a1ab563f54bc05b` |
|
|
260
|
-
| `certificateHash` | `sha256:86275d60d088483eefaf0bd31d79629b11342315816f3a1da26980e4a05352f4` |
|
|
367
|
+
Each entry includes `snapshot`, `expectedCertificateHash`, and `cerCreatedAt` for deterministic verification.
|
|
368
|
+
|
|
369
|
+
### Golden Fixtures (backward compatibility)
|
|
261
370
|
|
|
262
|
-
|
|
371
|
+
5 CER bundles produced with v0.1.0 semantics are committed at:
|
|
263
372
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
3. Arrays preserve insertion order.
|
|
268
|
-
4. Hashes are SHA-256 of the UTF-8 bytes of the resulting string.
|
|
373
|
+
```
|
|
374
|
+
packages/ai-execution/fixtures/golden/
|
|
375
|
+
```
|
|
269
376
|
|
|
270
|
-
|
|
271
|
-
1. Load `vector-001.snapshot.json`.
|
|
272
|
-
2. Recompute `inputHash` from `snapshot.input` and assert it matches `expected.inputHash`.
|
|
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`.
|
|
377
|
+
These bundles must verify with every future version of the library.
|
|
275
378
|
|
|
276
379
|
## Threat Model
|
|
277
380
|
|
|
278
|
-
This package provides **integrity and auditability** for AI execution records.
|
|
381
|
+
This package provides **integrity and auditability** for AI execution records.
|
|
279
382
|
|
|
280
383
|
### What it prevents
|
|
281
384
|
|
|
282
385
|
- **Output tampering** — any modification to the recorded output invalidates the `outputHash` and `certificateHash`.
|
|
283
|
-
- **Parameter laundering** — changing temperature, model, seed, or other parameters after the fact is detectable
|
|
386
|
+
- **Parameter laundering** — changing temperature, model, seed, or other parameters after the fact is detectable.
|
|
284
387
|
- **Silent record edits** — any modification to a sealed CER bundle is detectable via `verifyCer()`.
|
|
388
|
+
- **Chain forgery** — in multi-step workflows, `prevStepHash` links each step to the previous, making it impossible to insert, remove, or reorder steps without detection.
|
|
285
389
|
|
|
286
390
|
### What it does NOT prevent
|
|
287
391
|
|
|
288
|
-
- **Provider lying** — if the AI provider returns fabricated output, this package faithfully records it.
|
|
289
|
-
- **Model drift** — models change over time.
|
|
392
|
+
- **Provider lying** — if the AI provider returns fabricated output, this package faithfully records it.
|
|
393
|
+
- **Model drift** — models change over time. `modelVersion` helps track this but cannot prevent it.
|
|
290
394
|
- **Correctness of the answer** — this package records what was said, not whether it was right.
|
|
291
395
|
|
|
292
396
|
## Verification Best Practice
|
|
293
397
|
|
|
294
398
|
**Recommended flow:**
|
|
295
399
|
|
|
296
|
-
1. Call `
|
|
297
|
-
2. Call `
|
|
298
|
-
3.
|
|
299
|
-
4.
|
|
400
|
+
1. Call `certifyDecision(...)` (or `createSnapshot` + `sealCer`).
|
|
401
|
+
2. Call `verifyCer(bundle)` **before** storing — catches any in-memory corruption.
|
|
402
|
+
3. Store the full bundle as an immutable audit artifact.
|
|
403
|
+
4. Optionally call `attest(bundle, ...)` to register on the canonical node ledger.
|
|
300
404
|
5. On retrieval, call `verifyCer(bundle)` again to confirm integrity.
|
|
301
405
|
|
|
302
406
|
```typescript
|
|
303
|
-
const
|
|
304
|
-
const bundle = sealCer(snapshot);
|
|
407
|
+
const bundle = certifyDecision({ ... });
|
|
305
408
|
|
|
306
409
|
const check = verifyCer(bundle);
|
|
307
410
|
if (!check.ok) {
|
|
308
411
|
throw new Error(`CER integrity failure: ${check.errors.join('; ')}`);
|
|
309
412
|
}
|
|
310
413
|
|
|
311
|
-
await store(bundle);
|
|
414
|
+
await store(bundle);
|
|
415
|
+
|
|
416
|
+
// Optional: attest to canonical node
|
|
417
|
+
const proof = await attest(bundle, { nodeUrl, apiKey });
|
|
312
418
|
```
|
|
313
419
|
|
|
314
420
|
## Privacy & Data Handling
|
|
@@ -321,17 +427,46 @@ Snapshots include **raw prompt, input, and output by default**. This means:
|
|
|
321
427
|
**Recommendations:**
|
|
322
428
|
|
|
323
429
|
- For sensitive workloads, consider redacting input/output before calling `createSnapshot()`, then verify the redacted versions.
|
|
324
|
-
- A
|
|
430
|
+
- A dedicated redacted bundle type (`cer.ai.execution.redacted.v1`) is planned for a future release.
|
|
325
431
|
- Treat CER bundles with the same access controls as the underlying data they contain.
|
|
326
432
|
|
|
327
433
|
## API Reference
|
|
328
434
|
|
|
435
|
+
### Core
|
|
436
|
+
|
|
329
437
|
| Function | Description |
|
|
330
438
|
|----------|-------------|
|
|
331
439
|
| `createSnapshot(params)` | Create an AI execution snapshot with computed hashes |
|
|
332
440
|
| `verifySnapshot(snapshot)` | Verify snapshot hashes and structure |
|
|
333
441
|
| `sealCer(snapshot, options?)` | Seal a snapshot into a CER bundle |
|
|
334
442
|
| `verifyCer(bundle)` | Verify a CER bundle (snapshot + certificate hash) |
|
|
443
|
+
| `certifyDecision(params)` | One-call: createSnapshot + sealCer combined **(v0.2.0)** |
|
|
444
|
+
|
|
445
|
+
### Workflow
|
|
446
|
+
|
|
447
|
+
| Function/Class | Description |
|
|
448
|
+
|----------|-------------|
|
|
449
|
+
| `RunBuilder` | Multi-step workflow builder with step chaining **(v0.2.0)** |
|
|
450
|
+
| `RunBuilder.step(params)` | Add a step, returns sealed CER bundle |
|
|
451
|
+
| `RunBuilder.finalize()` | Returns RunSummary with all step hashes |
|
|
452
|
+
|
|
453
|
+
### Attestation
|
|
454
|
+
|
|
455
|
+
| Function | Description |
|
|
456
|
+
|----------|-------------|
|
|
457
|
+
| `attest(bundle, options)` | Post CER bundle to canonical node **(v0.2.0)** |
|
|
458
|
+
|
|
459
|
+
### Archive
|
|
460
|
+
|
|
461
|
+
| Function | Description |
|
|
462
|
+
|----------|-------------|
|
|
463
|
+
| `exportCer(bundle)` | Serialize bundle to canonical JSON string **(v0.2.0)** |
|
|
464
|
+
| `importCer(json)` | Parse + verify bundle from JSON string **(v0.2.0)** |
|
|
465
|
+
|
|
466
|
+
### Hashing
|
|
467
|
+
|
|
468
|
+
| Function | Description |
|
|
469
|
+
|----------|-------------|
|
|
335
470
|
| `computeInputHash(input)` | Compute hash for input (string or object) |
|
|
336
471
|
| `computeOutputHash(output)` | Compute hash for output (string or object) |
|
|
337
472
|
| `toCanonicalJson(value)` | Deterministic JSON serialization |
|
|
@@ -339,6 +474,29 @@ Snapshots include **raw prompt, input, and output by default**. This means:
|
|
|
339
474
|
| `hashUtf8(value)` | Hash a UTF-8 string |
|
|
340
475
|
| `hashCanonicalJson(value)` | Hash canonical JSON of a value |
|
|
341
476
|
|
|
477
|
+
### Providers
|
|
478
|
+
|
|
479
|
+
| Function | Description |
|
|
480
|
+
|----------|-------------|
|
|
481
|
+
| `runOpenAIChatExecution(params)` | OpenAI chat wrapper (sub-export) |
|
|
482
|
+
| `runAnthropicExecution(params)` | Anthropic Messages wrapper **(v0.2.0)** |
|
|
483
|
+
| `wrapProvider(config)` | Generic provider wrapper factory **(v0.2.0)** |
|
|
484
|
+
|
|
485
|
+
### Errors
|
|
486
|
+
|
|
487
|
+
| Class | Description |
|
|
488
|
+
|----------|-------------|
|
|
489
|
+
| `CerVerificationError` | Thrown by `importCer()` on invalid/tampered data **(v0.2.0)** |
|
|
490
|
+
| `CerAttestationError` | Thrown by `attest()` on failure **(v0.2.0)** |
|
|
491
|
+
|
|
492
|
+
## Version Plan
|
|
493
|
+
|
|
494
|
+
| Version | Status | Description |
|
|
495
|
+
|---------|--------|-------------|
|
|
496
|
+
| v0.1.0 | Released | Core snapshot + CER + verify + OpenAI adapter |
|
|
497
|
+
| **v0.2.0** | **Current** | certifyDecision, RunBuilder, attest, archive, Anthropic, wrapProvider, typed errors, workflow fields |
|
|
498
|
+
| v1.0.0 | Planned | API stabilization. Rename `sealCer`→`seal`, `verifyCer`→`verify` (old names kept as deprecated aliases). Freeze public API surface. |
|
|
499
|
+
|
|
342
500
|
## How to Publish
|
|
343
501
|
|
|
344
502
|
```bash
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v020.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/v020.test.ts"],"names":[],"mappings":""}
|