@nexart/ai-execution 0.7.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 +85 -7
- package/dist/index.cjs +58 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +149 -1
- package/dist/index.d.ts +149 -1
- package/dist/index.mjs +56 -1
- package/dist/index.mjs.map +1 -1
- package/dist/providers/anthropic.cjs +1 -1
- package/dist/providers/anthropic.cjs.map +1 -1
- package/dist/providers/anthropic.mjs +1 -1
- package/dist/providers/anthropic.mjs.map +1 -1
- package/dist/providers/openai.cjs +1 -1
- package/dist/providers/openai.cjs.map +1 -1
- package/dist/providers/openai.mjs +1 -1
- package/dist/providers/openai.mjs.map +1 -1
- package/dist/providers/wrap.cjs +1 -1
- package/dist/providers/wrap.cjs.map +1 -1
- package/dist/providers/wrap.mjs +1 -1
- package/dist/providers/wrap.mjs.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @nexart/ai-execution v0.
|
|
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,7 +7,7 @@ Tamper-evident records and Certified Execution Records (CER) for AI operations.
|
|
|
7
7
|
| Component | Version |
|
|
8
8
|
|---|---|
|
|
9
9
|
| Service | — |
|
|
10
|
-
| SDK | 0.
|
|
10
|
+
| SDK | 0.8.0 |
|
|
11
11
|
| Protocol | 1.2.0 |
|
|
12
12
|
|
|
13
13
|
## Why Not Just Store Logs?
|
|
@@ -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.
|
|
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 |
|
|
@@ -442,6 +442,49 @@ const result = validateProfile(bundle, 'AIEF_L4');
|
|
|
442
442
|
// { ok: boolean, errors: string[] }
|
|
443
443
|
```
|
|
444
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
|
+
|
|
445
488
|
## Redaction Semantics (v0.7.0+)
|
|
446
489
|
|
|
447
490
|
### Pre-seal verifiable redaction
|
|
@@ -467,11 +510,43 @@ Each redacted field becomes `{ _redacted: true, hash: "sha256:..." }` where `has
|
|
|
467
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 |
|
|
468
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`. |
|
|
469
512
|
|
|
470
|
-
###
|
|
513
|
+
### Verifiable redacted export (post-seal, new bundle)
|
|
471
514
|
|
|
472
|
-
`
|
|
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
|
|
473
548
|
|
|
474
|
-
|
|
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.
|
|
475
550
|
|
|
476
551
|
## Canonical JSON Constraints
|
|
477
552
|
|
|
@@ -515,6 +590,7 @@ Fixtures at `fixtures/vectors/` and `fixtures/golden/`. Cross-language implement
|
|
|
515
590
|
|---|---|
|
|
516
591
|
| `RunBuilder` | Multi-step workflow builder with prevStepHash chaining |
|
|
517
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. |
|
|
518
594
|
|
|
519
595
|
### AIEF Interop (v0.7.0+)
|
|
520
596
|
|
|
@@ -525,6 +601,7 @@ Fixtures at `fixtures/vectors/` and `fixtures/golden/`. Cross-language implement
|
|
|
525
601
|
| `hashToolOutput(value)` | Hash a tool output value: string → UTF-8 SHA-256; other → canonical JSON SHA-256 |
|
|
526
602
|
| `makeToolEvent(params)` | Build a `ToolEvent` record for inclusion in `snapshot.toolCalls` |
|
|
527
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. |
|
|
528
605
|
| `validateProfile(target, profile)` | Validate a snapshot or bundle against an AIEF strictness profile (does not affect hashing) |
|
|
529
606
|
|
|
530
607
|
### Attestation & Archive
|
|
@@ -607,7 +684,8 @@ Priority when multiple failures exist: `CANONICALIZATION_ERROR` > `SCHEMA_ERROR`
|
|
|
607
684
|
| v0.4.2 | `AttestationReceipt`, `getAttestationReceipt`, `certifyAndAttestDecision`, `attestIfNeeded` |
|
|
608
685
|
| v0.5.0 | Ed25519 signed receipt verification: `verifyNodeReceiptSignature`, `verifyBundleAttestation`, `fetchNodeKeys`, `selectNodeKey`; new attestation `CerVerifyCode` entries; `SPEC.md`; `NodeKeysDocument`, `SignedAttestationReceipt`, `NodeReceiptVerifyResult` types |
|
|
609
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 |
|
|
610
|
-
|
|
|
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 |
|
|
611
689
|
| v1.0.0 | Planned: API stabilization, freeze public API surface |
|
|
612
690
|
|
|
613
691
|
## Releasing
|
package/dist/index.cjs
CHANGED
|
@@ -37,6 +37,7 @@ __export(src_exports, {
|
|
|
37
37
|
attest: () => attest,
|
|
38
38
|
attestIfNeeded: () => attestIfNeeded,
|
|
39
39
|
certifyAndAttestDecision: () => certifyAndAttestDecision,
|
|
40
|
+
certifyAndAttestRun: () => certifyAndAttestRun,
|
|
40
41
|
certifyDecision: () => certifyDecision,
|
|
41
42
|
certifyDecisionFromProviderCall: () => certifyDecisionFromProviderCall,
|
|
42
43
|
computeInputHash: () => computeInputHash,
|
|
@@ -44,6 +45,7 @@ __export(src_exports, {
|
|
|
44
45
|
createClient: () => createClient,
|
|
45
46
|
createSnapshot: () => createSnapshot,
|
|
46
47
|
exportCer: () => exportCer,
|
|
48
|
+
exportVerifiableRedacted: () => exportVerifiableRedacted,
|
|
47
49
|
fetchNodeKeys: () => fetchNodeKeys,
|
|
48
50
|
getAttestationReceipt: () => getAttestationReceipt,
|
|
49
51
|
hasAttestation: () => hasAttestation,
|
|
@@ -189,7 +191,7 @@ function computeOutputHash(output) {
|
|
|
189
191
|
}
|
|
190
192
|
|
|
191
193
|
// src/snapshot.ts
|
|
192
|
-
var PACKAGE_VERSION = "0.
|
|
194
|
+
var PACKAGE_VERSION = "0.8.0";
|
|
193
195
|
function validateParameters(params) {
|
|
194
196
|
const errors = [];
|
|
195
197
|
if (typeof params.temperature !== "number" || !Number.isFinite(params.temperature)) {
|
|
@@ -2485,6 +2487,59 @@ function validateProfile(target, profile) {
|
|
|
2485
2487
|
}
|
|
2486
2488
|
return { ok: errors.length === 0, errors };
|
|
2487
2489
|
}
|
|
2490
|
+
|
|
2491
|
+
// src/exportRedacted.ts
|
|
2492
|
+
function exportVerifiableRedacted(bundle, policy, options) {
|
|
2493
|
+
const createdAt = options?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
2494
|
+
const redactedSnapshot = redactBeforeSeal(bundle.snapshot, policy);
|
|
2495
|
+
const provenance = {
|
|
2496
|
+
originalCertificateHash: bundle.certificateHash,
|
|
2497
|
+
redactionPolicy: { paths: [...policy.paths] },
|
|
2498
|
+
redactedAt: createdAt
|
|
2499
|
+
};
|
|
2500
|
+
const mergedMeta = {
|
|
2501
|
+
...bundle.meta,
|
|
2502
|
+
provenance
|
|
2503
|
+
};
|
|
2504
|
+
const newBundle = sealCer(redactedSnapshot, {
|
|
2505
|
+
createdAt,
|
|
2506
|
+
meta: mergedMeta,
|
|
2507
|
+
declaration: bundle.declaration
|
|
2508
|
+
});
|
|
2509
|
+
return {
|
|
2510
|
+
bundle: newBundle,
|
|
2511
|
+
originalCertificateHash: bundle.certificateHash
|
|
2512
|
+
};
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2515
|
+
// src/runHelper.ts
|
|
2516
|
+
async function certifyAndAttestRun(steps, options) {
|
|
2517
|
+
const run = new RunBuilder({
|
|
2518
|
+
runId: options?.runId,
|
|
2519
|
+
workflowId: options?.workflowId,
|
|
2520
|
+
conversationId: options?.conversationId,
|
|
2521
|
+
appId: options?.appId
|
|
2522
|
+
});
|
|
2523
|
+
const stepBundles = [];
|
|
2524
|
+
const receipts = [];
|
|
2525
|
+
for (const stepParams of steps) {
|
|
2526
|
+
const bundle = run.step(stepParams);
|
|
2527
|
+
stepBundles.push(bundle);
|
|
2528
|
+
if (options?.attestStep) {
|
|
2529
|
+
const receipt = await options.attestStep(bundle);
|
|
2530
|
+
receipts.push(receipt);
|
|
2531
|
+
} else {
|
|
2532
|
+
receipts.push(null);
|
|
2533
|
+
}
|
|
2534
|
+
}
|
|
2535
|
+
const runSummary = run.finalize();
|
|
2536
|
+
return {
|
|
2537
|
+
runSummary,
|
|
2538
|
+
stepBundles,
|
|
2539
|
+
receipts,
|
|
2540
|
+
finalStepHash: runSummary.finalStepHash
|
|
2541
|
+
};
|
|
2542
|
+
}
|
|
2488
2543
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2489
2544
|
0 && (module.exports = {
|
|
2490
2545
|
CerAttestationError,
|
|
@@ -2494,6 +2549,7 @@ function validateProfile(target, profile) {
|
|
|
2494
2549
|
attest,
|
|
2495
2550
|
attestIfNeeded,
|
|
2496
2551
|
certifyAndAttestDecision,
|
|
2552
|
+
certifyAndAttestRun,
|
|
2497
2553
|
certifyDecision,
|
|
2498
2554
|
certifyDecisionFromProviderCall,
|
|
2499
2555
|
computeInputHash,
|
|
@@ -2501,6 +2557,7 @@ function validateProfile(target, profile) {
|
|
|
2501
2557
|
createClient,
|
|
2502
2558
|
createSnapshot,
|
|
2503
2559
|
exportCer,
|
|
2560
|
+
exportVerifiableRedacted,
|
|
2504
2561
|
fetchNodeKeys,
|
|
2505
2562
|
getAttestationReceipt,
|
|
2506
2563
|
hasAttestation,
|