| ... | @@ -3967,6 +3967,130 @@ it.layer(BaseTestLayer)("OrchestrationProjectionPipeline", (it) => { | ... | @@ -3967,6 +3967,130 @@ it.layer(BaseTestLayer)("OrchestrationProjectionPipeline", (it) => { |
| 3967 | ]); | 3967 | ]); |
| 3968 | }), | 3968 | }), |
| 3969 | ); | 3969 | ); |
| | 3970 | |
| | 3971 | it.effect("does not let a later missing placeholder clobber a ready checkpoint", () => |
| | 3972 | Effect.gen(function* () { |
| | 3973 | const projectionPipeline = yield* OrchestrationProjectionPipeline; |
| | 3974 | const eventStore = yield* OrchestrationEventStore; |
| | 3975 | const sql = yield* SqlClient.SqlClient; |
| | 3976 | const appendAndProject = (event: Parameters<typeof eventStore.append>[0]) => |
| | 3977 | eventStore |
| | 3978 | .append(event) |
| | 3979 | .pipe(Effect.flatMap((savedEvent) => projectionPipeline.projectEvent(savedEvent))); |
| | 3980 | |
| | 3981 | yield* appendAndProject({ |
| | 3982 | type: "project.created", |
| | 3983 | eventId: EventId.make("evt-checkpoint-guard-1"), |
| | 3984 | aggregateKind: "project", |
| | 3985 | aggregateId: ProjectId.make("project-checkpoint-guard"), |
| | 3986 | occurredAt: "2026-02-26T14:00:00.000Z", |
| | 3987 | commandId: CommandId.make("cmd-checkpoint-guard-1"), |
| | 3988 | causationEventId: null, |
| | 3989 | correlationId: CorrelationId.make("cmd-checkpoint-guard-1"), |
| | 3990 | metadata: {}, |
| | 3991 | payload: { |
| | 3992 | projectId: ProjectId.make("project-checkpoint-guard"), |
| | 3993 | title: "Project Checkpoint Guard", |
| | 3994 | workspaceRoot: "/tmp/project-checkpoint-guard", |
| | 3995 | defaultModelSelection: null, |
| | 3996 | scripts: [], |
| | 3997 | createdAt: "2026-02-26T14:00:00.000Z", |
| | 3998 | updatedAt: "2026-02-26T14:00:00.000Z", |
| | 3999 | }, |
| | 4000 | }); |
| | 4001 | |
| | 4002 | yield* appendAndProject({ |
| | 4003 | type: "thread.created", |
| | 4004 | eventId: EventId.make("evt-checkpoint-guard-2"), |
| | 4005 | aggregateKind: "thread", |
| | 4006 | aggregateId: ThreadId.make("thread-checkpoint-guard"), |
| | 4007 | occurredAt: "2026-02-26T14:00:01.000Z", |
| | 4008 | commandId: CommandId.make("cmd-checkpoint-guard-2"), |
| | 4009 | causationEventId: null, |
| | 4010 | correlationId: CorrelationId.make("cmd-checkpoint-guard-2"), |
| | 4011 | metadata: {}, |
| | 4012 | payload: { |
| | 4013 | threadId: ThreadId.make("thread-checkpoint-guard"), |
| | 4014 | projectId: ProjectId.make("project-checkpoint-guard"), |
| | 4015 | title: "Thread Checkpoint Guard", |
| | 4016 | modelSelection: { |
| | 4017 | instanceId: ProviderInstanceId.make("codex"), |
| | 4018 | model: "gpt-5-codex", |
| | 4019 | }, |
| | 4020 | runtimeMode: "full-access", |
| | 4021 | interactionMode: "default", |
| | 4022 | branch: null, |
| | 4023 | worktreePath: null, |
| | 4024 | createdAt: "2026-02-26T14:00:01.000Z", |
| | 4025 | updatedAt: "2026-02-26T14:00:01.000Z", |
| | 4026 | }, |
| | 4027 | }); |
| | 4028 | |
| | 4029 | yield* appendAndProject({ |
| | 4030 | type: "thread.turn-diff-completed", |
| | 4031 | eventId: EventId.make("evt-checkpoint-guard-3"), |
| | 4032 | aggregateKind: "thread", |
| | 4033 | aggregateId: ThreadId.make("thread-checkpoint-guard"), |
| | 4034 | occurredAt: "2026-02-26T14:00:02.000Z", |
| | 4035 | commandId: CommandId.make("cmd-checkpoint-guard-3"), |
| | 4036 | causationEventId: null, |
| | 4037 | correlationId: CorrelationId.make("cmd-checkpoint-guard-3"), |
| | 4038 | metadata: {}, |
| | 4039 | payload: { |
| | 4040 | threadId: ThreadId.make("thread-checkpoint-guard"), |
| | 4041 | turnId: TurnId.make("turn-ready"), |
| | 4042 | checkpointTurnCount: 1, |
| | 4043 | checkpointRef: CheckpointRef.make("refs/t3/checkpoints/thread-checkpoint-guard/turn/1"), |
| | 4044 | status: "ready", |
| | 4045 | files: [], |
| | 4046 | assistantMessageId: MessageId.make("assistant-ready"), |
| | 4047 | completedAt: "2026-02-26T14:00:02.000Z", |
| | 4048 | }, |
| | 4049 | }); |
| | 4050 | |
| | 4051 | yield* appendAndProject({ |
| | 4052 | type: "thread.turn-diff-completed", |
| | 4053 | eventId: EventId.make("evt-checkpoint-guard-4"), |
| | 4054 | aggregateKind: "thread", |
| | 4055 | aggregateId: ThreadId.make("thread-checkpoint-guard"), |
| | 4056 | occurredAt: "2026-02-26T14:00:03.000Z", |
| | 4057 | commandId: CommandId.make("cmd-checkpoint-guard-4"), |
| | 4058 | causationEventId: null, |
| | 4059 | correlationId: CorrelationId.make("cmd-checkpoint-guard-4"), |
| | 4060 | metadata: {}, |
| | 4061 | payload: { |
| | 4062 | threadId: ThreadId.make("thread-checkpoint-guard"), |
| | 4063 | turnId: TurnId.make("turn-ready"), |
| | 4064 | checkpointTurnCount: 1, |
| | 4065 | checkpointRef: CheckpointRef.make("refs/t3/checkpoints/thread-checkpoint-guard/turn/1"), |
| | 4066 | status: "missing", |
| | 4067 | files: [], |
| | 4068 | assistantMessageId: MessageId.make("assistant-ready"), |
| | 4069 | completedAt: "2026-02-26T14:00:03.000Z", |
| | 4070 | }, |
| | 4071 | }); |
| | 4072 | |
| | 4073 | const turnRows = yield* sql<{ |
| | 4074 | readonly turnId: string; |
| | 4075 | readonly checkpointStatus: string | null; |
| | 4076 | readonly checkpointRef: string | null; |
| | 4077 | }>` |
| | 4078 | SELECT |
| | 4079 | turn_id AS "turnId", |
| | 4080 | checkpoint_status AS "checkpointStatus", |
| | 4081 | checkpoint_ref AS "checkpointRef" |
| | 4082 | FROM projection_turns |
| | 4083 | WHERE thread_id = 'thread-checkpoint-guard' |
| | 4084 | `; |
| | 4085 | assert.deepEqual(turnRows, [ |
| | 4086 | { |
| | 4087 | turnId: "turn-ready", |
| | 4088 | checkpointStatus: "ready", |
| | 4089 | checkpointRef: "refs/t3/checkpoints/thread-checkpoint-guard/turn/1", |
| | 4090 | }, |
| | 4091 | ]); |
| | 4092 | }), |
| | 4093 | ); |
| 3970 | }); | 4094 | }); |
| 3971 | | 4095 | |
| 3972 | it.layer(makeProjectionPipelinePrefixedTestLayer("t3-pending-turn-terminal-test-"))( | 4096 | it.layer(makeProjectionPipelinePrefixedTestLayer("t3-pending-turn-terminal-test-"))( |