diff --git a/apps/server/integration/OrchestrationEngineHarness.integration.ts b/apps/server/integration/OrchestrationEngineHarness.integration.ts index bc1229811a2441dfc61f3ea46b8983623f495291..fa388ba052f3a0e15a54d6c2b9e13733663bfb60 100644 --- a/apps/server/integration/OrchestrationEngineHarness.integration.ts +++ b/apps/server/integration/OrchestrationEngineHarness.integration.ts @@ -22,8 +22,7 @@ import * as Schema from "effect/Schema"; import * as Scope from "effect/Scope"; import * as Stream from "effect/Stream"; -import { CheckpointStoreLive } from "../src/checkpointing/Layers/CheckpointStore.ts"; -import { CheckpointStore } from "../src/checkpointing/Services/CheckpointStore.ts"; +import * as CheckpointStore from "../src/checkpointing/CheckpointStore.ts"; import { TextGeneration, type TextGenerationShape } from "../src/textGeneration/TextGeneration.ts"; import { OrchestrationCommandReceiptRepositoryLive } from "../src/persistence/Layers/OrchestrationCommandReceipts.ts"; import { OrchestrationEventStoreLive } from "../src/persistence/Layers/OrchestrationEventStore.ts"; @@ -180,7 +179,7 @@ export interface OrchestrationIntegrationHarness { readonly engine: OrchestrationEngineShape; readonly snapshotQuery: ProjectionSnapshotQuery["Service"]; readonly providerService: ProviderService["Service"]; - readonly checkpointStore: CheckpointStore["Service"]; + readonly checkpointStore: CheckpointStore.CheckpointStore["Service"]; readonly checkpointRepository: ProjectionCheckpointRepository["Service"]; readonly pendingApprovalRepository: ProjectionPendingApprovalRepository["Service"]; readonly waitForThread: ( @@ -296,7 +295,7 @@ export const makeOrchestrationIntegrationHarness = ( ); const providerRegistryLayer = makeProviderRegistryLayer(); - const checkpointStoreLayer = CheckpointStoreLive.pipe(Layer.provide(VcsDriverRegistry.layer)); + const checkpointStoreLayer = CheckpointStore.layer.pipe(Layer.provide(VcsDriverRegistry.layer)); const projectionSnapshotQueryLayer = OrchestrationProjectionSnapshotQueryLive; const runtimeServicesLayer = Layer.mergeAll( projectionSnapshotQueryLayer, @@ -399,7 +398,7 @@ export const makeOrchestrationIntegrationHarness = ( runtime.runPromise(Effect.service(ProviderService)), ).pipe(Effect.orDie); const checkpointStore = yield* tryRuntimePromise("load CheckpointStore service", () => - runtime.runPromise(Effect.service(CheckpointStore)), + runtime.runPromise(Effect.service(CheckpointStore.CheckpointStore)), ).pipe(Effect.orDie); const checkpointRepository = yield* tryRuntimePromise( "load ProjectionCheckpointRepository service", diff --git a/apps/server/src/checkpointing/CheckpointDiffQuery.test.ts b/apps/server/src/checkpointing/CheckpointDiffQuery.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..8654fa0fec13b24e7a231be26cd9032c49c75b72 --- /dev/null +++ b/apps/server/src/checkpointing/CheckpointDiffQuery.test.ts @@ -0,0 +1,418 @@ +import { CheckpointRef, ProjectId, ThreadId, TurnId } from "@t3tools/contracts"; +import { it } from "@effect/vitest"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; +import { describe, expect } from "vite-plus/test"; + +import * as ProjectionSnapshotQuery from "../orchestration/Services/ProjectionSnapshotQuery.ts"; +import { checkpointRefForThreadTurn } from "./Utils.ts"; +import * as CheckpointDiffQuery from "./CheckpointDiffQuery.ts"; +import * as CheckpointStore from "./CheckpointStore.ts"; + +function makeThreadCheckpointContext(input: { + readonly projectId: ProjectId; + readonly threadId: ThreadId; + readonly workspaceRoot: string; + readonly worktreePath: string | null; + readonly checkpointTurnCount: number; + readonly checkpointRef: CheckpointRef; +}): ProjectionSnapshotQuery.ProjectionThreadCheckpointContext { + return { + threadId: input.threadId, + projectId: input.projectId, + workspaceRoot: input.workspaceRoot, + worktreePath: input.worktreePath, + checkpoints: [ + { + turnId: TurnId.make("turn-1"), + checkpointTurnCount: input.checkpointTurnCount, + checkpointRef: input.checkpointRef, + status: "ready", + files: [], + assistantMessageId: null, + completedAt: "2026-01-01T00:00:00.000Z", + }, + ], + }; +} + +describe("CheckpointDiffQuery.layer", () => { + it.effect("uses the narrow full-thread context lookup for all-turns diffs", () => + Effect.gen(function* () { + const projectId = ProjectId.make("project-full-thread"); + const threadId = ThreadId.make("thread-full-thread"); + const toCheckpointRef = checkpointRefForThreadTurn(threadId, 4); + let getThreadCheckpointContextCalls = 0; + let getFullThreadDiffContextCalls = 0; + const diffCheckpointsCalls: Array<{ + readonly fromCheckpointRef: CheckpointRef; + readonly toCheckpointRef: CheckpointRef; + readonly cwd: string; + readonly ignoreWhitespace: boolean; + }> = []; + + const checkpointStore: CheckpointStore.CheckpointStore["Service"] = { + isGitRepository: () => Effect.succeed(true), + captureCheckpoint: () => Effect.void, + hasCheckpointRef: () => Effect.succeed(true), + restoreCheckpoint: () => Effect.succeed(true), + diffCheckpoints: ({ fromCheckpointRef, toCheckpointRef, cwd, ignoreWhitespace }) => + Effect.sync(() => { + diffCheckpointsCalls.push({ + fromCheckpointRef, + toCheckpointRef, + cwd, + ignoreWhitespace, + }); + return "full thread diff patch"; + }), + deleteCheckpointRefs: () => Effect.void, + }; + + const layer = CheckpointDiffQuery.layer.pipe( + Layer.provideMerge(Layer.succeed(CheckpointStore.CheckpointStore, checkpointStore)), + Layer.provideMerge( + Layer.succeed(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { + getCommandReadModel: () => + Effect.die("CheckpointDiffQuery should not request the command read model"), + getSnapshot: () => + Effect.die("CheckpointDiffQuery should not request the full orchestration snapshot"), + getShellSnapshot: () => + Effect.die("CheckpointDiffQuery should not request the orchestration shell snapshot"), + getArchivedShellSnapshot: () => + Effect.die("CheckpointDiffQuery should not request archived shell snapshots"), + getSnapshotSequence: () => Effect.succeed({ snapshotSequence: 0 }), + getCounts: () => Effect.succeed({ projectCount: 0, threadCount: 0 }), + getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()), + getProjectShellById: () => Effect.succeed(Option.none()), + getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()), + getThreadCheckpointContext: () => + Effect.sync(() => { + getThreadCheckpointContextCalls += 1; + return Option.none(); + }), + getFullThreadDiffContext: () => + Effect.sync(() => { + getFullThreadDiffContextCalls += 1; + return Option.some({ + threadId, + projectId, + workspaceRoot: "/tmp/workspace", + worktreePath: "/tmp/worktree", + latestCheckpointTurnCount: 4, + toCheckpointRef, + }); + }), + getThreadShellById: () => Effect.succeed(Option.none()), + getThreadDetailById: () => Effect.succeed(Option.none()), + }), + ), + ); + + const result = yield* Effect.gen(function* () { + const query = yield* CheckpointDiffQuery.CheckpointDiffQuery; + return yield* query.getFullThreadDiff({ + threadId, + toTurnCount: 4, + ignoreWhitespace: true, + }); + }).pipe(Effect.provide(layer)); + + expect(getThreadCheckpointContextCalls).toBe(0); + expect(getFullThreadDiffContextCalls).toBe(1); + expect(diffCheckpointsCalls).toEqual([ + { + cwd: "/tmp/worktree", + fromCheckpointRef: checkpointRefForThreadTurn(threadId, 0), + toCheckpointRef, + ignoreWhitespace: true, + }, + ]); + expect(result).toEqual({ + threadId, + fromTurnCount: 0, + toTurnCount: 4, + diff: "full thread diff patch", + }); + }), + ); + + it.effect("computes diffs using canonical turn-0 checkpoint refs", () => + Effect.gen(function* () { + const projectId = ProjectId.make("project-1"); + const threadId = ThreadId.make("thread-1"); + const toCheckpointRef = checkpointRefForThreadTurn(threadId, 1); + const diffCheckpointsCalls: Array<{ + readonly fromCheckpointRef: CheckpointRef; + readonly toCheckpointRef: CheckpointRef; + readonly cwd: string; + readonly ignoreWhitespace: boolean; + }> = []; + + const threadCheckpointContext = makeThreadCheckpointContext({ + projectId, + threadId, + workspaceRoot: "/tmp/workspace", + worktreePath: null, + checkpointTurnCount: 1, + checkpointRef: toCheckpointRef, + }); + + const checkpointStore: CheckpointStore.CheckpointStore["Service"] = { + isGitRepository: () => Effect.succeed(true), + captureCheckpoint: () => Effect.void, + hasCheckpointRef: () => Effect.succeed(true), + restoreCheckpoint: () => Effect.succeed(true), + diffCheckpoints: ({ fromCheckpointRef, toCheckpointRef, cwd, ignoreWhitespace }) => + Effect.sync(() => { + diffCheckpointsCalls.push({ + fromCheckpointRef, + toCheckpointRef, + cwd, + ignoreWhitespace, + }); + return "diff patch"; + }), + deleteCheckpointRefs: () => Effect.void, + }; + + const layer = CheckpointDiffQuery.layer.pipe( + Layer.provideMerge(Layer.succeed(CheckpointStore.CheckpointStore, checkpointStore)), + Layer.provideMerge( + Layer.succeed(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { + getCommandReadModel: () => + Effect.die("CheckpointDiffQuery should not request the command read model"), + getSnapshot: () => + Effect.die("CheckpointDiffQuery should not request the full orchestration snapshot"), + getShellSnapshot: () => + Effect.die("CheckpointDiffQuery should not request the orchestration shell snapshot"), + getArchivedShellSnapshot: () => + Effect.die("CheckpointDiffQuery should not request archived shell snapshots"), + getSnapshotSequence: () => Effect.succeed({ snapshotSequence: 0 }), + getCounts: () => Effect.succeed({ projectCount: 0, threadCount: 0 }), + getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()), + getProjectShellById: () => Effect.succeed(Option.none()), + getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()), + getThreadCheckpointContext: () => Effect.succeed(Option.some(threadCheckpointContext)), + getFullThreadDiffContext: () => Effect.die("unused"), + getThreadShellById: () => Effect.succeed(Option.none()), + getThreadDetailById: () => Effect.succeed(Option.none()), + }), + ), + ); + + const result = yield* Effect.gen(function* () { + const query = yield* CheckpointDiffQuery.CheckpointDiffQuery; + return yield* query.getTurnDiff({ + threadId, + fromTurnCount: 0, + toTurnCount: 1, + ignoreWhitespace: true, + }); + }).pipe(Effect.provide(layer)); + + const expectedFromRef = checkpointRefForThreadTurn(threadId, 0); + expect(diffCheckpointsCalls).toEqual([ + { + cwd: "/tmp/workspace", + fromCheckpointRef: expectedFromRef, + toCheckpointRef, + ignoreWhitespace: true, + }, + ]); + expect(result).toEqual({ + threadId, + fromTurnCount: 0, + toTurnCount: 1, + diff: "diff patch", + }); + }), + ); + + it.effect("defaults to hide whitespace changes", () => + Effect.gen(function* () { + const projectId = ProjectId.make("project-default-whitespace"); + const threadId = ThreadId.make("thread-default-whitespace"); + const toCheckpointRef = checkpointRefForThreadTurn(threadId, 1); + const diffCheckpointsCalls: Array<{ readonly ignoreWhitespace: boolean }> = []; + + const threadCheckpointContext = makeThreadCheckpointContext({ + projectId, + threadId, + workspaceRoot: "/tmp/workspace", + worktreePath: null, + checkpointTurnCount: 1, + checkpointRef: toCheckpointRef, + }); + + const checkpointStore: CheckpointStore.CheckpointStore["Service"] = { + isGitRepository: () => Effect.succeed(true), + captureCheckpoint: () => Effect.void, + hasCheckpointRef: () => Effect.succeed(true), + restoreCheckpoint: () => Effect.succeed(true), + diffCheckpoints: ({ ignoreWhitespace }) => + Effect.sync(() => { + diffCheckpointsCalls.push({ ignoreWhitespace }); + return "diff patch"; + }), + deleteCheckpointRefs: () => Effect.void, + }; + + const layer = CheckpointDiffQuery.layer.pipe( + Layer.provideMerge(Layer.succeed(CheckpointStore.CheckpointStore, checkpointStore)), + Layer.provideMerge( + Layer.succeed(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { + getCommandReadModel: () => + Effect.die("CheckpointDiffQuery should not request the command read model"), + getSnapshot: () => + Effect.die("CheckpointDiffQuery should not request the full orchestration snapshot"), + getShellSnapshot: () => + Effect.die("CheckpointDiffQuery should not request the orchestration shell snapshot"), + getArchivedShellSnapshot: () => + Effect.die("CheckpointDiffQuery should not request archived shell snapshots"), + getSnapshotSequence: () => Effect.succeed({ snapshotSequence: 0 }), + getCounts: () => Effect.succeed({ projectCount: 0, threadCount: 0 }), + getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()), + getProjectShellById: () => Effect.succeed(Option.none()), + getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()), + getThreadCheckpointContext: () => Effect.succeed(Option.some(threadCheckpointContext)), + getFullThreadDiffContext: () => Effect.die("unused"), + getThreadShellById: () => Effect.succeed(Option.none()), + getThreadDetailById: () => Effect.succeed(Option.none()), + }), + ), + ); + + yield* Effect.gen(function* () { + const query = yield* CheckpointDiffQuery.CheckpointDiffQuery; + return yield* query.getTurnDiff({ + threadId, + fromTurnCount: 0, + toTurnCount: 1, + }); + }).pipe(Effect.provide(layer)); + + expect(diffCheckpointsCalls).toEqual([{ ignoreWhitespace: true }]); + }), + ); + + it.effect("does not preflight checkpoint refs before diffing", () => + Effect.gen(function* () { + const projectId = ProjectId.make("project-no-preflight"); + const threadId = ThreadId.make("thread-no-preflight"); + const toCheckpointRef = checkpointRefForThreadTurn(threadId, 1); + let hasCheckpointRefCallCount = 0; + + const threadCheckpointContext = makeThreadCheckpointContext({ + projectId, + threadId, + workspaceRoot: "/tmp/workspace", + worktreePath: null, + checkpointTurnCount: 1, + checkpointRef: toCheckpointRef, + }); + + const checkpointStore: CheckpointStore.CheckpointStore["Service"] = { + isGitRepository: () => Effect.succeed(true), + captureCheckpoint: () => Effect.void, + hasCheckpointRef: () => + Effect.sync(() => { + hasCheckpointRefCallCount += 1; + return true; + }), + restoreCheckpoint: () => Effect.succeed(true), + diffCheckpoints: () => Effect.succeed("diff patch"), + deleteCheckpointRefs: () => Effect.void, + }; + + const layer = CheckpointDiffQuery.layer.pipe( + Layer.provideMerge(Layer.succeed(CheckpointStore.CheckpointStore, checkpointStore)), + Layer.provideMerge( + Layer.succeed(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { + getCommandReadModel: () => + Effect.die("CheckpointDiffQuery should not request the command read model"), + getSnapshot: () => + Effect.die("CheckpointDiffQuery should not request the full orchestration snapshot"), + getShellSnapshot: () => + Effect.die("CheckpointDiffQuery should not request the orchestration shell snapshot"), + getArchivedShellSnapshot: () => + Effect.die("CheckpointDiffQuery should not request archived shell snapshots"), + getSnapshotSequence: () => Effect.succeed({ snapshotSequence: 0 }), + getCounts: () => Effect.succeed({ projectCount: 0, threadCount: 0 }), + getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()), + getProjectShellById: () => Effect.succeed(Option.none()), + getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()), + getThreadCheckpointContext: () => Effect.succeed(Option.some(threadCheckpointContext)), + getFullThreadDiffContext: () => Effect.die("unused"), + getThreadShellById: () => Effect.succeed(Option.none()), + getThreadDetailById: () => Effect.succeed(Option.none()), + }), + ), + ); + + yield* Effect.gen(function* () { + const query = yield* CheckpointDiffQuery.CheckpointDiffQuery; + return yield* query.getTurnDiff({ + threadId, + fromTurnCount: 0, + toTurnCount: 1, + ignoreWhitespace: true, + }); + }).pipe(Effect.provide(layer)); + + expect(hasCheckpointRefCallCount).toBe(0); + }), + ); + + it.effect("fails when the thread is missing from the snapshot", () => + Effect.gen(function* () { + const threadId = ThreadId.make("thread-missing"); + + const checkpointStore: CheckpointStore.CheckpointStore["Service"] = { + isGitRepository: () => Effect.succeed(true), + captureCheckpoint: () => Effect.void, + hasCheckpointRef: () => Effect.succeed(true), + restoreCheckpoint: () => Effect.succeed(true), + diffCheckpoints: () => Effect.succeed(""), + deleteCheckpointRefs: () => Effect.void, + }; + + const layer = CheckpointDiffQuery.layer.pipe( + Layer.provideMerge(Layer.succeed(CheckpointStore.CheckpointStore, checkpointStore)), + Layer.provideMerge( + Layer.succeed(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { + getCommandReadModel: () => + Effect.die("CheckpointDiffQuery should not request the command read model"), + getSnapshot: () => + Effect.die("CheckpointDiffQuery should not request the full orchestration snapshot"), + getShellSnapshot: () => + Effect.die("CheckpointDiffQuery should not request the orchestration shell snapshot"), + getArchivedShellSnapshot: () => + Effect.die("CheckpointDiffQuery should not request archived shell snapshots"), + getSnapshotSequence: () => Effect.succeed({ snapshotSequence: 0 }), + getCounts: () => Effect.succeed({ projectCount: 0, threadCount: 0 }), + getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()), + getProjectShellById: () => Effect.succeed(Option.none()), + getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()), + getThreadCheckpointContext: () => Effect.succeed(Option.none()), + getFullThreadDiffContext: () => Effect.succeed(Option.none()), + getThreadShellById: () => Effect.succeed(Option.none()), + getThreadDetailById: () => Effect.succeed(Option.none()), + }), + ), + ); + + const error = yield* Effect.gen(function* () { + const query = yield* CheckpointDiffQuery.CheckpointDiffQuery; + return yield* query.getTurnDiff({ + threadId, + fromTurnCount: 0, + toTurnCount: 1, + }); + }).pipe(Effect.provide(layer), Effect.flip); + + expect(error.message).toContain("Thread 'thread-missing' not found."); + }), + ); +}); diff --git a/apps/server/src/checkpointing/CheckpointDiffQuery.ts b/apps/server/src/checkpointing/CheckpointDiffQuery.ts new file mode 100644 index 0000000000000000000000000000000000000000..d42c58dfff3cf9a111afdd4907f52d81d4aba226 --- /dev/null +++ b/apps/server/src/checkpointing/CheckpointDiffQuery.ts @@ -0,0 +1,280 @@ +/** + * CheckpointDiffQuery - Query interface for computed checkpoint diffs. + * + * Provides read-only diff operations across checkpoint snapshots used by + * orchestration APIs. + * + * @module CheckpointDiffQuery + */ +import { + type CheckpointRef, + OrchestrationGetTurnDiffResult, + type OrchestrationGetFullThreadDiffInput, + type OrchestrationGetFullThreadDiffResult, + type OrchestrationGetTurnDiffInput, + type OrchestrationGetTurnDiffResult as OrchestrationGetTurnDiffResultType, + type ThreadId, +} from "@t3tools/contracts"; +import * as Context from "effect/Context"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; +import * as Schema from "effect/Schema"; + +import * as ProjectionSnapshotQuery from "../orchestration/Services/ProjectionSnapshotQuery.ts"; +import { CheckpointInvariantError, CheckpointUnavailableError } from "./Errors.ts"; +import type { CheckpointServiceError } from "./Errors.ts"; +import { checkpointRefForThreadTurn } from "./Utils.ts"; +import * as CheckpointStore from "./CheckpointStore.ts"; + +/** Service tag for checkpoint diff queries. */ +export class CheckpointDiffQuery extends Context.Service< + CheckpointDiffQuery, + { + /** + * Read the patch diff for a single turn checkpoint transition. + * + * Verifies checkpoint availability in both projection state and filesystem. + */ + readonly getTurnDiff: ( + input: OrchestrationGetTurnDiffInput, + ) => Effect.Effect; + + /** + * Read the full patch diff across a thread range of checkpoints. + * + * Uses turn-diff semantics with `fromTurnCount = 0`. + */ + readonly getFullThreadDiff: ( + input: OrchestrationGetFullThreadDiffInput, + ) => Effect.Effect; + } +>()("t3/checkpointing/CheckpointDiffQuery") {} + +const isTurnDiffResult = Schema.is(OrchestrationGetTurnDiffResult); + +function buildTurnDiffResult( + input: { + readonly threadId: ThreadId; + readonly fromTurnCount: number; + readonly toTurnCount: number; + }, + diff: string, +): OrchestrationGetTurnDiffResultType { + return { + threadId: input.threadId, + fromTurnCount: input.fromTurnCount, + toTurnCount: input.toTurnCount, + diff, + }; +} + +export const make = Effect.gen(function* () { + const projectionSnapshotQuery = yield* ProjectionSnapshotQuery.ProjectionSnapshotQuery; + const checkpointStore = yield* CheckpointStore.CheckpointStore; + + const getTurnDiff: CheckpointDiffQuery["Service"]["getTurnDiff"] = Effect.fn("getTurnDiff")( + function* (input) { + const operation = "CheckpointDiffQuery.getTurnDiff"; + const ignoreWhitespace = input.ignoreWhitespace ?? true; + yield* Effect.annotateCurrentSpan({ + "checkpoint.thread_id": input.threadId, + "checkpoint.from_turn_count": input.fromTurnCount, + "checkpoint.to_turn_count": input.toTurnCount, + "checkpoint.ignore_whitespace": ignoreWhitespace, + }); + + if (input.fromTurnCount === input.toTurnCount) { + const emptyDiff: OrchestrationGetTurnDiffResultType = { + threadId: input.threadId, + fromTurnCount: input.fromTurnCount, + toTurnCount: input.toTurnCount, + diff: "", + }; + if (!isTurnDiffResult(emptyDiff)) { + return yield* new CheckpointInvariantError({ + operation, + detail: "Computed turn diff result does not satisfy contract schema.", + }); + } + return emptyDiff; + } + + const threadContext = yield* projectionSnapshotQuery + .getThreadCheckpointContext(input.threadId) + .pipe(Effect.withSpan("checkpoint.turnDiff.lookupContext")); + if (Option.isNone(threadContext)) { + return yield* new CheckpointInvariantError({ + operation, + detail: `Thread '${input.threadId}' not found.`, + }); + } + + const maxTurnCount = threadContext.value.checkpoints.reduce( + (max, checkpoint) => Math.max(max, checkpoint.checkpointTurnCount), + 0, + ); + if (input.toTurnCount > maxTurnCount) { + return yield* new CheckpointUnavailableError({ + threadId: input.threadId, + turnCount: input.toTurnCount, + detail: `Turn diff range exceeds current turn count: requested ${input.toTurnCount}, current ${maxTurnCount}.`, + }); + } + + const workspaceCwd = threadContext.value.worktreePath ?? threadContext.value.workspaceRoot; + if (!workspaceCwd) { + return yield* new CheckpointInvariantError({ + operation, + detail: `Workspace path missing for thread '${input.threadId}' when computing turn diff.`, + }); + } + + const fromCheckpointRef = + input.fromTurnCount === 0 + ? checkpointRefForThreadTurn(input.threadId, 0) + : threadContext.value.checkpoints.find( + (checkpoint) => checkpoint.checkpointTurnCount === input.fromTurnCount, + )?.checkpointRef; + if (!fromCheckpointRef) { + return yield* new CheckpointUnavailableError({ + threadId: input.threadId, + turnCount: input.fromTurnCount, + detail: `Checkpoint ref is unavailable for turn ${input.fromTurnCount}.`, + }); + } + + const toCheckpointRef = threadContext.value.checkpoints.find( + (checkpoint) => checkpoint.checkpointTurnCount === input.toTurnCount, + )?.checkpointRef; + if (!toCheckpointRef) { + return yield* new CheckpointUnavailableError({ + threadId: input.threadId, + turnCount: input.toTurnCount, + detail: `Checkpoint ref is unavailable for turn ${input.toTurnCount}.`, + }); + } + + const diff = yield* checkpointStore + .diffCheckpoints({ + cwd: workspaceCwd, + fromCheckpointRef, + toCheckpointRef, + fallbackFromToHead: false, + ignoreWhitespace, + }) + .pipe(Effect.withSpan("checkpoint.turnDiff.diffCheckpoints")); + + const turnDiff = buildTurnDiffResult(input, diff); + if (!isTurnDiffResult(turnDiff)) { + return yield* new CheckpointInvariantError({ + operation, + detail: "Computed turn diff result does not satisfy contract schema.", + }); + } + + return turnDiff; + }, + ); + + const getFullThreadDiff: CheckpointDiffQuery["Service"]["getFullThreadDiff"] = Effect.fn( + "CheckpointDiffQuery.getFullThreadDiff", + )(function* (input) { + const operation = "CheckpointDiffQuery.getFullThreadDiff"; + const ignoreWhitespace = input.ignoreWhitespace ?? true; + yield* Effect.annotateCurrentSpan({ + "checkpoint.thread_id": input.threadId, + "checkpoint.from_turn_count": 0, + "checkpoint.to_turn_count": input.toTurnCount, + "checkpoint.ignore_whitespace": ignoreWhitespace, + "checkpoint.diff_kind": "full-thread", + }); + + if (input.toTurnCount === 0) { + const emptyDiff = buildTurnDiffResult( + { + threadId: input.threadId, + fromTurnCount: 0, + toTurnCount: 0, + }, + "", + ); + if (!isTurnDiffResult(emptyDiff)) { + return yield* new CheckpointInvariantError({ + operation, + detail: "Computed full thread diff result does not satisfy contract schema.", + }); + } + return emptyDiff satisfies OrchestrationGetFullThreadDiffResult; + } + + const threadContext = yield* projectionSnapshotQuery + .getFullThreadDiffContext(input.threadId, input.toTurnCount) + .pipe(Effect.withSpan("checkpoint.fullThread.lookupContext")); + + if (Option.isNone(threadContext)) { + return yield* new CheckpointInvariantError({ + operation, + detail: `Thread '${input.threadId}' not found.`, + }); + } + + if (input.toTurnCount > threadContext.value.latestCheckpointTurnCount) { + return yield* new CheckpointUnavailableError({ + threadId: input.threadId, + turnCount: input.toTurnCount, + detail: `Turn diff range exceeds current turn count: requested ${input.toTurnCount}, current ${threadContext.value.latestCheckpointTurnCount}.`, + }); + } + + const workspaceCwd = threadContext.value.worktreePath ?? threadContext.value.workspaceRoot; + if (!workspaceCwd) { + return yield* new CheckpointInvariantError({ + operation, + detail: `Workspace path missing for thread '${input.threadId}' when computing full thread diff.`, + }); + } + + if (!threadContext.value.toCheckpointRef) { + return yield* new CheckpointUnavailableError({ + threadId: input.threadId, + turnCount: input.toTurnCount, + detail: `Checkpoint ref is unavailable for turn ${input.toTurnCount}.`, + }); + } + + const diff = yield* checkpointStore + .diffCheckpoints({ + cwd: workspaceCwd, + fromCheckpointRef: checkpointRefForThreadTurn(input.threadId, 0), + toCheckpointRef: threadContext.value.toCheckpointRef as CheckpointRef, + fallbackFromToHead: false, + ignoreWhitespace, + }) + .pipe(Effect.withSpan("checkpoint.fullThread.diffCheckpoints")); + + const turnDiff = buildTurnDiffResult( + { + threadId: input.threadId, + fromTurnCount: 0, + toTurnCount: input.toTurnCount, + }, + diff, + ); + if (!isTurnDiffResult(turnDiff)) { + return yield* new CheckpointInvariantError({ + operation, + detail: "Computed full thread diff result does not satisfy contract schema.", + }); + } + + return turnDiff satisfies OrchestrationGetFullThreadDiffResult; + }); + + return CheckpointDiffQuery.of({ + getTurnDiff, + getFullThreadDiff, + }); +}); + +export const layer = Layer.effect(CheckpointDiffQuery, make); diff --git a/apps/server/src/checkpointing/CheckpointStore.test.ts b/apps/server/src/checkpointing/CheckpointStore.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..d796bdfc4c1157b230a58fe154bdbb6fbf706c64 --- /dev/null +++ b/apps/server/src/checkpointing/CheckpointStore.test.ts @@ -0,0 +1,204 @@ +// @effect-diagnostics nodeBuiltinImport:off +import path from "node:path"; + +import * as NodeServices from "@effect/platform-node/NodeServices"; +import { it } from "@effect/vitest"; +import { ThreadId, type VcsError } from "@t3tools/contracts"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as PlatformError from "effect/PlatformError"; +import * as Scope from "effect/Scope"; +import { describe, expect } from "vite-plus/test"; + +import { checkpointRefForThreadTurn } from "./Utils.ts"; +import * as CheckpointStore from "./CheckpointStore.ts"; +import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts"; +import * as VcsProcess from "../vcs/VcsProcess.ts"; +import * as ServerConfig from "../config.ts"; + +const ServerConfigLayer = ServerConfig.ServerConfig.layerTest(process.cwd(), { + prefix: "t3-checkpoint-store-test-", +}); +const VcsProcessTestLayer = VcsProcess.layer.pipe(Layer.provide(NodeServices.layer)); +const VcsDriverTestLayer = VcsDriverRegistry.layer.pipe(Layer.provide(VcsProcessTestLayer)); +const CheckpointStoreTestLayer = CheckpointStore.layer.pipe( + Layer.provideMerge(VcsDriverTestLayer), + Layer.provideMerge(NodeServices.layer), +); +const TestLayer = CheckpointStoreTestLayer.pipe( + Layer.provideMerge(VcsProcessTestLayer), + Layer.provideMerge(VcsDriverTestLayer), + Layer.provideMerge(ServerConfigLayer), + Layer.provideMerge(NodeServices.layer), +); + +function makeTmpDir( + prefix = "checkpoint-store-test-", +): Effect.Effect { + return Effect.gen(function* () { + const fileSystem = yield* FileSystem.FileSystem; + return yield* fileSystem.makeTempDirectoryScoped({ prefix }); + }); +} + +function writeTextFile( + filePath: string, + contents: string, +): Effect.Effect { + return Effect.gen(function* () { + const fileSystem = yield* FileSystem.FileSystem; + yield* fileSystem.writeFileString(filePath, contents); + }); +} + +function git( + cwd: string, + args: ReadonlyArray, +): Effect.Effect { + return Effect.gen(function* () { + const process = yield* VcsProcess.VcsProcess; + const result = yield* process.run({ + operation: "CheckpointStore.test.git", + command: "git", + cwd, + args, + timeoutMs: 10_000, + }); + return result.stdout.trim(); + }); +} + +function initRepoWithCommit( + cwd: string, +): Effect.Effect< + void, + VcsError | PlatformError.PlatformError, + VcsProcess.VcsProcess | FileSystem.FileSystem +> { + return Effect.gen(function* () { + yield* git(cwd, ["init"]); + yield* git(cwd, ["config", "user.email", "test@test.com"]); + yield* git(cwd, ["config", "user.name", "Test"]); + yield* writeTextFile(path.join(cwd, "README.md"), "# test\n"); + yield* git(cwd, ["add", "."]); + yield* git(cwd, ["commit", "-m", "initial commit"]); + }); +} + +function buildLargeText(lineCount = 5_000): string { + return Array.from({ length: lineCount }, (_, index) => `line ${String(index).padStart(5, "0")}`) + .join("\n") + .concat("\n"); +} + +it.layer(TestLayer)("CheckpointStore.layer", (it) => { + describe("diffCheckpoints", () => { + it.effect("returns full oversized checkpoint diffs without truncation", () => + Effect.gen(function* () { + const tmp = yield* makeTmpDir(); + yield* initRepoWithCommit(tmp); + const checkpointStore = yield* CheckpointStore.CheckpointStore; + const threadId = ThreadId.make("thread-checkpoint-store"); + const fromCheckpointRef = checkpointRefForThreadTurn(threadId, 0); + const toCheckpointRef = checkpointRefForThreadTurn(threadId, 1); + + yield* checkpointStore.captureCheckpoint({ + cwd: tmp, + checkpointRef: fromCheckpointRef, + }); + yield* writeTextFile(path.join(tmp, "README.md"), buildLargeText()); + yield* checkpointStore.captureCheckpoint({ + cwd: tmp, + checkpointRef: toCheckpointRef, + }); + + const diff = yield* checkpointStore.diffCheckpoints({ + cwd: tmp, + fromCheckpointRef, + toCheckpointRef, + ignoreWhitespace: true, + }); + + expect(diff).toContain("diff --git"); + expect(diff).not.toContain("[truncated]"); + expect(diff).toContain("+line 04999"); + }), + ); + + it.effect("can hide indentation churn when changes wrap existing lines", () => + Effect.gen(function* () { + const tmp = yield* makeTmpDir(); + yield* initRepoWithCommit(tmp); + const checkpointStore = yield* CheckpointStore.CheckpointStore; + const threadId = ThreadId.make("thread-checkpoint-store-whitespace"); + const fromCheckpointRef = checkpointRefForThreadTurn(threadId, 0); + const toCheckpointRef = checkpointRefForThreadTurn(threadId, 1); + + const componentPath = path.join(tmp, "Component.tsx"); + yield* writeTextFile( + componentPath, + [ + "export function View() {", + " return (", + "
", + "

Title

", + "

Body

", + "
", + " );", + "}", + "", + ].join("\n"), + ); + yield* checkpointStore.captureCheckpoint({ + cwd: tmp, + checkpointRef: fromCheckpointRef, + }); + yield* writeTextFile( + componentPath, + [ + "export function View() {", + " return (", + "
", + " {isReady ? (", + "
", + "

Title

", + "

Body

", + "
", + " ) : null}", + "
", + " );", + "}", + "", + ].join("\n"), + ); + yield* checkpointStore.captureCheckpoint({ + cwd: tmp, + checkpointRef: toCheckpointRef, + }); + + const normalDiff = yield* checkpointStore.diffCheckpoints({ + cwd: tmp, + fromCheckpointRef, + toCheckpointRef, + ignoreWhitespace: false, + }); + const whitespaceIgnoredDiff = yield* checkpointStore.diffCheckpoints({ + cwd: tmp, + fromCheckpointRef, + toCheckpointRef, + ignoreWhitespace: true, + }); + + expect(normalDiff).toContain("diff --git"); + expect(normalDiff).toContain("-

Title

"); + expect(normalDiff).toContain("+

Title

"); + expect(whitespaceIgnoredDiff).toContain("diff --git"); + expect(whitespaceIgnoredDiff).toContain("+ {isReady ? ("); + expect(whitespaceIgnoredDiff).toContain("+
"); + expect(whitespaceIgnoredDiff).not.toContain("-

Title

"); + expect(whitespaceIgnoredDiff).not.toContain("+

Title

"); + }), + ); + }); +}); diff --git a/apps/server/src/checkpointing/CheckpointStore.ts b/apps/server/src/checkpointing/CheckpointStore.ts new file mode 100644 index 0000000000000000000000000000000000000000..ed47d5f117f583368f1b4bf058f22d999d89a553 --- /dev/null +++ b/apps/server/src/checkpointing/CheckpointStore.ts @@ -0,0 +1,171 @@ +/** + * CheckpointStore - Repository interface for filesystem-backed workspace checkpoints. + * + * Owns hidden Git-ref checkpoint capture/restore and diff computation for a + * workspace thread timeline. It does not store user-facing checkpoint metadata + * and does not coordinate provider conversation rollback. + * + * The live adapter resolves the active VCS driver once per checkpoint operation + * and delegates to the driver's optional checkpoint capability. + * + * Uses Effect `Context.Service` for dependency injection and exposes typed + * domain errors for checkpoint storage operations. + * + * @module CheckpointStore + */ +import { VcsUnsupportedOperationError, type CheckpointRef } from "@t3tools/contracts"; +import * as Context from "effect/Context"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; + +import type { CheckpointStoreError } from "./Errors.ts"; +import type { VcsCheckpointOps } from "../vcs/VcsDriver.ts"; +import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts"; + +export interface CaptureCheckpointInput { + readonly cwd: string; + readonly checkpointRef: CheckpointRef; +} + +export interface RestoreCheckpointInput { + readonly cwd: string; + readonly checkpointRef: CheckpointRef; + readonly fallbackToHead?: boolean; +} + +export interface DiffCheckpointsInput { + readonly cwd: string; + readonly fromCheckpointRef: CheckpointRef; + readonly toCheckpointRef: CheckpointRef; + readonly fallbackFromToHead?: boolean; + readonly ignoreWhitespace: boolean; +} + +export interface DeleteCheckpointRefsInput { + readonly cwd: string; + readonly checkpointRefs: ReadonlyArray; +} + +/** Service tag for checkpoint persistence and restore operations. */ +export class CheckpointStore extends Context.Service< + CheckpointStore, + { + /** Check whether cwd is inside a Git worktree. */ + readonly isGitRepository: (cwd: string) => Effect.Effect; + + /** + * Capture a checkpoint commit and store it at the provided checkpoint ref. + * + * Uses an isolated temporary Git index and writes a hidden ref. + */ + readonly captureCheckpoint: ( + input: CaptureCheckpointInput, + ) => Effect.Effect; + + /** Check whether a checkpoint ref exists. */ + readonly hasCheckpointRef: ( + input: Omit, + ) => Effect.Effect; + + /** + * Restore workspace and staging state to a checkpoint. + * + * Optionally falls back to current `HEAD` when the checkpoint ref is missing. + */ + readonly restoreCheckpoint: ( + input: RestoreCheckpointInput, + ) => Effect.Effect; + + /** + * Compute a patch diff between two checkpoint refs. + * + * Can optionally treat a missing "from" ref as `HEAD`. + */ + readonly diffCheckpoints: ( + input: DiffCheckpointsInput, + ) => Effect.Effect; + + /** + * Delete the provided checkpoint refs. + * + * Best-effort delete: missing refs are tolerated. + */ + readonly deleteCheckpointRefs: ( + input: DeleteCheckpointRefsInput, + ) => Effect.Effect; + } +>()("t3/checkpointing/CheckpointStore") {} + +export const make = Effect.gen(function* () { + const vcsRegistry = yield* VcsDriverRegistry.VcsDriverRegistry; + + const resolveCheckpoints = Effect.fn("CheckpointStore.resolveCheckpoints")(function* ( + operation: string, + cwd: string, + ) { + const handle = yield* vcsRegistry.resolve({ cwd }); + if (!handle.driver.checkpoints) { + return yield* new VcsUnsupportedOperationError({ + operation, + kind: handle.kind, + detail: `${handle.kind} driver does not implement checkpoint operations.`, + }); + } + return handle.driver.checkpoints satisfies VcsCheckpointOps; + }); + + const isGitRepository: CheckpointStore["Service"]["isGitRepository"] = (cwd) => + vcsRegistry.resolve({ cwd, requestedKind: "git" }).pipe( + Effect.map(() => true), + Effect.orElseSucceed(() => false), + ); + + const captureCheckpoint: CheckpointStore["Service"]["captureCheckpoint"] = Effect.fn( + "captureCheckpoint", + )(function* (input) { + const checkpoints = yield* resolveCheckpoints("CheckpointStore.captureCheckpoint", input.cwd); + return yield* checkpoints.captureCheckpoint(input); + }); + + const hasCheckpointRef: CheckpointStore["Service"]["hasCheckpointRef"] = Effect.fn( + "hasCheckpointRef", + )(function* (input) { + const checkpoints = yield* resolveCheckpoints("CheckpointStore.hasCheckpointRef", input.cwd); + return yield* checkpoints.hasCheckpointRef(input); + }); + + const restoreCheckpoint: CheckpointStore["Service"]["restoreCheckpoint"] = Effect.fn( + "restoreCheckpoint", + )(function* (input) { + const checkpoints = yield* resolveCheckpoints("CheckpointStore.restoreCheckpoint", input.cwd); + return yield* checkpoints.restoreCheckpoint(input); + }); + + const diffCheckpoints: CheckpointStore["Service"]["diffCheckpoints"] = Effect.fn( + "diffCheckpoints", + )(function* (input) { + const checkpoints = yield* resolveCheckpoints("CheckpointStore.diffCheckpoints", input.cwd); + return yield* checkpoints.diffCheckpoints(input); + }); + + const deleteCheckpointRefs: CheckpointStore["Service"]["deleteCheckpointRefs"] = Effect.fn( + "deleteCheckpointRefs", + )(function* (input) { + const checkpoints = yield* resolveCheckpoints( + "CheckpointStore.deleteCheckpointRefs", + input.cwd, + ); + return yield* checkpoints.deleteCheckpointRefs(input); + }); + + return CheckpointStore.of({ + isGitRepository, + captureCheckpoint, + hasCheckpointRef, + restoreCheckpoint, + diffCheckpoints, + deleteCheckpointRefs, + }); +}); + +export const layer = Layer.effect(CheckpointStore, make); diff --git a/apps/server/src/checkpointing/Layers/CheckpointDiffQuery.test.ts b/apps/server/src/checkpointing/Layers/CheckpointDiffQuery.test.ts deleted file mode 100644 index 9f31532855a9af7f282397def6650a0fcd7513f3..0000000000000000000000000000000000000000 --- a/apps/server/src/checkpointing/Layers/CheckpointDiffQuery.test.ts +++ /dev/null @@ -1,421 +0,0 @@ -import { CheckpointRef, ProjectId, ThreadId, TurnId } from "@t3tools/contracts"; -import * as Effect from "effect/Effect"; -import * as Layer from "effect/Layer"; -import * as Option from "effect/Option"; -import { describe, expect, it } from "vite-plus/test"; - -import { - ProjectionSnapshotQuery, - type ProjectionThreadCheckpointContext, -} from "../../orchestration/Services/ProjectionSnapshotQuery.ts"; -import { checkpointRefForThreadTurn } from "../Utils.ts"; -import { CheckpointDiffQueryLive } from "./CheckpointDiffQuery.ts"; -import { CheckpointStore, type CheckpointStoreShape } from "../Services/CheckpointStore.ts"; -import { CheckpointDiffQuery } from "../Services/CheckpointDiffQuery.ts"; - -function makeThreadCheckpointContext(input: { - readonly projectId: ProjectId; - readonly threadId: ThreadId; - readonly workspaceRoot: string; - readonly worktreePath: string | null; - readonly checkpointTurnCount: number; - readonly checkpointRef: CheckpointRef; -}): ProjectionThreadCheckpointContext { - return { - threadId: input.threadId, - projectId: input.projectId, - workspaceRoot: input.workspaceRoot, - worktreePath: input.worktreePath, - checkpoints: [ - { - turnId: TurnId.make("turn-1"), - checkpointTurnCount: input.checkpointTurnCount, - checkpointRef: input.checkpointRef, - status: "ready", - files: [], - assistantMessageId: null, - completedAt: "2026-01-01T00:00:00.000Z", - }, - ], - }; -} - -describe("CheckpointDiffQueryLive", () => { - it("uses the narrow full-thread context lookup for all-turns diffs", async () => { - const projectId = ProjectId.make("project-full-thread"); - const threadId = ThreadId.make("thread-full-thread"); - const toCheckpointRef = checkpointRefForThreadTurn(threadId, 4); - let getThreadCheckpointContextCalls = 0; - let getFullThreadDiffContextCalls = 0; - const diffCheckpointsCalls: Array<{ - readonly fromCheckpointRef: CheckpointRef; - readonly toCheckpointRef: CheckpointRef; - readonly cwd: string; - readonly ignoreWhitespace: boolean; - }> = []; - - const checkpointStore: CheckpointStoreShape = { - isGitRepository: () => Effect.succeed(true), - captureCheckpoint: () => Effect.void, - hasCheckpointRef: () => Effect.succeed(true), - restoreCheckpoint: () => Effect.succeed(true), - diffCheckpoints: ({ fromCheckpointRef, toCheckpointRef, cwd, ignoreWhitespace }) => - Effect.sync(() => { - diffCheckpointsCalls.push({ - fromCheckpointRef, - toCheckpointRef, - cwd, - ignoreWhitespace, - }); - return "full thread diff patch"; - }), - deleteCheckpointRefs: () => Effect.void, - }; - - const layer = CheckpointDiffQueryLive.pipe( - Layer.provideMerge(Layer.succeed(CheckpointStore, checkpointStore)), - Layer.provideMerge( - Layer.succeed(ProjectionSnapshotQuery, { - getCommandReadModel: () => - Effect.die("CheckpointDiffQuery should not request the command read model"), - getSnapshot: () => - Effect.die("CheckpointDiffQuery should not request the full orchestration snapshot"), - getShellSnapshot: () => - Effect.die("CheckpointDiffQuery should not request the orchestration shell snapshot"), - getArchivedShellSnapshot: () => - Effect.die("CheckpointDiffQuery should not request archived shell snapshots"), - getSnapshotSequence: () => Effect.succeed({ snapshotSequence: 0 }), - getCounts: () => Effect.succeed({ projectCount: 0, threadCount: 0 }), - getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()), - getProjectShellById: () => Effect.succeed(Option.none()), - getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()), - getThreadCheckpointContext: () => - Effect.sync(() => { - getThreadCheckpointContextCalls += 1; - return Option.none(); - }), - getFullThreadDiffContext: () => - Effect.sync(() => { - getFullThreadDiffContextCalls += 1; - return Option.some({ - threadId, - projectId, - workspaceRoot: "/tmp/workspace", - worktreePath: "/tmp/worktree", - latestCheckpointTurnCount: 4, - toCheckpointRef, - }); - }), - getThreadShellById: () => Effect.succeed(Option.none()), - getThreadDetailById: () => Effect.succeed(Option.none()), - }), - ), - ); - - const result = await Effect.runPromise( - Effect.gen(function* () { - const query = yield* CheckpointDiffQuery; - return yield* query.getFullThreadDiff({ - threadId, - toTurnCount: 4, - ignoreWhitespace: true, - }); - }).pipe(Effect.provide(layer)), - ); - - expect(getThreadCheckpointContextCalls).toBe(0); - expect(getFullThreadDiffContextCalls).toBe(1); - expect(diffCheckpointsCalls).toEqual([ - { - cwd: "/tmp/worktree", - fromCheckpointRef: checkpointRefForThreadTurn(threadId, 0), - toCheckpointRef, - ignoreWhitespace: true, - }, - ]); - expect(result).toEqual({ - threadId, - fromTurnCount: 0, - toTurnCount: 4, - diff: "full thread diff patch", - }); - }); - - it("computes diffs using canonical turn-0 checkpoint refs", async () => { - const projectId = ProjectId.make("project-1"); - const threadId = ThreadId.make("thread-1"); - const toCheckpointRef = checkpointRefForThreadTurn(threadId, 1); - const diffCheckpointsCalls: Array<{ - readonly fromCheckpointRef: CheckpointRef; - readonly toCheckpointRef: CheckpointRef; - readonly cwd: string; - readonly ignoreWhitespace: boolean; - }> = []; - - const threadCheckpointContext = makeThreadCheckpointContext({ - projectId, - threadId, - workspaceRoot: "/tmp/workspace", - worktreePath: null, - checkpointTurnCount: 1, - checkpointRef: toCheckpointRef, - }); - - const checkpointStore: CheckpointStoreShape = { - isGitRepository: () => Effect.succeed(true), - captureCheckpoint: () => Effect.void, - hasCheckpointRef: () => Effect.succeed(true), - restoreCheckpoint: () => Effect.succeed(true), - diffCheckpoints: ({ fromCheckpointRef, toCheckpointRef, cwd, ignoreWhitespace }) => - Effect.sync(() => { - diffCheckpointsCalls.push({ - fromCheckpointRef, - toCheckpointRef, - cwd, - ignoreWhitespace, - }); - return "diff patch"; - }), - deleteCheckpointRefs: () => Effect.void, - }; - - const layer = CheckpointDiffQueryLive.pipe( - Layer.provideMerge(Layer.succeed(CheckpointStore, checkpointStore)), - Layer.provideMerge( - Layer.succeed(ProjectionSnapshotQuery, { - getCommandReadModel: () => - Effect.die("CheckpointDiffQuery should not request the command read model"), - getSnapshot: () => - Effect.die("CheckpointDiffQuery should not request the full orchestration snapshot"), - getShellSnapshot: () => - Effect.die("CheckpointDiffQuery should not request the orchestration shell snapshot"), - getArchivedShellSnapshot: () => - Effect.die("CheckpointDiffQuery should not request archived shell snapshots"), - getSnapshotSequence: () => Effect.succeed({ snapshotSequence: 0 }), - getCounts: () => Effect.succeed({ projectCount: 0, threadCount: 0 }), - getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()), - getProjectShellById: () => Effect.succeed(Option.none()), - getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()), - getThreadCheckpointContext: () => Effect.succeed(Option.some(threadCheckpointContext)), - getFullThreadDiffContext: () => Effect.die("unused"), - getThreadShellById: () => Effect.succeed(Option.none()), - getThreadDetailById: () => Effect.succeed(Option.none()), - }), - ), - ); - - const result = await Effect.runPromise( - Effect.gen(function* () { - const query = yield* CheckpointDiffQuery; - return yield* query.getTurnDiff({ - threadId, - fromTurnCount: 0, - toTurnCount: 1, - ignoreWhitespace: true, - }); - }).pipe(Effect.provide(layer)), - ); - - const expectedFromRef = checkpointRefForThreadTurn(threadId, 0); - expect(diffCheckpointsCalls).toEqual([ - { - cwd: "/tmp/workspace", - fromCheckpointRef: expectedFromRef, - toCheckpointRef, - ignoreWhitespace: true, - }, - ]); - expect(result).toEqual({ - threadId, - fromTurnCount: 0, - toTurnCount: 1, - diff: "diff patch", - }); - }); - - it("defaults to hide whitespace changes", async () => { - const projectId = ProjectId.make("project-default-whitespace"); - const threadId = ThreadId.make("thread-default-whitespace"); - const toCheckpointRef = checkpointRefForThreadTurn(threadId, 1); - const diffCheckpointsCalls: Array<{ readonly ignoreWhitespace: boolean }> = []; - - const threadCheckpointContext = makeThreadCheckpointContext({ - projectId, - threadId, - workspaceRoot: "/tmp/workspace", - worktreePath: null, - checkpointTurnCount: 1, - checkpointRef: toCheckpointRef, - }); - - const checkpointStore: CheckpointStoreShape = { - isGitRepository: () => Effect.succeed(true), - captureCheckpoint: () => Effect.void, - hasCheckpointRef: () => Effect.succeed(true), - restoreCheckpoint: () => Effect.succeed(true), - diffCheckpoints: ({ ignoreWhitespace }) => - Effect.sync(() => { - diffCheckpointsCalls.push({ ignoreWhitespace }); - return "diff patch"; - }), - deleteCheckpointRefs: () => Effect.void, - }; - - const layer = CheckpointDiffQueryLive.pipe( - Layer.provideMerge(Layer.succeed(CheckpointStore, checkpointStore)), - Layer.provideMerge( - Layer.succeed(ProjectionSnapshotQuery, { - getCommandReadModel: () => - Effect.die("CheckpointDiffQuery should not request the command read model"), - getSnapshot: () => - Effect.die("CheckpointDiffQuery should not request the full orchestration snapshot"), - getShellSnapshot: () => - Effect.die("CheckpointDiffQuery should not request the orchestration shell snapshot"), - getArchivedShellSnapshot: () => - Effect.die("CheckpointDiffQuery should not request archived shell snapshots"), - getSnapshotSequence: () => Effect.succeed({ snapshotSequence: 0 }), - getCounts: () => Effect.succeed({ projectCount: 0, threadCount: 0 }), - getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()), - getProjectShellById: () => Effect.succeed(Option.none()), - getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()), - getThreadCheckpointContext: () => Effect.succeed(Option.some(threadCheckpointContext)), - getFullThreadDiffContext: () => Effect.die("unused"), - getThreadShellById: () => Effect.succeed(Option.none()), - getThreadDetailById: () => Effect.succeed(Option.none()), - }), - ), - ); - - await Effect.runPromise( - Effect.gen(function* () { - const query = yield* CheckpointDiffQuery; - return yield* query.getTurnDiff({ - threadId, - fromTurnCount: 0, - toTurnCount: 1, - }); - }).pipe(Effect.provide(layer)), - ); - - expect(diffCheckpointsCalls).toEqual([{ ignoreWhitespace: true }]); - }); - - it("does not preflight checkpoint refs before diffing", async () => { - const projectId = ProjectId.make("project-no-preflight"); - const threadId = ThreadId.make("thread-no-preflight"); - const toCheckpointRef = checkpointRefForThreadTurn(threadId, 1); - let hasCheckpointRefCallCount = 0; - - const threadCheckpointContext = makeThreadCheckpointContext({ - projectId, - threadId, - workspaceRoot: "/tmp/workspace", - worktreePath: null, - checkpointTurnCount: 1, - checkpointRef: toCheckpointRef, - }); - - const checkpointStore: CheckpointStoreShape = { - isGitRepository: () => Effect.succeed(true), - captureCheckpoint: () => Effect.void, - hasCheckpointRef: () => - Effect.sync(() => { - hasCheckpointRefCallCount += 1; - return true; - }), - restoreCheckpoint: () => Effect.succeed(true), - diffCheckpoints: () => Effect.succeed("diff patch"), - deleteCheckpointRefs: () => Effect.void, - }; - - const layer = CheckpointDiffQueryLive.pipe( - Layer.provideMerge(Layer.succeed(CheckpointStore, checkpointStore)), - Layer.provideMerge( - Layer.succeed(ProjectionSnapshotQuery, { - getCommandReadModel: () => - Effect.die("CheckpointDiffQuery should not request the command read model"), - getSnapshot: () => - Effect.die("CheckpointDiffQuery should not request the full orchestration snapshot"), - getShellSnapshot: () => - Effect.die("CheckpointDiffQuery should not request the orchestration shell snapshot"), - getArchivedShellSnapshot: () => - Effect.die("CheckpointDiffQuery should not request archived shell snapshots"), - getSnapshotSequence: () => Effect.succeed({ snapshotSequence: 0 }), - getCounts: () => Effect.succeed({ projectCount: 0, threadCount: 0 }), - getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()), - getProjectShellById: () => Effect.succeed(Option.none()), - getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()), - getThreadCheckpointContext: () => Effect.succeed(Option.some(threadCheckpointContext)), - getFullThreadDiffContext: () => Effect.die("unused"), - getThreadShellById: () => Effect.succeed(Option.none()), - getThreadDetailById: () => Effect.succeed(Option.none()), - }), - ), - ); - - await Effect.runPromise( - Effect.gen(function* () { - const query = yield* CheckpointDiffQuery; - return yield* query.getTurnDiff({ - threadId, - fromTurnCount: 0, - toTurnCount: 1, - ignoreWhitespace: true, - }); - }).pipe(Effect.provide(layer)), - ); - - expect(hasCheckpointRefCallCount).toBe(0); - }); - - it("fails when the thread is missing from the snapshot", async () => { - const threadId = ThreadId.make("thread-missing"); - - const checkpointStore: CheckpointStoreShape = { - isGitRepository: () => Effect.succeed(true), - captureCheckpoint: () => Effect.void, - hasCheckpointRef: () => Effect.succeed(true), - restoreCheckpoint: () => Effect.succeed(true), - diffCheckpoints: () => Effect.succeed(""), - deleteCheckpointRefs: () => Effect.void, - }; - - const layer = CheckpointDiffQueryLive.pipe( - Layer.provideMerge(Layer.succeed(CheckpointStore, checkpointStore)), - Layer.provideMerge( - Layer.succeed(ProjectionSnapshotQuery, { - getCommandReadModel: () => - Effect.die("CheckpointDiffQuery should not request the command read model"), - getSnapshot: () => - Effect.die("CheckpointDiffQuery should not request the full orchestration snapshot"), - getShellSnapshot: () => - Effect.die("CheckpointDiffQuery should not request the orchestration shell snapshot"), - getArchivedShellSnapshot: () => - Effect.die("CheckpointDiffQuery should not request archived shell snapshots"), - getSnapshotSequence: () => Effect.succeed({ snapshotSequence: 0 }), - getCounts: () => Effect.succeed({ projectCount: 0, threadCount: 0 }), - getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()), - getProjectShellById: () => Effect.succeed(Option.none()), - getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()), - getThreadCheckpointContext: () => Effect.succeed(Option.none()), - getFullThreadDiffContext: () => Effect.succeed(Option.none()), - getThreadShellById: () => Effect.succeed(Option.none()), - getThreadDetailById: () => Effect.succeed(Option.none()), - }), - ), - ); - - await expect( - Effect.runPromise( - Effect.gen(function* () { - const query = yield* CheckpointDiffQuery; - return yield* query.getTurnDiff({ - threadId, - fromTurnCount: 0, - toTurnCount: 1, - }); - }).pipe(Effect.provide(layer)), - ), - ).rejects.toThrow("Thread 'thread-missing' not found."); - }); -}); diff --git a/apps/server/src/checkpointing/Layers/CheckpointDiffQuery.ts b/apps/server/src/checkpointing/Layers/CheckpointDiffQuery.ts deleted file mode 100644 index b07c06ac9362046ad342ae0d9a4f00a58d1976a7..0000000000000000000000000000000000000000 --- a/apps/server/src/checkpointing/Layers/CheckpointDiffQuery.ts +++ /dev/null @@ -1,248 +0,0 @@ -import { - type CheckpointRef, - OrchestrationGetTurnDiffResult, - type ThreadId, - type OrchestrationGetFullThreadDiffResult, - type OrchestrationGetTurnDiffResult as OrchestrationGetTurnDiffResultType, -} from "@t3tools/contracts"; -import * as Effect from "effect/Effect"; -import * as Layer from "effect/Layer"; -import * as Option from "effect/Option"; -import * as Schema from "effect/Schema"; - -import { ProjectionSnapshotQuery } from "../../orchestration/Services/ProjectionSnapshotQuery.ts"; -import { CheckpointInvariantError, CheckpointUnavailableError } from "../Errors.ts"; -import { checkpointRefForThreadTurn } from "../Utils.ts"; -import { CheckpointStore } from "../Services/CheckpointStore.ts"; -import { - CheckpointDiffQuery, - type CheckpointDiffQueryShape, -} from "../Services/CheckpointDiffQuery.ts"; - -const isTurnDiffResult = Schema.is(OrchestrationGetTurnDiffResult); - -function buildTurnDiffResult( - input: { - readonly threadId: ThreadId; - readonly fromTurnCount: number; - readonly toTurnCount: number; - }, - diff: string, -): OrchestrationGetTurnDiffResultType { - return { - threadId: input.threadId, - fromTurnCount: input.fromTurnCount, - toTurnCount: input.toTurnCount, - diff, - }; -} - -const make = Effect.gen(function* () { - const projectionSnapshotQuery = yield* ProjectionSnapshotQuery; - const checkpointStore = yield* CheckpointStore; - - const getTurnDiff: CheckpointDiffQueryShape["getTurnDiff"] = Effect.fn("getTurnDiff")( - function* (input) { - const operation = "CheckpointDiffQuery.getTurnDiff"; - const ignoreWhitespace = input.ignoreWhitespace ?? true; - yield* Effect.annotateCurrentSpan({ - "checkpoint.thread_id": input.threadId, - "checkpoint.from_turn_count": input.fromTurnCount, - "checkpoint.to_turn_count": input.toTurnCount, - "checkpoint.ignore_whitespace": ignoreWhitespace, - }); - - if (input.fromTurnCount === input.toTurnCount) { - const emptyDiff: OrchestrationGetTurnDiffResultType = { - threadId: input.threadId, - fromTurnCount: input.fromTurnCount, - toTurnCount: input.toTurnCount, - diff: "", - }; - if (!isTurnDiffResult(emptyDiff)) { - return yield* new CheckpointInvariantError({ - operation, - detail: "Computed turn diff result does not satisfy contract schema.", - }); - } - return emptyDiff; - } - - const threadContext = yield* projectionSnapshotQuery - .getThreadCheckpointContext(input.threadId) - .pipe(Effect.withSpan("checkpoint.turnDiff.lookupContext")); - if (Option.isNone(threadContext)) { - return yield* new CheckpointInvariantError({ - operation, - detail: `Thread '${input.threadId}' not found.`, - }); - } - - const maxTurnCount = threadContext.value.checkpoints.reduce( - (max, checkpoint) => Math.max(max, checkpoint.checkpointTurnCount), - 0, - ); - if (input.toTurnCount > maxTurnCount) { - return yield* new CheckpointUnavailableError({ - threadId: input.threadId, - turnCount: input.toTurnCount, - detail: `Turn diff range exceeds current turn count: requested ${input.toTurnCount}, current ${maxTurnCount}.`, - }); - } - - const workspaceCwd = threadContext.value.worktreePath ?? threadContext.value.workspaceRoot; - if (!workspaceCwd) { - return yield* new CheckpointInvariantError({ - operation, - detail: `Workspace path missing for thread '${input.threadId}' when computing turn diff.`, - }); - } - - const fromCheckpointRef = - input.fromTurnCount === 0 - ? checkpointRefForThreadTurn(input.threadId, 0) - : threadContext.value.checkpoints.find( - (checkpoint) => checkpoint.checkpointTurnCount === input.fromTurnCount, - )?.checkpointRef; - if (!fromCheckpointRef) { - return yield* new CheckpointUnavailableError({ - threadId: input.threadId, - turnCount: input.fromTurnCount, - detail: `Checkpoint ref is unavailable for turn ${input.fromTurnCount}.`, - }); - } - - const toCheckpointRef = threadContext.value.checkpoints.find( - (checkpoint) => checkpoint.checkpointTurnCount === input.toTurnCount, - )?.checkpointRef; - if (!toCheckpointRef) { - return yield* new CheckpointUnavailableError({ - threadId: input.threadId, - turnCount: input.toTurnCount, - detail: `Checkpoint ref is unavailable for turn ${input.toTurnCount}.`, - }); - } - - const diff = yield* checkpointStore - .diffCheckpoints({ - cwd: workspaceCwd, - fromCheckpointRef, - toCheckpointRef, - fallbackFromToHead: false, - ignoreWhitespace, - }) - .pipe(Effect.withSpan("checkpoint.turnDiff.diffCheckpoints")); - - const turnDiff = buildTurnDiffResult(input, diff); - if (!isTurnDiffResult(turnDiff)) { - return yield* new CheckpointInvariantError({ - operation, - detail: "Computed turn diff result does not satisfy contract schema.", - }); - } - - return turnDiff; - }, - ); - - const getFullThreadDiff: CheckpointDiffQueryShape["getFullThreadDiff"] = Effect.fn( - "CheckpointDiffQuery.getFullThreadDiff", - )(function* (input) { - const operation = "CheckpointDiffQuery.getFullThreadDiff"; - const ignoreWhitespace = input.ignoreWhitespace ?? true; - yield* Effect.annotateCurrentSpan({ - "checkpoint.thread_id": input.threadId, - "checkpoint.from_turn_count": 0, - "checkpoint.to_turn_count": input.toTurnCount, - "checkpoint.ignore_whitespace": ignoreWhitespace, - "checkpoint.diff_kind": "full-thread", - }); - - if (input.toTurnCount === 0) { - const emptyDiff = buildTurnDiffResult( - { - threadId: input.threadId, - fromTurnCount: 0, - toTurnCount: 0, - }, - "", - ); - if (!isTurnDiffResult(emptyDiff)) { - return yield* new CheckpointInvariantError({ - operation, - detail: "Computed full thread diff result does not satisfy contract schema.", - }); - } - return emptyDiff satisfies OrchestrationGetFullThreadDiffResult; - } - - const threadContext = yield* projectionSnapshotQuery - .getFullThreadDiffContext(input.threadId, input.toTurnCount) - .pipe(Effect.withSpan("checkpoint.fullThread.lookupContext")); - - if (Option.isNone(threadContext)) { - return yield* new CheckpointInvariantError({ - operation, - detail: `Thread '${input.threadId}' not found.`, - }); - } - - if (input.toTurnCount > threadContext.value.latestCheckpointTurnCount) { - return yield* new CheckpointUnavailableError({ - threadId: input.threadId, - turnCount: input.toTurnCount, - detail: `Turn diff range exceeds current turn count: requested ${input.toTurnCount}, current ${threadContext.value.latestCheckpointTurnCount}.`, - }); - } - - const workspaceCwd = threadContext.value.worktreePath ?? threadContext.value.workspaceRoot; - if (!workspaceCwd) { - return yield* new CheckpointInvariantError({ - operation, - detail: `Workspace path missing for thread '${input.threadId}' when computing full thread diff.`, - }); - } - - if (!threadContext.value.toCheckpointRef) { - return yield* new CheckpointUnavailableError({ - threadId: input.threadId, - turnCount: input.toTurnCount, - detail: `Checkpoint ref is unavailable for turn ${input.toTurnCount}.`, - }); - } - - const diff = yield* checkpointStore - .diffCheckpoints({ - cwd: workspaceCwd, - fromCheckpointRef: checkpointRefForThreadTurn(input.threadId, 0), - toCheckpointRef: threadContext.value.toCheckpointRef as CheckpointRef, - fallbackFromToHead: false, - ignoreWhitespace, - }) - .pipe(Effect.withSpan("checkpoint.fullThread.diffCheckpoints")); - - const turnDiff = buildTurnDiffResult( - { - threadId: input.threadId, - fromTurnCount: 0, - toTurnCount: input.toTurnCount, - }, - diff, - ); - if (!isTurnDiffResult(turnDiff)) { - return yield* new CheckpointInvariantError({ - operation, - detail: "Computed full thread diff result does not satisfy contract schema.", - }); - } - - return turnDiff satisfies OrchestrationGetFullThreadDiffResult; - }); - - return { - getTurnDiff, - getFullThreadDiff, - } satisfies CheckpointDiffQueryShape; -}); - -export const CheckpointDiffQueryLive = Layer.effect(CheckpointDiffQuery, make); diff --git a/apps/server/src/checkpointing/Layers/CheckpointStore.test.ts b/apps/server/src/checkpointing/Layers/CheckpointStore.test.ts deleted file mode 100644 index 778956e5206b62b85c17fe128c0e9f2c73bca91d..0000000000000000000000000000000000000000 --- a/apps/server/src/checkpointing/Layers/CheckpointStore.test.ts +++ /dev/null @@ -1,206 +0,0 @@ -// @effect-diagnostics nodeBuiltinImport:off -import path from "node:path"; - -import * as NodeServices from "@effect/platform-node/NodeServices"; -import { it } from "@effect/vitest"; -import * as Effect from "effect/Effect"; -import * as FileSystem from "effect/FileSystem"; -import * as Layer from "effect/Layer"; -import * as PlatformError from "effect/PlatformError"; -import * as Scope from "effect/Scope"; -import { describe, expect } from "vite-plus/test"; - -import { checkpointRefForThreadTurn } from "../Utils.ts"; -import { CheckpointStoreLive } from "./CheckpointStore.ts"; -import { CheckpointStore } from "../Services/CheckpointStore.ts"; -import * as VcsDriverRegistry from "../../vcs/VcsDriverRegistry.ts"; -import * as VcsProcess from "../../vcs/VcsProcess.ts"; -import type { VcsError } from "@t3tools/contracts"; -import { ServerConfig } from "../../config.ts"; -import { ThreadId } from "@t3tools/contracts"; - -const ServerConfigLayer = ServerConfig.layerTest(process.cwd(), { - prefix: "t3-checkpoint-store-test-", -}); -const VcsProcessTestLayer = VcsProcess.layer.pipe(Layer.provide(NodeServices.layer)); -const VcsDriverTestLayer = VcsDriverRegistry.layer.pipe(Layer.provide(VcsProcessTestLayer)); -const CheckpointStoreTestLayer = CheckpointStoreLive.pipe( - Layer.provideMerge(VcsDriverTestLayer), - Layer.provideMerge(NodeServices.layer), -); -const TestLayer = CheckpointStoreTestLayer.pipe( - Layer.provideMerge(VcsProcessTestLayer), - Layer.provideMerge(VcsDriverTestLayer), - Layer.provideMerge(ServerConfigLayer), - Layer.provideMerge(NodeServices.layer), -); - -function makeTmpDir( - prefix = "checkpoint-store-test-", -): Effect.Effect { - return Effect.gen(function* () { - const fileSystem = yield* FileSystem.FileSystem; - return yield* fileSystem.makeTempDirectoryScoped({ prefix }); - }); -} - -function writeTextFile( - filePath: string, - contents: string, -): Effect.Effect { - return Effect.gen(function* () { - const fileSystem = yield* FileSystem.FileSystem; - yield* fileSystem.writeFileString(filePath, contents); - }); -} - -function git( - cwd: string, - args: ReadonlyArray, -): Effect.Effect { - return Effect.gen(function* () { - const process = yield* VcsProcess.VcsProcess; - const result = yield* process.run({ - operation: "CheckpointStore.test.git", - command: "git", - cwd, - args, - timeoutMs: 10_000, - }); - return result.stdout.trim(); - }); -} - -function initRepoWithCommit( - cwd: string, -): Effect.Effect< - void, - VcsError | PlatformError.PlatformError, - VcsProcess.VcsProcess | FileSystem.FileSystem -> { - return Effect.gen(function* () { - yield* git(cwd, ["init"]); - yield* git(cwd, ["config", "user.email", "test@test.com"]); - yield* git(cwd, ["config", "user.name", "Test"]); - yield* writeTextFile(path.join(cwd, "README.md"), "# test\n"); - yield* git(cwd, ["add", "."]); - yield* git(cwd, ["commit", "-m", "initial commit"]); - }); -} - -function buildLargeText(lineCount = 5_000): string { - return Array.from({ length: lineCount }, (_, index) => `line ${String(index).padStart(5, "0")}`) - .join("\n") - .concat("\n"); -} - -it.layer(TestLayer)("CheckpointStoreLive", (it) => { - describe("diffCheckpoints", () => { - it.effect("returns full oversized checkpoint diffs without truncation", () => - Effect.gen(function* () { - const tmp = yield* makeTmpDir(); - yield* initRepoWithCommit(tmp); - const checkpointStore = yield* CheckpointStore; - const threadId = ThreadId.make("thread-checkpoint-store"); - const fromCheckpointRef = checkpointRefForThreadTurn(threadId, 0); - const toCheckpointRef = checkpointRefForThreadTurn(threadId, 1); - - yield* checkpointStore.captureCheckpoint({ - cwd: tmp, - checkpointRef: fromCheckpointRef, - }); - yield* writeTextFile(path.join(tmp, "README.md"), buildLargeText()); - yield* checkpointStore.captureCheckpoint({ - cwd: tmp, - checkpointRef: toCheckpointRef, - }); - - const diff = yield* checkpointStore.diffCheckpoints({ - cwd: tmp, - fromCheckpointRef, - toCheckpointRef, - ignoreWhitespace: true, - }); - - expect(diff).toContain("diff --git"); - expect(diff).not.toContain("[truncated]"); - expect(diff).toContain("+line 04999"); - }), - ); - - it.effect("can hide indentation churn when changes wrap existing lines", () => - Effect.gen(function* () { - const tmp = yield* makeTmpDir(); - yield* initRepoWithCommit(tmp); - const checkpointStore = yield* CheckpointStore; - const threadId = ThreadId.make("thread-checkpoint-store-whitespace"); - const fromCheckpointRef = checkpointRefForThreadTurn(threadId, 0); - const toCheckpointRef = checkpointRefForThreadTurn(threadId, 1); - - const componentPath = path.join(tmp, "Component.tsx"); - yield* writeTextFile( - componentPath, - [ - "export function View() {", - " return (", - "
", - "

Title

", - "

Body

", - "
", - " );", - "}", - "", - ].join("\n"), - ); - yield* checkpointStore.captureCheckpoint({ - cwd: tmp, - checkpointRef: fromCheckpointRef, - }); - yield* writeTextFile( - componentPath, - [ - "export function View() {", - " return (", - "
", - " {isReady ? (", - "
", - "

Title

", - "

Body

", - "
", - " ) : null}", - "
", - " );", - "}", - "", - ].join("\n"), - ); - yield* checkpointStore.captureCheckpoint({ - cwd: tmp, - checkpointRef: toCheckpointRef, - }); - - const normalDiff = yield* checkpointStore.diffCheckpoints({ - cwd: tmp, - fromCheckpointRef, - toCheckpointRef, - ignoreWhitespace: false, - }); - const whitespaceIgnoredDiff = yield* checkpointStore.diffCheckpoints({ - cwd: tmp, - fromCheckpointRef, - toCheckpointRef, - ignoreWhitespace: true, - }); - - expect(normalDiff).toContain("diff --git"); - expect(normalDiff).toContain("-

Title

"); - expect(normalDiff).toContain("+

Title

"); - expect(whitespaceIgnoredDiff).toContain("diff --git"); - expect(whitespaceIgnoredDiff).toContain("+ {isReady ? ("); - expect(whitespaceIgnoredDiff).toContain("+
"); - expect(whitespaceIgnoredDiff).not.toContain("-

Title

"); - expect(whitespaceIgnoredDiff).not.toContain("+

Title

"); - }), - ); - }); -}); diff --git a/apps/server/src/checkpointing/Layers/CheckpointStore.ts b/apps/server/src/checkpointing/Layers/CheckpointStore.ts deleted file mode 100644 index 53b8d163e4c965652b72c16fb3a038446bc08d13..0000000000000000000000000000000000000000 --- a/apps/server/src/checkpointing/Layers/CheckpointStore.ts +++ /dev/null @@ -1,89 +0,0 @@ -/** - * CheckpointStoreLive - Filesystem checkpoint store adapter layer. - * - * Resolves the active VCS driver once per checkpoint operation and delegates - * checkpoint-specific behavior to the driver's optional checkpoint capability. - * - * @module CheckpointStoreLive - */ -import * as Effect from "effect/Effect"; -import * as Layer from "effect/Layer"; - -import { CheckpointStore, type CheckpointStoreShape } from "../Services/CheckpointStore.ts"; -import { VcsUnsupportedOperationError } from "@t3tools/contracts"; -import { VcsDriverRegistry } from "../../vcs/VcsDriverRegistry.ts"; -import type { VcsCheckpointOps } from "../../vcs/VcsDriver.ts"; - -const makeCheckpointStore = Effect.gen(function* () { - const vcsRegistry = yield* VcsDriverRegistry; - - const resolveCheckpoints = Effect.fn("CheckpointStore.resolveCheckpoints")(function* ( - operation: string, - cwd: string, - ) { - const handle = yield* vcsRegistry.resolve({ cwd }); - if (!handle.driver.checkpoints) { - return yield* new VcsUnsupportedOperationError({ - operation, - kind: handle.kind, - detail: `${handle.kind} driver does not implement checkpoint operations.`, - }); - } - return handle.driver.checkpoints satisfies VcsCheckpointOps; - }); - - const isGitRepository: CheckpointStoreShape["isGitRepository"] = (cwd) => - vcsRegistry.resolve({ cwd, requestedKind: "git" }).pipe( - Effect.map(() => true), - Effect.orElseSucceed(() => false), - ); - - const captureCheckpoint: CheckpointStoreShape["captureCheckpoint"] = Effect.fn( - "captureCheckpoint", - )(function* (input) { - const checkpoints = yield* resolveCheckpoints("CheckpointStore.captureCheckpoint", input.cwd); - return yield* checkpoints.captureCheckpoint(input); - }); - - const hasCheckpointRef: CheckpointStoreShape["hasCheckpointRef"] = Effect.fn("hasCheckpointRef")( - function* (input) { - const checkpoints = yield* resolveCheckpoints("CheckpointStore.hasCheckpointRef", input.cwd); - return yield* checkpoints.hasCheckpointRef(input); - }, - ); - - const restoreCheckpoint: CheckpointStoreShape["restoreCheckpoint"] = Effect.fn( - "restoreCheckpoint", - )(function* (input) { - const checkpoints = yield* resolveCheckpoints("CheckpointStore.restoreCheckpoint", input.cwd); - return yield* checkpoints.restoreCheckpoint(input); - }); - - const diffCheckpoints: CheckpointStoreShape["diffCheckpoints"] = Effect.fn("diffCheckpoints")( - function* (input) { - const checkpoints = yield* resolveCheckpoints("CheckpointStore.diffCheckpoints", input.cwd); - return yield* checkpoints.diffCheckpoints(input); - }, - ); - - const deleteCheckpointRefs: CheckpointStoreShape["deleteCheckpointRefs"] = Effect.fn( - "deleteCheckpointRefs", - )(function* (input) { - const checkpoints = yield* resolveCheckpoints( - "CheckpointStore.deleteCheckpointRefs", - input.cwd, - ); - return yield* checkpoints.deleteCheckpointRefs(input); - }); - - return { - isGitRepository, - captureCheckpoint, - hasCheckpointRef, - restoreCheckpoint, - diffCheckpoints, - deleteCheckpointRefs, - } satisfies CheckpointStoreShape; -}); - -export const CheckpointStoreLive = Layer.effect(CheckpointStore, makeCheckpointStore); diff --git a/apps/server/src/checkpointing/Services/CheckpointDiffQuery.ts b/apps/server/src/checkpointing/Services/CheckpointDiffQuery.ts deleted file mode 100644 index 4bb8b111827baf435940864b2a38f1d38bcbedd7..0000000000000000000000000000000000000000 --- a/apps/server/src/checkpointing/Services/CheckpointDiffQuery.ts +++ /dev/null @@ -1,49 +0,0 @@ -/** - * CheckpointDiffQuery - Query interface for computed checkpoint diffs. - * - * Provides read-only diff operations across checkpoint snapshots used by - * orchestration APIs. - * - * @module CheckpointDiffQuery - */ -import type { - OrchestrationGetFullThreadDiffInput, - OrchestrationGetFullThreadDiffResult, - OrchestrationGetTurnDiffInput, - OrchestrationGetTurnDiffResult, -} from "@t3tools/contracts"; -import * as Context from "effect/Context"; -import type * as Effect from "effect/Effect"; - -import type { CheckpointServiceError } from "../Errors.ts"; - -/** - * CheckpointDiffQueryShape - Service API for checkpoint diff queries. - */ -export interface CheckpointDiffQueryShape { - /** - * Read the patch diff for a single turn checkpoint transition. - * - * Verifies checkpoint availability in both projection state and filesystem. - */ - readonly getTurnDiff: ( - input: OrchestrationGetTurnDiffInput, - ) => Effect.Effect; - - /** - * Read the full patch diff across a thread range of checkpoints. - * - * Delegates to turn diff with `fromTurnCount = 0`. - */ - readonly getFullThreadDiff: ( - input: OrchestrationGetFullThreadDiffInput, - ) => Effect.Effect; -} - -/** - * CheckpointDiffQuery - Service tag for checkpoint diff queries. - */ -export class CheckpointDiffQuery extends Context.Service< - CheckpointDiffQuery, - CheckpointDiffQueryShape ->()("t3/checkpointing/Services/CheckpointDiffQuery") {} diff --git a/apps/server/src/checkpointing/Services/CheckpointStore.ts b/apps/server/src/checkpointing/Services/CheckpointStore.ts deleted file mode 100644 index a7c4c3dbef04d65bf609ee7dbbce43629f93e560..0000000000000000000000000000000000000000 --- a/apps/server/src/checkpointing/Services/CheckpointStore.ts +++ /dev/null @@ -1,101 +0,0 @@ -/** - * CheckpointStore - Repository interface for filesystem-backed workspace checkpoints. - * - * Owns hidden Git-ref checkpoint capture/restore and diff computation for a - * workspace thread timeline. It does not store user-facing checkpoint metadata - * and does not coordinate provider conversation rollback. - * - * Uses Effect `Context.Service` for dependency injection and exposes typed - * domain errors for checkpoint storage operations. - * - * @module CheckpointStore - */ -import * as Context from "effect/Context"; -import type * as Effect from "effect/Effect"; - -import type { CheckpointStoreError } from "../Errors.ts"; -import { CheckpointRef } from "@t3tools/contracts"; - -export interface CaptureCheckpointInput { - readonly cwd: string; - readonly checkpointRef: CheckpointRef; -} - -export interface RestoreCheckpointInput { - readonly cwd: string; - readonly checkpointRef: CheckpointRef; - readonly fallbackToHead?: boolean; -} - -export interface DiffCheckpointsInput { - readonly cwd: string; - readonly fromCheckpointRef: CheckpointRef; - readonly toCheckpointRef: CheckpointRef; - readonly fallbackFromToHead?: boolean; - readonly ignoreWhitespace: boolean; -} - -export interface DeleteCheckpointRefsInput { - readonly cwd: string; - readonly checkpointRefs: ReadonlyArray; -} - -/** - * CheckpointStoreShape - Service API for checkpoint capture/restore and diff access. - */ -export interface CheckpointStoreShape { - /** - * Check whether cwd is inside a Git worktree. - */ - readonly isGitRepository: (cwd: string) => Effect.Effect; - - /** - * Capture a checkpoint commit and store it at the provided checkpoint ref. - * - * Uses an isolated temporary Git index and writes a hidden ref. - */ - readonly captureCheckpoint: ( - input: CaptureCheckpointInput, - ) => Effect.Effect; - - /** - * Check whether a checkpoint ref exists. - */ - readonly hasCheckpointRef: ( - input: Omit, - ) => Effect.Effect; - - /** - * Restore workspace/staging state to a checkpoint. - * - * Optionally falls back to current `HEAD` when the checkpoint ref is missing. - */ - readonly restoreCheckpoint: ( - input: RestoreCheckpointInput, - ) => Effect.Effect; - - /** - * Compute patch diff between two checkpoint refs. - * - * Can optionally treat missing "from" ref as `HEAD`. - */ - readonly diffCheckpoints: ( - input: DiffCheckpointsInput, - ) => Effect.Effect; - - /** - * Delete the provided checkpoint refs. - * - * Best-effort delete: missing refs are tolerated. - */ - readonly deleteCheckpointRefs: ( - input: DeleteCheckpointRefsInput, - ) => Effect.Effect; -} - -/** - * CheckpointStore - Service tag for checkpoint persistence and restore operations. - */ -export class CheckpointStore extends Context.Service()( - "t3/checkpointing/Services/CheckpointStore", -) {} diff --git a/apps/server/src/orchestration/Layers/CheckpointReactor.test.ts b/apps/server/src/orchestration/Layers/CheckpointReactor.test.ts index 5e36f9f4babeaa5ffb9c881f017128a5c4f9baee..4bb5afbb476daeff9cfacfdd2dd322e64f2e71c9 100644 --- a/apps/server/src/orchestration/Layers/CheckpointReactor.test.ts +++ b/apps/server/src/orchestration/Layers/CheckpointReactor.test.ts @@ -30,8 +30,7 @@ import * as Scope from "effect/Scope"; import * as Stream from "effect/Stream"; import { afterEach, describe, expect, it, vi } from "vite-plus/test"; -import { CheckpointStoreLive } from "../../checkpointing/Layers/CheckpointStore.ts"; -import { CheckpointStore } from "../../checkpointing/Services/CheckpointStore.ts"; +import * as CheckpointStore from "../../checkpointing/CheckpointStore.ts"; import * as VcsDriverRegistry from "../../vcs/VcsDriverRegistry.ts"; import * as VcsProcess from "../../vcs/VcsProcess.ts"; import { VcsStatusBroadcaster } from "../../vcs/VcsStatusBroadcaster.ts"; @@ -247,7 +246,10 @@ async function waitForGitRefExists(cwd: string, ref: string, timeoutMs = 15_000) describe("CheckpointReactor", () => { let runtime: ManagedRuntime.ManagedRuntime< - OrchestrationEngineService | CheckpointReactor | CheckpointStore | ProjectionSnapshotQuery, + | OrchestrationEngineService + | CheckpointReactor + | CheckpointStore.CheckpointStore + | ProjectionSnapshotQuery, unknown > | null = null; let scope: Scope.Closeable | null = null; @@ -328,7 +330,7 @@ describe("CheckpointReactor", () => { Layer.provideMerge(RuntimeReceiptBusLive), Layer.provideMerge(Layer.succeed(ProviderService, provider.service)), Layer.provideMerge(vcsStatusBroadcasterLayer), - Layer.provideMerge(CheckpointStoreLive.pipe(Layer.provide(VcsDriverRegistry.layer))), + Layer.provideMerge(CheckpointStore.layer.pipe(Layer.provide(VcsDriverRegistry.layer))), Layer.provideMerge( WorkspaceEntries.layer.pipe( Layer.provide(WorkspacePathsLive), @@ -345,7 +347,9 @@ describe("CheckpointReactor", () => { const engine = await runtime.runPromise(Effect.service(OrchestrationEngineService)); const snapshotQuery = await runtime.runPromise(Effect.service(ProjectionSnapshotQuery)); const reactor = await runtime.runPromise(Effect.service(CheckpointReactor)); - const checkpointStore = await runtime.runPromise(Effect.service(CheckpointStore)); + const checkpointStore = await runtime.runPromise( + Effect.service(CheckpointStore.CheckpointStore), + ); scope = await Effect.runPromise(Scope.make("sequential")); await Effect.runPromise(reactor.start().pipe(Scope.provide(scope))); const drain = () => Effect.runPromise(reactor.drain); diff --git a/apps/server/src/orchestration/Layers/CheckpointReactor.ts b/apps/server/src/orchestration/Layers/CheckpointReactor.ts index 48ff133f56d2de95dd7c70fd807d554e106c6a97..3ba244ddf2c400cb9c7fa3e9f07d36a2a008e755 100644 --- a/apps/server/src/orchestration/Layers/CheckpointReactor.ts +++ b/apps/server/src/orchestration/Layers/CheckpointReactor.ts @@ -24,7 +24,7 @@ import { checkpointRefForThreadTurn, resolveThreadWorkspaceCwd, } from "../../checkpointing/Utils.ts"; -import { CheckpointStore } from "../../checkpointing/Services/CheckpointStore.ts"; +import * as CheckpointStore from "../../checkpointing/CheckpointStore.ts"; import { ProviderService } from "../../provider/Services/ProviderService.ts"; import { CheckpointReactor, type CheckpointReactorShape } from "../Services/CheckpointReactor.ts"; import { OrchestrationEngineService } from "../Services/OrchestrationEngine.ts"; @@ -81,7 +81,7 @@ const make = Effect.gen(function* () { const orchestrationEngine = yield* OrchestrationEngineService; const projectionSnapshotQuery = yield* ProjectionSnapshotQuery; const providerService = yield* ProviderService; - const checkpointStore = yield* CheckpointStore; + const checkpointStore = yield* CheckpointStore.CheckpointStore; const receiptBus = yield* RuntimeReceiptBus; const workspaceEntries = yield* WorkspaceEntries.WorkspaceEntries; const vcsStatusBroadcaster = yield* VcsStatusBroadcaster; diff --git a/apps/server/src/server.test.ts b/apps/server/src/server.test.ts index a1213880f144d9d01d174772538bdcdc474caaa0..62ad99b3e9c7e938e1f1eda67e1f9ffe0260f368 100644 --- a/apps/server/src/server.test.ts +++ b/apps/server/src/server.test.ts @@ -71,7 +71,7 @@ const TEST_EPOCH = DateTime.makeUnsafe("1970-01-01T00:00:00.000Z"); import * as ServerConfig from "./config.ts"; import { makeRoutesLayer } from "./server.ts"; -import * as CheckpointDiffQuery from "./checkpointing/Services/CheckpointDiffQuery.ts"; +import * as CheckpointDiffQuery from "./checkpointing/CheckpointDiffQuery.ts"; import * as GitManager from "./git/GitManager.ts"; import * as Keybindings from "./keybindings.ts"; import * as ExternalLauncher from "./process/externalLauncher.ts"; diff --git a/apps/server/src/server.ts b/apps/server/src/server.ts index 4e0cd792f2bd89a0bef343fbbb616d469984278b..987ba83deae887efe35234aa04ac940120e15835 100644 --- a/apps/server/src/server.ts +++ b/apps/server/src/server.ts @@ -25,8 +25,8 @@ import * as ProviderEventLoggers from "./provider/Layers/ProviderEventLoggers.ts import { ProviderServiceLive } from "./provider/Layers/ProviderService.ts"; import { ProviderSessionReaperLive } from "./provider/Layers/ProviderSessionReaper.ts"; import * as OpenCodeRuntime from "./provider/opencodeRuntime.ts"; -import { CheckpointDiffQueryLive } from "./checkpointing/Layers/CheckpointDiffQuery.ts"; -import { CheckpointStoreLive } from "./checkpointing/Layers/CheckpointStore.ts"; +import * as CheckpointDiffQuery from "./checkpointing/CheckpointDiffQuery.ts"; +import * as CheckpointStore from "./checkpointing/CheckpointStore.ts"; import * as AzureDevOpsCli from "./sourceControl/AzureDevOpsCli.ts"; import * as BitbucketApi from "./sourceControl/BitbucketApi.ts"; import * as GitHubCli from "./sourceControl/GitHubCli.ts"; @@ -232,8 +232,8 @@ const VcsLayerLive = Layer.empty.pipe( ); const CheckpointingLayerLive = Layer.empty.pipe( - Layer.provideMerge(CheckpointDiffQueryLive), - Layer.provideMerge(CheckpointStoreLive.pipe(Layer.provide(VcsDriverRegistryLayerLive))), + Layer.provideMerge(CheckpointDiffQuery.layer), + Layer.provideMerge(CheckpointStore.layer.pipe(Layer.provide(VcsDriverRegistryLayerLive))), ); const PortScannerLayerLive = PortScanner.layer.pipe(Layer.provide(ProcessRunner.layer)); diff --git a/apps/server/src/ws.ts b/apps/server/src/ws.ts index 01337541cd1905d7bca78649d46452a66ea5ebbc..cff18c94d918e87c94497bd55403ca895cdb3b4f 100644 --- a/apps/server/src/ws.ts +++ b/apps/server/src/ws.ts @@ -56,7 +56,7 @@ import { clamp } from "effect/Number"; import { HttpRouter, HttpServerRequest, HttpServerRespondable } from "effect/unstable/http"; import { RpcSerialization, RpcServer } from "effect/unstable/rpc"; -import * as CheckpointDiffQuery from "./checkpointing/Services/CheckpointDiffQuery.ts"; +import * as CheckpointDiffQuery from "./checkpointing/CheckpointDiffQuery.ts"; import * as ServerConfig from "./config.ts"; import * as Keybindings from "./keybindings.ts"; import * as ExternalLauncher from "./process/externalLauncher.ts"; diff --git a/docs/operations/effect-fn-checklist.md b/docs/operations/effect-fn-checklist.md index 1addfdf4dd4e1b5b01fb30ece0663cfc861aecc7..279b5646d32e54b93ec3e72befd7a3722d421963 100644 --- a/docs/operations/effect-fn-checklist.md +++ b/docs/operations/effect-fn-checklist.md @@ -130,12 +130,12 @@ Effect.fn("name")( - [x] [listener](/Users/julius/Development/Work/codething-mvp/apps/server/src/provider/Layers/CodexAdapter.ts#L1555) - [x] Remaining nested callback wrappers in this file -### `apps/server/src/checkpointing/Layers/CheckpointStore.ts` (`10`) +### `apps/server/src/checkpointing/CheckpointStore.ts` (`10`) -- [ ] [captureCheckpoint](/Users/julius/Development/Work/codething-mvp/apps/server/src/checkpointing/Layers/CheckpointStore.ts#L89) -- [ ] [restoreCheckpoint](/Users/julius/Development/Work/codething-mvp/apps/server/src/checkpointing/Layers/CheckpointStore.ts#L183) -- [ ] [diffCheckpoints](/Users/julius/Development/Work/codething-mvp/apps/server/src/checkpointing/Layers/CheckpointStore.ts#L220) -- [ ] [deleteCheckpointRefs](/Users/julius/Development/Work/codething-mvp/apps/server/src/checkpointing/Layers/CheckpointStore.ts#L252) +- [ ] [captureCheckpoint](/Users/julius/Development/Work/codething-mvp/apps/server/src/checkpointing/CheckpointStore.ts#L123) +- [ ] [restoreCheckpoint](/Users/julius/Development/Work/codething-mvp/apps/server/src/checkpointing/CheckpointStore.ts#L137) +- [ ] [diffCheckpoints](/Users/julius/Development/Work/codething-mvp/apps/server/src/checkpointing/CheckpointStore.ts#L144) +- [ ] [deleteCheckpointRefs](/Users/julius/Development/Work/codething-mvp/apps/server/src/checkpointing/CheckpointStore.ts#L151) - [ ] Nested callback wrappers in this file ### `apps/server/src/provider/Layers/EventNdjsonLogger.ts` (`9`) @@ -190,7 +190,7 @@ Effect.fn("name")( - [ ] [apps/server/src/persistence/Migrations.ts](/Users/julius/Development/Work/codething-mvp/apps/server/src/persistence/Migrations.ts) (`2`) - [ ] [apps/server/src/open.ts](/Users/julius/Development/Work/codething-mvp/apps/server/src/open.ts) (`2`) - [ ] [apps/server/src/git/Layers/ClaudeTextGeneration.ts](/Users/julius/Development/Work/codething-mvp/apps/server/src/git/Layers/ClaudeTextGeneration.ts) (`2`) -- [ ] [apps/server/src/checkpointing/Layers/CheckpointDiffQuery.ts](/Users/julius/Development/Work/codething-mvp/apps/server/src/checkpointing/Layers/CheckpointDiffQuery.ts) (`2`) +- [ ] [apps/server/src/checkpointing/CheckpointDiffQuery.ts](/Users/julius/Development/Work/codething-mvp/apps/server/src/checkpointing/CheckpointDiffQuery.ts) (`2`) - [ ] [apps/server/src/provider/makeManagedServerProvider.ts](/Users/julius/Development/Work/codething-mvp/apps/server/src/provider/makeManagedServerProvider.ts) (`1`) ``` diff --git a/docs/reference/encyclopedia.md b/docs/reference/encyclopedia.md index 76df004e044c10b365f0364c718490601ffd0431..82a58fd959c9334c18b02585a7c0d106e6b9ff88 100644 --- a/docs/reference/encyclopedia.md +++ b/docs/reference/encyclopedia.md @@ -172,8 +172,8 @@ The file patch and changed-file summary for one turn. It is usually computed in [16]: ./provider-architecture.md [17]: ../apps/server/src/provider/Layers/CodexAdapter.ts [18]: ./runtime-modes.md -[19]: ../apps/server/src/checkpointing/Services/CheckpointStore.ts -[20]: ../apps/server/src/checkpointing/Services/CheckpointDiffQuery.ts +[19]: ../apps/server/src/checkpointing/CheckpointStore.ts +[20]: ../apps/server/src/checkpointing/CheckpointDiffQuery.ts [21]: ../apps/server/src/persistence/Services/ProjectionCheckpoints.ts [22]: ../apps/server/src/checkpointing/Utils.ts [23]: ../apps/server/src/checkpointing/Diffs.ts diff --git a/oxlint-plugin-t3code/rules/no-manual-effect-runtime-in-tests.ts b/oxlint-plugin-t3code/rules/no-manual-effect-runtime-in-tests.ts index 7494476e27ebecba5bf4acb30460390cf482bad6..e6eff1e5c215a55f38a14b4b8ad590385f8fff55 100644 --- a/oxlint-plugin-t3code/rules/no-manual-effect-runtime-in-tests.ts +++ b/oxlint-plugin-t3code/rules/no-manual-effect-runtime-in-tests.ts @@ -25,7 +25,6 @@ const LEGACY_BASELINE = new Map([ ["apps/mobile/src/features/agent-awareness/liveActivityPreferences.test.ts", 1], ["apps/mobile/src/features/agent-awareness/remoteRegistration.test.ts", 2], ["apps/mobile/src/state/use-remote-environment-registry.test.ts", 2], - ["apps/server/src/checkpointing/Layers/CheckpointDiffQuery.test.ts", 5], ["apps/server/src/orchestration/commandInvariants.test.ts", 6], ["apps/server/src/orchestration/Layers/CheckpointReactor.test.ts", 42], ["apps/server/src/orchestration/Layers/OrchestrationEngine.test.ts", 5],