1import { assert } from "@effect/vitest";
2import type { ProviderReplayTranscript } from "@t3tools/contracts";
3
4import type { OrchestratorV2ScenarioResult } from "../../OrchestratorScenario.ts";
5import {
6 assertBaseProjection,
7 assertSemanticProjectionIntegrity,
8 assertVisibleUserMessagesExclude,
9 assertVisibleUserMessagesInclude,
10 OPENCODE2_REVERT_FIRST_PROMPT,
11 OPENCODE2_REVERT_SECOND_PROMPT,
12 projectionFor,
13} from "../shared.ts";
14
15/**
16 * The rollback stages a revert before the second turn's user message and
17 * commits it, so OpenCode's history ends with the first turn.
18 */
19export function assertOpenCode2RevertOutput(
20 result: OrchestratorV2ScenarioResult,
21 transcript: ProviderReplayTranscript,
22) {
23 assertBaseProjection({
24 result,
25 transcript,
26 runCount: 2,
27 runStatuses: ["completed", "rolled_back"],
28 });
29 const projection = projectionFor(result, transcript.scenario);
30 assertSemanticProjectionIntegrity(projection);
31 assertVisibleUserMessagesInclude(projection, [OPENCODE2_REVERT_FIRST_PROMPT]);
32 assertVisibleUserMessagesExclude(projection, [OPENCODE2_REVERT_SECOND_PROMPT]);
33 // The provider thread's head is the kept turn's prompt, OpenCode's last user message.
34 const kept = projection.providerTurns.find((turn) => turn.ordinal === 1);
35 assert.equal(
36 projection.providerThreads[0]?.nativeConversationHeadRef?.nativeId,
37 kept?.nativeTurnRef?.nativeId,
38 );
39}