@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 +27 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +14 -2
- package/package.json +2 -2
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
|
+
"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.
|
|
29
|
+
"@rulvar/core": "1.4.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/node": "^22.20.0",
|