@rulvar/testing 1.0.0 → 1.2.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/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as FAKE_MODEL, c as FakeAdapterOptions, d as FakeToolCallsValue, f as FakeWireErrorValue, i as createTestEngine, l as FakeCall, m as fakeWireError, n as TestEngine, o as FAKE_MODEL_REF, p as fakeToolCalls, r as TestRunHandle, s as FakeAdapter, t as CreateTestEngineOptions, u as FakeResponder } from "./test-engine-
|
|
1
|
+
import { a as FAKE_MODEL, c as FakeAdapterOptions, d as FakeToolCallsValue, f as FakeWireErrorValue, i as createTestEngine, l as FakeCall, m as fakeWireError, n as TestEngine, o as FAKE_MODEL_REF, p as fakeToolCalls, r as TestRunHandle, s as FakeAdapter, t as CreateTestEngineOptions, u as FakeResponder } from "./test-engine-DwJtTU7M.js";
|
|
2
2
|
import { AgentProfile, ChatEvent, ChatRequest, InvocationRole, JournalEntry, JournalStore, ModelCaps, ModelSpec, ProviderAdapter, ResumePreview, RunOutcome, WireError, Workflow, createEngine } from "@rulvar/core";
|
|
3
3
|
|
|
4
4
|
//#region src/replay-strict.d.ts
|
|
@@ -36,7 +36,7 @@ interface CassetteFixture {
|
|
|
36
36
|
}
|
|
37
37
|
declare function buildM2CassetteFixtures(): CassetteFixture[];
|
|
38
38
|
/**
|
|
39
|
-
* The frozen v1 journal
|
|
39
|
+
* The frozen v1 journal: a
|
|
40
40
|
* round-1 JSONL file with kinds agent, step, rand, external, approval and
|
|
41
41
|
* the legacy `v: 1` field (no hashVersion member). Returned as raw
|
|
42
42
|
* JSON-ready objects, one per line.
|
|
@@ -68,7 +68,7 @@ declare function normalizeM6Entries(entries: readonly JournalEntry[]): JournalEn
|
|
|
68
68
|
* Phase 1: record the pre-crash journal. The transcripts store carries
|
|
69
69
|
* the boundary checkpoint the resume restores from; the recorder keeps
|
|
70
70
|
* it in memory because the cassette pins only journal bytes (checkpoint
|
|
71
|
-
* blobs are engine-internal at-least-once state
|
|
71
|
+
* blobs are engine-internal at-least-once state).
|
|
72
72
|
*/
|
|
73
73
|
declare function recordOrchestratorCrash(): Promise<{
|
|
74
74
|
entries: JournalEntry[]; /** Boundary checkpoint blobs by ref, base64: the resume restores from them. */
|
|
@@ -98,14 +98,14 @@ interface VcrHeader {
|
|
|
98
98
|
type RedactFn = (value: string) => string;
|
|
99
99
|
/**
|
|
100
100
|
* Built-in redaction: authorization material never reaches cassette
|
|
101
|
-
* bytes
|
|
101
|
+
* bytes. Deliberately aggressive; compose a
|
|
102
102
|
* custom hook for payload-specific secrets.
|
|
103
103
|
*/
|
|
104
104
|
declare function defaultRedact(value: string): string;
|
|
105
105
|
/**
|
|
106
106
|
* The cassette key: a hash of the canonical wire-contract request. The
|
|
107
|
-
* engine-populated telemetry namespace is excluded (
|
|
108
|
-
*
|
|
107
|
+
* engine-populated telemetry namespace is excluded (never identity);
|
|
108
|
+
* everything else the adapter would send keys the
|
|
109
109
|
* row.
|
|
110
110
|
*/
|
|
111
111
|
declare function requestHash(req: ChatRequest): string;
|
|
@@ -133,8 +133,7 @@ declare function readCassette(path: string): VcrCassette;
|
|
|
133
133
|
/**
|
|
134
134
|
* Builds replay adapters from a cassette. `onMiss: 'throw'` is the
|
|
135
135
|
* hermetic CI mode; `'passthrough'` forwards unrecorded requests to the
|
|
136
|
-
* matching live adapter in `adapters` (a development convenience only
|
|
137
|
-
* docs/11 section 5.1).
|
|
136
|
+
* matching live adapter in `adapters` (a development convenience only).
|
|
138
137
|
*/
|
|
139
138
|
declare function replay(options: {
|
|
140
139
|
cassette: string;
|
package/dist/index.js
CHANGED
|
@@ -7,8 +7,7 @@ import { createHash } from "node:crypto";
|
|
|
7
7
|
* FakeAdapter (M1-T14): a REAL ProviderAdapter that resolves calls from
|
|
8
8
|
* declared patterns instead of the network, behind the same seam as live
|
|
9
9
|
* adapters, so unit tests run through the full engine: journal, scheduler,
|
|
10
|
-
* budget layers, and event stream
|
|
11
|
-
* and createTestEngine"). Calls cost zero USD.
|
|
10
|
+
* budget layers, and event stream. Calls cost zero USD.
|
|
12
11
|
*/
|
|
13
12
|
/** Scripts a tool-calling turn from a responder. */
|
|
14
13
|
function fakeToolCalls(...calls) {
|
|
@@ -204,8 +203,8 @@ var FakeAdapter = class {
|
|
|
204
203
|
/**
|
|
205
204
|
* createTestEngine (M1-T14): a full engine over FakeAdapter with zero
|
|
206
205
|
* network. Orchestration logic is exercised, not mocked around: journal,
|
|
207
|
-
* scheduler, budget layers, and event stream are all real
|
|
208
|
-
*
|
|
206
|
+
* scheduler, budget layers, and event stream are all real. Returned
|
|
207
|
+
* handles record their event stream so the
|
|
209
208
|
* matchers can assert over settled runs.
|
|
210
209
|
*/
|
|
211
210
|
function createTestEngine(options) {
|
|
@@ -257,8 +256,7 @@ function createTestEngine(options) {
|
|
|
257
256
|
* against an existing journal and throws JournalMissError on ANY live
|
|
258
257
|
* call: zero live calls or loud failure. Any production journal becomes a
|
|
259
258
|
* deterministic integration test; a journal with open suspensions
|
|
260
|
-
* completes with outcome 'suspended' and zero live calls
|
|
261
|
-
* section "Tier 3: replay-strict journal runs").
|
|
259
|
+
* completes with outcome 'suspended' and zero live calls.
|
|
262
260
|
*/
|
|
263
261
|
const FAKE_ROUTING = {
|
|
264
262
|
loop: FAKE_MODEL_REF,
|
|
@@ -317,10 +315,9 @@ async function replayRun(wf, args, options) {
|
|
|
317
315
|
* The COMMITTED files under repo cassettes/ and
|
|
318
316
|
* packages/testing/fixtures/frozen/ are the contract; these builders
|
|
319
317
|
* exist to regenerate them DELIBERATELY (scripts/record-m2-cassettes.mjs)
|
|
320
|
-
* and to fail loudly in CI when key derivation drifts (
|
|
321
|
-
*
|
|
322
|
-
*
|
|
323
|
-
* changeset).
|
|
318
|
+
* and to fail loudly in CI when key derivation drifts (regenerating
|
|
319
|
+
* fixtures to make a test pass is forbidden by policy; any diff requires
|
|
320
|
+
* an explicit hashVersion-bump changeset).
|
|
324
321
|
*/
|
|
325
322
|
const BASE_MS$1 = Date.parse("2026-02-01T00:00:00.000Z");
|
|
326
323
|
const SPAN$1 = "fixture-span";
|
|
@@ -866,7 +863,7 @@ function buildM2CassetteFixtures() {
|
|
|
866
863
|
return fixtures;
|
|
867
864
|
}
|
|
868
865
|
/**
|
|
869
|
-
* The frozen v1 journal
|
|
866
|
+
* The frozen v1 journal: a
|
|
870
867
|
* round-1 JSONL file with kinds agent, step, rand, external, approval and
|
|
871
868
|
* the legacy `v: 1` field (no hashVersion member). Returned as raw
|
|
872
869
|
* JSON-ready objects, one per line.
|
|
@@ -919,7 +916,7 @@ function buildFrozenV1JournalRaw() {
|
|
|
919
916
|
};
|
|
920
917
|
});
|
|
921
918
|
}
|
|
922
|
-
/** The
|
|
919
|
+
/** The agent identity worked example, frozen as executable data. */
|
|
923
920
|
const WORKED_EXAMPLE_INPUT = {
|
|
924
921
|
kind: "agent",
|
|
925
922
|
agentType: "reviewer",
|
|
@@ -1041,7 +1038,7 @@ function buildV2GoldenIdentity() {
|
|
|
1041
1038
|
* producer arrives with mode (c) in M6/M7. Timestamps and span ids are
|
|
1042
1039
|
* normalized deterministically after recording (matching never reads
|
|
1043
1040
|
* them); regeneration is DELIBERATE per the frozen-fixture policy
|
|
1044
|
-
* (docs/
|
|
1041
|
+
* (https://docs.rulvar.com/guide/testing).
|
|
1045
1042
|
*/
|
|
1046
1043
|
const BASE_MS = Date.parse("2026-02-01T00:00:00.000Z");
|
|
1047
1044
|
const SPAN = "fixture-span";
|
|
@@ -1175,7 +1172,7 @@ function subtreeChildReport() {
|
|
|
1175
1172
|
* The orchestrator-subtree shape: written through the KERNEL write APIs
|
|
1176
1173
|
* (the same calls mode (c) spawning uses from M6), since no script-mode
|
|
1177
1174
|
* producer can spawn agents under an agent scope yet; re-recorded again
|
|
1178
|
-
* live in M7 with the orchestrator producers
|
|
1175
|
+
* live in M7 with the orchestrator producers.
|
|
1179
1176
|
*/
|
|
1180
1177
|
async function recordAbandonSubtree() {
|
|
1181
1178
|
let tick = 0;
|
|
@@ -1283,7 +1280,7 @@ function frozenV1JournalPath() {
|
|
|
1283
1280
|
throw new Error("frozen v1-journal.jsonl not found next to the recorder");
|
|
1284
1281
|
}
|
|
1285
1282
|
/**
|
|
1286
|
-
* effort-defaults-shift (DEF-6
|
|
1283
|
+
* effort-defaults-shift (DEF-6): the frozen v1
|
|
1287
1284
|
* prefix (recorded without effort) is closed offline the way an
|
|
1288
1285
|
* operator would (the external resolves, the approval flow is
|
|
1289
1286
|
* abandoned under its authority), then the SAME flow resumes LIVE under
|
|
@@ -1342,7 +1339,7 @@ function liveEngine(store, agents) {
|
|
|
1342
1339
|
} }
|
|
1343
1340
|
});
|
|
1344
1341
|
}
|
|
1345
|
-
/** An offline kernel writer over the loaded priors
|
|
1342
|
+
/** An offline kernel writer over the loaded priors. */
|
|
1346
1343
|
async function offlineReplayer(store, runId) {
|
|
1347
1344
|
let tick = 500;
|
|
1348
1345
|
return new Replayer({
|
|
@@ -1369,7 +1366,7 @@ function suspendedSeqOf(entries, key) {
|
|
|
1369
1366
|
* DEF-4 timeout-vs-live-race, LIVE form: the run suspends on the
|
|
1370
1367
|
* decision, the LIVE resolution wins through RunHandle.resolveExternal,
|
|
1371
1368
|
* and the late timer attempt lands through the offline kernel writer as
|
|
1372
|
-
* the journaled noop (first-wins
|
|
1369
|
+
* the journaled noop (first-wins).
|
|
1373
1370
|
*/
|
|
1374
1371
|
async function recordTimeoutVsLiveRace() {
|
|
1375
1372
|
const store = new InMemoryStore();
|
|
@@ -1402,7 +1399,7 @@ async function recordTimeoutVsLiveRace() {
|
|
|
1402
1399
|
* the first closes individually (applied by external); ONE class-level
|
|
1403
1400
|
* decision entry closes the remaining two by class_decision with the
|
|
1404
1401
|
* decisionRef, and the late class attempt against the first lands noop
|
|
1405
|
-
* (
|
|
1402
|
+
* (the plan-side class producer has its own cassette,
|
|
1406
1403
|
* class-storm-single-turn).
|
|
1407
1404
|
*/
|
|
1408
1405
|
async function recordClassDecisionFanout() {
|
|
@@ -1740,7 +1737,7 @@ function normalizeM6Entries(entries) {
|
|
|
1740
1737
|
* Phase 1: record the pre-crash journal. The transcripts store carries
|
|
1741
1738
|
* the boundary checkpoint the resume restores from; the recorder keeps
|
|
1742
1739
|
* it in memory because the cassette pins only journal bytes (checkpoint
|
|
1743
|
-
* blobs are engine-internal at-least-once state
|
|
1740
|
+
* blobs are engine-internal at-least-once state).
|
|
1744
1741
|
*/
|
|
1745
1742
|
async function recordOrchestratorCrash() {
|
|
1746
1743
|
let orchestratorTurn = 0;
|
|
@@ -1803,8 +1800,8 @@ async function recordOrchestratorCrash() {
|
|
|
1803
1800
|
//#endregion
|
|
1804
1801
|
//#region src/vcr.ts
|
|
1805
1802
|
/**
|
|
1806
|
-
* VCR cassettes at the adapter boundary (M5-T04
|
|
1807
|
-
*
|
|
1803
|
+
* VCR cassettes at the adapter boundary (M5-T04): `record` wraps
|
|
1804
|
+
* live adapters and captures
|
|
1808
1805
|
* request/event pairs into a redacted JSONL cassette keyed by a hash of
|
|
1809
1806
|
* the canonical wire-contract request; `replay` serves recorded streams
|
|
1810
1807
|
* back with `onMiss: 'throw'` (hermetic CI) or `'passthrough'` (mixed
|
|
@@ -1823,7 +1820,7 @@ async function recordOrchestratorCrash() {
|
|
|
1823
1820
|
*/
|
|
1824
1821
|
/**
|
|
1825
1822
|
* Built-in redaction: authorization material never reaches cassette
|
|
1826
|
-
* bytes
|
|
1823
|
+
* bytes. Deliberately aggressive; compose a
|
|
1827
1824
|
* custom hook for payload-specific secrets.
|
|
1828
1825
|
*/
|
|
1829
1826
|
function defaultRedact(value) {
|
|
@@ -1843,8 +1840,8 @@ function canonicalJson(value) {
|
|
|
1843
1840
|
}
|
|
1844
1841
|
/**
|
|
1845
1842
|
* The cassette key: a hash of the canonical wire-contract request. The
|
|
1846
|
-
* engine-populated telemetry namespace is excluded (
|
|
1847
|
-
*
|
|
1843
|
+
* engine-populated telemetry namespace is excluded (never identity);
|
|
1844
|
+
* everything else the adapter would send keys the
|
|
1848
1845
|
* row.
|
|
1849
1846
|
*/
|
|
1850
1847
|
function requestHash(req) {
|
|
@@ -1918,8 +1915,7 @@ function readCassette(path) {
|
|
|
1918
1915
|
/**
|
|
1919
1916
|
* Builds replay adapters from a cassette. `onMiss: 'throw'` is the
|
|
1920
1917
|
* hermetic CI mode; `'passthrough'` forwards unrecorded requests to the
|
|
1921
|
-
* matching live adapter in `adapters` (a development convenience only
|
|
1922
|
-
* docs/11 section 5.1).
|
|
1918
|
+
* matching live adapter in `adapters` (a development convenience only).
|
|
1923
1919
|
*/
|
|
1924
1920
|
function replay(options) {
|
|
1925
1921
|
const { rows } = readCassette(options.cassette);
|
package/dist/matchers.d.ts
CHANGED
|
@@ -38,7 +38,7 @@ declare function fakeWireError(error: WireError): FakeWireErrorValue;
|
|
|
38
38
|
interface FakeAdapterOptions {
|
|
39
39
|
/**
|
|
40
40
|
* Patterns match on agentType, label, or a regex over the prompt; '*'
|
|
41
|
-
* is the fallback
|
|
41
|
+
* is the fallback.
|
|
42
42
|
*/
|
|
43
43
|
agents: Record<string, FakeResponder>;
|
|
44
44
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rulvar/testing",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.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.2.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/node": "^22.20.0",
|