npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@nexart/ai-execution

v0.5.0

Published

AI Execution Integrity — tamper-evident records and Certified Execution Records (CER) for AI operations

Downloads

627

Readme

@nexart/ai-execution v0.5.0

Tamper-evident records and Certified Execution Records (CER) for AI operations.

Why Not Just Store Logs?

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.

What This Does

This package creates integrity records for AI executions. Every time you call an AI model, it captures:

  • What you sent (input + prompt)
  • What you got back (output)
  • The exact parameters used (temperature, model, etc.)
  • SHA-256 hashes of everything for tamper detection

These records can be verified offline to detect any post-hoc modification and prove integrity of the recorded execution.

Important: This does NOT promise that an AI model will produce the same output twice, and it does not verify provider or model identity. LLMs are not deterministic. This package provides integrity and auditability — proof that the recorded input, output, and parameters have not been modified, and chain-of-custody for the execution record.

Compatibility Guarantees

  • 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.
  • Hashing rules are frozen for cer.ai.execution.v1. The canonicalization, SHA-256 computation, and certificate hash inputs (bundleType, version, createdAt, snapshot) are unchanged.
  • 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.
  • 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.

Installation

npm install @nexart/ai-execution

Quick Start

Single Decision (3 lines)

import { certifyDecision } from '@nexart/ai-execution';

const cer = certifyDecision({
  provider: 'openai',
  model: 'gpt-4o',
  prompt: 'Summarize.',
  input: userQuery,
  output: llmResponse,
  parameters: { temperature: 0.7, maxTokens: 1024, topP: null, seed: null },
});
console.log(cer.certificateHash); // "sha256:..."

Manual Snapshot + Seal

import { createSnapshot, sealCer, verify } from '@nexart/ai-execution';

const snapshot = createSnapshot({
  executionId: 'exec-001',
  provider: 'openai',
  model: 'gpt-4o',
  prompt: 'You are a helpful assistant.',
  input: 'What is 2+2?',
  parameters: { temperature: 0.7, maxTokens: 1024, topP: null, seed: null },
  output: 'The answer is 4.',
});

const bundle = sealCer(snapshot);
const result = verify(bundle);
console.log(result.ok); // true

Agentic Multi-Step Workflow

import { RunBuilder } from '@nexart/ai-execution';

const run = new RunBuilder({ runId: 'analysis-run', workflowId: 'data-pipeline' });

run.step({
  provider: 'openai', model: 'gpt-4o',
  prompt: 'Plan the analysis.',
  input: 'Analyze Q1 sales data.',
  output: 'I will: 1) load data, 2) compute totals, 3) summarize.',
  parameters: { temperature: 0.3, maxTokens: 512, topP: null, seed: null },
});

run.step({
  provider: 'openai', model: 'gpt-4o',
  prompt: 'Execute step 1.',
  input: 'Load and total Q1 data.',
  output: 'Total revenue: $1.2M.',
  parameters: { temperature: 0.3, maxTokens: 512, topP: null, seed: null },
});

const summary = run.finalize();
// { runId, stepCount: 2, steps: [...], finalStepHash: "sha256:..." }

Attest to Canonical Node

import { certifyDecision, attest } from '@nexart/ai-execution';

const cer = certifyDecision({ /* ... */ });
const proof = await attest(cer, {
  nodeUrl: 'https://node.nexart.io',
  apiKey: process.env.NEXART_API_KEY!,
});
console.log(proof.attestationId);

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.

Archive (Export / Import)

import { exportCer, importCer } from '@nexart/ai-execution';

const json = exportCer(bundle);       // canonical JSON string
const restored = importCer(json);     // parse + verify (throws on tamper)

Snapshot Format (ai.execution.v1)

Required vs Optional Fields

| Field | Required | Type | Notes | |---|---|---|---| | executionId | Yes | string | Caller-supplied unique ID | | provider | Yes | string | e.g. "openai", "anthropic" | | model | Yes | string | e.g. "gpt-4o" | | prompt | Yes | string | System prompt | | input | Yes | string \| object | User input (text or structured) | | output | Yes | string \| object | Model output (text or structured) | | parameters.temperature | Yes | number | Must be finite | | parameters.maxTokens | Yes | number | Must be finite | | timestamp | Optional | string | ISO 8601; defaults to new Date().toISOString() | | modelVersion | Optional | string \| null | Defaults to null | | parameters.topP | Optional | number \| null | Defaults to null | | parameters.seed | Optional | number \| null | Defaults to null | | sdkVersion | Optional | string \| null | Defaults to "0.4.2" | | appId | Optional | string \| null | Defaults to null | | runId | Optional | string \| null | Workflow run ID | | stepId | Optional | string \| null | Step identifier within a run | | stepIndex | Optional | number \| null | 0-based step position | | workflowId | Optional | string \| null | Workflow template ID | | conversationId | Optional | string \| null | Conversation/session ID | | prevStepHash | Optional | string \| null | certificateHash of previous step |

Auto-generated fields (set by createSnapshot, do not set manually): type, protocolVersion, executionSurface, inputHash, outputHash.

CER Bundle Format

{
  "bundleType": "cer.ai.execution.v1",
  "certificateHash": "sha256:...",
  "createdAt": "2026-02-12T00:00:00.000Z",
  "version": "0.1",
  "snapshot": { ... },
  "meta": { "source": "my-app", "tags": ["production"] }
}

Certificate Hash Computation

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.

Attestation

Endpoint: POST {nodeUrl}/api/attest

  • Authorization: Bearer {apiKey}
  • Body: the full CER bundle as JSON (auto-sanitized via sanitizeForAttestation in v0.4.0+)
  • Returns: AttestationResult with attestationId, nodeRuntimeHash, certificateHash, protocolVersion
  • Default timeout: 10 seconds (configurable via timeoutMs)
  • Validates: response certificateHash matches submitted bundle; all hashes in sha256:<64hex> format
  • Throws: CerAttestationError on mismatch, network error, timeout, or HTTP error

Attestation verifies internal integrity only. It does not re-run the model or validate the correctness of the AI output.

Attestation Receipt

After a successful attestation, you get a normalized AttestationReceipt:

type AttestationReceipt = {
  attestationId: string;
  certificateHash: string;    // sha256:...
  nodeRuntimeHash: string;    // sha256:...
  protocolVersion: string;
  nodeId?: string;
  attestedAt?: string;        // ISO 8601
  attestorKeyId?: string;     // kid of the signing key (v0.5.0+)
  signatureB64Url?: string;   // base64url Ed25519 signature (v0.5.0+)
};

Recommended one-call integration:

import { certifyAndAttestDecision } from '@nexart/ai-execution';

const { bundle, receipt } = await certifyAndAttestDecision(params, {
  nodeUrl: 'https://my-node.example.com',
  apiKey: process.env.NODE_API_KEY!,
});
// bundle is the sealed CER, receipt is the normalized attestation proof

Skip re-attestation when already attested:

import { attestIfNeeded, getAttestationReceipt } from '@nexart/ai-execution';

const { receipt } = await attestIfNeeded(bundle, options);
// or just read without network call:
const receipt = getAttestationReceipt(bundle); // null if not yet attested
  • 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
  • attestIfNeeded(bundle, options) — skips the node call if a valid receipt is already present; prevents double-attestation
  • certifyAndAttestDecision(params, options) — recommended one-call integration: certifyDecision + attest + normalized receipt

Signed Receipt Verification (v0.5.0+)

After a node signs the receipt, verify it offline without a round-trip:

import {
  verifyNodeReceiptSignature,
  verifyBundleAttestation,
  fetchNodeKeys,
  selectNodeKey,
} from '@nexart/ai-execution';

// One-call: fetches node keys, selects correct key, verifies Ed25519 signature
const result = await verifyBundleAttestation(bundle, {
  nodeUrl: 'https://my-node.example.com',
});
// result.ok === true  → signature is valid
// result.code         → CerVerifyCode enum value
// result.details      → string[] with failure explanation (when ok=false)

Verify against a specific key directly:

const result = await verifyNodeReceiptSignature({
  receipt: { attestationId: '...', certificateHash: 'sha256:...', ... },
  signatureB64Url: 'aDEKyu...Q',
  key: { jwk: { kty: 'OKP', crv: 'Ed25519', x: '<base64url pubkey>' } },
  // or: key: { rawB64Url: '<base64url raw 32 bytes>' }
});

Failure codes:

| Code | Meaning | |---|---| | ATTESTATION_MISSING | No signed receipt in bundle | | ATTESTATION_KEY_NOT_FOUND | kid not found in node keys document | | ATTESTATION_INVALID_SIGNATURE | Ed25519 signature did not verify | | ATTESTATION_KEY_FORMAT_UNSUPPORTED | Key cannot be decoded (wrong crv, no fields, etc.) |

Node keys document is fetched from {nodeUrl}/.well-known/nexart-node.json. See SPEC.md for the full shape.

Sanitization and Redaction

sanitizeForAttestation(bundle) returns a JSON-safe deep clone:

  • Removes keys with undefined values at all nesting levels
  • Rejects BigInt, functions, and symbols (throws)
  • Safe to serialize with JSON.stringify or canonical JSON

Recommended redaction pattern: delete keys or set them to null — never set to undefined, which is not valid JSON. Call sanitizeForAttestation before archiving or attesting if your bundle may contain undefined values.

Skip re-attestation: use hasAttestation(bundle) to check if a bundle already includes attestation fields before calling attest() again.

Canonical JSON Constraints

  1. Object keys sorted lexicographically (Unicode codepoint order) at every nesting level.
  2. No whitespace between tokens.
  3. Array order preserved.
  4. null serialized as null.
  5. Numbers must be finite. NaN, Infinity, -Infinity rejected (throw).
  6. undefined values in object properties omitted (key dropped).
  7. BigInt, functions, Symbol rejected (throw).
  8. Strings JSON-escaped.

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.

Error Types

| Error | When thrown | Structured data | |---|---|---| | CerVerificationError | importCer() on invalid/tampered data | .errors: string[] | | CerAttestationError | attest() on failure | .statusCode, .responseBody, .details: string[] |

Interoperability (Test Vectors)

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.

API Reference

Core

| Function | Description | |---|---| | createSnapshot(params) | Create snapshot with computed hashes | | verifySnapshot(snapshot) | Verify snapshot hashes and structure | | sealCer(snapshot, options?) | Seal snapshot into CER bundle | | verify(bundle) / verifyCer(bundle) | Verify CER bundle | | certifyDecision(params) | One-call: createSnapshot + sealCer |

Workflow

| Export | Description | |---|---| | RunBuilder | Multi-step workflow builder with prevStepHash chaining |

Attestation & Archive

| Function | Description | |---|---| | attest(bundle, options) | Post CER to canonical node (auto-sanitizes) | | certifyAndAttestDecision(params, options) | One-call: certifyDecision + attest + receipt | | attestIfNeeded(bundle, options) | Attest only if no receipt already present | | getAttestationReceipt(bundle) | Extract normalized AttestationReceipt or null | | verifyNodeReceiptSignature(params) | Verify an Ed25519-signed receipt offline (v0.5.0+) | | fetchNodeKeys(nodeUrl) | Fetch NodeKeysDocument from /.well-known/nexart-node.json (v0.5.0+) | | selectNodeKey(doc, kid?) | Select a key from a NodeKeysDocument by kid or activeKid (v0.5.0+) | | verifyBundleAttestation(bundle, options) | One-call offline attestation verification (v0.5.0+) | | sanitizeForAttestation(bundle) | Remove undefined keys, reject BigInt/functions/symbols | | hasAttestation(bundle) | Check if bundle already has attestation fields | | exportCer(bundle) | Serialize to canonical JSON string | | importCer(json) | Parse + verify from JSON string |

Reason Codes

CerVerifyCode — stable string-union constant exported from the package root:

| Code | When set | |---|---| | OK | Verification passed | | CERTIFICATE_HASH_MISMATCH | certificateHash doesn't match recomputed hash | | INPUT_HASH_MISMATCH | inputHash doesn't match recomputed hash | | OUTPUT_HASH_MISMATCH | outputHash doesn't match recomputed hash | | SNAPSHOT_HASH_MISMATCH | Both inputHash and outputHash are wrong | | INVALID_SHA256_FORMAT | A hash field doesn't start with sha256: | | SCHEMA_ERROR | Wrong bundleType/version, missing snapshot, non-finite parameters, etc. | | CANONICALIZATION_ERROR | toCanonicalJson threw during verification | | UNKNOWN_ERROR | Catch-all for unclassified failures | | ATTESTATION_MISSING | No signed receipt found in bundle (v0.5.0+) | | ATTESTATION_KEY_NOT_FOUND | kid not found in node keys document (v0.5.0+) | | ATTESTATION_INVALID_SIGNATURE | Ed25519 signature did not verify (v0.5.0+) | | ATTESTATION_KEY_FORMAT_UNSUPPORTED | Key cannot be decoded (v0.5.0+) |

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.

These codes are stable across all future versions. New codes may be added but existing codes will not be renamed or removed.

Providers (sub-exports)

| Function | Export path | |---|---| | runOpenAIChatExecution | @nexart/ai-execution/providers/openai | | runAnthropicExecution | @nexart/ai-execution/providers/anthropic | | wrapProvider | @nexart/ai-execution/providers/wrap |

Version History

| Version | Description | |---|---| | v0.1.0 | Core snapshot + CER + verify + OpenAI adapter | | v0.2.0 | certifyDecision, RunBuilder, attest, archive, Anthropic, wrapProvider, typed errors, workflow fields | | v0.3.0 | Attestation hardening (hash validation, timeout), verify alias, CerAttestationError.details, release hygiene | | v0.4.0 | Dual ESM/CJS build, sanitizeForAttestation, hasAttestation, auto-sanitize in attest(), fixed ERR_PACKAGE_PATH_NOT_EXPORTED | | v0.4.1 | Verification reason codes (CerVerifyCode), code + details on VerificationResult, README provenance wording tightened | | v0.4.2 | AttestationReceipt, getAttestationReceipt, certifyAndAttestDecision, attestIfNeeded | | v0.5.0 | Ed25519 signed receipt verification: verifyNodeReceiptSignature, verifyBundleAttestation, fetchNodeKeys, selectNodeKey; new attestation CerVerifyCode entries; SPEC.md; NodeKeysDocument, SignedAttestationReceipt, NodeReceiptVerifyResult types | | v1.0.0 | Planned: API stabilization, freeze public API surface |

Releasing

cd packages/ai-execution
npm run build
npm test
npm publish --access public

The release script automates build, test, version bump, and publish:

npm run release

License

MIT