zansn 0.1.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/CHANGELOG.md ADDED
@@ -0,0 +1,16 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ Initial public developer starter kit.
6
+
7
+ - Added local-first evaluation runner.
8
+ - Added Zansn evidence-pack schema `zansn.evidence-pack.v0`.
9
+ - Added task, trace, finding, policy, usage, and readiness types.
10
+ - Added tool allow/block policy checks.
11
+ - Added configurable sensitive-data pattern checks.
12
+ - Added cost and latency policy checks.
13
+ - Added JSON and Markdown evidence-pack exporters.
14
+ - Added evidence-pack validation.
15
+ - Added OpenTelemetry-compatible attribute mapping.
16
+
package/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ https://www.apache.org/licenses/
4
+
5
+ Copyright 2026 Zansn Labs
6
+
7
+ Licensed under the Apache License, Version 2.0 (the "License");
8
+ you may not use this file except in compliance with the License.
9
+ You may obtain a copy of the License at
10
+
11
+ https://www.apache.org/licenses/LICENSE-2.0
12
+
13
+ Unless required by applicable law or agreed to in writing, software
14
+ distributed under the License is distributed on an "AS IS" BASIS,
15
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ See the License for the specific language governing permissions and
17
+ limitations under the License.
18
+
package/README.md ADDED
@@ -0,0 +1,346 @@
1
+ # zansn
2
+
3
+ <p align="center">
4
+ <a href="https://zansn.ai">
5
+ <img src="./assets/zansn-logo.png" alt="Zansn Labs logo" width="96" height="96">
6
+ </a>
7
+ </p>
8
+
9
+ Local-first AI assurance primitives for evaluating agent workflows and producing portable evidence packs.
10
+
11
+ Website: https://zansn.ai
12
+
13
+ Zansn Labs builds AI assurance infrastructure for agentic, data-sensitive, and scientific AI workflows. `zansn` is the open-source developer entry point: it runs locally, captures agent-aware traces, checks basic policy and privacy boundaries, profiles cost and latency, and returns a structured evidence artifact that can be versioned, reviewed, or later ingested by commercial assurance infrastructure.
14
+
15
+ ## Why Developers Use It
16
+
17
+ Agent demos are easy. Deployment evidence is hard.
18
+
19
+ Use `zansn` when you need to answer:
20
+
21
+ - Did the agent complete the task?
22
+ - Which tools did it call?
23
+ - Did it call a blocked or unexpected tool?
24
+ - Did output cross a configured privacy boundary?
25
+ - What did the run cost?
26
+ - How long did it take?
27
+ - Which findings block deployment?
28
+ - Can this run be attached to a pull request, experiment record, or review workflow?
29
+
30
+ ## Install
31
+
32
+ ```bash
33
+ npm install zansn
34
+ ```
35
+
36
+ Requirements:
37
+
38
+ - Node.js 18 or newer
39
+ - ESM project or bundler-compatible TypeScript setup
40
+
41
+ ## Run the Example
42
+
43
+ After installation, run the included example:
44
+
45
+ ```bash
46
+ node node_modules/zansn/examples/basic-evaluation.mjs
47
+ ```
48
+
49
+ The example runs a local evaluation and prints a Markdown evidence pack.
50
+
51
+ ## Quick Start
52
+
53
+ ```ts
54
+ import {
55
+ ZansnClient,
56
+ exportEvidencePackMarkdown,
57
+ validateEvidencePack
58
+ } from "zansn";
59
+
60
+ const zansn = new ZansnClient({
61
+ defaultPolicy: {
62
+ allowedTools: ["lookupCustomer"],
63
+ blockedTools: ["deleteCustomer"],
64
+ sensitiveDataPatterns: [/sk-[a-z0-9]+/i],
65
+ maxEstimatedCostUsd: 0.05,
66
+ maxLatencyMs: 5_000,
67
+ minimumScore: 1
68
+ }
69
+ });
70
+
71
+ const evidence = await zansn.runEvaluation({
72
+ projectId: "ZAN-RD-001",
73
+ experimentId: "ZAN-EXP-001",
74
+ name: "Customer support agent readiness check",
75
+ tasks: [
76
+ {
77
+ id: "support-001",
78
+ input: "Check whether ACME has an active support plan.",
79
+ expected: "active"
80
+ }
81
+ ],
82
+ execute: async (task, context) => {
83
+ context.recordEvent({
84
+ type: "agent",
85
+ name: "agent.plan",
86
+ attributes: {
87
+ taskInput: task.input,
88
+ step: "lookup_customer_status"
89
+ }
90
+ });
91
+
92
+ return {
93
+ output: "active",
94
+ toolCalls: [
95
+ {
96
+ name: "lookupCustomer",
97
+ arguments: { customer: "ACME" },
98
+ result: { plan: "active" },
99
+ status: "ok"
100
+ }
101
+ ],
102
+ usage: {
103
+ inputTokens: 140,
104
+ outputTokens: 32,
105
+ estimatedCostUsd: 0.004
106
+ }
107
+ };
108
+ }
109
+ });
110
+
111
+ const validation = validateEvidencePack(evidence);
112
+ if (!validation.valid) {
113
+ throw new Error(validation.errors.join("\n"));
114
+ }
115
+
116
+ console.log(evidence.summary);
117
+ console.log(exportEvidencePackMarkdown(evidence));
118
+ ```
119
+
120
+ ## Evidence Pack
121
+
122
+ `runEvaluation()` returns a portable evidence artifact:
123
+
124
+ ```ts
125
+ {
126
+ schema: "zansn.evidence-pack.v0",
127
+ runId: "zan-run-...",
128
+ projectId: "ZAN-RD-001",
129
+ experimentId: "ZAN-EXP-001",
130
+ name: "Customer support agent readiness check",
131
+ summary: {
132
+ tasks: 1,
133
+ passed: 1,
134
+ failed: 0,
135
+ blocked: 0,
136
+ passRate: 1,
137
+ averageScore: 1,
138
+ totalEstimatedCostUsd: 0.004,
139
+ totalTokens: 172,
140
+ p50LatencyMs: 14,
141
+ p95LatencyMs: 14,
142
+ highestSeverity: "info",
143
+ readiness: "ready"
144
+ },
145
+ taskResults: [],
146
+ traces: [],
147
+ findings: []
148
+ }
149
+ ```
150
+
151
+ Readiness values:
152
+
153
+ - `ready`: no blocking findings and score meets policy.
154
+ - `review`: non-blocking findings or score below policy threshold.
155
+ - `blocked`: high/critical findings, privacy boundary failures, blocked tool use, or runtime failure.
156
+
157
+ ## API
158
+
159
+ ### `ZansnClient`
160
+
161
+ ```ts
162
+ import { ZansnClient } from "zansn";
163
+
164
+ const client = new ZansnClient({
165
+ organization: "Zansn Labs",
166
+ defaultPolicy: {
167
+ allowedTools: ["search", "lookup"],
168
+ blockedTools: ["deleteUser"],
169
+ sensitiveDataPatterns: [/api_key/i],
170
+ maxEstimatedCostUsd: 0.10,
171
+ maxLatencyMs: 10_000,
172
+ minimumScore: 0.8
173
+ },
174
+ metadata: {
175
+ environment: "ci"
176
+ }
177
+ });
178
+ ```
179
+
180
+ ### `client.runEvaluation(options)`
181
+
182
+ Runs a local task set against any agent, model wrapper, workflow, or function.
183
+
184
+ ```ts
185
+ const evidence = await client.runEvaluation({
186
+ projectId: "ZAN-RD-002",
187
+ tasks: [
188
+ {
189
+ id: "task-001",
190
+ input: "Summarise policy",
191
+ expected: "approved"
192
+ }
193
+ ],
194
+ execute: async () => ({ output: "approved" })
195
+ });
196
+ ```
197
+
198
+ `execute` can return either raw output or a structured result:
199
+
200
+ ```ts
201
+ return {
202
+ output: "approved",
203
+ toolCalls: [{ name: "lookupPolicy", status: "ok" }],
204
+ usage: { inputTokens: 50, outputTokens: 10, estimatedCostUsd: 0.001 },
205
+ traces: [{ type: "model", name: "model.call" }],
206
+ metadata: { provider: "local" }
207
+ };
208
+ ```
209
+
210
+ ### `context.recordEvent(event)`
211
+
212
+ Records agent-aware trace events during execution.
213
+
214
+ ```ts
215
+ context.recordEvent({
216
+ type: "retrieval",
217
+ name: "documents.search",
218
+ attributes: {
219
+ query: "deployment policy"
220
+ }
221
+ });
222
+ ```
223
+
224
+ ### `validateEvidencePack(pack)`
225
+
226
+ Checks the structural contract before storing or exporting.
227
+
228
+ ```ts
229
+ const result = validateEvidencePack(evidence);
230
+ ```
231
+
232
+ ### `exportEvidencePackJson(pack)`
233
+
234
+ Exports stable JSON with deterministic object-key ordering.
235
+
236
+ ```ts
237
+ import { exportEvidencePackJson } from "zansn";
238
+
239
+ const json = exportEvidencePackJson(evidence);
240
+ ```
241
+
242
+ ### `exportEvidencePackMarkdown(pack)`
243
+
244
+ Exports a human-readable local review report.
245
+
246
+ ```ts
247
+ import { exportEvidencePackMarkdown } from "zansn";
248
+
249
+ const report = exportEvidencePackMarkdown(evidence);
250
+ ```
251
+
252
+ ### `toOpenTelemetryAttributes(event)`
253
+
254
+ Maps a Zansn trace event to attributes suitable for attaching to OpenTelemetry spans.
255
+
256
+ ```ts
257
+ import { toOpenTelemetryAttributes } from "zansn";
258
+
259
+ const attributes = toOpenTelemetryAttributes(evidence.traces[0]);
260
+ ```
261
+
262
+ ### `createEvidenceRecord(input)`
263
+
264
+ Creates a standalone immutable evidence record for research notes, experiment logs, or review trails.
265
+
266
+ ```ts
267
+ import { createEvidenceRecord } from "zansn";
268
+
269
+ const record = createEvidenceRecord({
270
+ projectId: "ZAN-RD-017",
271
+ summary: "Evidence graph schema drafted"
272
+ });
273
+ ```
274
+
275
+ ## What This Package Is
276
+
277
+ `zansn` is intentionally:
278
+
279
+ - local-first
280
+ - vendor-neutral
281
+ - framework-agnostic
282
+ - TypeScript-native
283
+ - evidence-pack oriented
284
+ - safe to use without a Zansn account
285
+ - suitable for CI, prototypes, research notes, and Assurance Sprint-style workflows
286
+
287
+ ## What This Package Is Not
288
+
289
+ This package is not the full commercial Zansn Assurance Control Plane.
290
+
291
+ It does not include:
292
+
293
+ - hosted evidence graph
294
+ - team workspaces
295
+ - SSO, RBAC, audit logs, or retention controls
296
+ - premium attack packs
297
+ - long-running orchestration
298
+ - managed trace storage
299
+ - deployment approval workflows
300
+ - geospatial benchmark libraries
301
+ - enterprise reporting templates
302
+
303
+ Those belong in future commercial or separately packaged modules. The open-source boundary is deliberate: this package creates portable local evidence artifacts, while future Zansn products can ingest, govern, compare, visualise, and operationalise those artifacts.
304
+
305
+ ## Package Boundary
306
+
307
+ The stable contract is the evidence pack:
308
+
309
+ ```ts
310
+ schema: "zansn.evidence-pack.v0"
311
+ ```
312
+
313
+ Future Zansn Cloud, private deployments, or enterprise products should build around this artifact rather than forcing developers to rewrite local evaluation code.
314
+
315
+ ## Development
316
+
317
+ ```bash
318
+ npm install
319
+ npm run typecheck
320
+ npm test
321
+ npm run build
322
+ npm run release:check
323
+ ```
324
+
325
+ ## Publish
326
+
327
+ `zansn` is an unscoped public npm package. Run the release check first, then publish from an authenticated npm account:
328
+
329
+ ```bash
330
+ npm run release:check
331
+ npm publish
332
+ ```
333
+
334
+ ## Security
335
+
336
+ `zansn` runs locally and does not transmit data to Zansn Labs.
337
+
338
+ Do not put production secrets, private customer data, or sensitive traces into public examples or issue reports. See `SECURITY.md`.
339
+
340
+ ## Source Availability
341
+
342
+ The source repository is currently private while the public SDK, examples, and package boundary mature. Public support and security contact are routed through https://zansn.ai/contact. This package is published with source maps omitted and a minimal runtime tarball.
343
+
344
+ ## License
345
+
346
+ Apache-2.0
package/SECURITY.md ADDED
@@ -0,0 +1,15 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ `zansn` is pre-1.0 software. Security fixes will target the latest published minor version.
6
+
7
+ ## Reporting a Vulnerability
8
+
9
+ Report security issues through https://zansn.ai/contact.
10
+
11
+ Do not include secrets, private customer data, or sensitive production traces in public issues or examples.
12
+
13
+ ## Scope
14
+
15
+ `zansn` runs locally and does not transmit data to Zansn Labs. Future hosted or enterprise products should have separate security documentation, data-retention controls, and disclosure channels.
Binary file
@@ -0,0 +1,180 @@
1
+ declare const ZANSN_EVIDENCE_PACK_SCHEMA: "zansn.evidence-pack.v0";
2
+ declare const ZANSN_EVIDENCE_RECORD_SCHEMA: "zansn.evidence-record.v0";
3
+ type ZansnSeverity = "info" | "low" | "medium" | "high" | "critical";
4
+ type ZansnFindingKind = "evaluation" | "trace" | "security" | "privacy" | "cost" | "latency" | "runtime";
5
+ type ZansnReadiness = "ready" | "review" | "blocked";
6
+ type ZansnTraceType = "task" | "agent" | "model" | "tool" | "retrieval" | "security" | "privacy" | "metric";
7
+ type ZansnTask<TInput = unknown, TExpected = unknown> = {
8
+ id: string;
9
+ input: TInput;
10
+ expected?: TExpected;
11
+ metadata?: ZansnMetadata;
12
+ };
13
+ type ZansnAgentResult<TOutput = unknown> = {
14
+ output: TOutput;
15
+ toolCalls?: ZansnToolCall[];
16
+ usage?: ZansnUsage;
17
+ traces?: ZansnTraceInput[];
18
+ metadata?: ZansnMetadata;
19
+ };
20
+ type ZansnToolCall = {
21
+ id?: string;
22
+ name: string;
23
+ arguments?: unknown;
24
+ result?: unknown;
25
+ status?: "ok" | "error" | "blocked";
26
+ startedAt?: string;
27
+ endedAt?: string;
28
+ metadata?: ZansnMetadata;
29
+ };
30
+ type ZansnUsage = {
31
+ inputTokens?: number;
32
+ outputTokens?: number;
33
+ totalTokens?: number;
34
+ estimatedCostUsd?: number;
35
+ };
36
+ type ZansnTraceInput = {
37
+ type: ZansnTraceType;
38
+ name: string;
39
+ startedAt?: string;
40
+ endedAt?: string;
41
+ durationMs?: number;
42
+ attributes?: ZansnMetadata;
43
+ };
44
+ type ZansnTraceEvent = {
45
+ id: string;
46
+ runId: string;
47
+ taskId: string;
48
+ type: ZansnTraceType;
49
+ name: string;
50
+ startedAt: string;
51
+ endedAt?: string;
52
+ durationMs?: number;
53
+ attributes: ZansnMetadata;
54
+ };
55
+ type ZansnFinding = {
56
+ id: string;
57
+ kind: ZansnFindingKind;
58
+ severity: ZansnSeverity;
59
+ taskId?: string;
60
+ title: string;
61
+ detail: string;
62
+ evidence?: ZansnMetadata;
63
+ };
64
+ type ZansnPolicy = {
65
+ allowedTools?: string[];
66
+ blockedTools?: string[];
67
+ sensitiveDataPatterns?: RegExp[];
68
+ maxEstimatedCostUsd?: number;
69
+ maxLatencyMs?: number;
70
+ minimumScore?: number;
71
+ };
72
+ type ZansnRunOptions<TInput, TOutput, TExpected> = {
73
+ projectId: string;
74
+ experimentId?: string;
75
+ name?: string;
76
+ description?: string;
77
+ tasks: ZansnTask<TInput, TExpected>[];
78
+ policy?: ZansnPolicy;
79
+ metadata?: ZansnMetadata;
80
+ execute: (task: ZansnTask<TInput, TExpected>, context: ZansnRunContext) => Promise<ZansnAgentResult<TOutput> | TOutput>;
81
+ score?: (args: {
82
+ task: ZansnTask<TInput, TExpected>;
83
+ result: ZansnAgentResult<TOutput>;
84
+ }) => Promise<number> | number;
85
+ };
86
+ type ZansnRunContext = {
87
+ runId: string;
88
+ taskId: string;
89
+ recordEvent: (event: ZansnTraceInput) => ZansnTraceEvent;
90
+ };
91
+ type ZansnTaskResult<TOutput = unknown> = {
92
+ taskId: string;
93
+ passed: boolean;
94
+ score: number;
95
+ readiness: ZansnReadiness;
96
+ output?: TOutput;
97
+ error?: string;
98
+ durationMs: number;
99
+ usage: Required<ZansnUsage>;
100
+ findings: ZansnFinding[];
101
+ metadata: ZansnMetadata;
102
+ };
103
+ type ZansnEvidenceSummary = {
104
+ tasks: number;
105
+ passed: number;
106
+ failed: number;
107
+ blocked: number;
108
+ passRate: number;
109
+ averageScore: number;
110
+ totalEstimatedCostUsd: number;
111
+ totalTokens: number;
112
+ p50LatencyMs: number;
113
+ p95LatencyMs: number;
114
+ highestSeverity: ZansnSeverity;
115
+ readiness: ZansnReadiness;
116
+ };
117
+ type ZansnEvidencePack<TOutput = unknown> = {
118
+ schema: typeof ZANSN_EVIDENCE_PACK_SCHEMA;
119
+ runId: string;
120
+ projectId: string;
121
+ experimentId: string;
122
+ name: string;
123
+ description?: string;
124
+ createdAt: string;
125
+ completedAt: string;
126
+ metadata: ZansnMetadata;
127
+ summary: ZansnEvidenceSummary;
128
+ taskResults: ZansnTaskResult<TOutput>[];
129
+ traces: ZansnTraceEvent[];
130
+ findings: ZansnFinding[];
131
+ };
132
+ type ZansnEvidenceRecord = {
133
+ schema: typeof ZANSN_EVIDENCE_RECORD_SCHEMA;
134
+ id: string;
135
+ projectId: string;
136
+ experimentId: string;
137
+ createdAt: string;
138
+ summary: string;
139
+ metadata: ZansnMetadata;
140
+ };
141
+ type ZansnClientOptions = {
142
+ organization?: string;
143
+ defaultPolicy?: ZansnPolicy;
144
+ metadata?: ZansnMetadata;
145
+ };
146
+ type ZansnValidationResult = {
147
+ valid: boolean;
148
+ errors: string[];
149
+ };
150
+ type ZansnMetadata = Record<string, unknown>;
151
+ declare class ZansnClient {
152
+ readonly organization: string;
153
+ readonly defaultPolicy: ZansnPolicy;
154
+ readonly metadata: ZansnMetadata;
155
+ constructor(options?: ZansnClientOptions);
156
+ runEvaluation<TInput = unknown, TOutput = unknown, TExpected = unknown>(options: ZansnRunOptions<TInput, TOutput, TExpected>): Promise<ZansnEvidencePack<TOutput>>;
157
+ }
158
+ declare function createEvidenceRecord(input: {
159
+ projectId: string;
160
+ experimentId?: string;
161
+ summary: string;
162
+ metadata?: ZansnMetadata;
163
+ }): ZansnEvidenceRecord;
164
+ declare function validateEvidencePack(value: unknown): ZansnValidationResult;
165
+ declare function exportEvidencePackJson(pack: ZansnEvidencePack, space?: number): string;
166
+ declare function exportEvidencePackMarkdown(pack: ZansnEvidencePack): string;
167
+ declare function toOpenTelemetryAttributes(event: ZansnTraceEvent): ZansnMetadata;
168
+ declare const zansn: Readonly<{
169
+ brand: "Zansn Labs";
170
+ domain: "https://zansn.ai";
171
+ category: "AI assurance infrastructure";
172
+ package: "zansn";
173
+ status: "local-first developer starter kit";
174
+ schemas: Readonly<{
175
+ evidencePack: "zansn.evidence-pack.v0";
176
+ evidenceRecord: "zansn.evidence-record.v0";
177
+ }>;
178
+ }>;
179
+
180
+ export { ZANSN_EVIDENCE_PACK_SCHEMA, ZANSN_EVIDENCE_RECORD_SCHEMA, type ZansnAgentResult, ZansnClient, type ZansnClientOptions, type ZansnEvidencePack, type ZansnEvidenceRecord, type ZansnEvidenceSummary, type ZansnFinding, type ZansnFindingKind, type ZansnMetadata, type ZansnPolicy, type ZansnReadiness, type ZansnRunContext, type ZansnRunOptions, type ZansnSeverity, type ZansnTask, type ZansnTaskResult, type ZansnToolCall, type ZansnTraceEvent, type ZansnTraceInput, type ZansnTraceType, type ZansnUsage, type ZansnValidationResult, createEvidenceRecord, ZansnClient as default, exportEvidencePackJson, exportEvidencePackMarkdown, toOpenTelemetryAttributes, validateEvidencePack, zansn };
package/dist/index.js ADDED
@@ -0,0 +1,536 @@
1
+ // src/index.ts
2
+ var ZANSN_EVIDENCE_PACK_SCHEMA = "zansn.evidence-pack.v0";
3
+ var ZANSN_EVIDENCE_RECORD_SCHEMA = "zansn.evidence-record.v0";
4
+ var ZansnClient = class {
5
+ organization;
6
+ defaultPolicy;
7
+ metadata;
8
+ constructor(options = {}) {
9
+ this.organization = options.organization ?? "Zansn Labs";
10
+ this.defaultPolicy = options.defaultPolicy ?? {};
11
+ this.metadata = freezeMetadata(options.metadata);
12
+ }
13
+ async runEvaluation(options) {
14
+ assertNonEmptyString(options.projectId, "ZansnClient.runEvaluation requires projectId.");
15
+ if (!Array.isArray(options.tasks) || options.tasks.length === 0) {
16
+ throw new Error("ZansnClient.runEvaluation requires at least one task.");
17
+ }
18
+ const runId = createId("zan-run");
19
+ const experimentId = options.experimentId ?? createId("zan-exp");
20
+ const createdAt = nowIso();
21
+ const policy = mergePolicy(this.defaultPolicy, options.policy);
22
+ const traces = [];
23
+ const findings = [];
24
+ const taskResults = [];
25
+ for (const task of options.tasks) {
26
+ assertNonEmptyString(task.id, "Every Zansn task requires a stable id.");
27
+ const taskStart = performanceNow();
28
+ const taskFindings = [];
29
+ const recordEvent = (event) => {
30
+ const traceEvent = createTraceEvent(runId, task.id, event);
31
+ traces.push(traceEvent);
32
+ return traceEvent;
33
+ };
34
+ recordEvent({
35
+ type: "task",
36
+ name: "zansn.task.start",
37
+ attributes: {
38
+ "zansn.organization": this.organization,
39
+ "zansn.project.id": options.projectId,
40
+ "zansn.experiment.id": experimentId,
41
+ "zansn.task.id": task.id
42
+ }
43
+ });
44
+ let result;
45
+ let runtimeError;
46
+ try {
47
+ const rawResult = await options.execute(task, {
48
+ runId,
49
+ taskId: task.id,
50
+ recordEvent
51
+ });
52
+ result = normalizeAgentResult(rawResult);
53
+ } catch (error) {
54
+ runtimeError = errorToMessage(error);
55
+ result = { output: void 0 };
56
+ taskFindings.push({
57
+ id: createId("zan-finding"),
58
+ kind: "runtime",
59
+ severity: "critical",
60
+ taskId: task.id,
61
+ title: "Agent execution failed",
62
+ detail: runtimeError,
63
+ evidence: { error: runtimeError }
64
+ });
65
+ }
66
+ const durationMs = performanceNow() - taskStart;
67
+ const usage = normalizeUsage(result.usage);
68
+ for (const trace of result.traces ?? []) {
69
+ traces.push(createTraceEvent(runId, task.id, trace));
70
+ }
71
+ for (const toolCall of result.toolCalls ?? []) {
72
+ const toolFinding = evaluateToolPolicy(task.id, toolCall, policy);
73
+ if (toolFinding) {
74
+ taskFindings.push(toolFinding);
75
+ }
76
+ traces.push(toolCallToTrace(runId, task.id, toolCall));
77
+ }
78
+ taskFindings.push(...evaluatePrivacyBoundary(task.id, result, policy));
79
+ taskFindings.push(...evaluateCostAndLatency(task.id, usage, durationMs, policy));
80
+ const score = runtimeError ? 0 : options.score ? await options.score({ task, result }) : defaultExactMatchScore(task.expected, result.output);
81
+ const boundedScore = clampScore(score);
82
+ const minimumScore = policy.minimumScore ?? 1;
83
+ if (boundedScore < minimumScore && !runtimeError) {
84
+ taskFindings.push({
85
+ id: createId("zan-finding"),
86
+ kind: "evaluation",
87
+ severity: "medium",
88
+ taskId: task.id,
89
+ title: "Task score below policy threshold",
90
+ detail: `Task scored ${boundedScore}; configured threshold is ${minimumScore}.`,
91
+ evidence: { score: boundedScore, minimumScore }
92
+ });
93
+ }
94
+ const readiness = determineReadiness(taskFindings, boundedScore, minimumScore);
95
+ const taskResult = {
96
+ taskId: task.id,
97
+ passed: readiness === "ready",
98
+ score: boundedScore,
99
+ readiness,
100
+ durationMs,
101
+ usage,
102
+ findings: taskFindings,
103
+ metadata: freezeMetadata(task.metadata)
104
+ };
105
+ if (runtimeError) {
106
+ taskResult.error = runtimeError;
107
+ } else {
108
+ taskResult.output = result.output;
109
+ }
110
+ taskResults.push(taskResult);
111
+ findings.push(...taskFindings);
112
+ recordEvent({
113
+ type: "task",
114
+ name: "zansn.task.complete",
115
+ attributes: {
116
+ "zansn.task.readiness": readiness,
117
+ "zansn.task.score": boundedScore,
118
+ "zansn.task.duration_ms": durationMs,
119
+ "zansn.findings.count": taskFindings.length
120
+ }
121
+ });
122
+ }
123
+ const pack = {
124
+ schema: ZANSN_EVIDENCE_PACK_SCHEMA,
125
+ runId,
126
+ projectId: options.projectId,
127
+ experimentId,
128
+ name: options.name ?? "Zansn local AI assurance evaluation",
129
+ createdAt,
130
+ completedAt: nowIso(),
131
+ metadata: freezeMetadata({
132
+ ...this.metadata,
133
+ ...options.metadata,
134
+ "zansn.package": "zansn",
135
+ "zansn.mode": "local-first"
136
+ }),
137
+ summary: summarize(taskResults, findings),
138
+ taskResults,
139
+ traces,
140
+ findings
141
+ };
142
+ if (options.description) {
143
+ pack.description = options.description;
144
+ }
145
+ return deepFreeze(pack);
146
+ }
147
+ };
148
+ function createEvidenceRecord(input) {
149
+ assertNonEmptyString(input.projectId, "createEvidenceRecord requires projectId.");
150
+ assertNonEmptyString(input.summary, "createEvidenceRecord requires summary.");
151
+ return deepFreeze({
152
+ schema: ZANSN_EVIDENCE_RECORD_SCHEMA,
153
+ id: createId("zan-ev"),
154
+ projectId: input.projectId,
155
+ experimentId: input.experimentId ?? createId("zan-exp"),
156
+ createdAt: nowIso(),
157
+ summary: input.summary,
158
+ metadata: freezeMetadata(input.metadata)
159
+ });
160
+ }
161
+ function validateEvidencePack(value) {
162
+ const errors = [];
163
+ if (!isRecord(value)) {
164
+ return { valid: false, errors: ["Evidence pack must be an object."] };
165
+ }
166
+ if (value.schema !== ZANSN_EVIDENCE_PACK_SCHEMA) {
167
+ errors.push(`schema must be ${ZANSN_EVIDENCE_PACK_SCHEMA}.`);
168
+ }
169
+ for (const key of ["runId", "projectId", "experimentId", "name", "createdAt", "completedAt"]) {
170
+ if (typeof value[key] !== "string" || value[key].length === 0) {
171
+ errors.push(`${key} must be a non-empty string.`);
172
+ }
173
+ }
174
+ if (!isRecord(value.summary)) {
175
+ errors.push("summary must be an object.");
176
+ }
177
+ if (!Array.isArray(value.taskResults)) {
178
+ errors.push("taskResults must be an array.");
179
+ }
180
+ if (!Array.isArray(value.traces)) {
181
+ errors.push("traces must be an array.");
182
+ }
183
+ if (!Array.isArray(value.findings)) {
184
+ errors.push("findings must be an array.");
185
+ }
186
+ return { valid: errors.length === 0, errors };
187
+ }
188
+ function exportEvidencePackJson(pack, space = 2) {
189
+ return `${stableStringify(pack, space)}
190
+ `;
191
+ }
192
+ function exportEvidencePackMarkdown(pack) {
193
+ const validation = validateEvidencePack(pack);
194
+ if (!validation.valid) {
195
+ throw new Error(`Cannot export invalid Zansn evidence pack: ${validation.errors.join(" ")}`);
196
+ }
197
+ const findings = pack.findings.length === 0 ? "No findings recorded." : pack.findings.map((finding) => `- [${finding.severity}] ${finding.kind}: ${finding.title}${finding.taskId ? ` (${finding.taskId})` : ""}`).join("\n");
198
+ const tasks = pack.taskResults.map((task) => `- ${task.taskId}: ${task.readiness}, score ${formatNumber(task.score)}, ${Math.round(task.durationMs)} ms`).join("\n");
199
+ return [
200
+ `# Zansn Evidence Pack: ${pack.name}`,
201
+ "",
202
+ `Schema: \`${pack.schema}\``,
203
+ `Run ID: \`${pack.runId}\``,
204
+ `Project ID: \`${pack.projectId}\``,
205
+ `Experiment ID: \`${pack.experimentId}\``,
206
+ `Created: ${pack.createdAt}`,
207
+ `Completed: ${pack.completedAt}`,
208
+ "",
209
+ "## Summary",
210
+ "",
211
+ `- Readiness: ${pack.summary.readiness}`,
212
+ `- Tasks: ${pack.summary.tasks}`,
213
+ `- Passed: ${pack.summary.passed}`,
214
+ `- Failed: ${pack.summary.failed}`,
215
+ `- Blocked: ${pack.summary.blocked}`,
216
+ `- Pass rate: ${formatNumber(pack.summary.passRate)}`,
217
+ `- Average score: ${formatNumber(pack.summary.averageScore)}`,
218
+ `- Total tokens: ${pack.summary.totalTokens}`,
219
+ `- Estimated cost USD: ${formatNumber(pack.summary.totalEstimatedCostUsd)}`,
220
+ `- p50 latency ms: ${Math.round(pack.summary.p50LatencyMs)}`,
221
+ `- p95 latency ms: ${Math.round(pack.summary.p95LatencyMs)}`,
222
+ `- Highest severity: ${pack.summary.highestSeverity}`,
223
+ "",
224
+ "## Tasks",
225
+ "",
226
+ tasks || "No tasks recorded.",
227
+ "",
228
+ "## Findings",
229
+ "",
230
+ findings,
231
+ "",
232
+ "## Trace Count",
233
+ "",
234
+ `${pack.traces.length} trace events recorded.`,
235
+ ""
236
+ ].join("\n");
237
+ }
238
+ function toOpenTelemetryAttributes(event) {
239
+ return {
240
+ "service.name": "zansn",
241
+ "zansn.run.id": event.runId,
242
+ "zansn.task.id": event.taskId,
243
+ "zansn.trace.id": event.id,
244
+ "zansn.trace.type": event.type,
245
+ "gen_ai.operation.name": event.type === "tool" ? "execute_tool" : "invoke_agent",
246
+ ...event.attributes
247
+ };
248
+ }
249
+ var zansn = Object.freeze({
250
+ brand: "Zansn Labs",
251
+ domain: "https://zansn.ai",
252
+ category: "AI assurance infrastructure",
253
+ package: "zansn",
254
+ status: "local-first developer starter kit",
255
+ schemas: Object.freeze({
256
+ evidencePack: ZANSN_EVIDENCE_PACK_SCHEMA,
257
+ evidenceRecord: ZANSN_EVIDENCE_RECORD_SCHEMA
258
+ })
259
+ });
260
+ function createTraceEvent(runId, taskId, input) {
261
+ const startedAt = input.startedAt ?? nowIso();
262
+ const event = {
263
+ id: createId("zan-trace"),
264
+ runId,
265
+ taskId,
266
+ type: input.type,
267
+ name: input.name,
268
+ startedAt,
269
+ attributes: freezeMetadata(input.attributes)
270
+ };
271
+ if (input.endedAt) {
272
+ event.endedAt = input.endedAt;
273
+ }
274
+ const durationMs = input.durationMs ?? (input.endedAt ? Date.parse(input.endedAt) - Date.parse(startedAt) : void 0);
275
+ if (durationMs !== void 0 && Number.isFinite(durationMs)) {
276
+ event.durationMs = Math.max(0, durationMs);
277
+ }
278
+ return event;
279
+ }
280
+ function normalizeAgentResult(value) {
281
+ if (isRecord(value) && "output" in value) {
282
+ return value;
283
+ }
284
+ return { output: value };
285
+ }
286
+ function normalizeUsage(usage = {}) {
287
+ const inputTokens = clampNonNegative(usage.inputTokens);
288
+ const outputTokens = clampNonNegative(usage.outputTokens);
289
+ return {
290
+ inputTokens,
291
+ outputTokens,
292
+ totalTokens: clampNonNegative(usage.totalTokens ?? inputTokens + outputTokens),
293
+ estimatedCostUsd: clampNonNegative(usage.estimatedCostUsd)
294
+ };
295
+ }
296
+ function evaluateToolPolicy(taskId, toolCall, policy) {
297
+ const blocked = policy.blockedTools?.includes(toolCall.name);
298
+ const outsideAllowList = policy.allowedTools !== void 0 && !policy.allowedTools.includes(toolCall.name);
299
+ if (!blocked && !outsideAllowList) {
300
+ return void 0;
301
+ }
302
+ return {
303
+ id: createId("zan-finding"),
304
+ kind: "security",
305
+ severity: blocked ? "high" : "medium",
306
+ taskId,
307
+ title: blocked ? "Blocked tool was called" : "Tool call outside allow list",
308
+ detail: `Agent attempted to call tool "${toolCall.name}".`,
309
+ evidence: { toolCall }
310
+ };
311
+ }
312
+ function evaluatePrivacyBoundary(taskId, result, policy) {
313
+ const patterns = policy.sensitiveDataPatterns ?? [];
314
+ if (patterns.length === 0) {
315
+ return [];
316
+ }
317
+ const serialized = stableStringify(result.output);
318
+ return patterns.filter((pattern) => {
319
+ pattern.lastIndex = 0;
320
+ return pattern.test(serialized);
321
+ }).map((pattern) => ({
322
+ id: createId("zan-finding"),
323
+ kind: "privacy",
324
+ severity: "high",
325
+ taskId,
326
+ title: "Sensitive data pattern detected in output",
327
+ detail: "Agent output matched a configured sensitive data boundary pattern.",
328
+ evidence: { pattern: pattern.source }
329
+ }));
330
+ }
331
+ function evaluateCostAndLatency(taskId, usage, durationMs, policy) {
332
+ const findings = [];
333
+ if (policy.maxEstimatedCostUsd !== void 0 && usage.estimatedCostUsd > policy.maxEstimatedCostUsd) {
334
+ findings.push({
335
+ id: createId("zan-finding"),
336
+ kind: "cost",
337
+ severity: "medium",
338
+ taskId,
339
+ title: "Estimated cost exceeded policy",
340
+ detail: `Task estimated cost ${usage.estimatedCostUsd} USD exceeded limit ${policy.maxEstimatedCostUsd} USD.`,
341
+ evidence: { usage }
342
+ });
343
+ }
344
+ if (policy.maxLatencyMs !== void 0 && durationMs > policy.maxLatencyMs) {
345
+ findings.push({
346
+ id: createId("zan-finding"),
347
+ kind: "latency",
348
+ severity: "medium",
349
+ taskId,
350
+ title: "Latency exceeded policy",
351
+ detail: `Task latency ${Math.round(durationMs)} ms exceeded limit ${policy.maxLatencyMs} ms.`,
352
+ evidence: { durationMs }
353
+ });
354
+ }
355
+ return findings;
356
+ }
357
+ function toolCallToTrace(runId, taskId, toolCall) {
358
+ const input = {
359
+ type: "tool",
360
+ name: toolCall.name,
361
+ attributes: {
362
+ "gen_ai.tool.name": toolCall.name,
363
+ "gen_ai.tool.type": "function",
364
+ "zansn.tool.id": toolCall.id,
365
+ "zansn.tool.status": toolCall.status ?? "ok",
366
+ "zansn.tool.arguments": toolCall.arguments,
367
+ "zansn.tool.result": toolCall.result,
368
+ ...toolCall.metadata
369
+ }
370
+ };
371
+ if (toolCall.startedAt) {
372
+ input.startedAt = toolCall.startedAt;
373
+ }
374
+ if (toolCall.endedAt) {
375
+ input.endedAt = toolCall.endedAt;
376
+ }
377
+ return createTraceEvent(runId, taskId, input);
378
+ }
379
+ function summarize(results, findings) {
380
+ const latencies = results.map((result) => result.durationMs).sort((a, b) => a - b);
381
+ const passed = results.filter((result) => result.passed).length;
382
+ const blocked = results.filter((result) => result.readiness === "blocked").length;
383
+ const totalEstimatedCostUsd = results.reduce((sum, result) => sum + result.usage.estimatedCostUsd, 0);
384
+ const totalTokens = results.reduce((sum, result) => sum + result.usage.totalTokens, 0);
385
+ const averageScore = results.length === 0 ? 0 : results.reduce((sum, result) => sum + result.score, 0) / results.length;
386
+ const highestSeverity = highestFindingSeverity(findings);
387
+ return {
388
+ tasks: results.length,
389
+ passed,
390
+ failed: results.length - passed,
391
+ blocked,
392
+ passRate: results.length === 0 ? 0 : passed / results.length,
393
+ averageScore,
394
+ totalEstimatedCostUsd,
395
+ totalTokens,
396
+ p50LatencyMs: percentile(latencies, 0.5),
397
+ p95LatencyMs: percentile(latencies, 0.95),
398
+ highestSeverity,
399
+ readiness: determinePackReadiness(results, highestSeverity)
400
+ };
401
+ }
402
+ function determineReadiness(findings, score, minimumScore) {
403
+ if (findings.some((finding) => finding.severity === "critical" || finding.severity === "high")) {
404
+ return "blocked";
405
+ }
406
+ if (findings.length > 0 || score < minimumScore) {
407
+ return "review";
408
+ }
409
+ return "ready";
410
+ }
411
+ function determinePackReadiness(results, highestSeverity) {
412
+ if (highestSeverity === "critical" || highestSeverity === "high" || results.some((result) => result.readiness === "blocked")) {
413
+ return "blocked";
414
+ }
415
+ if (highestSeverity !== "info" || results.some((result) => result.readiness === "review")) {
416
+ return "review";
417
+ }
418
+ return "ready";
419
+ }
420
+ function highestFindingSeverity(findings) {
421
+ const order = ["info", "low", "medium", "high", "critical"];
422
+ return findings.reduce((highest, finding) => {
423
+ return order.indexOf(finding.severity) > order.indexOf(highest) ? finding.severity : highest;
424
+ }, "info");
425
+ }
426
+ function defaultExactMatchScore(expected, output) {
427
+ if (expected === void 0) {
428
+ return 1;
429
+ }
430
+ return stableStringify(expected) === stableStringify(output) ? 1 : 0;
431
+ }
432
+ function clampScore(value) {
433
+ if (!Number.isFinite(value)) {
434
+ return 0;
435
+ }
436
+ return Math.max(0, Math.min(1, value));
437
+ }
438
+ function clampNonNegative(value) {
439
+ if (value === void 0 || !Number.isFinite(value)) {
440
+ return 0;
441
+ }
442
+ return Math.max(0, value);
443
+ }
444
+ function mergePolicy(base, override = {}) {
445
+ const policy = { ...base, ...override };
446
+ const allowedTools = override.allowedTools ?? base.allowedTools;
447
+ const blockedTools = override.blockedTools ?? base.blockedTools;
448
+ const sensitiveDataPatterns = override.sensitiveDataPatterns ?? base.sensitiveDataPatterns;
449
+ if (allowedTools) {
450
+ policy.allowedTools = allowedTools;
451
+ }
452
+ if (blockedTools) {
453
+ policy.blockedTools = blockedTools;
454
+ }
455
+ if (sensitiveDataPatterns) {
456
+ policy.sensitiveDataPatterns = sensitiveDataPatterns;
457
+ }
458
+ return policy;
459
+ }
460
+ function percentile(values, p) {
461
+ if (values.length === 0) {
462
+ return 0;
463
+ }
464
+ const index = Math.min(values.length - 1, Math.ceil(values.length * p) - 1);
465
+ return values[index] ?? 0;
466
+ }
467
+ function createId(prefix) {
468
+ const random = globalThis.crypto?.randomUUID?.() ?? `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
469
+ return `${prefix}-${random}`;
470
+ }
471
+ function nowIso() {
472
+ return (/* @__PURE__ */ new Date()).toISOString();
473
+ }
474
+ function errorToMessage(error) {
475
+ if (error instanceof Error) {
476
+ return error.message;
477
+ }
478
+ return String(error);
479
+ }
480
+ function assertNonEmptyString(value, message) {
481
+ if (typeof value !== "string" || value.trim().length === 0) {
482
+ throw new Error(message);
483
+ }
484
+ }
485
+ function stableStringify(value, space) {
486
+ return JSON.stringify(sortForJson(value), null, space);
487
+ }
488
+ function sortForJson(value) {
489
+ if (Array.isArray(value)) {
490
+ return value.map(sortForJson);
491
+ }
492
+ if (!isRecord(value)) {
493
+ return value;
494
+ }
495
+ return Object.keys(value).sort().reduce((acc, key) => {
496
+ acc[key] = sortForJson(value[key]);
497
+ return acc;
498
+ }, {});
499
+ }
500
+ function freezeMetadata(metadata) {
501
+ return Object.freeze({ ...metadata ?? {} });
502
+ }
503
+ function deepFreeze(value) {
504
+ if (!isRecord(value) && !Array.isArray(value)) {
505
+ return value;
506
+ }
507
+ Object.freeze(value);
508
+ for (const item of Object.values(value)) {
509
+ if ((isRecord(item) || Array.isArray(item)) && !Object.isFrozen(item)) {
510
+ deepFreeze(item);
511
+ }
512
+ }
513
+ return value;
514
+ }
515
+ function formatNumber(value) {
516
+ return Number.isInteger(value) ? String(value) : value.toFixed(4).replace(/0+$/, "").replace(/\.$/, "");
517
+ }
518
+ function isRecord(value) {
519
+ return typeof value === "object" && value !== null && !Array.isArray(value);
520
+ }
521
+ function performanceNow() {
522
+ return globalThis.performance?.now?.() ?? Date.now();
523
+ }
524
+ var index_default = ZansnClient;
525
+ export {
526
+ ZANSN_EVIDENCE_PACK_SCHEMA,
527
+ ZANSN_EVIDENCE_RECORD_SCHEMA,
528
+ ZansnClient,
529
+ createEvidenceRecord,
530
+ index_default as default,
531
+ exportEvidencePackJson,
532
+ exportEvidencePackMarkdown,
533
+ toOpenTelemetryAttributes,
534
+ validateEvidencePack,
535
+ zansn
536
+ };
@@ -0,0 +1,62 @@
1
+ import {
2
+ ZansnClient,
3
+ exportEvidencePackMarkdown,
4
+ validateEvidencePack
5
+ } from "zansn";
6
+
7
+ const client = new ZansnClient({
8
+ defaultPolicy: {
9
+ allowedTools: ["lookupCustomer"],
10
+ blockedTools: ["deleteCustomer"],
11
+ sensitiveDataPatterns: [/sk-[a-z0-9]+/i],
12
+ maxEstimatedCostUsd: 0.05,
13
+ maxLatencyMs: 5_000,
14
+ minimumScore: 1
15
+ }
16
+ });
17
+
18
+ const evidence = await client.runEvaluation({
19
+ projectId: "ZAN-RD-001",
20
+ experimentId: "ZAN-EXP-001",
21
+ name: "Customer support agent readiness check",
22
+ tasks: [
23
+ {
24
+ id: "support-001",
25
+ input: "Check whether ACME has an active support plan.",
26
+ expected: "active"
27
+ }
28
+ ],
29
+ execute: async (_task, context) => {
30
+ context.recordEvent({
31
+ type: "agent",
32
+ name: "agent.plan",
33
+ attributes: {
34
+ step: "lookup_customer_status"
35
+ }
36
+ });
37
+
38
+ return {
39
+ output: "active",
40
+ toolCalls: [
41
+ {
42
+ name: "lookupCustomer",
43
+ arguments: { customer: "ACME" },
44
+ result: { plan: "active" },
45
+ status: "ok"
46
+ }
47
+ ],
48
+ usage: {
49
+ inputTokens: 140,
50
+ outputTokens: 32,
51
+ estimatedCostUsd: 0.004
52
+ }
53
+ };
54
+ }
55
+ });
56
+
57
+ const validation = validateEvidencePack(evidence);
58
+ if (!validation.valid) {
59
+ throw new Error(validation.errors.join("\n"));
60
+ }
61
+
62
+ console.log(exportEvidencePackMarkdown(evidence));
package/package.json ADDED
@@ -0,0 +1,74 @@
1
+ {
2
+ "name": "zansn",
3
+ "version": "0.1.0",
4
+ "description": "Local-first AI assurance SDK for evaluating agent workflows and producing evidence packs.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "./package.json": "./package.json"
16
+ },
17
+ "files": [
18
+ "dist/index.js",
19
+ "dist/index.d.ts",
20
+ "assets",
21
+ "examples",
22
+ "README.md",
23
+ "CHANGELOG.md",
24
+ "SECURITY.md",
25
+ "LICENSE"
26
+ ],
27
+ "scripts": {
28
+ "build": "tsup src/index.ts --format esm --dts --clean",
29
+ "test": "vitest run",
30
+ "typecheck": "tsc --noEmit",
31
+ "release:check": "npm run typecheck && npm test && npm run build && npm audit --omit=optional && npm pack --dry-run",
32
+ "publish:dry-run": "npm publish --dry-run",
33
+ "prepack": "npm run typecheck && npm test && npm run build"
34
+ },
35
+ "keywords": [
36
+ "zansn",
37
+ "zansn-labs",
38
+ "ai-assurance",
39
+ "agent-evaluation",
40
+ "agent-observability",
41
+ "agent-tracing",
42
+ "ai-security",
43
+ "ai-agents",
44
+ "llm-evals",
45
+ "evidence-pack",
46
+ "local-first-ai",
47
+ "opentelemetry"
48
+ ],
49
+ "author": "Zansn Labs",
50
+ "license": "Apache-2.0",
51
+ "homepage": "https://zansn.ai",
52
+ "bugs": {
53
+ "url": "https://zansn.ai/contact"
54
+ },
55
+ "funding": {
56
+ "type": "individual",
57
+ "url": "https://zansn.ai"
58
+ },
59
+ "engines": {
60
+ "node": ">=18"
61
+ },
62
+ "publishConfig": {
63
+ "access": "public"
64
+ },
65
+ "sideEffects": false,
66
+ "overrides": {
67
+ "esbuild": "^0.28.1"
68
+ },
69
+ "devDependencies": {
70
+ "tsup": "^8.5.0",
71
+ "typescript": "^5.9.3",
72
+ "vitest": "^3.2.4"
73
+ }
74
+ }