@attest-ai/core 0.6.0 → 1.0.0-rc.1

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/dist/esm/index.js CHANGED
@@ -6,6 +6,15 @@ var STEP_AGENT_CALL = "agent_call";
6
6
  var STATUS_PASS = "pass";
7
7
  var STATUS_SOFT_FAIL = "soft_fail";
8
8
  var STATUS_HARD_FAIL = "hard_fail";
9
+ var STATUS_NOT_EVALUATED = "not_evaluated";
10
+ var REASON_PROVIDER_UNAVAILABLE = "provider_unavailable";
11
+ var REASON_PROVIDER_NOT_CONFIGURED = "provider_not_configured";
12
+ var REASON_SIMULATION = "simulation";
13
+ var REASON_MISSING_EVIDENCE = "missing_evidence";
14
+ var REASON_SHORT_CIRCUITED = "short_circuited";
15
+ var OUTCOME_PASS = "pass";
16
+ var OUTCOME_FAIL = "fail";
17
+ var OUTCOME_SKIP = "skip";
9
18
  var TYPE_SCHEMA = "schema";
10
19
  var TYPE_CONSTRAINT = "constraint";
11
20
  var TYPE_TRACE = "trace";
@@ -31,6 +40,34 @@ function assertionId(id) {
31
40
  function agentId(id) {
32
41
  return id;
33
42
  }
43
+ var FailureClass = {
44
+ BrokenCode: "broken_code",
45
+ FlakyJudge: "flaky_judge",
46
+ BadRubric: "bad_rubric",
47
+ MissingTraceData: "missing_trace_data",
48
+ StochasticVariance: "stochastic_variance"
49
+ };
50
+ function layerForType(assertionType) {
51
+ switch (assertionType) {
52
+ case "schema":
53
+ return 1;
54
+ case "constraint":
55
+ return 2;
56
+ case "trace":
57
+ case "trace_tree":
58
+ return 3;
59
+ case "content":
60
+ return 4;
61
+ case "embedding":
62
+ return 5;
63
+ case "llm_judge":
64
+ return 6;
65
+ case "plugin":
66
+ return 8;
67
+ default:
68
+ return 0;
69
+ }
70
+ }
34
71
 
35
72
  // src/proto/errors.ts
36
73
  var ProtocolError = class extends Error {
@@ -57,6 +94,58 @@ var EngineTimeoutError = class extends Error {
57
94
  this.timeoutMs = timeoutMs;
58
95
  }
59
96
  };
97
+ var ProtocolDesyncError = class extends Error {
98
+ diagnostics;
99
+ constructor(message, diagnostics) {
100
+ super(message);
101
+ this.name = "ProtocolDesyncError";
102
+ this.diagnostics = diagnostics;
103
+ }
104
+ };
105
+
106
+ // src/proto/diagnostics.ts
107
+ var DEFAULT_BUFFER_SIZE = 32;
108
+ var MAX_LINE_PREVIEW = 512;
109
+ var ProtocolDiagnosticBuffer = class {
110
+ capacity;
111
+ entries = [];
112
+ constructor(capacity = DEFAULT_BUFFER_SIZE) {
113
+ if (capacity <= 0) {
114
+ throw new Error(
115
+ `ProtocolDiagnosticBuffer capacity must be positive, got ${capacity}`
116
+ );
117
+ }
118
+ this.capacity = capacity;
119
+ }
120
+ push(diagnostic) {
121
+ if (this.entries.length >= this.capacity) {
122
+ this.entries.shift();
123
+ }
124
+ this.entries.push(diagnostic);
125
+ }
126
+ snapshot() {
127
+ return this.entries.slice();
128
+ }
129
+ countWithin(windowMs, nowMs) {
130
+ const cutoff = nowMs - windowMs;
131
+ let count = 0;
132
+ for (let i = this.entries.length - 1; i >= 0; i -= 1) {
133
+ if (this.entries[i].timestampMs >= cutoff) {
134
+ count += 1;
135
+ } else {
136
+ break;
137
+ }
138
+ }
139
+ return count;
140
+ }
141
+ clear() {
142
+ this.entries.length = 0;
143
+ }
144
+ };
145
+ function previewLine(line) {
146
+ if (line.length <= MAX_LINE_PREVIEW) return line;
147
+ return line.slice(0, MAX_LINE_PREVIEW) + "\u2026";
148
+ }
60
149
 
61
150
  // src/proto/codec.ts
62
151
  function encodeRequest(id, method, params) {
@@ -119,15 +208,14 @@ function extractId(response) {
119
208
  }
120
209
 
121
210
  // src/version.ts
122
- var VERSION = "0.6.0";
123
- var ENGINE_VERSION = "0.6.0";
211
+ var VERSION = "1.0.0-rc.1";
212
+ var ENGINE_VERSION = "1.0.0-rc.1";
124
213
 
125
214
  // src/engine-manager.ts
126
- import { spawn, execSync } from "child_process";
215
+ import { spawn, spawnSync } from "child_process";
127
216
  import { createInterface } from "readline";
128
- import { existsSync as existsSync2, accessSync, constants as fsConstants } from "fs";
129
- import { resolve, join as join2, dirname } from "path";
130
- import { fileURLToPath } from "url";
217
+ import { existsSync, accessSync, statSync as statSync2, constants as fsConstants } from "fs";
218
+ import { delimiter, join as join2 } from "path";
131
219
 
132
220
  // src/engine-downloader.ts
133
221
  import * as https from "https";
@@ -139,10 +227,8 @@ import * as crypto from "crypto";
139
227
  var RELEASE_BASE = `https://github.com/attest-framework/attest/releases/download/v${ENGINE_VERSION}`;
140
228
  var CHECKSUMS_URL = `${RELEASE_BASE}/checksums-sha256.txt`;
141
229
  var MAX_REDIRECTS = 5;
142
- function attestBinDir() {
143
- const dir = path.join(os.homedir(), ".attest", "bin");
144
- fs.mkdirSync(dir, { recursive: true });
145
- return dir;
230
+ function versionedBinDir() {
231
+ return path.join(os.homedir(), ".attest", "bin", ENGINE_VERSION);
146
232
  }
147
233
  function platformKey() {
148
234
  const platform2 = os.platform();
@@ -170,20 +256,8 @@ function binaryFilename() {
170
256
  return os.platform() === "win32" ? "attest-engine.exe" : "attest-engine";
171
257
  }
172
258
  function cachedEnginePath() {
173
- const dir = path.join(os.homedir(), ".attest", "bin");
174
- const binPath = path.join(dir, binaryFilename());
175
- const versionFile = path.join(dir, ".engine-version");
176
- if (!fs.existsSync(binPath)) {
177
- return null;
178
- }
179
- if (!fs.existsSync(versionFile)) {
180
- return null;
181
- }
182
- const cachedVersion = fs.readFileSync(versionFile, "utf-8").trim();
183
- if (cachedVersion !== ENGINE_VERSION) {
184
- return null;
185
- }
186
- return binPath;
259
+ const binPath = path.join(versionedBinDir(), binaryFilename());
260
+ return fs.statSync(binPath, { throwIfNoEntry: false })?.isFile() ? binPath : null;
187
261
  }
188
262
  function parseChecksums(text) {
189
263
  const checksums = /* @__PURE__ */ new Map();
@@ -203,7 +277,7 @@ function httpsGet(url, redirectCount = 0) {
203
277
  new Error(`Too many redirects (>${MAX_REDIRECTS}) following ${url}`)
204
278
  );
205
279
  }
206
- return new Promise((resolve2, reject) => {
280
+ return new Promise((resolve, reject) => {
207
281
  const client = url.startsWith("https:") ? https : http;
208
282
  client.get(url, (res) => {
209
283
  const status = res.statusCode ?? 0;
@@ -214,7 +288,7 @@ function httpsGet(url, redirectCount = 0) {
214
288
  return;
215
289
  }
216
290
  res.resume();
217
- resolve2(httpsGet(location, redirectCount + 1));
291
+ resolve(httpsGet(location, redirectCount + 1));
218
292
  return;
219
293
  }
220
294
  if (status < 200 || status >= 300) {
@@ -224,14 +298,14 @@ function httpsGet(url, redirectCount = 0) {
224
298
  }
225
299
  const chunks = [];
226
300
  res.on("data", (chunk) => chunks.push(chunk));
227
- res.on("end", () => resolve2(Buffer.concat(chunks)));
301
+ res.on("end", () => resolve(Buffer.concat(chunks)));
228
302
  res.on("error", reject);
229
303
  }).on("error", reject);
230
304
  });
231
305
  }
232
306
  async function downloadEngine() {
233
307
  const key = platformKey();
234
- const filename = `attest-engine-${key}`;
308
+ const filename = `attest-engine-${key}${os.platform() === "win32" ? ".exe" : ""}`;
235
309
  const binaryUrl = `${RELEASE_BASE}/${filename}`;
236
310
  process.stderr.write(`[attest] Downloading engine v${ENGINE_VERSION} for ${key}...
237
311
  `);
@@ -251,19 +325,24 @@ async function downloadEngine() {
251
325
  const actualHash = crypto.createHash("sha256").update(data).digest("hex");
252
326
  if (actualHash !== expectedHash) {
253
327
  throw new Error(
254
- `SHA256 mismatch for ${filename}. Expected: ${expectedHash}, got: ${actualHash}. The download may be corrupted. Delete ~/.attest/bin/ and retry.`
328
+ `SHA256 mismatch for ${filename}. Expected: ${expectedHash}, got: ${actualHash}. The download may be corrupted. Retry or download manually.`
255
329
  );
256
330
  }
257
331
  process.stderr.write(`[attest] Checksum verified.
258
332
  `);
259
- const dir = attestBinDir();
260
- const destFilename = binaryFilename();
261
- const destPath = path.join(dir, destFilename);
262
- const tmpPath = path.join(dir, `.${destFilename}.tmp.${process.pid}`);
263
- fs.writeFileSync(tmpPath, data);
264
- fs.chmodSync(tmpPath, 493);
265
- fs.renameSync(tmpPath, destPath);
266
- fs.writeFileSync(path.join(dir, ".engine-version"), ENGINE_VERSION);
333
+ const dir = versionedBinDir();
334
+ fs.mkdirSync(dir, { recursive: true });
335
+ const destPath = path.join(dir, binaryFilename());
336
+ const suffix = `${String(process.pid)}.${crypto.randomBytes(8).toString("hex")}`;
337
+ const tmpPath = path.join(dir, `.${binaryFilename()}.tmp.${suffix}`);
338
+ try {
339
+ fs.writeFileSync(tmpPath, data, { flag: "wx", mode: 493 });
340
+ fs.chmodSync(tmpPath, 493);
341
+ fs.renameSync(tmpPath, destPath);
342
+ } catch (err) {
343
+ if (err.code !== "EEXIST") fs.rmSync(tmpPath, { force: true });
344
+ throw err;
345
+ }
267
346
  process.stderr.write(`[attest] Engine installed at ${destPath}
268
347
  `);
269
348
  return destPath;
@@ -271,45 +350,92 @@ async function downloadEngine() {
271
350
 
272
351
  // src/engine-manager.ts
273
352
  var ENGINE_BINARY_NAME = "attest-engine";
274
- async function findEngineBinary() {
275
- const envPath = process.env["ATTEST_ENGINE_PATH"];
276
- if (envPath) {
277
- if (!existsSync2(envPath)) {
278
- throw new Error(
279
- `ATTEST_ENGINE_PATH is set to '${envPath}' but the file does not exist.`
280
- );
281
- }
282
- return envPath;
283
- }
284
- try {
285
- const cmd = process.platform === "win32" ? "where" : "which";
286
- const found = execSync(`${cmd} ${ENGINE_BINARY_NAME}`, { encoding: "utf-8" }).trim();
287
- if (found) return found;
288
- } catch {
353
+ var VERSION_PROBE_TIMEOUT_MS = 1e4;
354
+ var EngineVersionMismatchError = class extends Error {
355
+ enginePath;
356
+ source;
357
+ found;
358
+ required;
359
+ constructor(enginePath, source, found, required) {
360
+ const reported = found === null ? "reports no version" : `reports version ${found}`;
361
+ super(
362
+ `attest-engine at ${enginePath} (from ${source}) ${reported}; this SDK requires attest-engine ${required}. Point ATTEST_ENGINE_PATH at a matching engine, or remove the mismatched one from PATH so the SDK can download ${required} into ~/.attest/bin/${required}/.`
363
+ );
364
+ this.name = "EngineVersionMismatchError";
365
+ this.enginePath = enginePath;
366
+ this.source = source;
367
+ this.found = found;
368
+ this.required = required;
289
369
  }
290
- const cached = cachedEnginePath();
291
- if (cached) return cached;
292
- const candidates = [
293
- join2(dirname(fileURLToPath(import.meta.url)), "..", "..", "..", "..", "..", "bin", ENGINE_BINARY_NAME),
294
- join2(process.cwd(), "bin", ENGINE_BINARY_NAME)
295
- ];
296
- for (const candidate of candidates) {
297
- const resolved = resolve(candidate);
370
+ };
371
+ function engineVersion(enginePath) {
372
+ const proc = spawnSync(enginePath, ["version"], {
373
+ stdio: ["ignore", "pipe", "pipe"],
374
+ encoding: "utf-8",
375
+ timeout: VERSION_PROBE_TIMEOUT_MS
376
+ });
377
+ if (proc.error) throw proc.error;
378
+ const out = proc.stdout.trim();
379
+ const prefix = `${ENGINE_BINARY_NAME} `;
380
+ if (proc.status !== 0 || !out.startsWith(prefix)) return null;
381
+ return out.slice(prefix.length).trim();
382
+ }
383
+ function onPath() {
384
+ for (const dir of (process.env["PATH"] ?? "").split(delimiter)) {
385
+ if (!dir) continue;
386
+ const candidate = join2(dir, binaryFilename());
298
387
  try {
299
- accessSync(resolved, fsConstants.X_OK);
300
- if (existsSync2(resolved)) return resolved;
388
+ accessSync(candidate, fsConstants.X_OK);
301
389
  } catch {
390
+ continue;
302
391
  }
392
+ if (statSync2(candidate).isFile()) return candidate;
393
+ }
394
+ return null;
395
+ }
396
+ function requireFile(enginePath, label) {
397
+ if (!existsSync(enginePath) || !statSync2(enginePath).isFile()) {
398
+ throw new Error(`${label}=${enginePath} does not exist or is not a file.`);
399
+ }
400
+ }
401
+ async function locateEngine(explicitPath, allowDownload) {
402
+ if (explicitPath) {
403
+ requireFile(explicitPath, "Engine path");
404
+ return [explicitPath, "explicit engine path"];
405
+ }
406
+ const envPath = process.env["ATTEST_ENGINE_PATH"];
407
+ if (envPath) {
408
+ requireFile(envPath, "ATTEST_ENGINE_PATH");
409
+ return [envPath, "ATTEST_ENGINE_PATH"];
410
+ }
411
+ const found = onPath();
412
+ if (found) return [found, "PATH"];
413
+ const cached = cachedEnginePath();
414
+ if (cached) return [cached, "engine cache"];
415
+ if (!allowDownload) {
416
+ throw new Error(
417
+ `No '${ENGINE_BINARY_NAME}' ${ENGINE_VERSION} in ATTEST_ENGINE_PATH, PATH or ~/.attest/bin/${ENGINE_VERSION}/.`
418
+ );
303
419
  }
304
- const noDownload = process.env["ATTEST_ENGINE_NO_DOWNLOAD"];
420
+ const noDownload = process.env["ATTEST_ENGINE_NO_DOWNLOAD"]?.toLowerCase();
305
421
  if (noDownload === "1" || noDownload === "true" || noDownload === "yes") {
306
422
  throw new Error(
307
- `Cannot find '${ENGINE_BINARY_NAME}' binary and auto-download is disabled (ATTEST_ENGINE_NO_DOWNLOAD is set). Install the engine manually:
308
- 1. Download from https://github.com/attest-framework/attest/releases
309
- 2. Place the binary on your PATH or set ATTEST_ENGINE_PATH`
423
+ `Cannot find '${ENGINE_BINARY_NAME}' ${ENGINE_VERSION} and ATTEST_ENGINE_NO_DOWNLOAD is set.
424
+ Install options:
425
+ 1. Build from source: make engine, then set ATTEST_ENGINE_PATH
426
+ 2. Download release v${ENGINE_VERSION}: https://github.com/attest-framework/attest/releases
427
+ 3. Allow auto-download by unsetting ATTEST_ENGINE_NO_DOWNLOAD`
310
428
  );
311
429
  }
312
- return downloadEngine();
430
+ return [await downloadEngine(), "download"];
431
+ }
432
+ async function resolveEngine(explicitPath, options = {}) {
433
+ const [enginePath, source] = await locateEngine(explicitPath, options.allowDownload ?? true);
434
+ const found = engineVersion(enginePath);
435
+ if (found !== ENGINE_VERSION) {
436
+ throw new EngineVersionMismatchError(enginePath, source, found, ENGINE_VERSION);
437
+ }
438
+ return enginePath;
313
439
  }
314
440
  var EngineManager = class {
315
441
  enginePath;
@@ -324,28 +450,41 @@ var EngineManager = class {
324
450
  this.logLevel = options?.logLevel ?? "warn";
325
451
  }
326
452
  async start() {
327
- if (!this.enginePath) {
328
- this.enginePath = await findEngineBinary();
329
- }
453
+ this.enginePath = await resolveEngine(this.enginePath || void 0);
330
454
  this.process = spawn(this.enginePath, [`--log-level=${this.logLevel}`], {
331
455
  stdio: ["pipe", "pipe", "pipe"]
332
456
  });
333
457
  this.reader = createInterface({ input: this.process.stdout });
334
- const result = await this.sendRequestInternal("initialize", {
458
+ try {
459
+ this.initResult = await this.initialize();
460
+ } catch (err) {
461
+ await this.stop();
462
+ throw err;
463
+ }
464
+ this.initialized = true;
465
+ return this.initResult;
466
+ }
467
+ /** Send initialize and check the engine's version and capabilities. */
468
+ async initialize() {
469
+ const init = await this.sendRequestInternal("initialize", {
335
470
  sdk_name: "attest-typescript",
336
471
  sdk_version: VERSION,
337
472
  protocol_version: 1,
338
473
  required_capabilities: ["layers_1_4"],
339
474
  preferred_encoding: "json"
340
475
  });
341
- this.initResult = result;
342
- if (!this.initResult.compatible) {
343
- throw new Error(
344
- `Engine incompatible. Missing capabilities: ${JSON.stringify(this.initResult.missing)}`
476
+ if (init.engine_version !== ENGINE_VERSION) {
477
+ throw new EngineVersionMismatchError(
478
+ this.enginePath,
479
+ "initialize",
480
+ init.engine_version,
481
+ ENGINE_VERSION
345
482
  );
346
483
  }
347
- this.initialized = true;
348
- return this.initResult;
484
+ if (!init.compatible) {
485
+ throw new Error(`Engine incompatible. Missing capabilities: ${JSON.stringify(init.missing)}`);
486
+ }
487
+ return init;
349
488
  }
350
489
  async stop() {
351
490
  if (this.process === null) return;
@@ -386,11 +525,11 @@ var EngineManager = class {
386
525
  }
387
526
  this.requestId += 1;
388
527
  const requestStr = encodeRequest(this.requestId, method, params);
389
- return new Promise((resolve2, reject) => {
528
+ return new Promise((resolve, reject) => {
390
529
  const onLine = (line) => {
391
530
  try {
392
531
  const response = decodeResponse(line);
393
- resolve2(extractResult(response));
532
+ resolve(extractResult(response));
394
533
  } catch (err) {
395
534
  reject(err);
396
535
  }
@@ -438,7 +577,11 @@ function config(options) {
438
577
  }
439
578
  }
440
579
  function isSimulationMode() {
441
- return _simulationMode || process.env["ATTEST_SIMULATION"] === "1" || process.env["ATTEST_SIMULATION"] === "true" || process.env["ATTEST_SIMULATION"] === "yes";
580
+ return _simulationMode || simulationEnvEnabled();
581
+ }
582
+ function simulationEnvEnabled() {
583
+ const env = (process.env["ATTEST_SIMULATION"] ?? "").trim().toLowerCase();
584
+ return env === "1" || env === "true" || env === "yes";
442
585
  }
443
586
  function getSampleRate() {
444
587
  if (_sampleRate > 0) return _sampleRate;
@@ -459,25 +602,11 @@ function resetConfig() {
459
602
  _sampleRate = 0;
460
603
  }
461
604
 
462
- // src/simulation.ts
463
- function simulationEvaluateBatch(assertions) {
464
- const results = assertions.map((assertion) => ({
465
- assertion_id: assertion.assertion_id,
466
- status: "pass",
467
- score: 1,
468
- explanation: `[simulation] assertion '${assertion.assertion_id}' passed (type: ${assertion.type})`,
469
- cost: 0,
470
- duration_ms: 0
471
- }));
472
- return {
473
- results,
474
- total_cost: 0,
475
- total_duration_ms: 0
476
- };
477
- }
478
-
479
605
  // src/client.ts
480
606
  var DEFAULT_TIMEOUT_MS = 3e4;
607
+ var DEFAULT_DIAGNOSTIC_BUFFER = 32;
608
+ var DEFAULT_DESYNC_THRESHOLD = 3;
609
+ var DEFAULT_DESYNC_WINDOW_MS = 1e3;
481
610
  function resolveTimeoutMs(override) {
482
611
  if (override !== void 0) return override;
483
612
  const env = process.env["ATTEST_ENGINE_TIMEOUT"];
@@ -499,6 +628,24 @@ function makeTimeoutPromise(method, timeoutMs) {
499
628
  }, timeoutMs);
500
629
  });
501
630
  }
631
+ function debugProtocolEnabled() {
632
+ const flag = process.env["ATTEST_DEBUG_PROTOCOL"];
633
+ return flag === "1" || flag === "true" || flag === "yes";
634
+ }
635
+ function envLoggerDefault() {
636
+ if (debugProtocolEnabled()) {
637
+ return {
638
+ warn: (msg, ...args) => console.warn(msg, ...args),
639
+ error: (msg, ...args) => console.error(msg, ...args)
640
+ };
641
+ }
642
+ return {
643
+ warn: () => {
644
+ },
645
+ error: () => {
646
+ }
647
+ };
648
+ }
502
649
  var AttestClient = class {
503
650
  engine;
504
651
  requestId = 0;
@@ -506,8 +653,34 @@ var AttestClient = class {
506
653
  writeChain = Promise.resolve();
507
654
  readerActive = false;
508
655
  lineHandler = null;
509
- constructor(engine) {
656
+ logger;
657
+ diagnosticBuffer;
658
+ diagnosticListeners = /* @__PURE__ */ new Set();
659
+ desyncThreshold;
660
+ desyncWindowMs;
661
+ desynced = false;
662
+ constructor(engine, options) {
510
663
  this.engine = engine;
664
+ this.logger = options?.logger ?? envLoggerDefault();
665
+ this.diagnosticBuffer = new ProtocolDiagnosticBuffer(
666
+ options?.diagnosticBufferSize ?? DEFAULT_DIAGNOSTIC_BUFFER
667
+ );
668
+ this.desyncThreshold = options?.desyncThreshold ?? DEFAULT_DESYNC_THRESHOLD;
669
+ this.desyncWindowMs = options?.desyncWindowMs ?? DEFAULT_DESYNC_WINDOW_MS;
670
+ if (options?.onDiagnostic !== void 0) {
671
+ this.diagnosticListeners.add(options.onDiagnostic);
672
+ }
673
+ }
674
+ /** Subscribe to protocol diagnostics. Returns an unsubscribe function. */
675
+ onProtocolDiagnostic(listener) {
676
+ this.diagnosticListeners.add(listener);
677
+ return () => {
678
+ this.diagnosticListeners.delete(listener);
679
+ };
680
+ }
681
+ /** Snapshot of the diagnostic ring buffer (most recent last). */
682
+ protocolDiagnostics() {
683
+ return this.diagnosticBuffer.snapshot();
511
684
  }
512
685
  startReader() {
513
686
  if (this.readerActive) return;
@@ -532,35 +705,101 @@ var AttestClient = class {
532
705
  this.lineHandler = null;
533
706
  this.readerActive = false;
534
707
  }
708
+ recordDiagnostic(kind, message, rawLine) {
709
+ const diagnostic = {
710
+ kind,
711
+ message,
712
+ rawLine: previewLine(rawLine),
713
+ timestampMs: Date.now()
714
+ };
715
+ this.diagnosticBuffer.push(diagnostic);
716
+ this.logger.warn(`[attest.protocol] ${kind}: ${message}`);
717
+ for (const listener of this.diagnosticListeners) {
718
+ try {
719
+ listener(diagnostic);
720
+ } catch (err) {
721
+ this.logger.error(
722
+ `[attest.protocol] diagnostic listener threw: ${err instanceof Error ? err.message : String(err)}`
723
+ );
724
+ }
725
+ }
726
+ this.maybeTriggerDesync(diagnostic.timestampMs);
727
+ }
728
+ maybeTriggerDesync(nowMs) {
729
+ if (this.desynced) return;
730
+ const recent = this.diagnosticBuffer.countWithin(this.desyncWindowMs, nowMs);
731
+ if (recent >= this.desyncThreshold) {
732
+ this.desynced = true;
733
+ const snapshot = this.diagnosticBuffer.snapshot();
734
+ const err = new ProtocolDesyncError(
735
+ `Engine protocol desync: ${recent} unroutable response lines within ${this.desyncWindowMs}ms.`,
736
+ snapshot
737
+ );
738
+ this.logger.error(`[attest.protocol] ${err.message}`);
739
+ this.failAll(err);
740
+ }
741
+ }
535
742
  handleLine(line) {
743
+ let response;
744
+ try {
745
+ response = decodeResponse(line);
746
+ } catch (err) {
747
+ this.handleDecodeFailure(line, err);
748
+ return;
749
+ }
536
750
  let reqId;
537
751
  try {
538
- const response = decodeResponse(line);
539
752
  reqId = extractId(response);
540
- const pending = this.pending.get(reqId);
541
- if (pending === void 0) return;
542
- this.pending.delete(reqId);
543
- try {
544
- const result = extractResult(response);
545
- pending.resolve(result);
546
- } catch (err) {
547
- pending.reject(err);
548
- }
549
753
  } catch (err) {
550
- if (err instanceof ProtocolError) {
551
- try {
552
- const raw = JSON.parse(line.trim());
553
- reqId = Number(raw.id ?? -1);
554
- } catch {
555
- reqId = -1;
556
- }
557
- const pending = this.pending.get(reqId);
558
- if (pending !== void 0) {
559
- this.pending.delete(reqId);
560
- pending.reject(err);
754
+ const message = err instanceof Error ? err.message : String(err);
755
+ this.recordDiagnostic("missing_id", message, line);
756
+ return;
757
+ }
758
+ const pending = this.pending.get(reqId);
759
+ if (pending === void 0) {
760
+ this.recordDiagnostic(
761
+ "unknown_id",
762
+ `no pending request for id=${reqId}`,
763
+ line
764
+ );
765
+ return;
766
+ }
767
+ this.pending.delete(reqId);
768
+ try {
769
+ const result = extractResult(response);
770
+ pending.resolve(result);
771
+ } catch (err) {
772
+ pending.reject(err);
773
+ }
774
+ }
775
+ handleDecodeFailure(line, err) {
776
+ if (err instanceof ProtocolError) {
777
+ let reqId = -1;
778
+ try {
779
+ const raw = JSON.parse(line.trim());
780
+ const idCandidate = raw["id"];
781
+ if (typeof idCandidate === "number" && Number.isFinite(idCandidate)) {
782
+ reqId = idCandidate;
561
783
  }
784
+ } catch {
785
+ reqId = -1;
562
786
  }
787
+ const pending = this.pending.get(reqId);
788
+ if (pending !== void 0) {
789
+ this.pending.delete(reqId);
790
+ pending.reject(err);
791
+ return;
792
+ }
793
+ this.recordDiagnostic(
794
+ "non_routable_error",
795
+ `engine error code=${err.code} (${err.errorMessage}) had no matching pending id`,
796
+ line
797
+ );
798
+ return;
563
799
  }
800
+ const message = err instanceof Error ? err.message : String(err);
801
+ const kind = classifyDecodeError(message);
802
+ this.recordDiagnostic(kind, message, line);
564
803
  }
565
804
  failAll(err) {
566
805
  for (const pending of this.pending.values()) {
@@ -569,6 +808,12 @@ var AttestClient = class {
569
808
  this.pending.clear();
570
809
  }
571
810
  async sendRequest(method, params, timeoutMs) {
811
+ if (this.desynced) {
812
+ throw new ProtocolDesyncError(
813
+ "Engine protocol is desynced; AttestClient will not send further requests.",
814
+ this.diagnosticBuffer.snapshot()
815
+ );
816
+ }
572
817
  const resolvedTimeout = resolveTimeoutMs(timeoutMs);
573
818
  if (!this.readerActive) {
574
819
  return Promise.race([
@@ -581,12 +826,12 @@ var AttestClient = class {
581
826
  throw new Error("Engine process not started.");
582
827
  }
583
828
  let capturedReqId;
584
- const requestPromise = new Promise((resolve2, reject) => {
829
+ const requestPromise = new Promise((resolve, reject) => {
585
830
  this.writeChain = this.writeChain.then(() => {
586
831
  this.requestId += 1;
587
832
  const reqId = this.requestId;
588
833
  capturedReqId = reqId;
589
- this.pending.set(reqId, { resolve: resolve2, reject });
834
+ this.pending.set(reqId, { resolve, reject });
590
835
  const requestStr = encodeRequest(reqId, method, params);
591
836
  cp.stdin.write(requestStr, (err) => {
592
837
  if (err) {
@@ -606,7 +851,7 @@ var AttestClient = class {
606
851
  }
607
852
  async evaluateBatch(trace, assertions, options) {
608
853
  if (isSimulationMode()) {
609
- return simulationEvaluateBatch(assertions);
854
+ requireSimulationCapability(this.engine["initResult"]);
610
855
  }
611
856
  const params = {
612
857
  trace,
@@ -645,10 +890,10 @@ var AttestClient = class {
645
890
  if (isSimulationMode()) {
646
891
  return {
647
892
  assertion_id: assertionId2,
648
- mean: 1,
893
+ mean: 0,
649
894
  stddev: 0,
650
895
  count: 0,
651
- latest_score: 1,
896
+ latest_score: 0,
652
897
  deviation: 0,
653
898
  status: "no_data"
654
899
  };
@@ -679,6 +924,23 @@ var AttestClient = class {
679
924
  return String(raw.message);
680
925
  }
681
926
  };
927
+ function classifyDecodeError(message) {
928
+ if (message.startsWith("malformed JSON")) return "malformed_json";
929
+ if (message.startsWith("expected JSON object")) return "non_object_response";
930
+ if (message.startsWith("invalid jsonrpc version")) return "invalid_jsonrpc_version";
931
+ if (message.startsWith("empty response line")) return "malformed_json";
932
+ return "malformed_json";
933
+ }
934
+ function requireSimulationCapability(init) {
935
+ if (init === null || init === void 0) {
936
+ throw new Error("simulation mode: the engine has not been initialized");
937
+ }
938
+ if (!(init.capabilities ?? []).includes("simulation_mode")) {
939
+ throw new Error(
940
+ `simulation mode is on but attest-engine ${init.engine_version} was not started in simulation mode (capability 'simulation_mode' missing), so it would call real providers. Set ATTEST_SIMULATION=1 before the engine starts (the Vitest fixtures do this for you) and use attest-engine 1.0 or later.`
941
+ );
942
+ }
943
+ }
682
944
 
683
945
  // src/trace.ts
684
946
  import { randomUUID } from "crypto";
@@ -769,6 +1031,20 @@ var TraceBuilder = class {
769
1031
  };
770
1032
 
771
1033
  // src/result.ts
1034
+ var KNOWN_STATUSES = /* @__PURE__ */ new Set([
1035
+ STATUS_PASS,
1036
+ STATUS_SOFT_FAIL,
1037
+ STATUS_HARD_FAIL,
1038
+ STATUS_NOT_EVALUATED
1039
+ ]);
1040
+ function normalizeAssertionResult(r) {
1041
+ if (KNOWN_STATUSES.has(r.status)) return r;
1042
+ return {
1043
+ ...r,
1044
+ status: STATUS_NOT_EVALUATED,
1045
+ explanation: `[unknown status '${r.status}'] ${r.explanation}`
1046
+ };
1047
+ }
772
1048
  var AgentResult = class {
773
1049
  trace;
774
1050
  assertionResults;
@@ -776,15 +1052,31 @@ var AgentResult = class {
776
1052
  totalDurationMs;
777
1053
  constructor(trace, assertionResults = [], totalCost = 0, totalDurationMs = 0) {
778
1054
  this.trace = trace;
779
- this.assertionResults = assertionResults;
1055
+ this.assertionResults = assertionResults.map(normalizeAssertionResult);
780
1056
  this.totalCost = totalCost;
781
1057
  this.totalDurationMs = totalDurationMs;
782
1058
  }
1059
+ /** True if every assertion has status "pass". */
783
1060
  get passed() {
784
1061
  return this.assertionResults.every((r) => r.status === STATUS_PASS);
785
1062
  }
1063
+ /**
1064
+ * Test outcome after the whole batch. Any hard_fail fails; otherwise any
1065
+ * not_evaluated skips (fails when `strict`); otherwise pass, including
1066
+ * when only soft_fail rows are present (see `softFailures`).
1067
+ */
1068
+ outcome(options) {
1069
+ if (this.hardFailures.length > 0) return OUTCOME_FAIL;
1070
+ if (this.notEvaluated.length > 0) {
1071
+ return options?.strict === true ? OUTCOME_FAIL : OUTCOME_SKIP;
1072
+ }
1073
+ return OUTCOME_PASS;
1074
+ }
1075
+ /** Failed assertions: hard_fail and soft_fail only. */
786
1076
  get failedAssertions() {
787
- return this.assertionResults.filter((r) => r.status !== STATUS_PASS);
1077
+ return this.assertionResults.filter(
1078
+ (r) => r.status === STATUS_HARD_FAIL || r.status === STATUS_SOFT_FAIL
1079
+ );
788
1080
  }
789
1081
  get hardFailures() {
790
1082
  return this.assertionResults.filter((r) => r.status === STATUS_HARD_FAIL);
@@ -792,14 +1084,226 @@ var AgentResult = class {
792
1084
  get softFailures() {
793
1085
  return this.assertionResults.filter((r) => r.status === STATUS_SOFT_FAIL);
794
1086
  }
1087
+ /** Assertions the engine did not evaluate; each carries a `reason`. */
1088
+ get notEvaluated() {
1089
+ return this.assertionResults.filter((r) => r.status === STATUS_NOT_EVALUATED);
1090
+ }
1091
+ /** Count of not_evaluated assertions per reason, in first-seen order. */
1092
+ get notEvaluatedReasons() {
1093
+ const counts = {};
1094
+ for (const r of this.notEvaluated) {
1095
+ const key = r.reason || "unknown";
1096
+ counts[key] = (counts[key] ?? 0) + 1;
1097
+ }
1098
+ return counts;
1099
+ }
1100
+ /**
1101
+ * Passed / evaluated assertions; null when nothing was evaluated.
1102
+ * not_evaluated rows are excluded from the denominator.
1103
+ */
1104
+ get passRate() {
1105
+ const evaluated = this.assertionResults.length - this.notEvaluated.length;
1106
+ return evaluated === 0 ? null : this.passCount / evaluated;
1107
+ }
795
1108
  get passCount() {
796
1109
  return this.assertionResults.filter((r) => r.status === STATUS_PASS).length;
797
1110
  }
1111
+ /** Number of failed assertions (hard_fail + soft_fail). */
798
1112
  get failCount() {
799
- return this.assertionResults.length - this.passCount;
1113
+ return this.failedAssertions.length;
1114
+ }
1115
+ /** "not evaluated: <reason>[, <reason> ×n ...]" for skipped tests. */
1116
+ skipReason() {
1117
+ const parts = Object.entries(this.notEvaluatedReasons).map(
1118
+ ([reason, n]) => n === 1 ? reason : `${reason} \xD7${n}`
1119
+ );
1120
+ return `not evaluated: ${parts.join(", ")}`;
800
1121
  }
801
1122
  };
802
1123
 
1124
+ // src/diagnostics.ts
1125
+ var ANSI_RESET = "\x1B[0m";
1126
+ var ANSI_RED = "\x1B[31m";
1127
+ var ANSI_YELLOW = "\x1B[33m";
1128
+ var ANSI_GREEN = "\x1B[32m";
1129
+ var ANSI_DIM = "\x1B[2m";
1130
+ var ANSI_BOLD = "\x1B[1m";
1131
+ var LAYER_NAMES = {
1132
+ 1: "schema",
1133
+ 2: "constraint",
1134
+ 3: "trace",
1135
+ 4: "content",
1136
+ 5: "embedding",
1137
+ 6: "llm_judge",
1138
+ 7: "trace_tree",
1139
+ 8: "plugin"
1140
+ };
1141
+ function layerLabel(layer, assertionType) {
1142
+ if (layer === 0 && assertionType) {
1143
+ return `L? ${assertionType}`;
1144
+ }
1145
+ const name = LAYER_NAMES[layer] ?? assertionType ?? "uncategorized";
1146
+ return `L${layer} ${name}`;
1147
+ }
1148
+ function statusGlyph(status, color) {
1149
+ let glyph;
1150
+ let c;
1151
+ if (status === STATUS_PASS) {
1152
+ glyph = "PASS";
1153
+ c = ANSI_GREEN;
1154
+ } else if (status === STATUS_SOFT_FAIL) {
1155
+ glyph = "SOFT";
1156
+ c = ANSI_YELLOW;
1157
+ } else if (status === STATUS_HARD_FAIL) {
1158
+ glyph = "FAIL";
1159
+ c = ANSI_RED;
1160
+ } else if (status === STATUS_NOT_EVALUATED) {
1161
+ glyph = "N/E";
1162
+ c = ANSI_DIM;
1163
+ } else {
1164
+ glyph = status.toUpperCase() || "????";
1165
+ c = ANSI_DIM;
1166
+ }
1167
+ return color ? `${c}${glyph}${ANSI_RESET}` : glyph;
1168
+ }
1169
+ function bold(s, color) {
1170
+ return color ? `${ANSI_BOLD}${s}${ANSI_RESET}` : s;
1171
+ }
1172
+ function dim(s, color) {
1173
+ return color ? `${ANSI_DIM}${s}${ANSI_RESET}` : s;
1174
+ }
1175
+ function truncate(value, limit = 280) {
1176
+ if (value.length <= limit) {
1177
+ return value;
1178
+ }
1179
+ return value.slice(0, limit - 3) + "...";
1180
+ }
1181
+ function renderAssertionFailure(result, options = {}) {
1182
+ const color = options.color ?? false;
1183
+ const testFile = options.testFile;
1184
+ const layer = result.layer ?? 0;
1185
+ const label = layerLabel(layer, result.type);
1186
+ const lines = [];
1187
+ lines.push(
1188
+ ` ${statusGlyph(result.status, color)} ${bold(result.assertion_id, color)} ${dim(`\u2014 ${label}`, color)}`
1189
+ );
1190
+ if (result.reason) {
1191
+ lines.push(` reason: not evaluated: ${result.reason}`);
1192
+ }
1193
+ if (result.trace_node_path) {
1194
+ lines.push(` trace path: ${result.trace_node_path}`);
1195
+ }
1196
+ if (result.status !== STATUS_NOT_EVALUATED) {
1197
+ lines.push(` score: ${result.score.toFixed(3)}`);
1198
+ }
1199
+ if (result.expected) {
1200
+ lines.push(` expected: ${truncate(result.expected)}`);
1201
+ }
1202
+ if (result.actual) {
1203
+ lines.push(` actual: ${truncate(result.actual)}`);
1204
+ }
1205
+ if (result.explanation) {
1206
+ lines.push(` detail: ${truncate(result.explanation)}`);
1207
+ }
1208
+ if (result.threshold_source && result.threshold_source !== "static") {
1209
+ lines.push(` threshold: ${result.threshold_source}`);
1210
+ }
1211
+ if (result.failure_class) {
1212
+ lines.push(` class: ${result.failure_class}`);
1213
+ }
1214
+ if (result.judge_metadata) {
1215
+ appendJudgeMetadata(lines, result.judge_metadata);
1216
+ }
1217
+ if (result.suggested_action) {
1218
+ lines.push(` hint: ${truncate(result.suggested_action)}`);
1219
+ }
1220
+ if ((result.cost ?? 0) > 0 || (result.duration_ms ?? 0) > 0) {
1221
+ const cost = result.cost ?? 0;
1222
+ const durMs = result.duration_ms ?? 0;
1223
+ lines.push(` cost/lat: $${cost.toFixed(6)} / ${durMs}ms`);
1224
+ }
1225
+ if (testFile) {
1226
+ lines.push(` source: ${testFile}`);
1227
+ }
1228
+ return lines.join("\n") + "\n";
1229
+ }
1230
+ function appendJudgeMetadata(lines, meta) {
1231
+ if (meta.model) {
1232
+ let rubric = meta.rubric_name ?? "default";
1233
+ if (meta.rubric_version) {
1234
+ rubric = `${rubric} @ ${meta.rubric_version}`;
1235
+ }
1236
+ lines.push(` judge: ${meta.model} / ${rubric}`);
1237
+ }
1238
+ if (meta.prompt_hash) {
1239
+ lines.push(` prompt: #${meta.prompt_hash}`);
1240
+ }
1241
+ const samples = meta.sample_scores ?? [];
1242
+ if (samples.length > 1) {
1243
+ const formatted = samples.map((s) => s.toFixed(2)).join(", ");
1244
+ const mean = (meta.score_mean ?? 0).toFixed(2);
1245
+ const stddev = (meta.score_stddev ?? 0).toFixed(2);
1246
+ const flag = meta.high_variance ? " \u26A0 HIGH VARIANCE" : "";
1247
+ lines.push(
1248
+ ` samples: [${formatted}] mean=${mean} stddev=${stddev}${flag}`
1249
+ );
1250
+ }
1251
+ const probes = meta.bias_probes ?? [];
1252
+ if (probes.length > 0) {
1253
+ const formatted = probes.map((p) => `${p.name} \u0394${p.delta >= 0 ? "+" : ""}${p.delta.toFixed(2)}`).join(", ");
1254
+ lines.push(` bias: ${formatted}`);
1255
+ }
1256
+ if (meta.calibration) {
1257
+ const c = meta.calibration;
1258
+ lines.push(
1259
+ ` calibrated: ${c.label_count} labels, agreement=${c.agreement.toFixed(2)}, \u03BA=${c.cohen_kappa.toFixed(2)}`
1260
+ );
1261
+ }
1262
+ }
1263
+ function renderSummary(result, options = {}) {
1264
+ const color = options.color ?? false;
1265
+ const passN = result.passCount;
1266
+ const soft = result.assertionResults.filter(
1267
+ (r) => r.status === STATUS_SOFT_FAIL
1268
+ ).length;
1269
+ const hard = result.assertionResults.filter(
1270
+ (r) => r.status === STATUS_HARD_FAIL
1271
+ ).length;
1272
+ const parts = [
1273
+ `${statusGlyph(STATUS_PASS, color)} ${passN}`,
1274
+ `${statusGlyph(STATUS_SOFT_FAIL, color)} ${soft}`,
1275
+ `${statusGlyph(STATUS_HARD_FAIL, color)} ${hard}`,
1276
+ `${statusGlyph(STATUS_NOT_EVALUATED, color)} ${result.notEvaluated.length}`,
1277
+ `cost $${result.totalCost.toFixed(6)}`,
1278
+ `dur ${result.totalDurationMs}ms`
1279
+ ];
1280
+ return parts.join(" | ");
1281
+ }
1282
+ function renderDiagnostics(result, options = {}) {
1283
+ const color = options.color ?? false;
1284
+ const includePassing = options.includePassing ?? false;
1285
+ const rows = includePassing ? result.assertionResults : result.assertionResults.filter((r) => r.status !== STATUS_PASS);
1286
+ if (rows.length === 0) {
1287
+ return dim(" (no failures)\n", color);
1288
+ }
1289
+ const out = [""];
1290
+ out.push(bold(`Attest diagnostic \u2014 ${headerCounts(result)}:`, color));
1291
+ for (const r of rows) {
1292
+ out.push(renderAssertionFailure(r, options));
1293
+ }
1294
+ out.push(dim(` Summary: ${renderSummary(result, { color })}`, color));
1295
+ return out.join("\n");
1296
+ }
1297
+ function headerCounts(result) {
1298
+ const total = result.assertionResults.length;
1299
+ const notEvaluated = result.notEvaluated.length;
1300
+ if (result.failCount === 0 && notEvaluated > 0) {
1301
+ return `${notEvaluated} of ${total} assertions not evaluated`;
1302
+ }
1303
+ const text = `${result.failCount} of ${total} assertions failed`;
1304
+ return notEvaluated > 0 ? `${text}, ${notEvaluated} not evaluated` : text;
1305
+ }
1306
+
803
1307
  // src/trace-tree.ts
804
1308
  var TraceTree = class {
805
1309
  root;
@@ -2004,6 +2508,222 @@ var ContinuousEvalRunner = class {
2004
2508
  }
2005
2509
  };
2006
2510
 
2511
+ // src/calibration.ts
2512
+ import { createHash as createHash2 } from "crypto";
2513
+ function computeAgreement(pairs, threshold) {
2514
+ if (pairs.length === 0) {
2515
+ throw new Error("no labeled pairs available");
2516
+ }
2517
+ if (threshold <= 0 || threshold >= 1) {
2518
+ throw new Error("threshold must be in (0, 1)");
2519
+ }
2520
+ const n = pairs.length;
2521
+ let humanPos = 0;
2522
+ let judgePos = 0;
2523
+ let bothPos = 0;
2524
+ let bothNeg = 0;
2525
+ for (const p of pairs) {
2526
+ const hPos = p.human >= threshold;
2527
+ const jPos = p.judge >= threshold;
2528
+ if (hPos) humanPos++;
2529
+ if (jPos) judgePos++;
2530
+ if (hPos && jPos) bothPos++;
2531
+ if (!hPos && !jPos) bothNeg++;
2532
+ }
2533
+ const humanNeg = n - humanPos;
2534
+ const judgeNeg = n - judgePos;
2535
+ const agreement = (bothPos + bothNeg) / n;
2536
+ const expected = (humanPos * judgePos + humanNeg * judgeNeg) / (n * n);
2537
+ const cohenKappa = expected < 1 ? (agreement - expected) / (1 - expected) : 0;
2538
+ const rocAuc = rocAUC(pairs, threshold);
2539
+ return { threshold, n, agreement, cohenKappa, rocAuc };
2540
+ }
2541
+ function rocAUC(pairs, threshold) {
2542
+ const rows = pairs.map((p) => ({
2543
+ score: p.judge,
2544
+ pos: p.human >= threshold
2545
+ }));
2546
+ const pos = rows.filter((r) => r.pos).length;
2547
+ const neg = rows.length - pos;
2548
+ if (pos === 0 || neg === 0) return 0;
2549
+ rows.sort((a, b) => a.score - b.score);
2550
+ let rankSumPos = 0;
2551
+ let i = 0;
2552
+ while (i < rows.length) {
2553
+ let j = i;
2554
+ while (j < rows.length && rows[j].score === rows[i].score) {
2555
+ j++;
2556
+ }
2557
+ const avgRank = (i + j + 1) / 2;
2558
+ for (let k = i; k < j; k++) {
2559
+ if (rows[k].pos) rankSumPos += avgRank;
2560
+ }
2561
+ i = j;
2562
+ }
2563
+ const auc = (rankSumPos - pos * (pos + 1) / 2) / (pos * neg);
2564
+ if (Number.isNaN(auc) || !Number.isFinite(auc)) return 0;
2565
+ return auc;
2566
+ }
2567
+ function loadLabelsCSV(text) {
2568
+ const rows = parseCSV(text);
2569
+ if (rows.length === 0) {
2570
+ throw new Error("empty CSV");
2571
+ }
2572
+ let start = 0;
2573
+ if (rows[0].length >= 2) {
2574
+ const firstHuman = parseFloatStrict(rows[0][1]);
2575
+ if (firstHuman === null) start = 1;
2576
+ }
2577
+ const out = [];
2578
+ for (let idx = start; idx < rows.length; idx++) {
2579
+ const row = rows[idx];
2580
+ const lineNum = idx + 1;
2581
+ if (row.length === 0) continue;
2582
+ if (row[0].trimStart().startsWith("#")) continue;
2583
+ if (row.length < 2) {
2584
+ throw new Error(
2585
+ `CSV line ${lineNum}: want at least 2 columns (input, human_label)`
2586
+ );
2587
+ }
2588
+ const human = parseFloatStrict(row[1]);
2589
+ if (human === null) {
2590
+ throw new Error(`CSV line ${lineNum}: human_label not a float: ${row[1]}`);
2591
+ }
2592
+ const rec = {
2593
+ input: row[0],
2594
+ humanLabel: human,
2595
+ judgeScore: 0,
2596
+ judgeKnown: false
2597
+ };
2598
+ if (row.length >= 3 && row[2].trim() !== "") {
2599
+ const judge = parseFloatStrict(row[2]);
2600
+ if (judge === null) {
2601
+ throw new Error(
2602
+ `CSV line ${lineNum}: judge_score not a float: ${row[2]}`
2603
+ );
2604
+ }
2605
+ rec.judgeScore = judge;
2606
+ rec.judgeKnown = true;
2607
+ }
2608
+ out.push(rec);
2609
+ }
2610
+ if (out.length === 0) {
2611
+ throw new Error("CSV contained no labeled rows");
2612
+ }
2613
+ return out;
2614
+ }
2615
+ function loadLabelsJSONL(text) {
2616
+ const out = [];
2617
+ const lines = text.split(/\r?\n/);
2618
+ for (let i = 0; i < lines.length; i++) {
2619
+ const stripped = lines[i].trim();
2620
+ if (!stripped) continue;
2621
+ let obj;
2622
+ try {
2623
+ obj = JSON.parse(stripped);
2624
+ } catch (err) {
2625
+ throw new Error(`JSONL line ${i + 1}: ${err.message}`);
2626
+ }
2627
+ if (Object.keys(obj).length === 0) continue;
2628
+ if (obj["human_label"] === void 0) {
2629
+ throw new Error(`JSONL line ${i + 1}: missing human_label`);
2630
+ }
2631
+ const human = parseJSONNumber(obj["human_label"]);
2632
+ if (human === null) {
2633
+ throw new Error(`JSONL line ${i + 1}: human_label is not a number`);
2634
+ }
2635
+ const rec = {
2636
+ input: typeof obj["input"] === "string" ? obj["input"] : "",
2637
+ humanLabel: human,
2638
+ judgeScore: 0,
2639
+ judgeKnown: false
2640
+ };
2641
+ if (obj["judge_score"] !== void 0 && obj["judge_score"] !== null) {
2642
+ const judge = parseJSONNumber(obj["judge_score"]);
2643
+ if (judge === null) {
2644
+ throw new Error(`JSONL line ${i + 1}: judge_score is not a number`);
2645
+ }
2646
+ rec.judgeScore = judge;
2647
+ rec.judgeKnown = true;
2648
+ }
2649
+ out.push(rec);
2650
+ }
2651
+ if (out.length === 0) {
2652
+ throw new Error("JSONL contained no labeled rows");
2653
+ }
2654
+ return out;
2655
+ }
2656
+ function promptHash(text) {
2657
+ return createHash2("sha256").update(text).digest("hex").slice(0, 16);
2658
+ }
2659
+ function parseFloatStrict(raw) {
2660
+ const trimmed = raw.trim();
2661
+ if (trimmed === "") return null;
2662
+ const n = Number(trimmed);
2663
+ if (Number.isNaN(n)) return null;
2664
+ return n;
2665
+ }
2666
+ function parseJSONNumber(value) {
2667
+ if (typeof value !== "number") return null;
2668
+ if (Number.isNaN(value)) return null;
2669
+ return value;
2670
+ }
2671
+ function parseCSV(text) {
2672
+ const rows = [];
2673
+ let current = [];
2674
+ let field = "";
2675
+ let inQuotes = false;
2676
+ let i = 0;
2677
+ while (i < text.length) {
2678
+ const ch = text[i];
2679
+ if (inQuotes) {
2680
+ if (ch === '"') {
2681
+ if (text[i + 1] === '"') {
2682
+ field += '"';
2683
+ i += 2;
2684
+ continue;
2685
+ }
2686
+ inQuotes = false;
2687
+ i++;
2688
+ continue;
2689
+ }
2690
+ field += ch;
2691
+ i++;
2692
+ continue;
2693
+ }
2694
+ if (ch === '"') {
2695
+ inQuotes = true;
2696
+ i++;
2697
+ continue;
2698
+ }
2699
+ if (ch === ",") {
2700
+ current.push(field);
2701
+ field = "";
2702
+ i++;
2703
+ continue;
2704
+ }
2705
+ if (ch === "\n") {
2706
+ current.push(field);
2707
+ rows.push(current);
2708
+ current = [];
2709
+ field = "";
2710
+ i++;
2711
+ continue;
2712
+ }
2713
+ if (ch === "\r") {
2714
+ i++;
2715
+ continue;
2716
+ }
2717
+ field += ch;
2718
+ i++;
2719
+ }
2720
+ if (field.length > 0 || current.length > 0) {
2721
+ current.push(field);
2722
+ rows.push(current);
2723
+ }
2724
+ return rows;
2725
+ }
2726
+
2007
2727
  // src/personas.ts
2008
2728
  var FRIENDLY_USER = {
2009
2729
  name: "FriendlyUser",
@@ -2438,15 +3158,27 @@ export {
2438
3158
  EngineTimeoutError,
2439
3159
  ExpectChain,
2440
3160
  FRIENDLY_USER,
3161
+ FailureClass,
2441
3162
  GeminiAdapter,
2442
3163
  LangChainAdapter,
2443
3164
  ManualAdapter,
2444
3165
  OTelAdapter,
3166
+ OUTCOME_FAIL,
3167
+ OUTCOME_PASS,
3168
+ OUTCOME_SKIP,
2445
3169
  OllamaAdapter,
2446
3170
  OpenAIAdapter,
2447
3171
  PluginRegistry,
3172
+ ProtocolDesyncError,
3173
+ ProtocolDiagnosticBuffer,
2448
3174
  ProtocolError,
3175
+ REASON_MISSING_EVIDENCE,
3176
+ REASON_PROVIDER_NOT_CONFIGURED,
3177
+ REASON_PROVIDER_UNAVAILABLE,
3178
+ REASON_SHORT_CIRCUITED,
3179
+ REASON_SIMULATION,
2449
3180
  STATUS_HARD_FAIL,
3181
+ STATUS_NOT_EVALUATED,
2450
3182
  STATUS_PASS,
2451
3183
  STATUS_SOFT_FAIL,
2452
3184
  STEP_AGENT_CALL,
@@ -2473,6 +3205,7 @@ export {
2473
3205
  agentId,
2474
3206
  assertionId,
2475
3207
  attestExpect,
3208
+ computeAgreement,
2476
3209
  config,
2477
3210
  decodeResponse,
2478
3211
  delegate,
@@ -2482,8 +3215,17 @@ export {
2482
3215
  extractResult,
2483
3216
  getSampleRate,
2484
3217
  isSimulationMode,
3218
+ layerForType,
3219
+ loadLabelsCSV,
3220
+ loadLabelsJSONL,
3221
+ previewLine,
3222
+ promptHash,
3223
+ renderAssertionFailure,
3224
+ renderDiagnostics,
3225
+ renderSummary,
3226
+ requireSimulationCapability,
2485
3227
  resetConfig,
2486
- simulationEvaluateBatch,
3228
+ simulationEnvEnabled,
2487
3229
  tier,
2488
3230
  traceId
2489
3231
  };