@nexart/ai-execution 0.6.0 → 0.8.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.6.0
1
+ # @nexart/ai-execution v0.8.0
2
2
 
3
3
  Tamper-evident records and Certified Execution Records (CER) for AI operations.
4
4
 
@@ -7,12 +7,12 @@ Tamper-evident records and Certified Execution Records (CER) for AI operations.
7
7
  | Component | Version |
8
8
  |---|---|
9
9
  | Service | — |
10
- | SDK | 0.6.0 |
10
+ | SDK | 0.8.0 |
11
11
  | Protocol | 1.2.0 |
12
12
 
13
13
  ## Why Not Just Store Logs?
14
14
 
15
- Logs tell you what happened. CERs prove integrity. 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 recorded execution has not been modified post-hoc, logs are insufficient. CERs provide the tamper-evident chain of custody that logs cannot. **CERs certify records, not model determinism or provider execution.**
15
+ Logs tell you what happened. CERs prove integrity. 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 hashed fields including input, output, parameters, and any recorded chain/tool evidence — invalidates the certificate hash. If you need to demonstrate to an auditor, regulator, or downstream system that a recorded execution has not been modified post-hoc, logs are insufficient. CERs provide the tamper-evident chain of custody that logs cannot. **CERs certify records, not model determinism or provider execution.**
16
16
 
17
17
  ## What This Does
18
18
 
@@ -105,7 +105,7 @@ const summary = run.finalize();
105
105
  // { runId, stepCount: 2, steps: [...], finalStepHash: "sha256:..." }
106
106
  ```
107
107
 
108
- ### Attest to Canonical Node
108
+ ### Attest to NexArt Attestation Node (optional)
109
109
 
110
110
  ```typescript
111
111
  import { certifyDecision, attest } from '@nexart/ai-execution';
@@ -118,7 +118,7 @@ const proof = await attest(cer, {
118
118
  console.log(proof.attestationId);
119
119
  ```
120
120
 
121
- 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.
121
+ Attestation verifies internal integrity only. It does not re-run the model. The node confirms the bundle's hashes are consistent and returns an independently verifiable signed receipt (when the node is configured for signing).
122
122
 
123
123
  ### Archive (Export / Import)
124
124
 
@@ -147,7 +147,7 @@ const restored = importCer(json); // parse + verify (throws on tamper)
147
147
  | `modelVersion` | Optional | `string \| null` | Defaults to `null` |
148
148
  | `parameters.topP` | Optional | `number \| null` | Defaults to `null` |
149
149
  | `parameters.seed` | Optional | `number \| null` | Defaults to `null` |
150
- | `sdkVersion` | Optional | `string \| null` | Defaults to `"0.4.2"` |
150
+ | `sdkVersion` | Optional | `string \| null` | Defaults to `"0.8.0"` |
151
151
  | `appId` | Optional | `string \| null` | Defaults to `null` |
152
152
  | `runId` | Optional | `string \| null` | Workflow run ID |
153
153
  | `stepId` | Optional | `string \| null` | Step identifier within a run |
@@ -155,6 +155,7 @@ const restored = importCer(json); // parse + verify (throws on tamper)
155
155
  | `workflowId` | Optional | `string \| null` | Workflow template ID |
156
156
  | `conversationId` | Optional | `string \| null` | Conversation/session ID |
157
157
  | `prevStepHash` | Optional | `string \| null` | certificateHash of previous step |
158
+ | `toolCalls` | Optional | `ToolEvent[]` | v0.7.0+ tool/dependency evidence records. **Included in `certificateHash` when present.** See Level 4 section. |
158
159
 
159
160
  Auto-generated fields (set by `createSnapshot`, do not set manually): `type`, `protocolVersion`, `executionSurface`, `inputHash`, `outputHash`.
160
161
 
@@ -166,14 +167,39 @@ Auto-generated fields (set by `createSnapshot`, do not set manually): `type`, `p
166
167
  "certificateHash": "sha256:...",
167
168
  "createdAt": "2026-02-12T00:00:00.000Z",
168
169
  "version": "0.1",
169
- "snapshot": { ... },
170
- "meta": { "source": "my-app", "tags": ["production"] }
170
+ "snapshot": { "..." : "..." },
171
+ "meta": { "source": "my-app", "tags": ["production"] },
172
+ "declaration": {
173
+ "stabilitySchemeId": "nexart-cer-v1",
174
+ "protectedSetId": "ai.execution.v1.full",
175
+ "protectedFields": ["snapshot", "bundleType", "version", "createdAt"],
176
+ "notes": "optional free text"
177
+ }
171
178
  }
172
179
  ```
173
180
 
174
181
  ### Certificate Hash Computation
175
182
 
176
- 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.
183
+ The `certificateHash` is SHA-256 of the UTF-8 bytes of the canonical JSON of exactly: `{ bundleType, version, createdAt, snapshot }`. `meta` and `declaration` are excluded. Key-ordering is recursive. This computation is identical across all SDK versions.
184
+
185
+ ### Declaration Block (v0.7.0+)
186
+
187
+ The optional `declaration` field is a self-describing metadata block for AIEF-02 conformance. It carries `stabilitySchemeId`, `protectedSetId`, and `protectedFields` so that verifiers can confirm which fields are covered by the certificate hash without side-channel knowledge.
188
+
189
+ **`declaration` is excluded from `certificateHash` by design.** It is purely informational — mutating it does not invalidate the bundle. Pass it via `sealCer(snapshot, { declaration: { ... } })`.
190
+
191
+ Verifiers MUST treat `declaration` as advisory. For `cer.ai.execution.v1`, the protected set is defined by the bundleType semantics: `{ bundleType, version, createdAt, snapshot }` is what is hashed.
192
+
193
+ ```typescript
194
+ const bundle = sealCer(snapshot, {
195
+ declaration: {
196
+ stabilitySchemeId: 'nexart-cer-v1',
197
+ protectedSetId: 'ai.execution.v1.full',
198
+ protectedFields: ['snapshot', 'bundleType', 'version', 'createdAt'],
199
+ },
200
+ });
201
+ // verifyCer(bundle).ok === true — declaration does not affect the result
202
+ ```
177
203
 
178
204
  ## Attestation
179
205
 
@@ -285,6 +311,243 @@ const result = await verifyNodeReceiptSignature({
285
311
 
286
312
  **Skip re-attestation:** use `hasAttestation(bundle)` to check if a bundle already includes attestation fields before calling `attest()` again.
287
313
 
314
+ ## AIEF Interop (v0.7.0+)
315
+
316
+ `verifyAief(bundle)` is an adapter over the existing `verifyCer()` / `verify()`. It returns the exact output shape required by AIEF §9.1 for cross-vendor verifier interoperability. The internal `verify()` return value is unchanged — `verifyAief` is purely additive.
317
+
318
+ ```typescript
319
+ import { verifyAief } from '@nexart/ai-execution';
320
+
321
+ const result = verifyAief(bundle);
322
+ // {
323
+ // result: 'PASS' | 'FAIL',
324
+ // reason: string | null, // null on PASS; AIEF §9.2 reason string on FAIL
325
+ // checks: {
326
+ // schemaSupported: boolean, // false if SCHEMA_ERROR or CANONICALIZATION_ERROR
327
+ // integrityValid: boolean, // false if any hash mismatch
328
+ // protectedSetValid: boolean, // false if any hash mismatch (mirrors integrityValid for v1)
329
+ // chainValid: boolean, // false only if CHAIN_BREAK_DETECTED
330
+ // },
331
+ // notes?: string[], // failure detail strings when available
332
+ // }
333
+ ```
334
+
335
+ **What we map / what we don't rename:**
336
+
337
+ | NexArt `CerVerifyCode` | AIEF `reason` |
338
+ |---|---|
339
+ | `OK` | `ok` (reason is `null`) |
340
+ | `CERTIFICATE_HASH_MISMATCH` | `integrityProofMismatch` |
341
+ | `INPUT_HASH_MISMATCH` | `integrityProofMismatch` |
342
+ | `OUTPUT_HASH_MISMATCH` | `integrityProofMismatch` |
343
+ | `TOOL_OUTPUT_HASH_MISMATCH` | `integrityProofMismatch` |
344
+ | `SCHEMA_ERROR` | `unsupportedSchema` |
345
+ | `CANONICALIZATION_ERROR` | `malformedArtifact` |
346
+ | `INVALID_SHA256_FORMAT` | `malformedArtifact` |
347
+ | `UNKNOWN_ERROR` | `malformedArtifact` |
348
+ | `INCOMPLETE_ARTIFACT` | `incompleteArtifact` |
349
+ | `TOOL_EVIDENCE_MISSING` | `incompleteArtifact` |
350
+ | `CHAIN_BREAK_DETECTED` | `chainBreakDetected` |
351
+ | `VERIFICATION_MATERIAL_UNAVAILABLE` | `verificationMaterialUnavailable` |
352
+ | `ATTESTATION_MISSING` | `verificationMaterialUnavailable` |
353
+ | `ATTESTATION_KEY_NOT_FOUND` | `verificationMaterialUnavailable` |
354
+ | `ATTESTATION_KEY_FORMAT_UNSUPPORTED` | `verificationMaterialUnavailable` |
355
+ | `ATTESTATION_INVALID_SIGNATURE` | `signatureInvalid` |
356
+
357
+ The AIEF reason strings are not renamed or mapped to NexArt-specific vocabulary — they are passed through verbatim for AIEF conformance. Per AIEF §9.0 rule #7, `chainValid` is `true` when chain fields are absent from the snapshot.
358
+
359
+ `mapToAiefReason(code: string): string` is also exported if you need to convert a `CerVerifyCode` to an AIEF reason string directly. Unknown codes fall back to `"malformedArtifact"`.
360
+
361
+ ## Level 4 (Optional): Chain + Tool Evidence (v0.7.0+)
362
+
363
+ > **If you do nothing, nothing changes.** All Level 4 features are additive and opt-in. Existing bundles without `toolCalls` verify identically.
364
+
365
+ ### Tool Calls
366
+
367
+ Add a `toolCalls` array to the snapshot to record evidence of external tool or dependency invocations. When present, `toolCalls` is included in the `certificateHash` computation — tool evidence is part of the sealed record.
368
+
369
+ `toolCalls` provide tamper-evident evidence of what was recorded about a tool/dependency call. They do not prove the external tool actually executed unless the tool itself provides independent verifiable proof (e.g., its own signed receipt).
370
+
371
+ ```typescript
372
+ import { makeToolEvent, createSnapshot, sealCer } from '@nexart/ai-execution';
373
+
374
+ const webResult = await fetch('https://api.example.com/data');
375
+ const data = await webResult.json();
376
+
377
+ const toolEvent = makeToolEvent({
378
+ toolId: 'web-search',
379
+ output: data, // hashed automatically
380
+ input: { query: 'Q1 revenue' }, // optional; hashed if provided
381
+ evidenceRef: 'https://api.example.com/data', // optional URL/ID
382
+ });
383
+
384
+ const snapshot = createSnapshot({
385
+ // ...
386
+ toolCalls: [toolEvent],
387
+ });
388
+ const bundle = sealCer(snapshot);
389
+ ```
390
+
391
+ `ToolEvent` shape:
392
+
393
+ | Field | Required | Description |
394
+ |---|---|---|
395
+ | `toolId` | **Yes** | Identifier of the tool/dependency called |
396
+ | `at` | **Yes** | ISO 8601 timestamp (defaults to `new Date()` in `makeToolEvent`) |
397
+ | `outputHash` | **Yes** | `sha256:<64hex>` of the tool output |
398
+ | `inputHash` | No | `sha256:<64hex>` of the tool input (optional) |
399
+ | `evidenceRef` | No | URL or external ID pointing to the raw evidence |
400
+ | `error` | No | Error message if the tool call failed |
401
+
402
+ `hashToolOutput(value)` hashes a tool output value: strings → SHA-256 of UTF-8 bytes; anything else → SHA-256 of canonical JSON bytes.
403
+
404
+ ### Chain Verification
405
+
406
+ `verifyRunSummary(summary, bundles, opts?)` validates that a `RunBuilder` multi-step run forms an unbroken cryptographic chain. It detects insertion, deletion, and reordering of steps.
407
+
408
+ ```typescript
409
+ import { RunBuilder, verifyRunSummary } from '@nexart/ai-execution';
410
+
411
+ const run = new RunBuilder({ runId: 'my-run' });
412
+ run.step({ /* step 0 */ });
413
+ run.step({ /* step 1 */ });
414
+
415
+ const summary = run.finalize();
416
+ const bundles = run.getBundles(); // or retrieve from storage
417
+
418
+ const result = verifyRunSummary(summary, bundles);
419
+ // { ok: boolean, code: CerVerifyCode, errors: string[], breakAt?: number }
420
+ ```
421
+
422
+ `verifyRunSummary` returns `RunSummaryVerifyResult`:
423
+ - `ok: true` — full chain is valid
424
+ - `ok: false` with `INCOMPLETE_ARTIFACT` — step count mismatch
425
+ - `ok: false` with `CHAIN_BREAK_DETECTED` — stepIndex/prevStepHash/certificateHash mismatch; `breakAt` is the index of the first broken link
426
+
427
+ ### Profiles (Opt-in Strictness)
428
+
429
+ `validateProfile(target, profile)` applies extra field-presence checks at creation time. It never affects `certificateHash` or `verifyCer()`.
430
+
431
+ | Profile | What it enforces |
432
+ |---|---|
433
+ | `'flexible'` | No extra validation (default SDK behaviour) |
434
+ | `'AIEF_L2'` | AIEF-01 required fields: `executionId`, `timestamp`, `provider`, `model`, `input`, `output`, `inputHash`, `outputHash` |
435
+ | `'AIEF_L3'` | Same as `AIEF_L2` |
436
+ | `'AIEF_L4'` | AIEF_L3 + validates each `ToolEvent` in `toolCalls`; requires `prevStepHash` when `stepIndex > 0` |
437
+
438
+ ```typescript
439
+ import { validateProfile } from '@nexart/ai-execution';
440
+
441
+ const result = validateProfile(bundle, 'AIEF_L4');
442
+ // { ok: boolean, errors: string[] }
443
+ ```
444
+
445
+ ## Opinionated Run Helper
446
+
447
+ `certifyAndAttestRun(steps, options?)` combines RunBuilder step creation, optional per-step attestation, and finalization into a single call. It does not change `RunBuilder` semantics — it creates an internal RunBuilder and returns all artifacts together.
448
+
449
+ ```typescript
450
+ import { certifyAndAttestRun, verifyRunSummary, attest } from '@nexart/ai-execution';
451
+
452
+ const { runSummary, stepBundles, receipts, finalStepHash } =
453
+ await certifyAndAttestRun(
454
+ [step0Params, step1Params, step2Params],
455
+ {
456
+ runId: 'analysis-run',
457
+ workflowId: 'data-pipeline',
458
+ // Optional: attest each step immediately after sealing
459
+ attestStep: (bundle) => attest(bundle, { nodeUrl, apiKey }),
460
+ },
461
+ );
462
+
463
+ verifyRunSummary(runSummary, stepBundles); // { ok: true }
464
+ ```
465
+
466
+ **Return shape:**
467
+
468
+ | Field | Type | Description |
469
+ |---|---|---|
470
+ | `runSummary` | `RunSummary` | From `RunBuilder.finalize()` — stepCount, steps, finalStepHash |
471
+ | `stepBundles` | `CerAiExecutionBundle[]` | Sealed bundles in step order (index 0 = step 0) |
472
+ | `receipts` | `(AttestationReceipt \| null)[]` | Attestation receipts in step order; `null` if `attestStep` was not provided |
473
+ | `finalStepHash` | `string \| null` | Alias for `runSummary.finalStepHash` |
474
+
475
+ **Testing without network:** inject a mock `attestStep` to test the full flow without hitting a node:
476
+
477
+ ```typescript
478
+ const { runSummary, stepBundles, receipts } = await certifyAndAttestRun(steps, {
479
+ attestStep: async (bundle) => ({
480
+ attestationId: 'mock-' + bundle.certificateHash.slice(7, 15),
481
+ certificateHash: bundle.certificateHash,
482
+ nodeRuntimeHash: 'sha256:' + 'a'.repeat(64),
483
+ protocolVersion: '1.2.0',
484
+ }),
485
+ });
486
+ ```
487
+
488
+ ## Redaction Semantics (v0.7.0+)
489
+
490
+ ### Pre-seal verifiable redaction
491
+
492
+ `redactBeforeSeal(snapshot, policy)` replaces sensitive snapshot fields with stable envelopes **before** sealing. Because the `certificateHash` is computed over the already-redacted snapshot, the resulting bundle passes `verifyCer()` unchanged.
493
+
494
+ ```typescript
495
+ import { redactBeforeSeal, sealCer, verify } from '@nexart/ai-execution';
496
+
497
+ const redacted = redactBeforeSeal(snapshot, { paths: ['input', 'output'] });
498
+ const bundle = sealCer(redacted);
499
+ verify(bundle).ok; // true — hash matches the redacted snapshot
500
+ ```
501
+
502
+ Each redacted field becomes `{ _redacted: true, hash: "sha256:..." }` where `hash` is the SHA-256 of the original value. This lets authorized reviewers confirm what was there without accessing the raw content.
503
+
504
+ **Supported fields for pre-seal redaction:**
505
+
506
+ | Field | Supported | Notes |
507
+ |---|---|---|
508
+ | `input` | **Yes** | `inputHash` is recomputed from the envelope |
509
+ | `output` | **Yes** | `outputHash` is recomputed from the envelope |
510
+ | `toolCalls[n].output` *(via `ToolEvent.outputHash`)* | **Yes** | The `outputHash` already stores only the hash — the raw tool output need not be in the bundle at all |
511
+ | `prompt` and other schema-validated strings | **No** | `verifySnapshot` validates these as non-empty strings. Replacing with an object envelope causes `verifyCer()` to return `SCHEMA_ERROR`. |
512
+
513
+ ### Verifiable redacted export (post-seal, new bundle)
514
+
515
+ `exportVerifiableRedacted(bundle, policy, options?)` is the right choice when you already have a sealed bundle and want to share a sanitized version that is still independently verifiable. It:
516
+
517
+ 1. Applies `redactBeforeSeal()` to the original snapshot
518
+ 2. Re-seals the redacted snapshot into a **new** bundle with a **new** `certificateHash`
519
+ 3. Stores the original `certificateHash` in `meta.provenance.originalCertificateHash` as an informational cross-reference
520
+
521
+ ```typescript
522
+ import { certifyDecision, exportVerifiableRedacted, verify } from '@nexart/ai-execution';
523
+
524
+ const original = certifyDecision({ ... });
525
+
526
+ const { bundle, originalCertificateHash } = exportVerifiableRedacted(
527
+ original,
528
+ { paths: ['input', 'output'] },
529
+ );
530
+
531
+ verify(bundle).ok; // true — new bundle verifies
532
+ bundle.certificateHash !== original.certificateHash; // true — different hash
533
+ bundle.meta.provenance.originalCertificateHash; // 'sha256:...' — reference only
534
+ bundle.snapshot.input; // { _redacted: true, hash: 'sha256:...' }
535
+ ```
536
+
537
+ **Provenance semantics:** `meta.provenance.originalCertificateHash` is reference metadata only. It is not part of the new `certificateHash` computation. Recipients of the new bundle cannot verify the original bundle's integrity from it — they can only confirm what hash the original had. If you need to prove the original's integrity, keep the original bundle available alongside the redacted one.
538
+
539
+ **Rule of thumb for choosing a redaction approach:**
540
+
541
+ | Approach | `verify()` passes | Original hash preserved | Notes |
542
+ |---|---|---|---|
543
+ | `redactBeforeSeal(snapshot, policy)` + `sealCer()` | ✅ | N/A — no original exists yet | Use when building a redacted bundle from scratch |
544
+ | `exportVerifiableRedacted(bundle, policy)` | ✅ | Reference only in `meta.provenance` | Use when you have a sealed bundle and need a shareable sanitized copy |
545
+ | `sanitizeForStorage(bundle, options)` | ❌ | N/A — hash broken by design | Use only for storage; do not pass result to `verify()` |
546
+
547
+ ### Post-hoc redaction breaks integrity — by design
548
+
549
+ `sanitizeForStorage(bundle, options)` can replace arbitrary paths with a string placeholder, but it does **not** recompute any content hashes. This means `verifyCer()` will return `CERTIFICATE_HASH_MISMATCH` on the result. This is intentional: post-hoc redaction is a lossy, storage-only operation. If you need a verifiable record after storage-side redaction, use `exportVerifiableRedacted` instead.
550
+
288
551
  ## Canonical JSON Constraints
289
552
 
290
553
  1. Object keys sorted lexicographically (Unicode codepoint order) at every nesting level.
@@ -326,6 +589,20 @@ Fixtures at `fixtures/vectors/` and `fixtures/golden/`. Cross-language implement
326
589
  | Export | Description |
327
590
  |---|---|
328
591
  | `RunBuilder` | Multi-step workflow builder with prevStepHash chaining |
592
+ | `verifyRunSummary(summary, bundles, opts?)` | Verify that a RunSummary + step bundles form an unbroken cryptographic chain (v0.7.0+) |
593
+ | `certifyAndAttestRun(steps, options?)` | One-call: create RunBuilder internally, certify all steps, optionally attest each bundle, return `{ runSummary, stepBundles, receipts, finalStepHash }`. Injectable `attestStep` for mocking. |
594
+
595
+ ### AIEF Interop (v0.7.0+)
596
+
597
+ | Function | Description |
598
+ |---|---|
599
+ | `verifyAief(bundle)` | Verify a CER bundle and return the exact AIEF §9.1 output shape |
600
+ | `mapToAiefReason(code)` | Convert a `CerVerifyCode` string to an AIEF §9.2 reason string |
601
+ | `hashToolOutput(value)` | Hash a tool output value: string → UTF-8 SHA-256; other → canonical JSON SHA-256 |
602
+ | `makeToolEvent(params)` | Build a `ToolEvent` record for inclusion in `snapshot.toolCalls` |
603
+ | `redactBeforeSeal(snapshot, policy)` | Pre-seal redaction: replace `input`/`output` with verifiable envelopes before sealing |
604
+ | `exportVerifiableRedacted(bundle, policy, options?)` | Post-seal: produce a new sealed bundle with redacted snapshot + `meta.provenance.originalCertificateHash`. `verify()` passes on the new bundle. Original is unchanged. |
605
+ | `validateProfile(target, profile)` | Validate a snapshot or bundle against an AIEF strictness profile (does not affect hashing) |
329
606
 
330
607
  ### Attestation & Archive
331
608
 
@@ -377,6 +654,11 @@ Fixtures at `fixtures/vectors/` and `fixtures/golden/`. Cross-language implement
377
654
  | `ATTESTATION_KEY_NOT_FOUND` | kid not found in node keys document (v0.5.0+) |
378
655
  | `ATTESTATION_INVALID_SIGNATURE` | Ed25519 signature did not verify (v0.5.0+) |
379
656
  | `ATTESTATION_KEY_FORMAT_UNSUPPORTED` | Key cannot be decoded (v0.5.0+) |
657
+ | `CHAIN_BREAK_DETECTED` | `verifyRunSummary` detected a broken prevStepHash link or reordered step (v0.7.0+) |
658
+ | `INCOMPLETE_ARTIFACT` | Step count mismatch between RunSummary and provided bundles (v0.7.0+) |
659
+ | `VERIFICATION_MATERIAL_UNAVAILABLE` | Required verification material (keys, receipt) is absent (v0.7.0+) |
660
+ | `TOOL_EVIDENCE_MISSING` | Required tool call evidence absent in an AIEF_L4 context (v0.7.0+) |
661
+ | `TOOL_OUTPUT_HASH_MISMATCH` | A recorded `outputHash` in `toolCalls` does not match the provided tool output (v0.7.0+) |
380
662
 
381
663
  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`.
382
664
 
@@ -401,7 +683,9 @@ Priority when multiple failures exist: `CANONICALIZATION_ERROR` > `SCHEMA_ERROR`
401
683
  | v0.4.1 | Verification reason codes (`CerVerifyCode`), `code` + `details` on `VerificationResult`, README provenance wording tightened |
402
684
  | v0.4.2 | `AttestationReceipt`, `getAttestationReceipt`, `certifyAndAttestDecision`, `attestIfNeeded` |
403
685
  | v0.5.0 | Ed25519 signed receipt verification: `verifyNodeReceiptSignature`, `verifyBundleAttestation`, `fetchNodeKeys`, `selectNodeKey`; new attestation `CerVerifyCode` entries; `SPEC.md`; `NodeKeysDocument`, `SignedAttestationReceipt`, `NodeReceiptVerifyResult` types |
404
- | **v0.6.0** | Frictionless integration: `certifyDecisionFromProviderCall` (OpenAI/Anthropic/Gemini/Mistral/Bedrock drop-in); `sanitizeForStorage` + `sanitizeForStamp` redaction helpers; `createClient(defaults)` factory; regression fixture suite; all backward-compatible, no hash changes |
686
+ | v0.6.0 | Frictionless integration: `certifyDecisionFromProviderCall` (OpenAI/Anthropic/Gemini/Mistral/Bedrock drop-in); `sanitizeForStorage` + `sanitizeForStamp` redaction helpers; `createClient(defaults)` factory; regression fixture suite; all backward-compatible, no hash changes |
687
+ | v0.7.0 | AIEF alignment: `verifyAief()` (AIEF §9.1 adapter); `verifyRunSummary()` chain verifier; `makeToolEvent()` + `hashToolOutput()` + `snapshot.toolCalls` (AIEF-06); `BundleDeclaration` block (`stabilitySchemeId`, `protectedSetId`, `protectedFields`) excluded from `certificateHash`; `redactBeforeSeal()` pre-seal verifiable redaction; `validateProfile()` (flexible/AIEF_L2/L3/L4); 5 new `CerVerifyCode` entries; backward-compatible, no hash changes |
688
+ | **v0.8.0** | Helper APIs: `exportVerifiableRedacted()` (post-seal re-seal with redacted snapshot + provenance metadata); `certifyAndAttestRun()` (one-call multi-step certify + optional per-step attestation with injectable mock); test determinism fix (no time-dependent first-run failures); all v0.1–v0.7 bundles verify identically, no hashing or canonicalization changes |
405
689
  | v1.0.0 | Planned: API stabilization, freeze public API surface |
406
690
 
407
691
  ## Releasing