@rulvar/testing 1.3.1 → 1.4.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/README.md ADDED
@@ -0,0 +1,27 @@
1
+ # @rulvar/testing
2
+
3
+ The rulvar test harness: `createTestEngine` and the deterministic
4
+ `FakeAdapter` for fast typed unit tests, VCR cassettes with secret
5
+ redaction, replay-strict runs that fail on any unexpected live call, and
6
+ matchers for Vitest and Jest. Also exports `record`, `replay`, and
7
+ `replayRun`.
8
+
9
+ Part of [rulvar](https://rulvar.com), an embeddable TypeScript engine
10
+ for durable, budget-bounded multi-agent LLM workflows, where a completed
11
+ LLM call is never paid for twice. Full documentation:
12
+ [docs.rulvar.com](https://docs.rulvar.com).
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ pnpm add -D @rulvar/testing
18
+ ```
19
+
20
+ ## Documentation
21
+
22
+ - [Testing](https://docs.rulvar.com/guide/testing)
23
+ - [API reference](https://docs.rulvar.com/api/%40rulvar/testing/)
24
+
25
+ ## License
26
+
27
+ [Apache-2.0](https://github.com/o-stepper/rulvar/blob/main/LICENSE)
package/dist/index.d.ts CHANGED
@@ -29,46 +29,57 @@ declare function replayRun<A, R>(wf: Workflow<A, R>, args: A, options: ReplayRun
29
29
  //#endregion
30
30
  //#region src/cassettes/build-fixtures.d.ts
31
31
  /** One cassette fixture file: id, provenance note, and the journal. */
32
+ /** @internal */
32
33
  interface CassetteFixture {
33
34
  id: string;
34
35
  note: string;
35
36
  entries: JournalEntry[];
36
37
  }
38
+ /** @internal */
37
39
  declare function buildM2CassetteFixtures(): CassetteFixture[];
38
40
  /**
39
41
  * The frozen v1 journal: a
40
42
  * round-1 JSONL file with kinds agent, step, rand, external, approval and
41
43
  * the legacy `v: 1` field (no hashVersion member). Returned as raw
42
44
  * JSON-ready objects, one per line.
45
+ * @internal
43
46
  */
44
47
  declare function buildFrozenV1JournalRaw(): Array<Record<string, unknown>>;
45
48
  /**
46
49
  * v2 golden identity fixtures: worked examples per spawn kind (M2-T12).
47
50
  * The keys freeze the hashVersion 2 profile; the v1 members freeze the
48
51
  * effort-insensitive projection and the incomparable domain.
52
+ * @internal
49
53
  */
50
54
  declare function buildV2GoldenIdentity(): Record<string, unknown>;
51
55
  //#endregion
52
56
  //#region src/cassettes/record-live.d.ts
57
+ /** @internal */
53
58
  declare function recordLiveCassettes(): Promise<CassetteFixture[]>;
54
59
  //#endregion
55
60
  //#region src/cassettes/m6-orchestrator.d.ts
61
+ /** @internal */
56
62
  declare const M6_ORCH_RUN_ID = "m6-orchestrator-crash";
63
+ /** @internal */
57
64
  declare const M6_ORCH_GOAL = "m6 cassette: gather two facts";
65
+ /** @internal */
58
66
  declare const M6_ORCH_PROFILES: {
59
67
  worker: {
60
68
  description: string;
61
69
  };
62
70
  };
63
71
  /** Extracts spawn handles from the tool results the model saw. */
72
+ /** @internal */
64
73
  declare function handlesInRequest(req: ChatRequest): number[];
65
74
  /** Fixes wall clock and spans; everything else is deterministic already. */
75
+ /** @internal */
66
76
  declare function normalizeM6Entries(entries: readonly JournalEntry[]): JournalEntry[];
67
77
  /**
68
78
  * Phase 1: record the pre-crash journal. The transcripts store carries
69
79
  * the boundary checkpoint the resume restores from; the recorder keeps
70
80
  * it in memory because the cassette pins only journal bytes (checkpoint
71
81
  * blobs are engine-internal at-least-once state).
82
+ * @internal
72
83
  */
73
84
  declare function recordOrchestratorCrash(): Promise<{
74
85
  entries: JournalEntry[]; /** Boundary checkpoint blobs by ref, base64: the resume restores from them. */
package/dist/index.js CHANGED
@@ -209,7 +209,7 @@ var FakeAdapter = class {
209
209
  */
210
210
  function createTestEngine(options) {
211
211
  const fake = new FakeAdapter({ agents: options.agents });
212
- const store = new InMemoryStore();
212
+ const store = new InMemoryStore({ quiet: true });
213
213
  const profiles = { ...options.profiles };
214
214
  for (const key of Object.keys(options.agents)) if (key !== "*" && profiles[key] === void 0) profiles[key] = {};
215
215
  const engine = createEngine({
@@ -609,6 +609,7 @@ const PROMPTS = {
609
609
  crossCheck: "cross-check the citations",
610
610
  assessTone: "assess the tone"
611
611
  };
612
+ /** @internal */
612
613
  function buildM2CassetteFixtures() {
613
614
  const fixtures = [];
614
615
  {
@@ -867,6 +868,7 @@ function buildM2CassetteFixtures() {
867
868
  * round-1 JSONL file with kinds agent, step, rand, external, approval and
868
869
  * the legacy `v: 1` field (no hashVersion member). Returned as raw
869
870
  * JSON-ready objects, one per line.
871
+ * @internal
870
872
  */
871
873
  function buildFrozenV1JournalRaw() {
872
874
  const j = new FixtureJournal();
@@ -934,6 +936,7 @@ const WORKED_EXAMPLE_INPUT = {
934
936
  * v2 golden identity fixtures: worked examples per spawn kind (M2-T12).
935
937
  * The keys freeze the hashVersion 2 profile; the v1 members freeze the
936
938
  * effort-insensitive projection and the incomparable domain.
939
+ * @internal
937
940
  */
938
941
  function buildV2GoldenIdentity() {
939
942
  return {
@@ -1602,6 +1605,7 @@ async function recordDoubleAbandonIdempotent() {
1602
1605
  })).applied) throw new Error("double-abandon: the second abandon must fold noop");
1603
1606
  return normalizeEntries(await store.load("record"));
1604
1607
  }
1608
+ /** @internal */
1605
1609
  async function recordLiveCassettes() {
1606
1610
  const fixtures = [];
1607
1611
  fixtures.push({
@@ -1709,13 +1713,17 @@ async function recordLiveCassettes() {
1709
1713
  }
1710
1714
  //#endregion
1711
1715
  //#region src/cassettes/m6-orchestrator.ts
1716
+ /** @internal */
1712
1717
  const M6_ORCH_RUN_ID = "m6-orchestrator-crash";
1718
+ /** @internal */
1713
1719
  const M6_ORCH_GOAL = "m6 cassette: gather two facts";
1720
+ /** @internal */
1714
1721
  const M6_ORCH_PROFILES = { worker: { description: "does one task" } };
1715
1722
  function agentTypeOf(call) {
1716
1723
  return (call.req.providerOptions?.rulvar)?.agentType ?? "";
1717
1724
  }
1718
1725
  /** Extracts spawn handles from the tool results the model saw. */
1726
+ /** @internal */
1719
1727
  function handlesInRequest(req) {
1720
1728
  const handles = [];
1721
1729
  for (const msg of req.messages) for (const part of msg.parts) if (part.type === "tool-result") {
@@ -1725,6 +1733,7 @@ function handlesInRequest(req) {
1725
1733
  return handles;
1726
1734
  }
1727
1735
  /** Fixes wall clock and spans; everything else is deterministic already. */
1736
+ /** @internal */
1728
1737
  function normalizeM6Entries(entries) {
1729
1738
  return entries.map((entry) => ({
1730
1739
  ...entry,
@@ -1738,6 +1747,7 @@ function normalizeM6Entries(entries) {
1738
1747
  * the boundary checkpoint the resume restores from; the recorder keeps
1739
1748
  * it in memory because the cassette pins only journal bytes (checkpoint
1740
1749
  * blobs are engine-internal at-least-once state).
1750
+ * @internal
1741
1751
  */
1742
1752
  async function recordOrchestratorCrash() {
1743
1753
  let orchestratorTurn = 0;
@@ -1918,7 +1928,9 @@ function readCassette(path) {
1918
1928
  * matching live adapter in `adapters` (a development convenience only).
1919
1929
  */
1920
1930
  function replay(options) {
1921
- const { rows } = readCassette(options.cassette);
1931
+ const { header, rows } = readCassette(options.cassette);
1932
+ const oldestSupported = CURRENT_HASH_VERSION - 1;
1933
+ if (typeof header.hashVersion !== "number" || header.hashVersion < oldestSupported || header.hashVersion > CURRENT_HASH_VERSION) throw new ConfigError(`${options.cassette} was recorded under hashVersion ${String(header.hashVersion)}, outside the supported window [${oldestSupported}, ${CURRENT_HASH_VERSION}]; record the cassette again on a current engine`);
1922
1934
  const byAdapter = /* @__PURE__ */ new Map();
1923
1935
  for (const row of rows) {
1924
1936
  const forAdapter = byAdapter.get(row.adapterId) ?? /* @__PURE__ */ new Map();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rulvar/testing",
3
- "version": "1.3.1",
3
+ "version": "1.4.0",
4
4
  "description": "rulvar test harness: createTestEngine, FakeAdapter, VCR cassettes, replay-strict runs, matchers.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -26,7 +26,7 @@
26
26
  "access": "public"
27
27
  },
28
28
  "dependencies": {
29
- "@rulvar/core": "1.3.1"
29
+ "@rulvar/core": "1.4.0"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@types/node": "^22.20.0",