1import * as NodeCrypto from "@effect/platform-node/NodeCrypto";
2import { assert, it } from "@effect/vitest";
3import * as DateTime from "effect/DateTime";
4import * as Effect from "effect/Effect";
5import * as Layer from "effect/Layer";
6import {
7 CheckpointId,
8 EventId,
9 MessageId,
10 NodeId,
11 ProjectId,
12 ProviderInstanceId,
13 ProviderThreadId,
14 RunId,
15 ThreadId,
16} from "@t3tools/contracts";
17import * as CheckpointDiffQuery from "../checkpointing/CheckpointDiffQuery.ts";
18import * as CheckpointStore from "../checkpointing/CheckpointStore.ts";
19import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
20import { checkpointRefForScopeOrdinal } from "./CheckpointService.ts";
21import * as ProjectionStore from "./ProjectionStore.ts";
22import * as ThreadManagement from "./ThreadManagementService.ts";
23import * as SqlitePersistence from "../persistence/Sqlite.ts";
24const layerProjection = Layer.mergeAll(
25 ProjectionStore.layer.pipe(Layer.provideMerge(SqlitePersistence.layerMemory)),
26 SqlitePersistence.layerMemory,
27 IdAllocator.layer,
28 NodeCrypto.layer,
29);
30it.effect("resolves the thread baseline after a second root run replaces scope ownership", () =>
31 Effect.gen(function* () {
32 const ids = yield* IdAllocator.IdAllocatorV2;
33 const projections = yield* ProjectionStore.ProjectionStoreV2;
34 const now = DateTime.makeUnsafe("2026-09-08T12:00:00.000Z");
35 const threadId = ThreadId.make("thread:audit-root-scope");
36 const firstRunId = RunId.make("run:audit-root-scope:1");
37 const secondRunId = RunId.make("run:audit-root-scope:2");
38 const firstNodeId = NodeId.make("node:audit-root-scope:1");
39 const secondNodeId = NodeId.make("node:audit-root-scope:2");
40 const providerThreadId = ProviderThreadId.make("provider-thread:audit-root-scope");
41 const providerInstanceId = ProviderInstanceId.make("codex");
42 const scopeId = yield* ids.allocate.checkpointScope({ threadId, name: "root" });
43 const checkpointRefs = [
44 yield* checkpointRefForScopeOrdinal({ scopeId, ordinalWithinScope: 0 }),
45 yield* checkpointRefForScopeOrdinal({ scopeId, ordinalWithinScope: 1 }),
46 yield* checkpointRefForScopeOrdinal({ scopeId, ordinalWithinScope: 2 }),
47 ];
48 const baselineId = CheckpointId.make("checkpoint:audit-root-scope:0");
49 const firstCheckpointId = CheckpointId.make("checkpoint:audit-root-scope:1");
50 const secondCheckpointId = CheckpointId.make("checkpoint:audit-root-scope:2");
51 const modelSelection = { instanceId: providerInstanceId, model: "gpt-5.4" } as const;
52
53 yield* projections.apply({
54 id: EventId.make("event:audit-root-scope:thread"),
55 type: "thread.created",
56 threadId,
57 providerInstanceId,
58 occurredAt: now,
59 payload: {
60 createdBy: "user",
61 creationSource: "web",
62 id: threadId,
63 projectId: ProjectId.make("project:audit-root-scope"),
64 title: "Checkpoint scope probe",
65 providerInstanceId,
66 modelSelection,
67 runtimeMode: "full-access",
68 interactionMode: "default",
69 branch: null,
70 worktreePath: null,
71 activeProviderThreadId: providerThreadId,
72 lineage: { parentThreadId: null, relationshipToParent: null, rootThreadId: threadId },
73 forkedFrom: null,
74 createdAt: now,
75 updatedAt: now,
76 archivedAt: null,
77 settledOverride: null,
78 settledAt: null,
79 lastVisitedAt: null,
80 deletedAt: null,
81 },
82 });
83
84 const applyRun = (input: {
85 readonly runId: RunId;
86 readonly nodeId: NodeId;
87 readonly ordinal: number;
88 readonly checkpointId: CheckpointId;
89 }) =>
90 projections.apply({
91 id: EventId.make(`event:audit-root-scope:run:${input.ordinal}`),
92 type: "run.updated",
93 threadId,
94 runId: input.runId,
95 nodeId: input.nodeId,
96 providerInstanceId,
97 occurredAt: now,
98 payload: {
99 id: input.runId,
100 threadId,
101 ordinal: input.ordinal,
102 providerInstanceId,
103 modelSelection,
104 providerThreadId,
105 userMessageId: MessageId.make(`message:audit-root-scope:${input.ordinal}`),
106 rootNodeId: input.nodeId,
107 activeAttemptId: null,
108 status: "completed" as const,
109 requestedAt: now,
110 startedAt: now,
111 completedAt: now,
112 checkpointId: input.checkpointId,
113 contextHandoffId: null,
114 },
115 });
116 const applyScope = (runId: RunId, nodeId: NodeId, ordinal: number) =>
117 projections.apply({
118 id: EventId.make(`event:audit-root-scope:scope:${ordinal}`),
119 type: "checkpoint-scope.created",
120 threadId,
121 runId,
122 nodeId,
123 providerInstanceId,
124 occurredAt: now,
125 payload: {
126 id: scopeId,
127 threadId,
128 runId,
129 nodeId,
130 parentScopeId: null,
131 providerThreadId,
132 kind: "root_run" as const,
133 ordinalWithinParent: 0,
134 advancesAppRunCount: true,
135 cwd: ordinal === 1 ? "/repo" : "/prepared-repo",
136 createdAt: now,
137 },
138 });
139 const applyCheckpoint = (input: {
140 readonly id: CheckpointId;
141 readonly runId: RunId | null;
142 readonly nodeId: NodeId;
143 readonly ordinal: number;
144 readonly appRunOrdinal: number | null;
145 }) =>
146 projections.apply({
147 id: EventId.make(`event:audit-root-scope:checkpoint:${input.ordinal}`),
148 type: "checkpoint.captured",
149 threadId,
150 ...(input.runId === null ? {} : { runId: input.runId }),
151 nodeId: input.nodeId,
152 providerInstanceId,
153 occurredAt: now,
154 payload: {
155 id: input.id,
156 scopeId,
157 threadId,
158 runId: input.runId,
159 nodeId: input.nodeId,
160 parentCheckpointId:
161 input.ordinal === 0 ? null : input.ordinal === 1 ? baselineId : firstCheckpointId,
162 ordinalWithinScope: input.ordinal,
163 appRunOrdinal: input.appRunOrdinal,
164 ref: checkpointRefs[input.ordinal]!,
165 status: "ready" as const,
166 files: [],
167 capturedAt: now,
168 },
169 });
170
171 yield* applyRun({
172 runId: firstRunId,
173 nodeId: firstNodeId,
174 ordinal: 1,
175 checkpointId: firstCheckpointId,
176 });
177 yield* applyScope(firstRunId, firstNodeId, 1);
178 yield* applyCheckpoint({
179 id: baselineId,
180 runId: null,
181 nodeId: firstNodeId,
182 ordinal: 0,
183 appRunOrdinal: null,
184 });
185 yield* applyCheckpoint({
186 id: firstCheckpointId,
187 runId: firstRunId,
188 nodeId: firstNodeId,
189 ordinal: 1,
190 appRunOrdinal: 1,
191 });
192 yield* applyRun({
193 runId: secondRunId,
194 nodeId: secondNodeId,
195 ordinal: 2,
196 checkpointId: secondCheckpointId,
197 });
198 yield* applyScope(secondRunId, secondNodeId, 2);
199 yield* applyCheckpoint({
200 id: secondCheckpointId,
201 runId: secondRunId,
202 nodeId: secondNodeId,
203 ordinal: 2,
204 appRunOrdinal: 2,
205 });
206
207 const context = yield* projections.getCheckpointContext(threadId);
208 assert.deepEqual(
209 context.checkpoints.map((checkpoint) => checkpoint.appRunOrdinal),
210 [null, 1, 2],
211 );
212 assert.equal(context.checkpointScopes.length, 1);
213 assert.equal(context.checkpointScopes[0]?.runId, secondRunId);
214
215 const layerQuery = CheckpointDiffQuery.layer.pipe(
216 Layer.provide(
217 Layer.mergeAll(
218 Layer.mock(ThreadManagement.ThreadManagementService)({
219 getCheckpointContext: () => Effect.succeed(context),
220 }),
221 Layer.mock(CheckpointStore.CheckpointStore)({
222 listAuthoredPaths: () => Effect.succeed(null),
223 diffCheckpoints: (input) => {
224 assert.equal(input.cwd, "/prepared-repo");
225 assert.equal(input.fromCheckpointRef, checkpointRefs[0]);
226 assert.equal(input.toCheckpointRef, checkpointRefs[2]);
227 return Effect.succeed("two-run diff");
228 },
229 }),
230 ),
231 ),
232 );
233 const result = yield* Effect.gen(function* () {
234 const query = yield* CheckpointDiffQuery.CheckpointDiffQuery;
235 return yield* query.getFullThreadDiff({ threadId, toTurnCount: 2 });
236 }).pipe(Effect.provide(layerQuery));
237 assert.equal(result.diff, "two-run diff");
238 }).pipe(Effect.provide(layerProjection)),
239);