import { EnvironmentId, EventId, ORCHESTRATION_V2_WS_METHODS, ThreadId, TurnItemId, type OrchestrationV2ThreadDetailSnapshot, type OrchestrationV2ThreadProjection, type OrchestrationV2ThreadStreamItem, type OrchestrationV2TurnItem, } from "@t3tools/contracts"; import { describe, expect, it } from "@effect/vitest"; import * as DateTime from "effect/DateTime"; import * as Deferred from "effect/Deferred"; import * as Effect from "effect/Effect"; import * as Exit from "effect/Exit"; import * as Fiber from "effect/Fiber"; import * as Option from "effect/Option"; import * as Queue from "effect/Queue"; import * as Ref from "effect/Ref"; import * as Scope from "effect/Scope"; import * as Stream from "effect/Stream"; import * as SubscriptionRef from "effect/SubscriptionRef"; import * as TestClock from "effect/testing/TestClock"; import type { WsRpcProtocolClient } from "../rpc/protocol.ts"; import { AVAILABLE_CONNECTION_STATE, PrimaryConnectionTarget, type PreparedConnection, type SupervisorConnectionState, } from "../connection/model.ts"; import * as ConnectionWakeups from "../connection/wakeups.ts"; import * as EnvironmentSupervisor from "../connection/supervisor.ts"; import * as Persistence from "../platform/persistence.ts"; import * as RpcSession from "../rpc/session.ts"; import { v2Projection, v2ThreadId } from "./orchestrationV2TestFixtures.ts"; import { EMPTY_ENVIRONMENT_THREAD_STATE, makeEnvironmentThreadState, ThreadSnapshotLoader, type EnvironmentThreadState, type ThreadSnapshotLoadResult, } from "./threads.ts"; const TARGET = new PrimaryConnectionTarget({ environmentId: EnvironmentId.make("environment-1"), label: "Test environment", httpBaseUrl: "https://environment.example.test", wsBaseUrl: "wss://environment.example.test", }); const THREAD_ID = v2ThreadId; const CACHED_SNAPSHOT_SEQUENCE = 7; const PREPARED: PreparedConnection = { environmentId: TARGET.environmentId, label: TARGET.label, httpBaseUrl: TARGET.httpBaseUrl, socketUrl: TARGET.wsBaseUrl, httpAuthorization: null, target: TARGET, }; const BASE_PROJECTION: OrchestrationV2ThreadProjection = { ...v2Projection, thread: { ...v2Projection.thread, title: "Cached thread" }, }; type TestThreadInput = OrchestrationV2ThreadStreamItem | Error; function testSession( client: WsRpcProtocolClient, config?: { readonly completionMarker?: boolean }, ): RpcSession.RpcSession { return { client, initialConfig: Effect.succeed( config?.completionMarker === true ? ({ threadResumeCompletionMarker: true } as never) : ({} as never), ), subscribeServerConfig: (input) => client.subscribeServerConfig(input), ready: Effect.void, probe: Effect.void, closed: Effect.never, }; } function awaitThreadState( observed: Queue.Queue, predicate: (state: EnvironmentThreadState) => boolean, ) { return Queue.take(observed).pipe( Effect.repeat({ until: predicate, }), ); } const makeHarness = Effect.fn("TestEnvironmentThreads.makeHarness")(function* (options?: { readonly cached?: OrchestrationV2ThreadProjection; readonly cachedHistory?: { readonly historyCursor: string | null; readonly hasMoreHistory: boolean; readonly latestLocalTurnOrdinal?: number | null; }; readonly httpSnapshot?: ThreadSnapshotLoadResult; readonly completionMarker?: boolean; readonly resumeCache?: NonNullable[1]>; readonly loadCached?: Effect.Effect>; readonly saveThread?: Persistence.EnvironmentCacheStore["Service"]["saveThread"]; }) { const inputs = yield* Queue.unbounded(); const observed = yield* Queue.unbounded(); const latest = yield* Ref.make(EMPTY_ENVIRONMENT_THREAD_STATE); const retryCount = yield* Ref.make(0); const subscriptionCount = yield* Ref.make(0); const loaderCalls = yield* Ref.make(0); const lastSubscribeAfterSequence = yield* Ref.make(undefined); const lastRequestCompletionMarker = yield* Ref.make(false); const wakeups = yield* Queue.unbounded(); const savedThreads = yield* Ref.make>([]); const removedThreads = yield* Ref.make>([]); const supervisorState = yield* SubscriptionRef.make( AVAILABLE_CONNECTION_STATE, ); const streamFrom = (queue: Queue.Queue) => Stream.fromQueue(queue).pipe( Stream.mapEffect((input) => input instanceof Error ? Effect.fail(input) : Effect.succeed(input), ), ); const client = { [ORCHESTRATION_V2_WS_METHODS.subscribeThread]: (input: { readonly afterSequence?: number; readonly requestCompletionMarker?: true; }) => Stream.unwrap( Ref.updateAndGet(subscriptionCount, (count) => count + 1).pipe( Effect.andThen(Ref.set(lastSubscribeAfterSequence, input.afterSequence)), Effect.andThen( Ref.set(lastRequestCompletionMarker, input.requestCompletionMarker === true), ), Effect.as(streamFrom(inputs)), ), ), } as unknown as WsRpcProtocolClient; const supervisorSession = yield* SubscriptionRef.make>( Option.some(testSession(client, options)), ); const prepared = yield* SubscriptionRef.make>( Option.some(PREPARED), ); const snapshotLoader = ThreadSnapshotLoader.of({ load: (_prepared, threadId) => Ref.update(loaderCalls, (count) => count + 1).pipe( Effect.as( threadId === THREAD_ID ? (options?.httpSnapshot ?? ({ _tag: "unavailable" } satisfies ThreadSnapshotLoadResult)) : ({ _tag: "unavailable" } satisfies ThreadSnapshotLoadResult), ), ), }); const supervisor = EnvironmentSupervisor.EnvironmentSupervisor.of({ target: TARGET, state: supervisorState, session: supervisorSession, prepared, connect: Effect.void, disconnect: Effect.void, retryNow: Ref.update(retryCount, (count) => count + 1), } satisfies EnvironmentSupervisor.EnvironmentSupervisor["Service"]); const cache = Persistence.EnvironmentCacheStore.of({ loadShell: () => Effect.succeed(Option.none()), saveShell: () => Effect.void, loadThread: (_environmentId, threadId) => options?.loadCached ?? Effect.succeed( threadId === THREAD_ID && options?.cached !== undefined ? Option.some({ snapshotSequence: CACHED_SNAPSHOT_SEQUENCE, projection: options.cached, ...(options.cachedHistory === undefined ? {} : { historyCursor: options.cachedHistory.historyCursor, hasMoreHistory: options.cachedHistory.hasMoreHistory, ...(options.cachedHistory.latestLocalTurnOrdinal === undefined ? {} : { latestLocalTurnOrdinal: options.cachedHistory.latestLocalTurnOrdinal, }), }), }) : Option.none(), ), saveThread: (environmentId, thread) => Ref.update(savedThreads, (current) => [...current, thread]).pipe( Effect.andThen(options?.saveThread?.(environmentId, thread) ?? Effect.void), ), removeThread: (_environmentId, threadId) => Ref.update(removedThreads, (current) => [...current, threadId]), loadServerConfig: () => Effect.succeed(Option.none()), saveServerConfig: () => Effect.void, loadVcsRefs: () => Effect.succeed(Option.none()), saveVcsRefs: () => Effect.void, removeVcsRefs: () => Effect.void, clearVcsRefs: () => Effect.void, clear: () => Effect.void, }); const threadState = yield* makeEnvironmentThreadState(THREAD_ID, options?.resumeCache).pipe( Effect.provideService(EnvironmentSupervisor.EnvironmentSupervisor, supervisor), Effect.provideService(Persistence.EnvironmentCacheStore, cache), Effect.provideService(ThreadSnapshotLoader, snapshotLoader), Effect.provideService( ConnectionWakeups.ConnectionWakeups, ConnectionWakeups.ConnectionWakeups.of({ changes: Stream.fromQueue(wakeups) }), ), ); yield* SubscriptionRef.changes(threadState).pipe( Stream.runForEach((state) => Ref.set(latest, state).pipe(Effect.andThen(Queue.offer(observed, state))), ), Effect.forkScoped, ); return { threadState, inputs, observed, latest, retryCount, subscriptionCount, loaderCalls, lastSubscribeAfterSequence, lastRequestCompletionMarker, supervisorState, supervisorSession, savedThreads, removedThreads, wakeups, replaceSession: SubscriptionRef.set( supervisorSession, Option.some(testSession(client, options)), ), }; }); const snapshot = ( projection: OrchestrationV2ThreadProjection, snapshotSequence = 1, ): OrchestrationV2ThreadStreamItem => ({ kind: "snapshot", snapshotSequence, projection, }); const synchronized = (): OrchestrationV2ThreadStreamItem => ({ kind: "synchronized" }); const titleUpdated = (title: string, sequence = 2): OrchestrationV2ThreadStreamItem => { const occurredAt = DateTime.makeUnsafe("2026-06-20T01:00:00.000Z"); return { kind: "event", sequence, event: { id: EventId.make("event-title"), type: "thread.metadata-updated", threadId: THREAD_ID, occurredAt, payload: { ...v2Projection.thread, title, updatedAt: occurredAt }, }, }; }; const deleted = (sequence = 3): OrchestrationV2ThreadStreamItem => { const occurredAt = DateTime.makeUnsafe("2026-06-20T02:00:00.000Z"); return { kind: "event", sequence, event: { id: EventId.make("event-deleted"), type: "thread.deleted", threadId: THREAD_ID, occurredAt, payload: { ...v2Projection.thread, updatedAt: occurredAt, deletedAt: occurredAt }, }, }; }; describe("EnvironmentThreads", () => { for (const source of ["disk", "HTTP"] as const) { it.effect(`does not rewrite an unchanged ${source} snapshot on navigation or warm return`, () => Effect.gen(function* () { const resumeCache: NonNullable[1]> = { snapshot: undefined, owner: undefined, }; const firstSaved = yield* Effect.scoped( Effect.gen(function* () { const h = yield* makeHarness({ resumeCache, ...(source === "disk" ? { cached: BASE_PROJECTION } : { httpSnapshot: { _tag: "present", snapshot: { snapshotSequence: 7, projection: BASE_PROJECTION }, }, }), }); yield* awaitThreadState(h.observed, (value) => value.status === "live"); if (source === "HTTP") yield* TestClock.adjust("500 millis"); return h.savedThreads; }), ); expect(yield* Ref.get(firstSaved)).toHaveLength(source === "disk" ? 0 : 1); const nextSaved = yield* Effect.scoped( Effect.gen(function* () { const h = yield* makeHarness({ resumeCache }); yield* awaitThreadState(h.observed, (value) => value.status === "live"); return h.savedThreads; }), ); expect(yield* Ref.get(nextSaved)).toEqual([]); }), ); } it.effect("persists a complete bounded HTTP window only once", () => Effect.gen(function* () { const saved = yield* Effect.scoped( Effect.gen(function* () { const h = yield* makeHarness({ httpSnapshot: { _tag: "present", snapshot: { snapshotSequence: 7, projection: BASE_PROJECTION }, history: { historyCursor: null, hasMoreHistory: false, latestLocalTurnOrdinal: 12, }, }, }); yield* awaitThreadState(h.observed, (value) => value.status === "live"); yield* TestClock.adjust("500 millis"); return h.savedThreads; }), ); expect(yield* Ref.get(saved)).toEqual([{ snapshotSequence: 7, projection: BASE_PROJECTION }]); }), ); it.effect("retries a failed background cache write when the scope closes", () => Effect.gen(function* () { let attempts = 0; yield* Effect.scoped( Effect.gen(function* () { const h = yield* makeHarness({ httpSnapshot: { _tag: "present", snapshot: { snapshotSequence: 7, projection: BASE_PROJECTION }, }, saveThread: () => Effect.suspend(() => { attempts += 1; return attempts === 1 ? Effect.fail( new Persistence.ConnectionPersistenceError({ operation: "save-thread", message: "Test storage failure", }), ) : Effect.void; }), }); yield* awaitThreadState(h.observed, (value) => value.status === "live"); yield* TestClock.adjust("500 millis"); expect(attempts).toBe(1); }), ); expect(attempts).toBe(2); }), ); it.effect("flushes newer data after an older background write completes", () => Effect.gen(function* () { const writing = yield* Deferred.make(); const written = yield* Deferred.make(); const release = yield* Deferred.make(); const saved = yield* Effect.scoped( Effect.gen(function* () { const h = yield* makeHarness({ cached: BASE_PROJECTION, saveThread: (_environmentId, snapshot) => snapshot.snapshotSequence === 8 ? Deferred.succeed(writing, undefined).pipe( Effect.andThen(Deferred.await(release)), Effect.andThen(Deferred.succeed(written, undefined)), ) : Effect.void, }); yield* Queue.offer(h.inputs, titleUpdated("First update", 8)); yield* awaitThreadState( h.observed, (value) => Option.getOrNull(value.data)?.thread.title === "First update", ); yield* TestClock.adjust("500 millis"); yield* Deferred.await(writing); yield* Queue.offer(h.inputs, titleUpdated("Newer update", 9)); yield* awaitThreadState( h.observed, (value) => Option.getOrNull(value.data)?.thread.title === "Newer update", ); yield* Deferred.succeed(release, undefined); yield* Deferred.await(written); return h.savedThreads; }), ); expect( (yield* Ref.get(saved)).map((snapshot) => [ snapshot.snapshotSequence, snapshot.projection.thread.title, ]), ).toEqual([ [8, "First update"], [9, "Newer update"], ]); }), ); it.effect("does not resume past a canceled event whose data was not applied", () => Effect.gen(function* () { const resumeCache: NonNullable[1]> = { snapshot: undefined, owner: undefined, }; const scope = yield* Effect.acquireRelease(Scope.make(), (scope) => Scope.close(scope, Exit.void), ); const first = yield* makeHarness({ httpSnapshot: { _tag: "present", snapshot: { projection: BASE_PROJECTION, snapshotSequence: CACHED_SNAPSHOT_SEQUENCE, }, }, resumeCache, }).pipe(Effect.provideService(Scope.Scope, scope)); yield* awaitThreadState(first.observed, (value) => value.status === "live"); const applying = yield* Deferred.make(); const update = titleUpdated("Not applied", 8); if (update.kind !== "event") return yield* Effect.die("Expected an event"); let sequenceReads = 0; Object.defineProperty(update, "sequence", { get: () => { if (++sequenceReads === 2) Deferred.doneUnsafe(applying, Exit.void); return 8; }, }); // Hold the projection write while the event advances its cursor. Closing // the scope must resume from the last fully applied projection. yield* first.threadState.semaphore.take(1); yield* Queue.offer(first.inputs, update); yield* Deferred.await(applying); yield* Scope.close(scope, Exit.void); yield* first.threadState.semaphore.release(1); const resumed = yield* makeHarness({ resumeCache }); const state = yield* awaitThreadState(resumed.observed, (value) => value.status === "live"); expect(Option.getOrThrow(state.data).thread.title).toBe(BASE_PROJECTION.thread.title); expect(yield* Ref.get(resumed.lastSubscribeAfterSequence)).toBe(CACHED_SNAPSHOT_SEQUENCE); expect(yield* Ref.get(first.savedThreads)).toEqual([ { projection: BASE_PROJECTION, snapshotSequence: CACHED_SNAPSHOT_SEQUENCE, }, ]); }), ); it.effect("prevents an old scope from replacing its successor's cached data", () => Effect.gen(function* () { const resumeCache: NonNullable[1]> = { snapshot: undefined, owner: undefined, }; const oldScope = yield* Effect.acquireRelease(Scope.make(), (scope) => Scope.close(scope, Exit.void), ); const old = yield* makeHarness({ cached: BASE_PROJECTION, resumeCache }).pipe( Effect.provideService(Scope.Scope, oldScope), ); yield* awaitThreadState(old.observed, (value) => value.status === "live"); const successor = yield* makeHarness({ resumeCache }); yield* Queue.offer(successor.inputs, titleUpdated("Successor title", 9)); yield* awaitThreadState( successor.observed, (value) => Option.getOrNull(value.data)?.thread.title === "Successor title", ); yield* Queue.offer(old.inputs, titleUpdated("Old scope title", 8)); yield* awaitThreadState( old.observed, (value) => Option.getOrNull(value.data)?.thread.title === "Old scope title", ); yield* Scope.close(oldScope, Exit.void); expect(resumeCache.snapshot?.sequence).toBe(9); expect(Option.getOrThrow(resumeCache.snapshot!.state.data).thread.title).toBe( "Successor title", ); expect(yield* Ref.get(old.savedThreads)).toEqual([]); }), ); it.effect("does not let a delayed cache read replace an initialized successor", () => Effect.gen(function* () { const resumeCache: NonNullable[1]> = { snapshot: undefined, owner: undefined, }; const loading = yield* Deferred.make(); const response = yield* Deferred.make>(); const oldFiber = yield* makeHarness({ resumeCache, loadCached: Deferred.succeed(loading, undefined).pipe( Effect.andThen(Deferred.await(response)), ), }).pipe(Effect.forkScoped); yield* Deferred.await(loading); const successor = yield* makeHarness({ cached: BASE_PROJECTION, resumeCache }); yield* awaitThreadState(successor.observed, (value) => value.status === "live"); yield* Deferred.succeed( response, Option.some({ snapshotSequence: 3, projection: { ...BASE_PROJECTION, thread: { ...BASE_PROJECTION.thread, title: "Old disk data" }, }, }), ); const old = yield* Fiber.join(oldFiber); yield* awaitThreadState(old.observed, (value) => value.status === "live"); expect(resumeCache.snapshot?.sequence).toBe(CACHED_SNAPSHOT_SEQUENCE); expect(Option.getOrThrow(resumeCache.snapshot!.state.data).thread.title).toBe( BASE_PROJECTION.thread.title, ); }), ); it.effect("does not let an old deletion remove its successor's persisted cache", () => Effect.gen(function* () { const resumeCache: NonNullable[1]> = { snapshot: undefined, owner: undefined, }; const old = yield* makeHarness({ cached: BASE_PROJECTION, resumeCache }); yield* awaitThreadState(old.observed, (value) => value.status === "live"); const successor = yield* makeHarness({ resumeCache }); yield* Queue.offer(successor.inputs, titleUpdated("Successor title", 9)); yield* awaitThreadState( successor.observed, (value) => Option.getOrNull(value.data)?.thread.title === "Successor title", ); const update = deleted(); if (update.kind !== "event") return yield* Effect.die("Expected an event"); yield* Queue.offer(old.inputs, { ...update, sequence: 8 }); yield* awaitThreadState(old.observed, (value) => value.status === "deleted"); expect(resumeCache.snapshot?.sequence).toBe(9); expect(yield* Ref.get(old.removedThreads)).toEqual([]); }), ); it.effect("retains a deletion instead of restoring the old disk snapshot", () => Effect.gen(function* () { const resumeCache: NonNullable[1]> = { snapshot: undefined, owner: undefined, }; yield* Effect.scoped( Effect.gen(function* () { const first = yield* makeHarness({ cached: BASE_PROJECTION, resumeCache }); const update = deleted(); if (update.kind !== "event") return yield* Effect.die("Expected an event"); yield* Queue.offer(first.inputs, { ...update, sequence: 8 }); yield* awaitThreadState(first.observed, (value) => value.status === "deleted"); }), ); const resumed = yield* makeHarness({ cached: BASE_PROJECTION, resumeCache }); const state = yield* awaitThreadState( resumed.observed, (value) => value.status === "deleted", ); expect(Option.isNone(state.data)).toBe(true); expect(yield* Ref.get(resumed.loaderCalls)).toBe(0); }), ); it.effect("publishes cached data immediately from a warm cache", () => Effect.gen(function* () { const harness = yield* makeHarness({ cached: BASE_PROJECTION }); const state = yield* awaitThreadState(harness.observed, (value) => Option.isSome(value.data)); expect(Option.getOrThrow(state.data)).toEqual(BASE_PROJECTION); expect(Option.isNone(state.error)).toBe(true); }), ); it.effect("resumes a warm cache via afterSequence without an HTTP fetch", () => Effect.gen(function* () { const harness = yield* makeHarness({ cached: BASE_PROJECTION }); // The warm cache reaches live from the cached data, and a live event // applies on top of it. yield* Queue.offer(harness.inputs, titleUpdated("Live title", CACHED_SNAPSHOT_SEQUENCE + 1)); yield* awaitThreadState( harness.observed, (value) => value.status === "live" && Option.isSome(value.data) && value.data.value.thread.title === "Live title", ); // The subscription resumed from the cached sequence and never fetched the // full snapshot over HTTP. expect(yield* Ref.get(harness.lastSubscribeAfterSequence)).toBe(CACHED_SNAPSHOT_SEQUENCE); expect(yield* Ref.get(harness.loaderCalls)).toBe(0); }), ); it.effect("reduces live events and persists the latest thread", () => Effect.gen(function* () { const harness = yield* makeHarness({ cached: BASE_PROJECTION }); yield* Queue.offer(harness.inputs, snapshot(BASE_PROJECTION)); yield* Queue.offer(harness.inputs, titleUpdated("Live title")); const state = yield* awaitThreadState( harness.observed, (value) => value.status === "live" && Option.isSome(value.data) && value.data.value.thread.title === "Live title", ); yield* TestClock.adjust("500 millis"); yield* Effect.yieldNow; expect(Option.getOrThrow(state.data).thread.title).toBe("Live title"); expect((yield* Ref.get(harness.savedThreads)).at(-1)?.projection.thread.title).toBe( "Live title", ); expect((yield* Ref.get(harness.savedThreads)).at(-1)?.snapshotSequence).toBe(2); }), ); it.effect("seeds the thread from the HTTP snapshot and resumes live events", () => Effect.gen(function* () { const httpProjection: OrchestrationV2ThreadProjection = { ...BASE_PROJECTION, thread: { ...BASE_PROJECTION.thread, title: "HTTP title" }, }; const harness = yield* makeHarness({ httpSnapshot: { _tag: "present", snapshot: { snapshotSequence: 1, projection: httpProjection }, }, }); // No socket snapshot is pushed; only a live event arrives over the socket. // It can only be applied if the HTTP snapshot already seeded the thread. yield* Queue.offer(harness.inputs, titleUpdated("Live title", 2)); const state = yield* awaitThreadState( harness.observed, (value) => value.status === "live" && Option.isSome(value.data) && value.data.value.thread.title === "Live title", ); expect(Option.getOrThrow(state.data).thread.title).toBe("Live title"); // Cold cache: the full snapshot was loaded over HTTP and the socket // resumed from that snapshot's sequence. expect(yield* Ref.get(harness.loaderCalls)).toBeGreaterThanOrEqual(1); expect(yield* Ref.get(harness.lastSubscribeAfterSequence)).toBe(1); }), ); it.effect("installs bounded snapshot history meta and resumes via afterSequence", () => Effect.gen(function* () { const httpProjection: OrchestrationV2ThreadProjection = { ...BASE_PROJECTION, thread: { ...BASE_PROJECTION.thread, title: "Bounded title" }, }; const harness = yield* makeHarness({ httpSnapshot: { _tag: "present", snapshot: { snapshotSequence: 11, projection: httpProjection }, history: { historyCursor: "opaque-cursor", hasMoreHistory: true, }, }, }); yield* Queue.offer(harness.inputs, titleUpdated("Live after bounded", 12)); const state = yield* awaitThreadState( harness.observed, (value) => value.status === "live" && Option.isSome(value.data) && value.data.value.thread.title === "Live after bounded" && value.history.hasMoreHistory, ); expect(state.history).toMatchObject({ historyCursor: "opaque-cursor", hasMoreHistory: true, loading: false, error: null, expanded: false, }); expect(yield* Ref.get(harness.lastSubscribeAfterSequence)).toBe(11); // Bounded HTTP success must not require a socket snapshot frame. expect(Option.getOrThrow(state.data).thread.title).toBe("Live after bounded"); }), ); it.effect("persists progressive history meta with a settled bounded snapshot", () => Effect.gen(function* () { const httpProjection: OrchestrationV2ThreadProjection = { ...BASE_PROJECTION, thread: { ...BASE_PROJECTION.thread, title: "Bounded cache title" }, }; const harness = yield* makeHarness({ httpSnapshot: { _tag: "present", snapshot: { snapshotSequence: 4, projection: httpProjection }, history: { historyCursor: "cursor-oldest", hasMoreHistory: true, }, }, }); // Bounded install alone (settled) must enqueue progressive meta with the // projection. Never persist the partial window as a complete full snapshot. yield* awaitThreadState( harness.observed, (value) => Option.isSome(value.data) && value.data.value.thread.title === "Bounded cache title" && value.history.historyCursor === "cursor-oldest", ); yield* TestClock.adjust("500 millis"); yield* Effect.yieldNow; const savedAll = yield* Ref.get(harness.savedThreads); expect(savedAll.length).toBeGreaterThanOrEqual(1); const first = savedAll[0]; expect(first?.snapshotSequence).toBe(4); expect(first?.projection.thread.title).toBe("Bounded cache title"); expect(first?.historyCursor).toBe("cursor-oldest"); expect(first?.hasMoreHistory).toBe(true); // No earlier complete-looking entry without progressive meta. expect( savedAll.some( (entry) => entry.projection.thread.title === "Bounded cache title" && entry.historyCursor === undefined && entry.hasMoreHistory === undefined, ), ).toBe(false); // A later live update still carries the progressive cursor. yield* Queue.offer(harness.inputs, titleUpdated("Settled bounded", 5)); yield* awaitThreadState( harness.observed, (value) => value.status === "live" && Option.isSome(value.data) && value.data.value.thread.title === "Settled bounded", ); yield* TestClock.adjust("500 millis"); yield* Effect.yieldNow; const saved = (yield* Ref.get(harness.savedThreads)).at(-1); expect(saved?.snapshotSequence).toBe(5); expect(saved?.projection.thread.title).toBe("Settled bounded"); expect(saved?.historyCursor).toBe("cursor-oldest"); expect(saved?.hasMoreHistory).toBe(true); }), ); it.effect("warm resume restores progressive history meta and skips HTTP", () => Effect.gen(function* () { const harness = yield* makeHarness({ cached: { ...BASE_PROJECTION, thread: { ...BASE_PROJECTION.thread, title: "Cached bounded" }, }, cachedHistory: { historyCursor: "warm-cursor", hasMoreHistory: true, }, }); // Apply a live event so the subscription is known to have resumed from cache. yield* Queue.offer( harness.inputs, titleUpdated("Cached bounded live", CACHED_SNAPSHOT_SEQUENCE + 1), ); const state = yield* awaitThreadState( harness.observed, (value) => value.status === "live" && Option.isSome(value.data) && value.data.value.thread.title === "Cached bounded live" && value.history.hasMoreHistory, ); expect(state.history).toMatchObject({ historyCursor: "warm-cursor", hasMoreHistory: true, expanded: false, }); // Warm progressive cache must not re-download; resume via afterSequence. expect(yield* Ref.get(harness.loaderCalls)).toBe(0); expect(yield* Ref.get(harness.lastSubscribeAfterSequence)).toBe(CACHED_SNAPSHOT_SEQUENCE); }), ); it.effect("legacy warm cache without history meta stays complete (no false load-earlier)", () => Effect.gen(function* () { const harness = yield* makeHarness({ cached: BASE_PROJECTION }); const state = yield* awaitThreadState(harness.observed, (value) => Option.isSome(value.data)); expect(state.history).toMatchObject({ historyCursor: null, hasMoreHistory: false, expanded: false, }); expect(yield* Ref.get(harness.loaderCalls)).toBe(0); }), ); it.effect("socket snapshot clears progressive history meta left from a bounded window", () => Effect.gen(function* () { const harness = yield* makeHarness({ cached: { ...BASE_PROJECTION, thread: { ...BASE_PROJECTION.thread, title: "Warm progressive" }, }, cachedHistory: { historyCursor: "stale-cursor", hasMoreHistory: true, }, }); yield* Queue.offer( harness.inputs, snapshot( { ...BASE_PROJECTION, thread: { ...BASE_PROJECTION.thread, title: "Full socket snapshot" }, }, CACHED_SNAPSHOT_SEQUENCE + 1, ), ); const state = yield* awaitThreadState( harness.observed, (value) => value.status === "live" && Option.isSome(value.data) && value.data.value.thread.title === "Full socket snapshot" && value.history.historyCursor === null && value.history.hasMoreHistory === false, ); expect(state.history).toMatchObject({ historyCursor: null, hasMoreHistory: false, expanded: false, loading: false, error: null, }); // Settled full snapshot persistence must not keep the stale cursor. yield* TestClock.adjust("500 millis"); yield* Effect.yieldNow; const saved = (yield* Ref.get(harness.savedThreads)).at(-1); expect(saved?.projection.thread.title).toBe("Full socket snapshot"); expect(saved?.historyCursor).toBeUndefined(); expect(saved?.hasMoreHistory).toBeUndefined(); }), ); it.effect("live events preserve progressive history meta under atomic setThread", () => Effect.gen(function* () { const harness = yield* makeHarness({ httpSnapshot: { _tag: "present", snapshot: { snapshotSequence: 8, projection: { ...BASE_PROJECTION, thread: { ...BASE_PROJECTION.thread, title: "Bounded seed" }, }, }, history: { historyCursor: "keep-me", hasMoreHistory: true, }, }, }); yield* Queue.offer(harness.inputs, titleUpdated("Live preserves meta", 9)); const state = yield* awaitThreadState( harness.observed, (value) => value.status === "live" && Option.isSome(value.data) && value.data.value.thread.title === "Live preserves meta", ); expect(state.history).toMatchObject({ historyCursor: "keep-me", hasMoreHistory: true, expanded: false, }); }), ); it.effect("bounded HTTP install sets projection and progressive meta atomically", () => Effect.gen(function* () { // One setThread with explicit history (not applyItem reset + later meta). const harness = yield* makeHarness({ httpSnapshot: { _tag: "present", snapshot: { snapshotSequence: 2, projection: { ...BASE_PROJECTION, thread: { ...BASE_PROJECTION.thread, title: "Bounded atomic install" }, }, }, history: { historyCursor: "post-install-cursor", hasMoreHistory: true, }, }, }); const state = yield* awaitThreadState( harness.observed, (value) => Option.isSome(value.data) && value.data.value.thread.title === "Bounded atomic install" && value.history.historyCursor === "post-install-cursor" && value.history.hasMoreHistory === true, ); expect(state.history).toMatchObject({ historyCursor: "post-install-cursor", hasMoreHistory: true, loading: false, error: null, expanded: false, }); expect(yield* Ref.get(harness.lastSubscribeAfterSequence)).toBe(2); }), ); it.effect("dropped partial-timeline turn item is a true applyItem no-op", () => Effect.gen(function* () { const recent = { id: TurnItemId.make("item-window"), threadId: THREAD_ID, runId: null, nodeId: null, providerThreadId: null, providerTurnId: null, nativeItemRef: null, parentItemId: null, ordinal: 10, status: "completed" as const, title: null, startedAt: DateTime.makeUnsafe("2026-06-20T00:00:00.000Z"), completedAt: DateTime.makeUnsafe("2026-06-20T00:00:00.000Z"), updatedAt: DateTime.makeUnsafe("2026-06-20T00:00:00.000Z"), type: "command_execution" as const, input: "pwd", output: "ok", exitCode: 0, } satisfies OrchestrationV2TurnItem; const recentRow = { position: 0, visibility: "local" as const, sourceThreadId: THREAD_ID, sourceItemId: recent.id, item: recent, }; const boundedProjection: OrchestrationV2ThreadProjection = { ...BASE_PROJECTION, thread: { ...BASE_PROJECTION.thread, title: "Partial noop" }, turnItems: [recent], visibleTurnItems: [recentRow], }; const harness = yield* makeHarness({ httpSnapshot: { _tag: "present", snapshot: { snapshotSequence: 5, projection: boundedProjection, latestLocalTurnOrdinal: 10, }, history: { historyCursor: "partial-cursor", hasMoreHistory: true, latestLocalTurnOrdinal: 10, }, }, }); const seeded = yield* awaitThreadState( harness.observed, (value) => value.status === "live" && Option.isSome(value.data) && value.data.value.thread.title === "Partial noop" && value.history.historyCursor === "partial-cursor", ); const seededProjection = Option.getOrThrow(seeded.data); yield* TestClock.adjust("500 millis"); yield* Effect.yieldNow; const savedBefore = (yield* Ref.get(harness.savedThreads)).length; const older = { ...recent, id: TurnItemId.make("item-old-outside"), ordinal: 3, output: "must-not-append", } satisfies OrchestrationV2TurnItem; yield* Queue.offer(harness.inputs, { kind: "event", sequence: 6, event: { id: EventId.make("event-old-partial"), type: "turn-item.updated", threadId: THREAD_ID, occurredAt: DateTime.makeUnsafe("2026-06-20T01:00:00.000Z"), payload: older, }, }); // Allow the event to be processed without requiring a state transition. for (let attempt = 0; attempt < 30; attempt += 1) { yield* Effect.yieldNow; } // Drive a later unrelated title update so we know the stream continued. yield* Queue.offer(harness.inputs, titleUpdated("After dropped event", 7)); const after = yield* awaitThreadState( harness.observed, (value) => value.status === "live" && Option.isSome(value.data) && value.data.value.thread.title === "After dropped event", ); // The dropped event must not have appended the old item. expect(Option.getOrThrow(after.data).turnItems.map((item) => String(item.id))).toEqual([ String(recent.id), ]); // Watermark unchanged (only advanced on successful newer turn-item applies). expect(after.history.latestLocalTurnOrdinal).toBe(10); // No persistence enqueue from the dropped event itself. const savedAfterDrop = (yield* Ref.get(harness.savedThreads)).length; expect(savedAfterDrop).toBe(savedBefore); // Seeded projection reference path: event path kept partial meta intact. expect(after.history.historyCursor).toBe("partial-cursor"); expect(after.history.hasMoreHistory).toBe(true); // Title event applied; drop itself did not clear progressive meta. expect(seededProjection.turnItems.map((item) => String(item.id))).toEqual([ String(recent.id), ]); }), ); it.effect("installs and advances latestLocalTurnOrdinal for partial progressive windows", () => Effect.gen(function* () { const harness = yield* makeHarness({ httpSnapshot: { _tag: "present", snapshot: { snapshotSequence: 3, projection: { ...BASE_PROJECTION, thread: { ...BASE_PROJECTION.thread, title: "Watermark seed" }, }, latestLocalTurnOrdinal: 15, }, history: { historyCursor: "wm-cursor", hasMoreHistory: true, latestLocalTurnOrdinal: 15, }, }, }); const seeded = yield* awaitThreadState( harness.observed, (value) => Option.isSome(value.data) && value.data.value.thread.title === "Watermark seed" && value.history.latestLocalTurnOrdinal === 15, ); expect(seeded.history.latestLocalTurnOrdinal).toBe(15); yield* TestClock.adjust("500 millis"); yield* Effect.yieldNow; const savedSeed = (yield* Ref.get(harness.savedThreads)).at(-1); expect(savedSeed?.latestLocalTurnOrdinal).toBe(15); const newer = { id: TurnItemId.make("item-newer-live"), threadId: THREAD_ID, runId: null, nodeId: null, providerThreadId: null, providerTurnId: null, nativeItemRef: null, parentItemId: null, ordinal: 22, status: "completed" as const, title: null, startedAt: DateTime.makeUnsafe("2026-06-20T00:00:00.000Z"), completedAt: DateTime.makeUnsafe("2026-06-20T00:00:00.000Z"), updatedAt: DateTime.makeUnsafe("2026-06-20T00:00:00.000Z"), type: "command_execution" as const, input: "echo newer", output: "newer", exitCode: 0, } satisfies OrchestrationV2TurnItem; yield* Queue.offer(harness.inputs, { kind: "event", sequence: 4, event: { id: EventId.make("event-newer-item"), type: "turn-item.updated", threadId: THREAD_ID, occurredAt: DateTime.makeUnsafe("2026-06-20T01:00:00.000Z"), payload: newer, }, }); const advanced = yield* awaitThreadState( harness.observed, (value) => value.status === "live" && Option.isSome(value.data) && value.history.latestLocalTurnOrdinal === 22 && value.data.value.turnItems.some((item) => String(item.id) === String(newer.id)), ); expect(advanced.history.latestLocalTurnOrdinal).toBe(22); expect(advanced.history.historyCursor).toBe("wm-cursor"); }), ); it.effect("warm resume restores latestLocalTurnOrdinal from progressive cache", () => Effect.gen(function* () { const harness = yield* makeHarness({ cached: { ...BASE_PROJECTION, thread: { ...BASE_PROJECTION.thread, title: "Warm watermark" }, }, cachedHistory: { historyCursor: "warm-wm-cursor", hasMoreHistory: true, latestLocalTurnOrdinal: 33, }, }); const state = yield* awaitThreadState( harness.observed, (value) => Option.isSome(value.data) && value.data.value.thread.title === "Warm watermark" && value.history.latestLocalTurnOrdinal === 33, ); expect(state.history).toMatchObject({ historyCursor: "warm-wm-cursor", hasMoreHistory: true, latestLocalTurnOrdinal: 33, expanded: false, }); expect(yield* Ref.get(harness.loaderCalls)).toBe(0); }), ); it.effect("marks a cold definitive HTTP miss deleted without socket subscribe or retry", () => Effect.gen(function* () { const harness = yield* makeHarness({ httpSnapshot: { _tag: "missing" }, }); const state = yield* awaitThreadState( harness.observed, (value) => value.status === "deleted", ); expect(Option.isNone(state.data)).toBe(true); expect(Option.isNone(state.error)).toBe(true); expect(yield* Ref.get(harness.loaderCalls)).toBeGreaterThanOrEqual(1); expect(yield* Ref.get(harness.removedThreads)).toEqual([THREAD_ID]); expect(yield* Ref.get(harness.subscriptionCount)).toBe(0); // A definitive miss must not schedule the expected-failure retry path. yield* TestClock.adjust("1 second"); for (let attempt = 0; attempt < 20; attempt += 1) { yield* Effect.yieldNow; } yield* harness.replaceSession; yield* Queue.offer(harness.wakeups, "application-active"); for (let attempt = 0; attempt < 20; attempt += 1) { yield* Effect.yieldNow; } expect(yield* Ref.get(harness.subscriptionCount)).toBe(0); expect(yield* Ref.get(harness.retryCount)).toBe(0); expect(yield* Ref.get(harness.loaderCalls)).toBe(1); expect((yield* Ref.get(harness.latest)).status).toBe("deleted"); }), ); it.effect("falls back to the socket when the HTTP snapshot is only unavailable", () => Effect.gen(function* () { const harness = yield* makeHarness({ httpSnapshot: { _tag: "unavailable" }, }); yield* Queue.offer( harness.inputs, snapshot({ ...BASE_PROJECTION, thread: { ...BASE_PROJECTION.thread, title: "Socket title" }, }), ); const state = yield* awaitThreadState( harness.observed, (value) => value.status === "live" && Option.isSome(value.data) && value.data.value.thread.title === "Socket title", ); expect(Option.getOrThrow(state.data).thread.title).toBe("Socket title"); expect(yield* Ref.get(harness.loaderCalls)).toBeGreaterThanOrEqual(1); expect(yield* Ref.get(harness.subscriptionCount)).toBe(1); expect(yield* Ref.get(harness.removedThreads)).toEqual([]); }), ); it.effect("ignores replayed thread events at or below the snapshot sequence", () => Effect.gen(function* () { const harness = yield* makeHarness({ cached: BASE_PROJECTION }); yield* Queue.offer(harness.inputs, snapshot(BASE_PROJECTION)); yield* Queue.offer(harness.inputs, titleUpdated("Replayed title", 1)); yield* Queue.offer(harness.inputs, titleUpdated("Live title", 2)); const state = yield* awaitThreadState( harness.observed, (value) => value.status === "live" && Option.isSome(value.data) && value.data.value.thread.title === "Live title", ); expect(Option.getOrThrow(state.data).thread.title).toBe("Live title"); }), ); it.effect("removes cached data when the thread is deleted", () => Effect.gen(function* () { const harness = yield* makeHarness({ cached: BASE_PROJECTION }); yield* Queue.offer(harness.inputs, snapshot(BASE_PROJECTION)); yield* Queue.offer(harness.inputs, deleted()); const state = yield* awaitThreadState( harness.observed, (value) => value.status === "deleted", ); expect(Option.isNone(state.data)).toBe(true); expect(yield* Ref.get(harness.removedThreads)).toEqual([THREAD_ID]); }), ); it.effect("preserves data after a domain failure and resumes on a replacement session", () => Effect.gen(function* () { const harness = yield* makeHarness({ cached: BASE_PROJECTION }); yield* Queue.offer(harness.inputs, snapshot(BASE_PROJECTION)); yield* Queue.offer(harness.inputs, new Error("stream failed")); const state = yield* awaitThreadState(harness.observed, (value) => Option.isSome(value.error), ); expect(Option.getOrThrow(state.data)).toEqual(BASE_PROJECTION); expect(Option.getOrThrow(state.error)).toBe("stream failed"); expect(yield* Ref.get(harness.retryCount)).toBe(0); yield* harness.replaceSession; for (let attempt = 0; attempt < 100; attempt += 1) { if ((yield* Ref.get(harness.subscriptionCount)) >= 2) { break; } yield* Effect.yieldNow; } yield* Queue.offer( harness.inputs, snapshot({ ...BASE_PROJECTION, thread: { ...BASE_PROJECTION.thread, title: "Recovered thread" }, }), ); const recovered = yield* awaitThreadState( harness.observed, (value) => value.status === "live" && Option.isSome(value.data) && value.data.value.thread.title === "Recovered thread", ); expect(Option.isNone(recovered.error)).toBe(true); expect(yield* Ref.get(harness.subscriptionCount)).toBe(2); }), ); it.effect("recovers from a transient domain failure without replacing the session", () => Effect.gen(function* () { const harness = yield* makeHarness(); yield* Queue.offer(harness.inputs, new Error("thread not found yet")); const failed = yield* awaitThreadState(harness.observed, (value) => Option.isSome(value.error), ); expect(Option.getOrThrow(failed.error)).toBe("thread not found yet"); expect(yield* Ref.get(harness.subscriptionCount)).toBe(1); yield* TestClock.adjust("250 millis"); for (let attempt = 0; attempt < 100; attempt += 1) { if ((yield* Ref.get(harness.subscriptionCount)) >= 2) { break; } yield* Effect.yieldNow; } yield* Queue.offer( harness.inputs, snapshot({ ...BASE_PROJECTION, thread: { ...BASE_PROJECTION.thread, title: "Materialized thread" }, }), ); const recovered = yield* awaitThreadState( harness.observed, (value) => value.status === "live" && Option.isSome(value.data) && value.data.value.thread.title === "Materialized thread", ); expect(Option.isNone(recovered.error)).toBe(true); expect(yield* Ref.get(harness.subscriptionCount)).toBe(2); expect(yield* Ref.get(harness.retryCount)).toBe(0); }), ); it.effect("does not overwrite a live snapshot when the supervisor becomes ready", () => Effect.gen(function* () { const harness = yield* makeHarness({ cached: BASE_PROJECTION }); yield* SubscriptionRef.set(harness.supervisorState, { desired: true, network: "online", phase: "connecting", stage: "synchronizing", attempt: 1, generation: 0, lastFailure: null, retryAt: null, }); yield* Queue.offer(harness.inputs, snapshot(BASE_PROJECTION)); yield* awaitThreadState(harness.observed, (value) => value.status === "live"); yield* SubscriptionRef.set(harness.supervisorState, { desired: true, network: "online", phase: "connected", stage: null, attempt: 1, generation: 1, lastFailure: null, retryAt: null, }); for (let index = 0; index < 10; index += 1) { yield* Effect.yieldNow; } expect((yield* Ref.get(harness.latest)).status).toBe("live"); }), ); it.effect("keeps replayed updates synchronizing until the completion marker arrives", () => Effect.gen(function* () { const harness = yield* makeHarness({ cached: BASE_PROJECTION, completionMarker: true }); yield* awaitThreadState( harness.observed, (value) => value.status === "synchronizing" && Option.isSome(value.data), ); expect(yield* Ref.get(harness.lastRequestCompletionMarker)).toBe(true); yield* Queue.offer( harness.inputs, titleUpdated("Caught-up title", CACHED_SNAPSHOT_SEQUENCE + 1), ); const catchingUp = yield* awaitThreadState( harness.observed, (value) => value.status === "synchronizing" && Option.isSome(value.data) && value.data.value.thread.title === "Caught-up title", ); expect(catchingUp.status).toBe("synchronizing"); yield* Queue.offer(harness.inputs, synchronized()); const live = yield* awaitThreadState( harness.observed, (value) => value.status === "live" && Option.isSome(value.data), ); expect(Option.getOrThrow(live.data).thread.title).toBe("Caught-up title"); }), ); it.effect("resumes replacement sessions from the latest applied sequence", () => Effect.gen(function* () { const harness = yield* makeHarness({ cached: BASE_PROJECTION, completionMarker: true }); yield* Queue.offer( harness.inputs, titleUpdated("Latest title", CACHED_SNAPSHOT_SEQUENCE + 1), ); yield* Queue.offer(harness.inputs, synchronized()); yield* awaitThreadState( harness.observed, (value) => value.status === "live" && Option.isSome(value.data) && value.data.value.thread.title === "Latest title", ); yield* harness.replaceSession; for (let attempt = 0; attempt < 100; attempt += 1) { if ((yield* Ref.get(harness.subscriptionCount)) >= 2) break; yield* Effect.yieldNow; } expect(yield* Ref.get(harness.subscriptionCount)).toBe(2); expect(yield* Ref.get(harness.lastSubscribeAfterSequence)).toBe(CACHED_SNAPSHOT_SEQUENCE + 1); expect((yield* Ref.get(harness.latest)).status).toBe("synchronizing"); }), ); it.effect("resubscribes on app foreground from the latest applied sequence", () => Effect.gen(function* () { const harness = yield* makeHarness({ cached: BASE_PROJECTION, completionMarker: true }); yield* Queue.offer( harness.inputs, titleUpdated("Latest title", CACHED_SNAPSHOT_SEQUENCE + 1), ); yield* Queue.offer(harness.inputs, synchronized()); yield* awaitThreadState( harness.observed, (value) => value.status === "live" && Option.isSome(value.data) && value.data.value.thread.title === "Latest title", ); yield* Queue.offer(harness.wakeups, "application-active"); const synchronizing = yield* awaitThreadState( harness.observed, (value) => value.status === "synchronizing" && Option.isSome(value.data), ); for (let attempt = 0; attempt < 100; attempt += 1) { if ((yield* Ref.get(harness.subscriptionCount)) >= 2) break; yield* Effect.yieldNow; } expect(synchronizing.status).toBe("synchronizing"); expect(yield* Ref.get(harness.subscriptionCount)).toBe(2); expect(yield* Ref.get(harness.lastSubscribeAfterSequence)).toBe(CACHED_SNAPSHOT_SEQUENCE + 1); expect(yield* Ref.get(harness.lastRequestCompletionMarker)).toBe(true); expect(yield* Ref.get(harness.loaderCalls)).toBe(0); yield* Queue.offer(harness.inputs, synchronized()); const live = yield* awaitThreadState( harness.observed, (value) => value.status === "live" && Option.isSome(value.data), ); expect(Option.getOrThrow(live.data).thread.title).toBe("Latest title"); yield* Queue.offer(harness.wakeups, "application-active-probe"); for (let attempt = 0; attempt < 100; attempt += 1) { if ((yield* Ref.get(harness.subscriptionCount)) >= 3) break; yield* Effect.yieldNow; } expect(yield* Ref.get(harness.subscriptionCount)).toBe(3); yield* Queue.offer(harness.wakeups, "application-active-reconnect"); for (let attempt = 0; attempt < 10; attempt += 1) { yield* Effect.yieldNow; } expect(yield* Ref.get(harness.subscriptionCount)).toBe(3); }), ); });