Skip to content
Source
Sign inSign up
Open History to see the last commit

orchestrationCache.test.ts

123 lines4.5 KB

1import { describe, expect, it } from "@effect/vitest";
2import { EnvironmentId, EventId, TurnItemId } from "@t3tools/contracts";
3import * as Effect from "effect/Effect";
4import * as Option from "effect/Option";
5import * as Schema from "effect/Schema";
6​
7import {
8 ORCHESTRATION_CACHE_SCHEMA_VERSION,
9 ORCHESTRATION_THREAD_CACHE_SCHEMA_VERSION,
10 StoredOrchestrationShellSnapshot,
11 StoredOrchestrationThreadSnapshot,
12 decodeOrDiscardOrchestrationCache,
13} from "./orchestrationCache.ts";
14import {
15 v2Now,
16 v2Projection,
17 v2ShellSnapshot,
18 v2ThreadId,
19} from "../state/orchestrationV2TestFixtures.ts";
20​
21import { applyOrchestrationV2ProjectionEvent } from "../state/orchestrationV2Projection.ts";
22​
23const environmentId = EnvironmentId.make("environment-cache-test");
24const StoredThreadSnapshotJson = Schema.fromJsonString(StoredOrchestrationThreadSnapshot);
25const encodeStoredShellSnapshot = Schema.encodeSync(StoredOrchestrationShellSnapshot);
26const decodeStoredShellSnapshotSync = Schema.decodeUnknownSync(StoredOrchestrationShellSnapshot);
27const encodeStoredThreadSnapshotJson = Schema.encodeSync(StoredThreadSnapshotJson);
28const decodeStoredThreadSnapshotJson = Schema.decodeUnknownSync(StoredThreadSnapshotJson);
29​
30class TestCacheDecodeError extends Schema.TaggedError<TestCacheDecodeError>()(
31 "TestCacheDecodeError",
32 {
33 message: Schema.String,
34 },
35) {}
36​
37describe("orchestration cache envelopes", () => {
38 it("preserves live provider notices through persisted thread snapshots", () => {
39 const item = {
40 id: TurnItemId.make("notice-cache"),
41 threadId: v2ThreadId,
42 runId: null,
43 nodeId: null,
44 providerThreadId: null,
45 providerTurnId: null,
46 nativeItemRef: null,
47 parentItemId: null,
48 ordinal: 1,
49 status: "completed" as const,
50 title: null,
51 startedAt: v2Now,
52 completedAt: v2Now,
53 updatedAt: v2Now,
54 type: "system_notice" as const,
55 message: "Safeguards flagged this message. Switched to Opus 4.8.",
56 };
57 const projection = applyOrchestrationV2ProjectionEvent(v2Projection, {
58 id: EventId.make("notice-cache-event"),
59 type: "turn-item.updated",
60 threadId: v2ThreadId,
61 occurredAt: v2Now,
62 payload: item,
63 });
64 expect(projection).not.toBeNull();
65 if (projection === null) throw new Error("Expected live notice projection");
66 const encoded = encodeStoredThreadSnapshotJson({
67 schemaVersion: ORCHESTRATION_THREAD_CACHE_SCHEMA_VERSION,
68 environmentId,
69 threadId: v2ThreadId,
70 snapshot: { snapshotSequence: 5, projection },
71 });
72 const decoded = decodeStoredThreadSnapshotJson(encoded).snapshot.projection;
73 expect(decoded.turnItems.find((entry) => entry.id === item.id)).toEqual(item);
74 expect(decoded.visibleTurnItems.find((entry) => entry.item.id === item.id)?.item).toEqual(item);
75 });
76​
77 it("rejects thread snapshots saved before human history was bounded, but not shells", () => {
78 const currentThread = encodeStoredThreadSnapshotJson({
79 schemaVersion: ORCHESTRATION_THREAD_CACHE_SCHEMA_VERSION,
80 environmentId,
81 threadId: v2ThreadId,
82 snapshot: { snapshotSequence: 4, projection: v2Projection },
83 });
84 for (const staleVersion of [3, 4]) {
85 const staleThread = currentThread.replace(
86 `"schemaVersion":${ORCHESTRATION_THREAD_CACHE_SCHEMA_VERSION}`,
87 `"schemaVersion":${staleVersion}`,
88 );
89 expect(() => decodeStoredThreadSnapshotJson(staleThread)).toThrow();
90 }
91 const shell = encodeStoredShellSnapshot({
92 schemaVersion: ORCHESTRATION_CACHE_SCHEMA_VERSION,
93 environmentId,
94 snapshot: v2ShellSnapshot,
95 });
96 expect(decodeStoredShellSnapshotSync(shell).schemaVersion).toBe(3);
97 });
98​
99 it.effect("discards V1-versioned cache envelopes after a decode failure", () =>
100 Effect.gen(function* () {
101 let discardCount = 0;
102 const encodedShell = encodeStoredShellSnapshot({
103 schemaVersion: ORCHESTRATION_CACHE_SCHEMA_VERSION,
104 environmentId,
105 snapshot: v2ShellSnapshot,
106 });
107 const decoded = Effect.try({
108 try: () => decodeStoredShellSnapshotSync({ ...encodedShell, schemaVersion: 1 }),
109 catch: (cause) => new TestCacheDecodeError({ message: String(cause) }),
110 }).pipe(Effect.asSome);
111​
112 const result = yield* decodeOrDiscardOrchestrationCache(
113 decoded,
114 Effect.sync(() => {
115 discardCount += 1;
116 }),
117 );
118​
119 expect(Option.isNone(result)).toBe(true);
120 expect(discardCount).toBe(1);
121 }),
122 );
123});