@veriproof/sdk-core
@veriproof/sdk-core is the TypeScript foundation for VeriProof instrumentation. It handles OpenTelemetry configuration, governance session helpers, transport reliability, Merkle utilities, and optional payload encryption. Framework adapters (LangChain.js, Vercel AI SDK, etc.) build on top of this package.
Compatibility: Node.js ≥ 18, TypeScript ≥ 5.0, OpenTelemetry JS ≥ 2.1
Installation
npm install @veriproof/sdk-coreWith a framework adapter:
npm install @veriproof/sdk-core @veriproof/sdk-instrumentation-ai-sdk aiSetup
configureVeriproof
Call this once at process startup, before any AI framework initializes:
import { configureVeriproof, VeriproofSdkOptions } from '@veriproof/sdk-core';
const { provider, exporter } = configureVeriproof(
VeriproofSdkOptions.createDevelopment({
apiKey: process.env.VERIPROOF_API_KEY!,
applicationId: 'your-application-id',
}),
{
serviceName: 'your-application-id',
setGlobal: true, // registers as the global TracerProvider
},
);After this call, any OTel span created in the process — by your code or by any adapter — is automatically exported to VeriProof.
ConfigureVeriproofOptions
| Option | Type | Default | Description |
|---|---|---|---|
serviceName | string | required | Sets service.name on the OTel resource |
setGlobal | boolean | false | Register as the global OTel TracerProvider |
batchExport | boolean | true | Use BatchSpanProcessor (recommended for production) |
resourceAttributes | Record<string, ...> | — | Additional OTel resource attributes |
tracerProvider | TracerProvider | — | Provide an existing provider instead of creating one |
VeriproofSdkOptions factory
// Development / sandbox
VeriproofSdkOptions.createDevelopment({ apiKey, applicationId, tenantId? })
// Production
VeriproofSdkOptions.createProduction({ apiKey, applicationId, tenantId? })
// From environment variables: reads VERIPROOF_API_KEY and VERIPROOF_APPLICATION_ID
veriproofClientOptionsFromEnv()Building sessions
Use VeriproofClient to construct and emit governance session traces. The client provides a fluent builder API:
import {
VeriproofClient,
VeriproofSdkOptions,
DecisionContext,
SessionMetadata,
StepTags,
} from '@veriproof/sdk-core';
const client = new VeriproofClient(
VeriproofSdkOptions.createProduction({ apiKey: process.env.VERIPROOF_API_KEY!, applicationId: 'loan-underwriting' }),
);
const session = client
.startSession('loan-decision')
.withSessionMetadata(
SessionMetadata.forTransaction('txn-4421').withEnvironment('production'),
)
.addStep('retrieve_credit_profile', { output: { score: 742 } }, StepTags.retrieval())
.addChatTurn(
'Should we approve applicant #4421?',
'Approve with standard terms — strong credit and acceptable leverage.',
'gpt-4o',
)
.setDecision(
DecisionContext.withOptions(
'Loan decision',
['approve', 'deny', 'manual-review'],
'approve',
'Score 742 exceeds threshold; no recent derogatory marks.',
0.93,
),
);
await session.complete();VeriproofSession methods
| Method | Returns | Description |
|---|---|---|
.addStep(name, data, tags?) | this | Record a discrete processing step |
.addChatTurn(userMsg, assistantMsg, model?) | this | Record one LLM conversation turn |
.setDecision(decision) | this | Attach a DecisionContext or string description |
.setOutcome(outcome) | this | Set a SessionOutcome on the root span |
.setRiskLevel(level) | this | Set a RiskLevel on the root span |
.addRiskFactor(factor) | this | Append a RiskFactor observation |
.setDescription(text) | this | Free-form description for this session |
.setExpectedOutcome(text) | this | Expected outcome for A/B or regression comparison |
.setBaselineSessionId(id) | this | Link to a baseline session for comparison |
.withTags(...tags) | this | Attach string tags to the root span |
.withMetadata(key, value) | this | Attach a veriproof.metadata.* attribute to the most recent step |
.withTransactionType(type) | this | Domain-specific transaction label |
.withSessionMetadata(meta) | this | Attach a SessionMetadata object |
.complete() | Promise<SessionCompletionResult> | Flush and seal the session trace |
DecisionContext
// Decision with options
DecisionContext.withOptions(
label, // e.g. "Loan decision"
options, // e.g. ['approve', 'deny', 'manual-review']
selected, // e.g. 'approve'
reasoning?, // explanatory text
confidence?, // 0.0 – 1.0
)
// Simple decision
DecisionContext.withScore(label, score, threshold?)Enumerations
| Enum | Members |
|---|---|
SessionOutcome | Unknown, Success, Failure, Escalated, Cancelled, Partial |
RiskLevel | Low, Medium, High, Critical |
RiskSeverity | Low, Medium, High, Critical |
StepType | Unknown, LlmCall, ToolCall, Retrieval, Decision, UserInteraction, Validation, Output, Error, AgentDelegation |
DataSensitivity | Public, Internal, Sensitive, Pii, Restricted |
DataFormat | Text, Json, Markdown, Html, Binary, Audio, Image, Pdf |
VeriproofEnvironment | Local, Development, Staging, Production |
Merkle utilities
import { computeMerkleRootHex, MerkleTree } from '@veriproof/sdk-core';
const tree = new MerkleTree();
tree.addLeaf({ step: 'retrieve_credit_profile', score: 742 });
tree.addLeaf({ step: 'evaluate_rule_set', passed: true });
tree.addLeaf({ decision: 'approve', confidence: 0.93 });
const root = tree.getRootHex(); // SHA-256 Merkle root (hex)
const proof = tree.generateProof(2); // proof for the third leafMerkleTree methods:
| Method | Description |
|---|---|
.addLeaf(data) | Hash and append a leaf (accepts object, string, or Uint8Array) |
.getRootHex() | Return the hex Merkle root |
.generateProof(index) | Return a MerkleProof for the leaf at index |
computeMerkleRootHex(leaves) — standalone function that computes the root from an array of strings.
Transport
The VeriproofIngestTransport handles HTTP communication with the ingest API. It includes per-endpoint circuit breaking, overflow buffering, and retry with exponential backoff.
import { VeriproofIngestTransport } from '@veriproof/sdk-core';
const transport = new VeriproofIngestTransport(options);
await transport.submit(payload); // single payload
await transport.submitBatch(payloads); // multiple payloads
const healthy = transport.isHealthy(); // false when circuit is open
await transport.forceFlush(); // drain retry buffer immediately
await transport.shutdown(); // graceful shutdownAccess metrics via transport.metrics → TransportMetrics (export counts, circuit breaker state, last error/success timestamp).
Circuit breaker behavior
| State | Behavior |
|---|---|
| Closed (normal) | Exports proceed; failures trigger retries |
| Open (failing) | Exports buffered; no outbound calls made |
| Half-open (recovering) | One probe export sent; success closes the circuit |
Data encryption
DataEncryptionService provides AES-256-GCM authenticated encryption. Use it when you need to protect sensitive span fields before they leave the process.
import { DataEncryptionService } from '@veriproof/sdk-core';
const masterKey = DataEncryptionService.generateKeyHex();
const svc = new DataEncryptionService(masterKey);
const encrypted = svc.encrypt('sensitive data', 'session-context');
const plain = svc.decryptToString(encrypted, 'session-context');DataEncryptionService API:
| Method | Description |
|---|---|
new DataEncryptionService(key) | Construct with a ≥16-byte master key (raw bytes, 64-char hex, or base64) |
.encrypt(plaintext, context?) | Encrypt; optional HKDF context derives an independent sub-key |
.decrypt(data, context?) | Decrypt; throws if GCM auth tag doesn’t match |
.decryptToString(data, context?) | Decrypt and return as UTF-8 string |
.deriveKey(options) | HKDF-SHA256 sub-key derivation |
DataEncryptionService.serialize(data) | Compact iv.ciphertext.authTag (base64url) encoding |
DataEncryptionService.deserialize(compact) | Inverse of serialize |
DataEncryptionService.generateKey() | Generate a cryptographically random 32-byte key |
DataEncryptionService.generateKeyHex() | Generate a random 32-byte key as a 64-char hex string |
Overflow buffering
LogBuffer<T> is a typed ring buffer with configurable overflow behavior, used internally by the transport layer and available for custom use:
import { LogBuffer, BufferFullMode } from '@veriproof/sdk-core';
const buf = new LogBuffer<string>(100, BufferFullMode.DropOldest);
buf.write('span-abc'); // returns { accepted: true, dropped: [] }
const items = buf.drain(); // removes and returns all items atomically
const snap = buf.snapshot(); // non-destructive copy
buf.prependMany(['priority-item']); // insert at frontBufferFullMode.DropOldest evicts the oldest item when at capacity. BufferFullMode.DropWrite rejects the new item.
OTel compatibility
If your application already exports spans to another backend (Datadog, Honeycomb, etc.), you can add VeriProof as a second processor on the same TracerProvider:
import { configureVeriproof } from '@veriproof/sdk-core';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
const { provider } = configureVeriproof(options, { serviceName: 'your-app', setGlobal: true });
// Add your secondary exporter on the returned provider
provider.addSpanProcessor(new SimpleSpanProcessor(new OTLPTraceExporter({
url: 'https://api.honeycomb.io/v1/traces',
})));FAQ
No spans are exported — what do I check first?
Make sure setGlobal: true is set in ConfigureVeriproofOptions, or that you are passing the returned provider explicitly to your OTel wiring. Without this, spans created by framework adapters may not reach the VeriProof exporter.
Can I call configureVeriproof more than once?
No — the second call logs a warning and is ignored. The first call wins. Ensure your telemetry bootstrap file loads before any adapter.
Does the SDK work with Deno or Bun?
Node.js ≥ 18 is the tested runtime. Bun compatibility is experimental. Deno is not currently supported.
Next steps
- SDK Overview — adapter package selection guide
- Using Multiple Adapters — combine LangChain.js, Vercel AI SDK, and others
- SDK Troubleshooting — diagnose missing spans and export failures