@nexart/ai-execution 0.4.1 → 0.5.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 CHANGED
@@ -1,4 +1,4 @@
1
- # @nexart/ai-execution v0.4.1
1
+ # @nexart/ai-execution v0.5.0
2
2
 
3
3
  Tamper-evident records and Certified Execution Records (CER) for AI operations.
4
4
 
@@ -21,7 +21,7 @@ These records can be verified offline to detect any post-hoc modification and pr
21
21
 
22
22
  ## Compatibility Guarantees
23
23
 
24
- - **v0.1.0, v0.2.0, and v0.3.0 bundles verify forever.** Any CER bundle produced by any prior version will pass `verify()` in v0.4.1 and all future versions.
24
+ - **v0.1.0, v0.2.0, and v0.3.0 bundles verify forever.** Any CER bundle produced by any prior version will pass `verify()` in v0.4.2 and all future versions.
25
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
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
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.
@@ -139,7 +139,7 @@ const restored = importCer(json); // parse + verify (throws on tamper)
139
139
  | `modelVersion` | Optional | `string \| null` | Defaults to `null` |
140
140
  | `parameters.topP` | Optional | `number \| null` | Defaults to `null` |
141
141
  | `parameters.seed` | Optional | `number \| null` | Defaults to `null` |
142
- | `sdkVersion` | Optional | `string \| null` | Defaults to `"0.4.1"` |
142
+ | `sdkVersion` | Optional | `string \| null` | Defaults to `"0.4.2"` |
143
143
  | `appId` | Optional | `string \| null` | Defaults to `null` |
144
144
  | `runId` | Optional | `string \| null` | Workflow run ID |
145
145
  | `stepId` | Optional | `string \| null` | Step identifier within a run |
@@ -180,6 +180,92 @@ Endpoint: `POST {nodeUrl}/api/attest`
180
180
 
181
181
  Attestation verifies internal integrity only. It does not re-run the model or validate the correctness of the AI output.
182
182
 
183
+ ### Attestation Receipt
184
+
185
+ After a successful attestation, you get a normalized `AttestationReceipt`:
186
+
187
+ ```ts
188
+ type AttestationReceipt = {
189
+ attestationId: string;
190
+ certificateHash: string; // sha256:...
191
+ nodeRuntimeHash: string; // sha256:...
192
+ protocolVersion: string;
193
+ nodeId?: string;
194
+ attestedAt?: string; // ISO 8601
195
+ attestorKeyId?: string; // kid of the signing key (v0.5.0+)
196
+ signatureB64Url?: string; // base64url Ed25519 signature (v0.5.0+)
197
+ };
198
+ ```
199
+
200
+ **Recommended one-call integration:**
201
+
202
+ ```ts
203
+ import { certifyAndAttestDecision } from '@nexart/ai-execution';
204
+
205
+ const { bundle, receipt } = await certifyAndAttestDecision(params, {
206
+ nodeUrl: 'https://my-node.example.com',
207
+ apiKey: process.env.NODE_API_KEY!,
208
+ });
209
+ // bundle is the sealed CER, receipt is the normalized attestation proof
210
+ ```
211
+
212
+ **Skip re-attestation when already attested:**
213
+
214
+ ```ts
215
+ import { attestIfNeeded, getAttestationReceipt } from '@nexart/ai-execution';
216
+
217
+ const { receipt } = await attestIfNeeded(bundle, options);
218
+ // or just read without network call:
219
+ const receipt = getAttestationReceipt(bundle); // null if not yet attested
220
+ ```
221
+
222
+ - `getAttestationReceipt(bundle)` — extracts a normalized receipt from any supported shape (top-level fields or `bundle.meta.attestation`); returns `null` if required fields are missing, never throws
223
+ - `attestIfNeeded(bundle, options)` — skips the node call if a valid receipt is already present; prevents double-attestation
224
+ - `certifyAndAttestDecision(params, options)` — recommended one-call integration: `certifyDecision` + `attest` + normalized receipt
225
+
226
+ ### Signed Receipt Verification (v0.5.0+)
227
+
228
+ After a node signs the receipt, verify it offline without a round-trip:
229
+
230
+ ```ts
231
+ import {
232
+ verifyNodeReceiptSignature,
233
+ verifyBundleAttestation,
234
+ fetchNodeKeys,
235
+ selectNodeKey,
236
+ } from '@nexart/ai-execution';
237
+
238
+ // One-call: fetches node keys, selects correct key, verifies Ed25519 signature
239
+ const result = await verifyBundleAttestation(bundle, {
240
+ nodeUrl: 'https://my-node.example.com',
241
+ });
242
+ // result.ok === true → signature is valid
243
+ // result.code → CerVerifyCode enum value
244
+ // result.details → string[] with failure explanation (when ok=false)
245
+ ```
246
+
247
+ **Verify against a specific key directly:**
248
+
249
+ ```ts
250
+ const result = await verifyNodeReceiptSignature({
251
+ receipt: { attestationId: '...', certificateHash: 'sha256:...', ... },
252
+ signatureB64Url: 'aDEKyu...Q',
253
+ key: { jwk: { kty: 'OKP', crv: 'Ed25519', x: '<base64url pubkey>' } },
254
+ // or: key: { rawB64Url: '<base64url raw 32 bytes>' }
255
+ });
256
+ ```
257
+
258
+ **Failure codes:**
259
+
260
+ | Code | Meaning |
261
+ |---|---|
262
+ | `ATTESTATION_MISSING` | No signed receipt in bundle |
263
+ | `ATTESTATION_KEY_NOT_FOUND` | kid not found in node keys document |
264
+ | `ATTESTATION_INVALID_SIGNATURE` | Ed25519 signature did not verify |
265
+ | `ATTESTATION_KEY_FORMAT_UNSUPPORTED` | Key cannot be decoded (wrong crv, no fields, etc.) |
266
+
267
+ **Node keys document** is fetched from `{nodeUrl}/.well-known/nexart-node.json`. See `SPEC.md` for the full shape.
268
+
183
269
  ### Sanitization and Redaction
184
270
 
185
271
  `sanitizeForAttestation(bundle)` returns a JSON-safe deep clone:
@@ -238,6 +324,13 @@ Fixtures at `fixtures/vectors/` and `fixtures/golden/`. Cross-language implement
238
324
  | Function | Description |
239
325
  |---|---|
240
326
  | `attest(bundle, options)` | Post CER to canonical node (auto-sanitizes) |
327
+ | `certifyAndAttestDecision(params, options)` | One-call: certifyDecision + attest + receipt |
328
+ | `attestIfNeeded(bundle, options)` | Attest only if no receipt already present |
329
+ | `getAttestationReceipt(bundle)` | Extract normalized `AttestationReceipt` or `null` |
330
+ | `verifyNodeReceiptSignature(params)` | Verify an Ed25519-signed receipt offline (v0.5.0+) |
331
+ | `fetchNodeKeys(nodeUrl)` | Fetch `NodeKeysDocument` from `/.well-known/nexart-node.json` (v0.5.0+) |
332
+ | `selectNodeKey(doc, kid?)` | Select a key from a `NodeKeysDocument` by kid or activeKid (v0.5.0+) |
333
+ | `verifyBundleAttestation(bundle, options)` | One-call offline attestation verification (v0.5.0+) |
241
334
  | `sanitizeForAttestation(bundle)` | Remove `undefined` keys, reject BigInt/functions/symbols |
242
335
  | `hasAttestation(bundle)` | Check if bundle already has attestation fields |
243
336
  | `exportCer(bundle)` | Serialize to canonical JSON string |
@@ -258,6 +351,10 @@ Fixtures at `fixtures/vectors/` and `fixtures/golden/`. Cross-language implement
258
351
  | `SCHEMA_ERROR` | Wrong bundleType/version, missing snapshot, non-finite parameters, etc. |
259
352
  | `CANONICALIZATION_ERROR` | `toCanonicalJson` threw during verification |
260
353
  | `UNKNOWN_ERROR` | Catch-all for unclassified failures |
354
+ | `ATTESTATION_MISSING` | No signed receipt found in bundle (v0.5.0+) |
355
+ | `ATTESTATION_KEY_NOT_FOUND` | kid not found in node keys document (v0.5.0+) |
356
+ | `ATTESTATION_INVALID_SIGNATURE` | Ed25519 signature did not verify (v0.5.0+) |
357
+ | `ATTESTATION_KEY_FORMAT_UNSUPPORTED` | Key cannot be decoded (v0.5.0+) |
261
358
 
262
359
  Priority when multiple failures exist: `CANONICALIZATION_ERROR` > `SCHEMA_ERROR` > `INVALID_SHA256_FORMAT` > `CERTIFICATE_HASH_MISMATCH` > `INPUT_HASH_MISMATCH` > `OUTPUT_HASH_MISMATCH` > `SNAPSHOT_HASH_MISMATCH` > `UNKNOWN_ERROR`.
263
360
 
@@ -279,7 +376,9 @@ Priority when multiple failures exist: `CANONICALIZATION_ERROR` > `SCHEMA_ERROR`
279
376
  | v0.2.0 | certifyDecision, RunBuilder, attest, archive, Anthropic, wrapProvider, typed errors, workflow fields |
280
377
  | v0.3.0 | Attestation hardening (hash validation, timeout), `verify` alias, `CerAttestationError.details`, release hygiene |
281
378
  | v0.4.0 | Dual ESM/CJS build, `sanitizeForAttestation`, `hasAttestation`, auto-sanitize in `attest()`, fixed `ERR_PACKAGE_PATH_NOT_EXPORTED` |
282
- | **v0.4.1** | Verification reason codes (`CerVerifyCode`), `code` + `details` on `VerificationResult`, README provenance wording tightened |
379
+ | v0.4.1 | Verification reason codes (`CerVerifyCode`), `code` + `details` on `VerificationResult`, README provenance wording tightened |
380
+ | v0.4.2 | `AttestationReceipt`, `getAttestationReceipt`, `certifyAndAttestDecision`, `attestIfNeeded` |
381
+ | **v0.5.0** | Ed25519 signed receipt verification: `verifyNodeReceiptSignature`, `verifyBundleAttestation`, `fetchNodeKeys`, `selectNodeKey`; new attestation `CerVerifyCode` entries; `SPEC.md`; `NodeKeysDocument`, `SignedAttestationReceipt`, `NodeReceiptVerifyResult` types |
283
382
  | v1.0.0 | Planned: API stabilization, freeze public API surface |
284
383
 
285
384
  ## Releasing