@attest-ai/core 0.9.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/cjs/index.cjs +272 -114
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +47 -16
- package/dist/cjs/proto/index.cjs +36 -0
- package/dist/cjs/proto/index.cjs.map +1 -1
- package/dist/cjs/proto/index.d.cts +34 -1
- package/dist/esm/cli.d.ts +19 -4
- package/dist/esm/cli.js +450 -407
- package/dist/esm/cli.js.map +1 -1
- package/dist/esm/index.d.ts +47 -16
- package/dist/esm/index.js +263 -115
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/proto/index.d.ts +34 -1
- package/dist/esm/proto/index.js +26 -0
- package/dist/esm/proto/index.js.map +1 -1
- package/package.json +4 -3
package/dist/cjs/index.cjs
CHANGED
|
@@ -50,17 +50,27 @@ __export(index_exports, {
|
|
|
50
50
|
EngineTimeoutError: () => EngineTimeoutError,
|
|
51
51
|
ExpectChain: () => ExpectChain,
|
|
52
52
|
FRIENDLY_USER: () => FRIENDLY_USER,
|
|
53
|
+
FailureClass: () => FailureClass,
|
|
53
54
|
GeminiAdapter: () => GeminiAdapter,
|
|
54
55
|
LangChainAdapter: () => LangChainAdapter,
|
|
55
56
|
ManualAdapter: () => ManualAdapter,
|
|
56
57
|
OTelAdapter: () => OTelAdapter,
|
|
58
|
+
OUTCOME_FAIL: () => OUTCOME_FAIL,
|
|
59
|
+
OUTCOME_PASS: () => OUTCOME_PASS,
|
|
60
|
+
OUTCOME_SKIP: () => OUTCOME_SKIP,
|
|
57
61
|
OllamaAdapter: () => OllamaAdapter,
|
|
58
62
|
OpenAIAdapter: () => OpenAIAdapter,
|
|
59
63
|
PluginRegistry: () => PluginRegistry,
|
|
60
64
|
ProtocolDesyncError: () => ProtocolDesyncError,
|
|
61
65
|
ProtocolDiagnosticBuffer: () => ProtocolDiagnosticBuffer,
|
|
62
66
|
ProtocolError: () => ProtocolError,
|
|
67
|
+
REASON_MISSING_EVIDENCE: () => REASON_MISSING_EVIDENCE,
|
|
68
|
+
REASON_PROVIDER_NOT_CONFIGURED: () => REASON_PROVIDER_NOT_CONFIGURED,
|
|
69
|
+
REASON_PROVIDER_UNAVAILABLE: () => REASON_PROVIDER_UNAVAILABLE,
|
|
70
|
+
REASON_SHORT_CIRCUITED: () => REASON_SHORT_CIRCUITED,
|
|
71
|
+
REASON_SIMULATION: () => REASON_SIMULATION,
|
|
63
72
|
STATUS_HARD_FAIL: () => STATUS_HARD_FAIL,
|
|
73
|
+
STATUS_NOT_EVALUATED: () => STATUS_NOT_EVALUATED,
|
|
64
74
|
STATUS_PASS: () => STATUS_PASS,
|
|
65
75
|
STATUS_SOFT_FAIL: () => STATUS_SOFT_FAIL,
|
|
66
76
|
STEP_AGENT_CALL: () => STEP_AGENT_CALL,
|
|
@@ -105,8 +115,9 @@ __export(index_exports, {
|
|
|
105
115
|
renderAssertionFailure: () => renderAssertionFailure,
|
|
106
116
|
renderDiagnostics: () => renderDiagnostics,
|
|
107
117
|
renderSummary: () => renderSummary,
|
|
118
|
+
requireSimulationCapability: () => requireSimulationCapability,
|
|
108
119
|
resetConfig: () => resetConfig,
|
|
109
|
-
|
|
120
|
+
simulationEnvEnabled: () => simulationEnvEnabled,
|
|
110
121
|
tier: () => tier,
|
|
111
122
|
traceId: () => traceId
|
|
112
123
|
});
|
|
@@ -120,6 +131,15 @@ var STEP_AGENT_CALL = "agent_call";
|
|
|
120
131
|
var STATUS_PASS = "pass";
|
|
121
132
|
var STATUS_SOFT_FAIL = "soft_fail";
|
|
122
133
|
var STATUS_HARD_FAIL = "hard_fail";
|
|
134
|
+
var STATUS_NOT_EVALUATED = "not_evaluated";
|
|
135
|
+
var REASON_PROVIDER_UNAVAILABLE = "provider_unavailable";
|
|
136
|
+
var REASON_PROVIDER_NOT_CONFIGURED = "provider_not_configured";
|
|
137
|
+
var REASON_SIMULATION = "simulation";
|
|
138
|
+
var REASON_MISSING_EVIDENCE = "missing_evidence";
|
|
139
|
+
var REASON_SHORT_CIRCUITED = "short_circuited";
|
|
140
|
+
var OUTCOME_PASS = "pass";
|
|
141
|
+
var OUTCOME_FAIL = "fail";
|
|
142
|
+
var OUTCOME_SKIP = "skip";
|
|
123
143
|
var TYPE_SCHEMA = "schema";
|
|
124
144
|
var TYPE_CONSTRAINT = "constraint";
|
|
125
145
|
var TYPE_TRACE = "trace";
|
|
@@ -145,6 +165,13 @@ function assertionId(id) {
|
|
|
145
165
|
function agentId(id) {
|
|
146
166
|
return id;
|
|
147
167
|
}
|
|
168
|
+
var FailureClass = {
|
|
169
|
+
BrokenCode: "broken_code",
|
|
170
|
+
FlakyJudge: "flaky_judge",
|
|
171
|
+
BadRubric: "bad_rubric",
|
|
172
|
+
MissingTraceData: "missing_trace_data",
|
|
173
|
+
StochasticVariance: "stochastic_variance"
|
|
174
|
+
};
|
|
148
175
|
function layerForType(assertionType) {
|
|
149
176
|
switch (assertionType) {
|
|
150
177
|
case "schema":
|
|
@@ -306,15 +333,14 @@ function extractId(response) {
|
|
|
306
333
|
}
|
|
307
334
|
|
|
308
335
|
// src/version.ts
|
|
309
|
-
var VERSION = "0.
|
|
310
|
-
var ENGINE_VERSION = "0.
|
|
336
|
+
var VERSION = "1.0.0-rc.1";
|
|
337
|
+
var ENGINE_VERSION = "1.0.0-rc.1";
|
|
311
338
|
|
|
312
339
|
// src/engine-manager.ts
|
|
313
340
|
var import_node_child_process = require("child_process");
|
|
314
341
|
var import_node_readline = require("readline");
|
|
315
342
|
var import_node_fs = require("fs");
|
|
316
343
|
var import_node_path = require("path");
|
|
317
|
-
var import_node_url = require("url");
|
|
318
344
|
|
|
319
345
|
// src/engine-downloader.ts
|
|
320
346
|
var https = __toESM(require("https"), 1);
|
|
@@ -326,10 +352,8 @@ var crypto = __toESM(require("crypto"), 1);
|
|
|
326
352
|
var RELEASE_BASE = `https://github.com/attest-framework/attest/releases/download/v${ENGINE_VERSION}`;
|
|
327
353
|
var CHECKSUMS_URL = `${RELEASE_BASE}/checksums-sha256.txt`;
|
|
328
354
|
var MAX_REDIRECTS = 5;
|
|
329
|
-
function
|
|
330
|
-
|
|
331
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
332
|
-
return dir;
|
|
355
|
+
function versionedBinDir() {
|
|
356
|
+
return path.join(os.homedir(), ".attest", "bin", ENGINE_VERSION);
|
|
333
357
|
}
|
|
334
358
|
function platformKey() {
|
|
335
359
|
const platform2 = os.platform();
|
|
@@ -357,20 +381,8 @@ function binaryFilename() {
|
|
|
357
381
|
return os.platform() === "win32" ? "attest-engine.exe" : "attest-engine";
|
|
358
382
|
}
|
|
359
383
|
function cachedEnginePath() {
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
-
const versionFile = path.join(dir, ".engine-version");
|
|
363
|
-
if (!fs.existsSync(binPath)) {
|
|
364
|
-
return null;
|
|
365
|
-
}
|
|
366
|
-
if (!fs.existsSync(versionFile)) {
|
|
367
|
-
return null;
|
|
368
|
-
}
|
|
369
|
-
const cachedVersion = fs.readFileSync(versionFile, "utf-8").trim();
|
|
370
|
-
if (cachedVersion !== ENGINE_VERSION) {
|
|
371
|
-
return null;
|
|
372
|
-
}
|
|
373
|
-
return binPath;
|
|
384
|
+
const binPath = path.join(versionedBinDir(), binaryFilename());
|
|
385
|
+
return fs.statSync(binPath, { throwIfNoEntry: false })?.isFile() ? binPath : null;
|
|
374
386
|
}
|
|
375
387
|
function parseChecksums(text) {
|
|
376
388
|
const checksums = /* @__PURE__ */ new Map();
|
|
@@ -390,7 +402,7 @@ function httpsGet(url, redirectCount = 0) {
|
|
|
390
402
|
new Error(`Too many redirects (>${MAX_REDIRECTS}) following ${url}`)
|
|
391
403
|
);
|
|
392
404
|
}
|
|
393
|
-
return new Promise((
|
|
405
|
+
return new Promise((resolve, reject) => {
|
|
394
406
|
const client = url.startsWith("https:") ? https : http;
|
|
395
407
|
client.get(url, (res) => {
|
|
396
408
|
const status = res.statusCode ?? 0;
|
|
@@ -401,7 +413,7 @@ function httpsGet(url, redirectCount = 0) {
|
|
|
401
413
|
return;
|
|
402
414
|
}
|
|
403
415
|
res.resume();
|
|
404
|
-
|
|
416
|
+
resolve(httpsGet(location, redirectCount + 1));
|
|
405
417
|
return;
|
|
406
418
|
}
|
|
407
419
|
if (status < 200 || status >= 300) {
|
|
@@ -411,14 +423,14 @@ function httpsGet(url, redirectCount = 0) {
|
|
|
411
423
|
}
|
|
412
424
|
const chunks = [];
|
|
413
425
|
res.on("data", (chunk) => chunks.push(chunk));
|
|
414
|
-
res.on("end", () =>
|
|
426
|
+
res.on("end", () => resolve(Buffer.concat(chunks)));
|
|
415
427
|
res.on("error", reject);
|
|
416
428
|
}).on("error", reject);
|
|
417
429
|
});
|
|
418
430
|
}
|
|
419
431
|
async function downloadEngine() {
|
|
420
432
|
const key = platformKey();
|
|
421
|
-
const filename = `attest-engine-${key}`;
|
|
433
|
+
const filename = `attest-engine-${key}${os.platform() === "win32" ? ".exe" : ""}`;
|
|
422
434
|
const binaryUrl = `${RELEASE_BASE}/${filename}`;
|
|
423
435
|
process.stderr.write(`[attest] Downloading engine v${ENGINE_VERSION} for ${key}...
|
|
424
436
|
`);
|
|
@@ -438,66 +450,117 @@ async function downloadEngine() {
|
|
|
438
450
|
const actualHash = crypto.createHash("sha256").update(data).digest("hex");
|
|
439
451
|
if (actualHash !== expectedHash) {
|
|
440
452
|
throw new Error(
|
|
441
|
-
`SHA256 mismatch for ${filename}. Expected: ${expectedHash}, got: ${actualHash}. The download may be corrupted.
|
|
453
|
+
`SHA256 mismatch for ${filename}. Expected: ${expectedHash}, got: ${actualHash}. The download may be corrupted. Retry or download manually.`
|
|
442
454
|
);
|
|
443
455
|
}
|
|
444
456
|
process.stderr.write(`[attest] Checksum verified.
|
|
445
457
|
`);
|
|
446
|
-
const dir =
|
|
447
|
-
|
|
448
|
-
const destPath = path.join(dir,
|
|
449
|
-
const
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
458
|
+
const dir = versionedBinDir();
|
|
459
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
460
|
+
const destPath = path.join(dir, binaryFilename());
|
|
461
|
+
const suffix = `${String(process.pid)}.${crypto.randomBytes(8).toString("hex")}`;
|
|
462
|
+
const tmpPath = path.join(dir, `.${binaryFilename()}.tmp.${suffix}`);
|
|
463
|
+
try {
|
|
464
|
+
fs.writeFileSync(tmpPath, data, { flag: "wx", mode: 493 });
|
|
465
|
+
fs.chmodSync(tmpPath, 493);
|
|
466
|
+
fs.renameSync(tmpPath, destPath);
|
|
467
|
+
} catch (err) {
|
|
468
|
+
if (err.code !== "EEXIST") fs.rmSync(tmpPath, { force: true });
|
|
469
|
+
throw err;
|
|
470
|
+
}
|
|
454
471
|
process.stderr.write(`[attest] Engine installed at ${destPath}
|
|
455
472
|
`);
|
|
456
473
|
return destPath;
|
|
457
474
|
}
|
|
458
475
|
|
|
459
476
|
// src/engine-manager.ts
|
|
460
|
-
var import_meta = {};
|
|
461
477
|
var ENGINE_BINARY_NAME = "attest-engine";
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
478
|
+
var VERSION_PROBE_TIMEOUT_MS = 1e4;
|
|
479
|
+
var EngineVersionMismatchError = class extends Error {
|
|
480
|
+
enginePath;
|
|
481
|
+
source;
|
|
482
|
+
found;
|
|
483
|
+
required;
|
|
484
|
+
constructor(enginePath, source, found, required) {
|
|
485
|
+
const reported = found === null ? "reports no version" : `reports version ${found}`;
|
|
486
|
+
super(
|
|
487
|
+
`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}/.`
|
|
488
|
+
);
|
|
489
|
+
this.name = "EngineVersionMismatchError";
|
|
490
|
+
this.enginePath = enginePath;
|
|
491
|
+
this.source = source;
|
|
492
|
+
this.found = found;
|
|
493
|
+
this.required = required;
|
|
477
494
|
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
const
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
495
|
+
};
|
|
496
|
+
function engineVersion(enginePath) {
|
|
497
|
+
const proc = (0, import_node_child_process.spawnSync)(enginePath, ["version"], {
|
|
498
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
499
|
+
encoding: "utf-8",
|
|
500
|
+
timeout: VERSION_PROBE_TIMEOUT_MS
|
|
501
|
+
});
|
|
502
|
+
if (proc.error) throw proc.error;
|
|
503
|
+
const out = proc.stdout.trim();
|
|
504
|
+
const prefix = `${ENGINE_BINARY_NAME} `;
|
|
505
|
+
if (proc.status !== 0 || !out.startsWith(prefix)) return null;
|
|
506
|
+
return out.slice(prefix.length).trim();
|
|
507
|
+
}
|
|
508
|
+
function onPath() {
|
|
509
|
+
for (const dir of (process.env["PATH"] ?? "").split(import_node_path.delimiter)) {
|
|
510
|
+
if (!dir) continue;
|
|
511
|
+
const candidate = (0, import_node_path.join)(dir, binaryFilename());
|
|
486
512
|
try {
|
|
487
|
-
(0, import_node_fs.accessSync)(
|
|
488
|
-
if ((0, import_node_fs.existsSync)(resolved)) return resolved;
|
|
513
|
+
(0, import_node_fs.accessSync)(candidate, import_node_fs.constants.X_OK);
|
|
489
514
|
} catch {
|
|
515
|
+
continue;
|
|
490
516
|
}
|
|
517
|
+
if ((0, import_node_fs.statSync)(candidate).isFile()) return candidate;
|
|
518
|
+
}
|
|
519
|
+
return null;
|
|
520
|
+
}
|
|
521
|
+
function requireFile(enginePath, label) {
|
|
522
|
+
if (!(0, import_node_fs.existsSync)(enginePath) || !(0, import_node_fs.statSync)(enginePath).isFile()) {
|
|
523
|
+
throw new Error(`${label}=${enginePath} does not exist or is not a file.`);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
async function locateEngine(explicitPath, allowDownload) {
|
|
527
|
+
if (explicitPath) {
|
|
528
|
+
requireFile(explicitPath, "Engine path");
|
|
529
|
+
return [explicitPath, "explicit engine path"];
|
|
491
530
|
}
|
|
492
|
-
const
|
|
531
|
+
const envPath = process.env["ATTEST_ENGINE_PATH"];
|
|
532
|
+
if (envPath) {
|
|
533
|
+
requireFile(envPath, "ATTEST_ENGINE_PATH");
|
|
534
|
+
return [envPath, "ATTEST_ENGINE_PATH"];
|
|
535
|
+
}
|
|
536
|
+
const found = onPath();
|
|
537
|
+
if (found) return [found, "PATH"];
|
|
538
|
+
const cached = cachedEnginePath();
|
|
539
|
+
if (cached) return [cached, "engine cache"];
|
|
540
|
+
if (!allowDownload) {
|
|
541
|
+
throw new Error(
|
|
542
|
+
`No '${ENGINE_BINARY_NAME}' ${ENGINE_VERSION} in ATTEST_ENGINE_PATH, PATH or ~/.attest/bin/${ENGINE_VERSION}/.`
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
const noDownload = process.env["ATTEST_ENGINE_NO_DOWNLOAD"]?.toLowerCase();
|
|
493
546
|
if (noDownload === "1" || noDownload === "true" || noDownload === "yes") {
|
|
494
547
|
throw new Error(
|
|
495
|
-
`Cannot find '${ENGINE_BINARY_NAME}'
|
|
496
|
-
|
|
497
|
-
|
|
548
|
+
`Cannot find '${ENGINE_BINARY_NAME}' ${ENGINE_VERSION} and ATTEST_ENGINE_NO_DOWNLOAD is set.
|
|
549
|
+
Install options:
|
|
550
|
+
1. Build from source: make engine, then set ATTEST_ENGINE_PATH
|
|
551
|
+
2. Download release v${ENGINE_VERSION}: https://github.com/attest-framework/attest/releases
|
|
552
|
+
3. Allow auto-download by unsetting ATTEST_ENGINE_NO_DOWNLOAD`
|
|
498
553
|
);
|
|
499
554
|
}
|
|
500
|
-
return downloadEngine();
|
|
555
|
+
return [await downloadEngine(), "download"];
|
|
556
|
+
}
|
|
557
|
+
async function resolveEngine(explicitPath, options = {}) {
|
|
558
|
+
const [enginePath, source] = await locateEngine(explicitPath, options.allowDownload ?? true);
|
|
559
|
+
const found = engineVersion(enginePath);
|
|
560
|
+
if (found !== ENGINE_VERSION) {
|
|
561
|
+
throw new EngineVersionMismatchError(enginePath, source, found, ENGINE_VERSION);
|
|
562
|
+
}
|
|
563
|
+
return enginePath;
|
|
501
564
|
}
|
|
502
565
|
var EngineManager = class {
|
|
503
566
|
enginePath;
|
|
@@ -512,28 +575,41 @@ var EngineManager = class {
|
|
|
512
575
|
this.logLevel = options?.logLevel ?? "warn";
|
|
513
576
|
}
|
|
514
577
|
async start() {
|
|
515
|
-
|
|
516
|
-
this.enginePath = await findEngineBinary();
|
|
517
|
-
}
|
|
578
|
+
this.enginePath = await resolveEngine(this.enginePath || void 0);
|
|
518
579
|
this.process = (0, import_node_child_process.spawn)(this.enginePath, [`--log-level=${this.logLevel}`], {
|
|
519
580
|
stdio: ["pipe", "pipe", "pipe"]
|
|
520
581
|
});
|
|
521
582
|
this.reader = (0, import_node_readline.createInterface)({ input: this.process.stdout });
|
|
522
|
-
|
|
583
|
+
try {
|
|
584
|
+
this.initResult = await this.initialize();
|
|
585
|
+
} catch (err) {
|
|
586
|
+
await this.stop();
|
|
587
|
+
throw err;
|
|
588
|
+
}
|
|
589
|
+
this.initialized = true;
|
|
590
|
+
return this.initResult;
|
|
591
|
+
}
|
|
592
|
+
/** Send initialize and check the engine's version and capabilities. */
|
|
593
|
+
async initialize() {
|
|
594
|
+
const init = await this.sendRequestInternal("initialize", {
|
|
523
595
|
sdk_name: "attest-typescript",
|
|
524
596
|
sdk_version: VERSION,
|
|
525
597
|
protocol_version: 1,
|
|
526
598
|
required_capabilities: ["layers_1_4"],
|
|
527
599
|
preferred_encoding: "json"
|
|
528
600
|
});
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
601
|
+
if (init.engine_version !== ENGINE_VERSION) {
|
|
602
|
+
throw new EngineVersionMismatchError(
|
|
603
|
+
this.enginePath,
|
|
604
|
+
"initialize",
|
|
605
|
+
init.engine_version,
|
|
606
|
+
ENGINE_VERSION
|
|
533
607
|
);
|
|
534
608
|
}
|
|
535
|
-
|
|
536
|
-
|
|
609
|
+
if (!init.compatible) {
|
|
610
|
+
throw new Error(`Engine incompatible. Missing capabilities: ${JSON.stringify(init.missing)}`);
|
|
611
|
+
}
|
|
612
|
+
return init;
|
|
537
613
|
}
|
|
538
614
|
async stop() {
|
|
539
615
|
if (this.process === null) return;
|
|
@@ -574,11 +650,11 @@ var EngineManager = class {
|
|
|
574
650
|
}
|
|
575
651
|
this.requestId += 1;
|
|
576
652
|
const requestStr = encodeRequest(this.requestId, method, params);
|
|
577
|
-
return new Promise((
|
|
653
|
+
return new Promise((resolve, reject) => {
|
|
578
654
|
const onLine = (line) => {
|
|
579
655
|
try {
|
|
580
656
|
const response = decodeResponse(line);
|
|
581
|
-
|
|
657
|
+
resolve(extractResult(response));
|
|
582
658
|
} catch (err) {
|
|
583
659
|
reject(err);
|
|
584
660
|
}
|
|
@@ -626,7 +702,11 @@ function config(options) {
|
|
|
626
702
|
}
|
|
627
703
|
}
|
|
628
704
|
function isSimulationMode() {
|
|
629
|
-
return _simulationMode ||
|
|
705
|
+
return _simulationMode || simulationEnvEnabled();
|
|
706
|
+
}
|
|
707
|
+
function simulationEnvEnabled() {
|
|
708
|
+
const env = (process.env["ATTEST_SIMULATION"] ?? "").trim().toLowerCase();
|
|
709
|
+
return env === "1" || env === "true" || env === "yes";
|
|
630
710
|
}
|
|
631
711
|
function getSampleRate() {
|
|
632
712
|
if (_sampleRate > 0) return _sampleRate;
|
|
@@ -647,24 +727,6 @@ function resetConfig() {
|
|
|
647
727
|
_sampleRate = 0;
|
|
648
728
|
}
|
|
649
729
|
|
|
650
|
-
// src/simulation.ts
|
|
651
|
-
function simulationEvaluateBatch(assertions) {
|
|
652
|
-
const results = assertions.map((assertion) => ({
|
|
653
|
-
assertion_id: assertion.assertion_id,
|
|
654
|
-
status: "pass",
|
|
655
|
-
score: 1,
|
|
656
|
-
explanation: `[simulation] assertion '${assertion.assertion_id}' passed (type: ${assertion.type})`,
|
|
657
|
-
cost: 0,
|
|
658
|
-
duration_ms: 0
|
|
659
|
-
}));
|
|
660
|
-
return {
|
|
661
|
-
results,
|
|
662
|
-
total_cost: 0,
|
|
663
|
-
total_duration_ms: 0,
|
|
664
|
-
simulated: true
|
|
665
|
-
};
|
|
666
|
-
}
|
|
667
|
-
|
|
668
730
|
// src/client.ts
|
|
669
731
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
670
732
|
var DEFAULT_DIAGNOSTIC_BUFFER = 32;
|
|
@@ -889,12 +951,12 @@ var AttestClient = class {
|
|
|
889
951
|
throw new Error("Engine process not started.");
|
|
890
952
|
}
|
|
891
953
|
let capturedReqId;
|
|
892
|
-
const requestPromise = new Promise((
|
|
954
|
+
const requestPromise = new Promise((resolve, reject) => {
|
|
893
955
|
this.writeChain = this.writeChain.then(() => {
|
|
894
956
|
this.requestId += 1;
|
|
895
957
|
const reqId = this.requestId;
|
|
896
958
|
capturedReqId = reqId;
|
|
897
|
-
this.pending.set(reqId, { resolve
|
|
959
|
+
this.pending.set(reqId, { resolve, reject });
|
|
898
960
|
const requestStr = encodeRequest(reqId, method, params);
|
|
899
961
|
cp.stdin.write(requestStr, (err) => {
|
|
900
962
|
if (err) {
|
|
@@ -914,7 +976,7 @@ var AttestClient = class {
|
|
|
914
976
|
}
|
|
915
977
|
async evaluateBatch(trace, assertions, options) {
|
|
916
978
|
if (isSimulationMode()) {
|
|
917
|
-
|
|
979
|
+
requireSimulationCapability(this.engine["initResult"]);
|
|
918
980
|
}
|
|
919
981
|
const params = {
|
|
920
982
|
trace,
|
|
@@ -953,10 +1015,10 @@ var AttestClient = class {
|
|
|
953
1015
|
if (isSimulationMode()) {
|
|
954
1016
|
return {
|
|
955
1017
|
assertion_id: assertionId2,
|
|
956
|
-
mean:
|
|
1018
|
+
mean: 0,
|
|
957
1019
|
stddev: 0,
|
|
958
1020
|
count: 0,
|
|
959
|
-
latest_score:
|
|
1021
|
+
latest_score: 0,
|
|
960
1022
|
deviation: 0,
|
|
961
1023
|
status: "no_data"
|
|
962
1024
|
};
|
|
@@ -994,6 +1056,16 @@ function classifyDecodeError(message) {
|
|
|
994
1056
|
if (message.startsWith("empty response line")) return "malformed_json";
|
|
995
1057
|
return "malformed_json";
|
|
996
1058
|
}
|
|
1059
|
+
function requireSimulationCapability(init) {
|
|
1060
|
+
if (init === null || init === void 0) {
|
|
1061
|
+
throw new Error("simulation mode: the engine has not been initialized");
|
|
1062
|
+
}
|
|
1063
|
+
if (!(init.capabilities ?? []).includes("simulation_mode")) {
|
|
1064
|
+
throw new Error(
|
|
1065
|
+
`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.`
|
|
1066
|
+
);
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
997
1069
|
|
|
998
1070
|
// src/trace.ts
|
|
999
1071
|
var import_node_crypto = require("crypto");
|
|
@@ -1084,6 +1156,20 @@ var TraceBuilder = class {
|
|
|
1084
1156
|
};
|
|
1085
1157
|
|
|
1086
1158
|
// src/result.ts
|
|
1159
|
+
var KNOWN_STATUSES = /* @__PURE__ */ new Set([
|
|
1160
|
+
STATUS_PASS,
|
|
1161
|
+
STATUS_SOFT_FAIL,
|
|
1162
|
+
STATUS_HARD_FAIL,
|
|
1163
|
+
STATUS_NOT_EVALUATED
|
|
1164
|
+
]);
|
|
1165
|
+
function normalizeAssertionResult(r) {
|
|
1166
|
+
if (KNOWN_STATUSES.has(r.status)) return r;
|
|
1167
|
+
return {
|
|
1168
|
+
...r,
|
|
1169
|
+
status: STATUS_NOT_EVALUATED,
|
|
1170
|
+
explanation: `[unknown status '${r.status}'] ${r.explanation}`
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1087
1173
|
var AgentResult = class {
|
|
1088
1174
|
trace;
|
|
1089
1175
|
assertionResults;
|
|
@@ -1091,15 +1177,31 @@ var AgentResult = class {
|
|
|
1091
1177
|
totalDurationMs;
|
|
1092
1178
|
constructor(trace, assertionResults = [], totalCost = 0, totalDurationMs = 0) {
|
|
1093
1179
|
this.trace = trace;
|
|
1094
|
-
this.assertionResults = assertionResults;
|
|
1180
|
+
this.assertionResults = assertionResults.map(normalizeAssertionResult);
|
|
1095
1181
|
this.totalCost = totalCost;
|
|
1096
1182
|
this.totalDurationMs = totalDurationMs;
|
|
1097
1183
|
}
|
|
1184
|
+
/** True if every assertion has status "pass". */
|
|
1098
1185
|
get passed() {
|
|
1099
1186
|
return this.assertionResults.every((r) => r.status === STATUS_PASS);
|
|
1100
1187
|
}
|
|
1188
|
+
/**
|
|
1189
|
+
* Test outcome after the whole batch. Any hard_fail fails; otherwise any
|
|
1190
|
+
* not_evaluated skips (fails when `strict`); otherwise pass, including
|
|
1191
|
+
* when only soft_fail rows are present (see `softFailures`).
|
|
1192
|
+
*/
|
|
1193
|
+
outcome(options) {
|
|
1194
|
+
if (this.hardFailures.length > 0) return OUTCOME_FAIL;
|
|
1195
|
+
if (this.notEvaluated.length > 0) {
|
|
1196
|
+
return options?.strict === true ? OUTCOME_FAIL : OUTCOME_SKIP;
|
|
1197
|
+
}
|
|
1198
|
+
return OUTCOME_PASS;
|
|
1199
|
+
}
|
|
1200
|
+
/** Failed assertions: hard_fail and soft_fail only. */
|
|
1101
1201
|
get failedAssertions() {
|
|
1102
|
-
return this.assertionResults.filter(
|
|
1202
|
+
return this.assertionResults.filter(
|
|
1203
|
+
(r) => r.status === STATUS_HARD_FAIL || r.status === STATUS_SOFT_FAIL
|
|
1204
|
+
);
|
|
1103
1205
|
}
|
|
1104
1206
|
get hardFailures() {
|
|
1105
1207
|
return this.assertionResults.filter((r) => r.status === STATUS_HARD_FAIL);
|
|
@@ -1107,11 +1209,40 @@ var AgentResult = class {
|
|
|
1107
1209
|
get softFailures() {
|
|
1108
1210
|
return this.assertionResults.filter((r) => r.status === STATUS_SOFT_FAIL);
|
|
1109
1211
|
}
|
|
1212
|
+
/** Assertions the engine did not evaluate; each carries a `reason`. */
|
|
1213
|
+
get notEvaluated() {
|
|
1214
|
+
return this.assertionResults.filter((r) => r.status === STATUS_NOT_EVALUATED);
|
|
1215
|
+
}
|
|
1216
|
+
/** Count of not_evaluated assertions per reason, in first-seen order. */
|
|
1217
|
+
get notEvaluatedReasons() {
|
|
1218
|
+
const counts = {};
|
|
1219
|
+
for (const r of this.notEvaluated) {
|
|
1220
|
+
const key = r.reason || "unknown";
|
|
1221
|
+
counts[key] = (counts[key] ?? 0) + 1;
|
|
1222
|
+
}
|
|
1223
|
+
return counts;
|
|
1224
|
+
}
|
|
1225
|
+
/**
|
|
1226
|
+
* Passed / evaluated assertions; null when nothing was evaluated.
|
|
1227
|
+
* not_evaluated rows are excluded from the denominator.
|
|
1228
|
+
*/
|
|
1229
|
+
get passRate() {
|
|
1230
|
+
const evaluated = this.assertionResults.length - this.notEvaluated.length;
|
|
1231
|
+
return evaluated === 0 ? null : this.passCount / evaluated;
|
|
1232
|
+
}
|
|
1110
1233
|
get passCount() {
|
|
1111
1234
|
return this.assertionResults.filter((r) => r.status === STATUS_PASS).length;
|
|
1112
1235
|
}
|
|
1236
|
+
/** Number of failed assertions (hard_fail + soft_fail). */
|
|
1113
1237
|
get failCount() {
|
|
1114
|
-
return this.
|
|
1238
|
+
return this.failedAssertions.length;
|
|
1239
|
+
}
|
|
1240
|
+
/** "not evaluated: <reason>[, <reason> ×n ...]" for skipped tests. */
|
|
1241
|
+
skipReason() {
|
|
1242
|
+
const parts = Object.entries(this.notEvaluatedReasons).map(
|
|
1243
|
+
([reason, n]) => n === 1 ? reason : `${reason} \xD7${n}`
|
|
1244
|
+
);
|
|
1245
|
+
return `not evaluated: ${parts.join(", ")}`;
|
|
1115
1246
|
}
|
|
1116
1247
|
};
|
|
1117
1248
|
|
|
@@ -1151,6 +1282,9 @@ function statusGlyph(status, color) {
|
|
|
1151
1282
|
} else if (status === STATUS_HARD_FAIL) {
|
|
1152
1283
|
glyph = "FAIL";
|
|
1153
1284
|
c = ANSI_RED;
|
|
1285
|
+
} else if (status === STATUS_NOT_EVALUATED) {
|
|
1286
|
+
glyph = "N/E";
|
|
1287
|
+
c = ANSI_DIM;
|
|
1154
1288
|
} else {
|
|
1155
1289
|
glyph = status.toUpperCase() || "????";
|
|
1156
1290
|
c = ANSI_DIM;
|
|
@@ -1178,10 +1312,15 @@ function renderAssertionFailure(result, options = {}) {
|
|
|
1178
1312
|
lines.push(
|
|
1179
1313
|
` ${statusGlyph(result.status, color)} ${bold(result.assertion_id, color)} ${dim(`\u2014 ${label}`, color)}`
|
|
1180
1314
|
);
|
|
1315
|
+
if (result.reason) {
|
|
1316
|
+
lines.push(` reason: not evaluated: ${result.reason}`);
|
|
1317
|
+
}
|
|
1181
1318
|
if (result.trace_node_path) {
|
|
1182
1319
|
lines.push(` trace path: ${result.trace_node_path}`);
|
|
1183
1320
|
}
|
|
1184
|
-
|
|
1321
|
+
if (result.status !== STATUS_NOT_EVALUATED) {
|
|
1322
|
+
lines.push(` score: ${result.score.toFixed(3)}`);
|
|
1323
|
+
}
|
|
1185
1324
|
if (result.expected) {
|
|
1186
1325
|
lines.push(` expected: ${truncate(result.expected)}`);
|
|
1187
1326
|
}
|
|
@@ -1194,6 +1333,9 @@ function renderAssertionFailure(result, options = {}) {
|
|
|
1194
1333
|
if (result.threshold_source && result.threshold_source !== "static") {
|
|
1195
1334
|
lines.push(` threshold: ${result.threshold_source}`);
|
|
1196
1335
|
}
|
|
1336
|
+
if (result.failure_class) {
|
|
1337
|
+
lines.push(` class: ${result.failure_class}`);
|
|
1338
|
+
}
|
|
1197
1339
|
if (result.judge_metadata) {
|
|
1198
1340
|
appendJudgeMetadata(lines, result.judge_metadata);
|
|
1199
1341
|
}
|
|
@@ -1256,6 +1398,7 @@ function renderSummary(result, options = {}) {
|
|
|
1256
1398
|
`${statusGlyph(STATUS_PASS, color)} ${passN}`,
|
|
1257
1399
|
`${statusGlyph(STATUS_SOFT_FAIL, color)} ${soft}`,
|
|
1258
1400
|
`${statusGlyph(STATUS_HARD_FAIL, color)} ${hard}`,
|
|
1401
|
+
`${statusGlyph(STATUS_NOT_EVALUATED, color)} ${result.notEvaluated.length}`,
|
|
1259
1402
|
`cost $${result.totalCost.toFixed(6)}`,
|
|
1260
1403
|
`dur ${result.totalDurationMs}ms`
|
|
1261
1404
|
];
|
|
@@ -1264,23 +1407,27 @@ function renderSummary(result, options = {}) {
|
|
|
1264
1407
|
function renderDiagnostics(result, options = {}) {
|
|
1265
1408
|
const color = options.color ?? false;
|
|
1266
1409
|
const includePassing = options.includePassing ?? false;
|
|
1267
|
-
const rows = includePassing ? result.assertionResults : result.
|
|
1410
|
+
const rows = includePassing ? result.assertionResults : result.assertionResults.filter((r) => r.status !== STATUS_PASS);
|
|
1268
1411
|
if (rows.length === 0) {
|
|
1269
1412
|
return dim(" (no failures)\n", color);
|
|
1270
1413
|
}
|
|
1271
1414
|
const out = [""];
|
|
1272
|
-
out.push(
|
|
1273
|
-
bold(
|
|
1274
|
-
`Attest diagnostic \u2014 ${rows.length} of ${result.assertionResults.length} assertions failed:`,
|
|
1275
|
-
color
|
|
1276
|
-
)
|
|
1277
|
-
);
|
|
1415
|
+
out.push(bold(`Attest diagnostic \u2014 ${headerCounts(result)}:`, color));
|
|
1278
1416
|
for (const r of rows) {
|
|
1279
1417
|
out.push(renderAssertionFailure(r, options));
|
|
1280
1418
|
}
|
|
1281
1419
|
out.push(dim(` Summary: ${renderSummary(result, { color })}`, color));
|
|
1282
1420
|
return out.join("\n");
|
|
1283
1421
|
}
|
|
1422
|
+
function headerCounts(result) {
|
|
1423
|
+
const total = result.assertionResults.length;
|
|
1424
|
+
const notEvaluated = result.notEvaluated.length;
|
|
1425
|
+
if (result.failCount === 0 && notEvaluated > 0) {
|
|
1426
|
+
return `${notEvaluated} of ${total} assertions not evaluated`;
|
|
1427
|
+
}
|
|
1428
|
+
const text = `${result.failCount} of ${total} assertions failed`;
|
|
1429
|
+
return notEvaluated > 0 ? `${text}, ${notEvaluated} not evaluated` : text;
|
|
1430
|
+
}
|
|
1284
1431
|
|
|
1285
1432
|
// src/trace-tree.ts
|
|
1286
1433
|
var TraceTree = class {
|
|
@@ -3137,17 +3284,27 @@ var LangChainAdapter = class {
|
|
|
3137
3284
|
EngineTimeoutError,
|
|
3138
3285
|
ExpectChain,
|
|
3139
3286
|
FRIENDLY_USER,
|
|
3287
|
+
FailureClass,
|
|
3140
3288
|
GeminiAdapter,
|
|
3141
3289
|
LangChainAdapter,
|
|
3142
3290
|
ManualAdapter,
|
|
3143
3291
|
OTelAdapter,
|
|
3292
|
+
OUTCOME_FAIL,
|
|
3293
|
+
OUTCOME_PASS,
|
|
3294
|
+
OUTCOME_SKIP,
|
|
3144
3295
|
OllamaAdapter,
|
|
3145
3296
|
OpenAIAdapter,
|
|
3146
3297
|
PluginRegistry,
|
|
3147
3298
|
ProtocolDesyncError,
|
|
3148
3299
|
ProtocolDiagnosticBuffer,
|
|
3149
3300
|
ProtocolError,
|
|
3301
|
+
REASON_MISSING_EVIDENCE,
|
|
3302
|
+
REASON_PROVIDER_NOT_CONFIGURED,
|
|
3303
|
+
REASON_PROVIDER_UNAVAILABLE,
|
|
3304
|
+
REASON_SHORT_CIRCUITED,
|
|
3305
|
+
REASON_SIMULATION,
|
|
3150
3306
|
STATUS_HARD_FAIL,
|
|
3307
|
+
STATUS_NOT_EVALUATED,
|
|
3151
3308
|
STATUS_PASS,
|
|
3152
3309
|
STATUS_SOFT_FAIL,
|
|
3153
3310
|
STEP_AGENT_CALL,
|
|
@@ -3192,8 +3349,9 @@ var LangChainAdapter = class {
|
|
|
3192
3349
|
renderAssertionFailure,
|
|
3193
3350
|
renderDiagnostics,
|
|
3194
3351
|
renderSummary,
|
|
3352
|
+
requireSimulationCapability,
|
|
3195
3353
|
resetConfig,
|
|
3196
|
-
|
|
3354
|
+
simulationEnvEnabled,
|
|
3197
3355
|
tier,
|
|
3198
3356
|
traceId
|
|
3199
3357
|
});
|