@attest-ai/core 1.0.0-rc.4 → 1.0.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.
@@ -5,8 +5,8 @@ import { Interface } from 'node:readline';
5
5
  import { AsyncLocalStorage } from 'node:async_hooks';
6
6
  import { EventEmitter } from 'node:events';
7
7
 
8
- declare const VERSION = "1.0.0-rc.4";
9
- declare const ENGINE_VERSION = "1.0.0-rc.4";
8
+ declare const VERSION = "1.0.0";
9
+ declare const ENGINE_VERSION = "1.0.0";
10
10
 
11
11
  declare class EngineManager {
12
12
  private enginePath;
@@ -1120,6 +1120,12 @@ interface RecordResult<T> {
1120
1120
  value: T;
1121
1121
  fixture: string;
1122
1122
  verified: boolean;
1123
+ /**
1124
+ * `verified`, `not_verified` or `nothing_to_verify`: a healthy run with no tool call,
1125
+ * structured output or cost to draft a contract from. `record` throws for
1126
+ * `not_verified`; `nothing_to_verify` returns `verified: false` and no test file.
1127
+ */
1128
+ outcome: "verified" | "nothing_to_verify";
1123
1129
  testFile: string | null;
1124
1130
  }
1125
1131
  /**
@@ -1135,8 +1141,10 @@ interface RecordResult<T> {
1135
1141
  * abandoned and no fixture is written. Captures cannot overlap.
1136
1142
  *
1137
1143
  * @throws CaptureError when another capture is running, the engine writes no fixture,
1138
- * or the fixture is not verified (its drafted contracts did not hold; the fixture is
1139
- * still written and no test is drafted).
1144
+ * or the fixture is not verified (a drafted contract did not hold, the trace check
1145
+ * failed or no model call was recorded; the fixture is still written and no test is
1146
+ * drafted). A healthy run with nothing to draft a contract from does not throw: the
1147
+ * result has `outcome: "nothing_to_verify"`, `verified: false` and no test file.
1140
1148
  */
1141
1149
  declare function record<T>(name: string, fn: (capture: Capture) => T | Promise<T>, opts?: RecordOptions): Promise<RecordResult<T>>;
1142
1150
 
@@ -1337,6 +1345,20 @@ declare class PluginRegistry {
1337
1345
  listPlugins(pluginType?: string): string[];
1338
1346
  }
1339
1347
  declare function executePluginAssertion(plugin: AttestPlugin, trace: Trace, spec: Record<string, unknown>, client: AttestClient, traceId: string, assertionId: string, timeoutMs?: number): Promise<PluginResult>;
1348
+ /** Explicitly register a plugin instance into the registry. */
1349
+ declare function registerPlugin(registry: PluginRegistry, name: string, plugin: AttestPlugin): void;
1350
+ /**
1351
+ * Discover and load plugins declared by the project's direct dependencies.
1352
+ *
1353
+ * An installed dependency declares plugins in its `package.json` as
1354
+ * `"attest": {"plugins": ["./dist/plugin.js"]}`. Each entry is imported and
1355
+ * its default export (or, without one, every export implementing the
1356
+ * plugin shape) is registered under the plugin's `name`. A failure loading
1357
+ * one entry, or reading the project's `package.json`, is logged and does not
1358
+ * raise, as with Python's `load_entrypoint_plugins`. Dependencies that are declared but not
1359
+ * installed are skipped. Returns the number of plugins loaded.
1360
+ */
1361
+ declare function loadPackagePlugins(registry: PluginRegistry, projectRoot?: string): Promise<number>;
1340
1362
 
1341
1363
  declare class Sampler {
1342
1364
  private readonly rate;
@@ -1439,6 +1461,132 @@ declare const ADVERSARIAL_USER: SimulatePersona;
1439
1461
  declare const CONFUSED_USER: SimulatePersona;
1440
1462
  declare const COOPERATIVE_USER: SimulatePersona;
1441
1463
 
1464
+ /** Mock tool implementations active for the current async scope. */
1465
+ type MockToolFn = (args: Record<string, unknown>) => unknown;
1466
+
1467
+ /** Registry of mock tool implementations, activated for the duration of `run`. */
1468
+ declare class MockToolRegistry {
1469
+ private readonly tools;
1470
+ register(name: string, fn: MockToolFn): void;
1471
+ /**
1472
+ * Run `fn` with the registered mocks active. `TraceBuilder.addToolCall`
1473
+ * returns the mock's result for a registered tool name. Scopes are
1474
+ * isolated per async call chain (AsyncLocalStorage).
1475
+ */
1476
+ run<T>(fn: () => T): T;
1477
+ }
1478
+ type MockTool<F extends MockToolFn> = F & {
1479
+ readonly mockToolName: string;
1480
+ };
1481
+ /** Tag `fn` as the mock for tool `name`; the returned function behaves like `fn`. */
1482
+ declare function mockTool<F extends MockToolFn>(name: string, fn: F): MockTool<F>;
1483
+
1484
+ interface FaultInjectOptions {
1485
+ /** Probability in [0, 1] that a call throws. Default 0. */
1486
+ errorRate?: number;
1487
+ /** Upper bound of uniform random delay per call, in ms. Default 0. */
1488
+ latencyJitterMs?: number;
1489
+ /** Seed for a deterministic fault sequence. */
1490
+ seed?: number;
1491
+ }
1492
+ /**
1493
+ * Wrap `fn` so calls throw `Error("Injected fault in <name>")` at
1494
+ * `errorRate` and are delayed by up to `latencyJitterMs`. The RNG is
1495
+ * created once per wrapped function, so a fixed seed gives a fixed
1496
+ * sequence across calls.
1497
+ */
1498
+ declare function faultInject<A extends unknown[], R>(options: FaultInjectOptions, fn: (...args: A) => R | Promise<R>): (...args: A) => Promise<R>;
1499
+
1500
+ /**
1501
+ * Anything with a test outcome: `AgentResult`, `RepeatResult` and
1502
+ * `ScenarioResult`. Aggregates nest.
1503
+ */
1504
+ interface Verdict {
1505
+ readonly notEvaluatedReasons: Readonly<Record<string, number>>;
1506
+ outcome(options?: {
1507
+ strict?: boolean;
1508
+ }): Outcome;
1509
+ skipReason(): string;
1510
+ }
1511
+ type Renderable = Verdict & {
1512
+ renderDiagnostics?(options?: {
1513
+ color?: boolean;
1514
+ }): string;
1515
+ };
1516
+ /** Diagnostics of one verdict: an `AgentResult` or an aggregate. */
1517
+ declare function renderVerdictDiagnostics(verdict: Renderable, color?: boolean): string;
1518
+
1519
+ /** Results of `n` trials, aggregated under the assertion-status rules. */
1520
+ declare class RepeatResult implements Verdict {
1521
+ readonly results: readonly Verdict[];
1522
+ constructor(results?: readonly Verdict[]);
1523
+ get count(): number;
1524
+ /** Trials skipped because an assertion was not evaluated. */
1525
+ get notEvaluated(): number;
1526
+ /** Passing / evaluated trials; null when no trial was evaluated. */
1527
+ get passRate(): number | null;
1528
+ /** True when every trial's outcome is pass (soft failures included). */
1529
+ get allPassed(): boolean;
1530
+ outcome(options?: {
1531
+ strict?: boolean;
1532
+ }): Outcome;
1533
+ get notEvaluatedReasons(): Readonly<Record<string, number>>;
1534
+ skipReason(): string;
1535
+ renderDiagnostics(options?: {
1536
+ color?: boolean;
1537
+ }): string;
1538
+ }
1539
+ /** Run `fn` `n` times sequentially and collect the verdicts. */
1540
+ declare function repeat<A extends unknown[]>(n: number, fn: (...args: A) => Verdict | Promise<Verdict>): (...args: A) => Promise<RepeatResult>;
1541
+
1542
+ interface ScenarioConfig {
1543
+ persona?: SimulatePersona;
1544
+ mockTools: Readonly<Record<string, MockToolFn>>;
1545
+ faultErrorRate: number;
1546
+ faultLatencyJitterMs: number;
1547
+ maxTurns: number;
1548
+ seed?: number;
1549
+ }
1550
+ /** Passed to the scenario body on every turn. */
1551
+ interface ScenarioContext {
1552
+ readonly persona: SimulatePersona | undefined;
1553
+ /** Zero-based turn index. */
1554
+ readonly turn: number;
1555
+ }
1556
+ /** One verdict per turn; the aggregate follows the assertion-status rules. */
1557
+ declare class ScenarioResult implements Verdict {
1558
+ readonly config: ScenarioConfig;
1559
+ readonly results: readonly AgentResult[];
1560
+ constructor(config: ScenarioConfig, results?: readonly AgentResult[]);
1561
+ /** True when the aggregate `outcome()` is pass. */
1562
+ get passed(): boolean;
1563
+ outcome(options?: {
1564
+ strict?: boolean;
1565
+ }): Outcome;
1566
+ get notEvaluatedReasons(): Readonly<Record<string, number>>;
1567
+ skipReason(): string;
1568
+ renderDiagnostics(options?: {
1569
+ color?: boolean;
1570
+ }): string;
1571
+ }
1572
+ interface ScenarioOptions {
1573
+ persona?: SimulatePersona;
1574
+ mockTools?: Record<string, MockToolFn>;
1575
+ /** Probability per turn of throwing `Injected fault at turn N`. Default 0. */
1576
+ faultErrorRate?: number;
1577
+ /** Upper bound of uniform random delay per turn, in ms. Default 0. */
1578
+ faultLatencyJitterMs?: number;
1579
+ /** Turns to run. Default 1. */
1580
+ maxTurns?: number;
1581
+ /** Seed for a deterministic fault sequence. */
1582
+ seed?: number;
1583
+ }
1584
+ /**
1585
+ * Run `fn` for `maxTurns` turns with mock tools active, injecting faults
1586
+ * and latency per turn. `fn` receives the persona and turn index.
1587
+ */
1588
+ declare function scenario<A extends unknown[]>(options: ScenarioOptions, fn: (ctx: ScenarioContext, ...args: A) => AgentResult | Promise<AgentResult>): (...args: A) => Promise<ScenarioResult>;
1589
+
1442
1590
  declare class ManualAdapter {
1443
1591
  private readonly agentId;
1444
1592
  constructor(agentId?: string);
@@ -1663,5 +1811,5 @@ interface TraceAdapter {
1663
1811
  traceFromResponse(response: unknown, options?: Record<string, unknown>): Trace;
1664
1812
  }
1665
1813
 
1666
- export { ADVERSARIAL_USER, Agent, AgentResult, AlertDispatcher, AnthropicAdapter, Assertion, AssertionResult, AttestClient, BatchBudget, BatchContext, CONFUSED_USER, COOPERATIVE_USER, CaptureError, ContinuousEvalRunner, ConversationMessage, DriftReport, ENGINE_VERSION, EgressBlockedError, EngineManager, EvaluateBatchResult, ExpectChain, FRIENDLY_USER, GeminiAdapter, InitializeResult, LangChainAdapter, ManualAdapter, OTelAdapter, OllamaAdapter, OpenAIAdapter, Outcome, PluginRegistry, ProtocolDiagnostic, ProtocolLogger, ReplayError, ReplayMissError, ReplayNotEvaluatedError, Sampler, SimulateFaultConfig, SimulatePersona, Step, TIER_1, TIER_2, TIER_3, Trace, TraceBuilder, TraceTree, Usage, VERSION, WorldChecks, WorldError, activeBuilder, addUsage, agent, attestExpect, computeAgreement, config, delegate, dslCall, egressBlockRequested, emitVitest, executePluginAssertion, firstMiss, formatUsage, getSampleRate, installEgressGuard, isProviderHost, isSimulationMode, liveLine, loadFixture, loadLabelsCSV, loadLabelsJSONL, parseUsage, promptHash, record, renderAssertionFailure, renderDiagnostics, renderSummary, renderVitest, requireSimulationCapability, resetConfig, resolveMode, simulationEnvEnabled, startReplay, startWorld, sumUsage, testPathFor, tier, uninstallEgressGuard, usageSummaryLine, zeroUsage };
1667
- export type { AgreementResult, AlertPayload, AttestClientOptions, AttestPlugin, Capture, Contract, ContractResult, Fixture, LabelPair, LabeledRecord, PluginResult, RecordOptions, RecordResult, RenderOptions, ReplayMode, ReplayModeSetting, ReplayOptions, ReplayRecord, ReplaySession, ReplaySummary, TraceAdapter, WorldOptions, WorldSession, WorldSnapshot };
1814
+ export { ADVERSARIAL_USER, Agent, AgentResult, AlertDispatcher, AnthropicAdapter, Assertion, AssertionResult, AttestClient, BatchBudget, BatchContext, CONFUSED_USER, COOPERATIVE_USER, CaptureError, ContinuousEvalRunner, ConversationMessage, DriftReport, ENGINE_VERSION, EgressBlockedError, EngineManager, EvaluateBatchResult, ExpectChain, FRIENDLY_USER, GeminiAdapter, InitializeResult, LangChainAdapter, ManualAdapter, MockToolRegistry, OTelAdapter, OllamaAdapter, OpenAIAdapter, Outcome, PluginRegistry, ProtocolDiagnostic, ProtocolLogger, RepeatResult, ReplayError, ReplayMissError, ReplayNotEvaluatedError, Sampler, ScenarioResult, SimulateFaultConfig, SimulatePersona, Step, TIER_1, TIER_2, TIER_3, Trace, TraceBuilder, TraceTree, Usage, VERSION, WorldChecks, WorldError, activeBuilder, addUsage, agent, attestExpect, computeAgreement, config, delegate, dslCall, egressBlockRequested, emitVitest, executePluginAssertion, faultInject, firstMiss, formatUsage, getSampleRate, installEgressGuard, isProviderHost, isSimulationMode, liveLine, loadFixture, loadLabelsCSV, loadLabelsJSONL, loadPackagePlugins, mockTool, parseUsage, promptHash, record, registerPlugin, renderAssertionFailure, renderDiagnostics, renderSummary, renderVerdictDiagnostics, renderVitest, repeat, requireSimulationCapability, resetConfig, resolveMode, scenario, simulationEnvEnabled, startReplay, startWorld, sumUsage, testPathFor, tier, uninstallEgressGuard, usageSummaryLine, zeroUsage };
1815
+ export type { AgreementResult, AlertPayload, AttestClientOptions, AttestPlugin, Capture, Contract, ContractResult, FaultInjectOptions, Fixture, LabelPair, LabeledRecord, MockTool, MockToolFn, PluginResult, RecordOptions, RecordResult, RenderOptions, ReplayMode, ReplayModeSetting, ReplayOptions, ReplayRecord, ReplaySession, ReplaySummary, ScenarioConfig, ScenarioContext, ScenarioOptions, TraceAdapter, Verdict, WorldOptions, WorldSession, WorldSnapshot };
package/dist/esm/cli.js CHANGED
@@ -8,8 +8,8 @@ import * as path4 from "path";
8
8
  import { fileURLToPath as fileURLToPath2 } from "url";
9
9
 
10
10
  // src/version.ts
11
- var VERSION = "1.0.0-rc.4";
12
- var ENGINE_VERSION = "1.0.0-rc.4";
11
+ var VERSION = "1.0.0";
12
+ var ENGINE_VERSION = "1.0.0";
13
13
 
14
14
  // src/engine-manager.ts
15
15
  import { spawn, spawnSync } from "child_process";
@@ -319,10 +319,17 @@ jobs:
319
319
  - name: Run tests
320
320
  id: tests
321
321
  continue-on-error: true
322
+ # Traces (prompts, outputs, tool args; not redacted) travel in the artifact; on a public
323
+ # repository any signed-in GitHub user can download it. Enable on private repositories only:
324
+ # env:
325
+ # ATTEST_REPORT_TRACES: "1"
322
326
  run: npx vitest run ${REPORT_ARGS}
323
327
  - name: Write the JUnit report
324
328
  if: always()
325
329
  run: npx attest report junit --report attest-report.json --out attest-junit.xml
330
+ - name: Write the failure viewer
331
+ if: always()
332
+ run: npx attest view attest-report.json --no-open --out attest-report.html
326
333
  - name: Upload the Attest report
327
334
  if: always()
328
335
  uses: actions/upload-artifact@v7
@@ -331,6 +338,7 @@ jobs:
331
338
  path: |
332
339
  attest-report.json
333
340
  attest-junit.xml
341
+ attest-report.html
334
342
  if-no-files-found: error
335
343
  - name: Evaluate the merge policy
336
344
  if: always()
@@ -786,7 +794,8 @@ function captureAndEmit(engine, args) {
786
794
  }
787
795
  const fixture = result.fixture;
788
796
  if (!result.verified) {
789
- process.stderr.write(` test: none drafted for unverified ${fixture}
797
+ const reason = result.outcome === "nothing_to_verify" ? "with nothing to verify" : "for unverified";
798
+ process.stderr.write(` test: none drafted ${reason} ${fixture}
790
799
  `);
791
800
  return code;
792
801
  }