@rulvar/testing 1.13.0 → 1.14.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
@@ -27,6 +27,67 @@ declare function replayRun<A, R>(wf: Workflow<A, R>, args: A, options: ReplayRun
27
27
  preview: ResumePreview;
28
28
  }>;
29
29
  //#endregion
30
+ //#region src/live.d.ts
31
+ /**
32
+ * True only when `RULVAR_LIVE_TESTS` is exactly `'1'` AND every named
33
+ * environment key is set to a non-empty value. Gate live tests as
34
+ * `it.skipIf(!liveTestEnabled('ANTHROPIC_API_KEY'))(...)` so an
35
+ * unrelated key in the shell never triggers a paid provider call from
36
+ * an ordinary test run.
37
+ */
38
+ declare function liveTestEnabled(...requiredEnvKeys: string[]): boolean;
39
+ interface RunLiveSmokeOptions {
40
+ /** Total attempts including the first (default 3, minimum 1). */
41
+ attempts?: number;
42
+ /**
43
+ * Backoff before retry n (1-based) is `baseDelayMs * n` (default
44
+ * 2000). Pass 0 to retry without sleeping (unit tests).
45
+ */
46
+ baseDelayMs?: number;
47
+ }
48
+ /**
49
+ * The classified result of a bounded live smoke. `attempts` is how many
50
+ * streams were actually opened; only `'exhausted'` reaches the
51
+ * configured bound.
52
+ */
53
+ type LiveSmokeOutcome = {
54
+ status: "ok";
55
+ attempts: number;
56
+ events: ChatEvent[];
57
+ } | {
58
+ status: "failed";
59
+ attempts: number;
60
+ error: WireError;
61
+ events: ChatEvent[];
62
+ } | {
63
+ status: "exhausted";
64
+ attempts: number;
65
+ errors: WireError[];
66
+ } | {
67
+ status: "no-terminal";
68
+ attempts: number;
69
+ events: ChatEvent[];
70
+ };
71
+ /**
72
+ * Drains `adapter.stream(req)` with a bounded retry policy and classifies
73
+ * the outcome instead of throwing:
74
+ *
75
+ * - `'ok'`: a `finish` event arrived (the events of the successful
76
+ * attempt are included for further assertions).
77
+ * - `'failed'`: a terminal error with `retryable: false`; never retried,
78
+ * diagnostics preserved.
79
+ * - `'exhausted'`: every attempt ended in a `retryable: true` error; the
80
+ * per-attempt errors are preserved in order.
81
+ * - `'no-terminal'`: the stream ended with neither `finish` nor `error`,
82
+ * which violates the provider SPI; never retried (spending again on a
83
+ * misbehaving adapter is wrong).
84
+ *
85
+ * Retries only ever follow typed retryable errors, so a live smoke never
86
+ * converts a real adapter failure into a pass and never spends more than
87
+ * `attempts` calls.
88
+ */
89
+ declare function runLiveSmoke(adapter: Pick<ProviderAdapter, "stream">, req: ChatRequest, options?: RunLiveSmokeOptions): Promise<LiveSmokeOutcome>;
90
+ //#endregion
30
91
  //#region src/cassettes/build-fixtures.d.ts
31
92
  /** One cassette fixture file: id, provenance note, and the journal. */
32
93
  /** @internal */
@@ -152,4 +213,4 @@ declare function replay(options: {
152
213
  adapters?: ProviderAdapter[];
153
214
  }): ProviderAdapter[];
154
215
  //#endregion
155
- export { type CassetteFixture, type CreateTestEngineOptions, FAKE_MODEL, FAKE_MODEL_REF, FakeAdapter, type FakeAdapterOptions, type FakeCall, type FakeResponder, type FakeToolCallsValue, type FakeWireErrorValue, M6_ORCH_GOAL, M6_ORCH_PROFILES, M6_ORCH_RUN_ID, RedactFn, type ReplayRunOptions, type TestEngine, type TestRunHandle, VcrCassette, VcrMissError, VcrRow, buildFrozenV1JournalRaw, buildM2CassetteFixtures, buildV2GoldenIdentity, createTestEngine, defaultRedact, fakeToolCalls, fakeWireError, handlesInRequest, normalizeM6Entries, readCassette, record, recordLiveCassettes, recordOrchestratorCrash, replay, replayRun, requestHash };
216
+ export { type CassetteFixture, type CreateTestEngineOptions, FAKE_MODEL, FAKE_MODEL_REF, FakeAdapter, type FakeAdapterOptions, type FakeCall, type FakeResponder, type FakeToolCallsValue, type FakeWireErrorValue, type LiveSmokeOutcome, M6_ORCH_GOAL, M6_ORCH_PROFILES, M6_ORCH_RUN_ID, RedactFn, type ReplayRunOptions, type RunLiveSmokeOptions, type TestEngine, type TestRunHandle, VcrCassette, VcrMissError, VcrRow, buildFrozenV1JournalRaw, buildM2CassetteFixtures, buildV2GoldenIdentity, createTestEngine, defaultRedact, fakeToolCalls, fakeWireError, handlesInRequest, liveTestEnabled, normalizeM6Entries, readCassette, record, recordLiveCassettes, recordOrchestratorCrash, replay, replayRun, requestHash, runLiveSmoke };
package/dist/index.js CHANGED
@@ -353,6 +353,78 @@ async function replayRun(wf, args, options) {
353
353
  };
354
354
  }
355
355
  //#endregion
356
+ //#region src/live.ts
357
+ /**
358
+ * True only when `RULVAR_LIVE_TESTS` is exactly `'1'` AND every named
359
+ * environment key is set to a non-empty value. Gate live tests as
360
+ * `it.skipIf(!liveTestEnabled('ANTHROPIC_API_KEY'))(...)` so an
361
+ * unrelated key in the shell never triggers a paid provider call from
362
+ * an ordinary test run.
363
+ */
364
+ function liveTestEnabled(...requiredEnvKeys) {
365
+ if (process.env.RULVAR_LIVE_TESTS !== "1") return false;
366
+ return requiredEnvKeys.every((key) => {
367
+ const value = process.env[key];
368
+ return value !== void 0 && value !== "";
369
+ });
370
+ }
371
+ /**
372
+ * Drains `adapter.stream(req)` with a bounded retry policy and classifies
373
+ * the outcome instead of throwing:
374
+ *
375
+ * - `'ok'`: a `finish` event arrived (the events of the successful
376
+ * attempt are included for further assertions).
377
+ * - `'failed'`: a terminal error with `retryable: false`; never retried,
378
+ * diagnostics preserved.
379
+ * - `'exhausted'`: every attempt ended in a `retryable: true` error; the
380
+ * per-attempt errors are preserved in order.
381
+ * - `'no-terminal'`: the stream ended with neither `finish` nor `error`,
382
+ * which violates the provider SPI; never retried (spending again on a
383
+ * misbehaving adapter is wrong).
384
+ *
385
+ * Retries only ever follow typed retryable errors, so a live smoke never
386
+ * converts a real adapter failure into a pass and never spends more than
387
+ * `attempts` calls.
388
+ */
389
+ async function runLiveSmoke(adapter, req, options) {
390
+ const attempts = Math.max(1, options?.attempts ?? 3);
391
+ const baseDelayMs = options?.baseDelayMs ?? 2e3;
392
+ const retryableErrors = [];
393
+ for (let attempt = 1; attempt <= attempts; attempt += 1) {
394
+ const events = [];
395
+ for await (const event of adapter.stream(req)) events.push(event);
396
+ if (events.some((event) => event.type === "finish")) return {
397
+ status: "ok",
398
+ attempts: attempt,
399
+ events
400
+ };
401
+ const errorEvent = events.find((event) => event.type === "error");
402
+ if (errorEvent === void 0) return {
403
+ status: "no-terminal",
404
+ attempts: attempt,
405
+ events
406
+ };
407
+ if (!errorEvent.error.retryable) return {
408
+ status: "failed",
409
+ attempts: attempt,
410
+ error: errorEvent.error,
411
+ events
412
+ };
413
+ retryableErrors.push(errorEvent.error);
414
+ if (attempt < attempts && baseDelayMs > 0) await delay(baseDelayMs * attempt);
415
+ }
416
+ return {
417
+ status: "exhausted",
418
+ attempts,
419
+ errors: retryableErrors
420
+ };
421
+ }
422
+ function delay(ms) {
423
+ return new Promise((resolve) => {
424
+ setTimeout(resolve, ms);
425
+ });
426
+ }
427
+ //#endregion
356
428
  //#region src/cassettes/build-fixtures.ts
357
429
  /**
358
430
  * M2 cassette and frozen-fixture builders (M2-T12). Fixtures are
@@ -2017,4 +2089,4 @@ function replay(options) {
2017
2089
  });
2018
2090
  }
2019
2091
  //#endregion
2020
- export { FAKE_MODEL, FAKE_MODEL_REF, FakeAdapter, M6_ORCH_GOAL, M6_ORCH_PROFILES, M6_ORCH_RUN_ID, VcrMissError, buildFrozenV1JournalRaw, buildM2CassetteFixtures, buildV2GoldenIdentity, createTestEngine, defaultRedact, fakeToolCalls, fakeWireError, handlesInRequest, normalizeM6Entries, readCassette, record, recordLiveCassettes, recordOrchestratorCrash, replay, replayRun, requestHash };
2092
+ export { FAKE_MODEL, FAKE_MODEL_REF, FakeAdapter, M6_ORCH_GOAL, M6_ORCH_PROFILES, M6_ORCH_RUN_ID, VcrMissError, buildFrozenV1JournalRaw, buildM2CassetteFixtures, buildV2GoldenIdentity, createTestEngine, defaultRedact, fakeToolCalls, fakeWireError, handlesInRequest, liveTestEnabled, normalizeM6Entries, readCassette, record, recordLiveCassettes, recordOrchestratorCrash, replay, replayRun, requestHash, runLiveSmoke };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rulvar/testing",
3
- "version": "1.13.0",
3
+ "version": "1.14.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.13.0"
29
+ "@rulvar/core": "1.14.0"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@types/node": "^22.20.0",