authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-02-22 11:28:50-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-02-26 13:20:36-08:00
log5bfd0598e763ba8221ae90b6b68524391a8492a0
tree72028c083e0f5f5031273c87e97685765f084892
parent060a8cef00dee1728be2fa7cd96ad0332ad15353

more effect: add new service definitions


5 files changed, 721 insertions(+), 0 deletions(-)

apps/server/src/provider/Errors.ts created+282
...@@ -0,0 +1,282 @@
1import { Schema } from "effect";
2
3/**
4 * CheckpointValidationError - Invalid checkpoint API input.
5 */
6export class CheckpointValidationError extends Schema.TaggedErrorClass<CheckpointValidationError>()(
7 "CheckpointValidationError",
8 {
9 operation: Schema.String,
10 issue: Schema.String,
11 cause: Schema.optional(Schema.Defect),
12 },
13) {
14 override get message(): string {
15 return `Checkpoint validation failed in ${this.operation}: ${this.issue}`;
16 }
17}
18
19/**
20 * CheckpointGitCommandError - Git command execution failed in checkpoint store.
21 */
22export class CheckpointGitCommandError extends Schema.TaggedErrorClass<CheckpointGitCommandError>()(
23 "CheckpointGitCommandError",
24 {
25 operation: Schema.String,
26 command: Schema.String,
27 cwd: Schema.String,
28 detail: Schema.String,
29 cause: Schema.optional(Schema.Defect),
30 },
31) {
32 override get message(): string {
33 return `Checkpoint git command failed in ${this.operation}: ${this.command} (${this.cwd}) - ${this.detail}`;
34 }
35}
36
37/**
38 * CheckpointUnavailableError - Expected checkpoint does not exist.
39 */
40export class CheckpointUnavailableError extends Schema.TaggedErrorClass<CheckpointUnavailableError>()(
41 "CheckpointUnavailableError",
42 {
43 threadId: Schema.String,
44 turnCount: Schema.Number,
45 detail: Schema.String,
46 cause: Schema.optional(Schema.Defect),
47 },
48) {
49 override get message(): string {
50 return `Checkpoint unavailable for thread ${this.threadId} turn ${this.turnCount}: ${this.detail}`;
51 }
52}
53
54/**
55 * CheckpointRepositoryError - Checkpointing unavailable for cwd/repository.
56 */
57export class CheckpointRepositoryError extends Schema.TaggedErrorClass<CheckpointRepositoryError>()(
58 "CheckpointRepositoryError",
59 {
60 cwd: Schema.String,
61 detail: Schema.String,
62 cause: Schema.optional(Schema.Defect),
63 },
64) {
65 override get message(): string {
66 return `Checkpoint repository error for ${this.cwd}: ${this.detail}`;
67 }
68}
69
70/**
71 * CodexValidationError - Invalid Codex API input.
72 */
73export class CodexValidationError extends Schema.TaggedErrorClass<CodexValidationError>()(
74 "CodexValidationError",
75 {
76 operation: Schema.String,
77 issue: Schema.String,
78 cause: Schema.optional(Schema.Defect),
79 },
80) {
81 override get message(): string {
82 return `Codex validation failed in ${this.operation}: ${this.issue}`;
83 }
84}
85
86/**
87 * CodexSessionNotFoundError - Session id is unknown.
88 */
89export class CodexSessionNotFoundError extends Schema.TaggedErrorClass<CodexSessionNotFoundError>()(
90 "CodexSessionNotFoundError",
91 {
92 sessionId: Schema.String,
93 cause: Schema.optional(Schema.Defect),
94 },
95) {
96 override get message(): string {
97 return `Unknown codex session: ${this.sessionId}`;
98 }
99}
100
101/**
102 * CodexSessionClosedError - Session exists but is closed.
103 */
104export class CodexSessionClosedError extends Schema.TaggedErrorClass<CodexSessionClosedError>()(
105 "CodexSessionClosedError",
106 {
107 sessionId: Schema.String,
108 cause: Schema.optional(Schema.Defect),
109 },
110) {
111 override get message(): string {
112 return `Codex session is closed: ${this.sessionId}`;
113 }
114}
115
116/**
117 * CodexProtocolError - Invalid/unexpected JSON-RPC payload from app-server.
118 */
119export class CodexProtocolError extends Schema.TaggedErrorClass<CodexProtocolError>()(
120 "CodexProtocolError",
121 {
122 operation: Schema.String,
123 detail: Schema.String,
124 cause: Schema.optional(Schema.Defect),
125 },
126) {
127 override get message(): string {
128 return `Codex protocol error in ${this.operation}: ${this.detail}`;
129 }
130}
131
132/**
133 * CodexRequestError - JSON-RPC request failed or timed out.
134 */
135export class CodexRequestError extends Schema.TaggedErrorClass<CodexRequestError>()(
136 "CodexRequestError",
137 {
138 method: Schema.String,
139 detail: Schema.String,
140 cause: Schema.optional(Schema.Defect),
141 },
142) {
143 override get message(): string {
144 return `Codex request failed for ${this.method}: ${this.detail}`;
145 }
146}
147
148/**
149 * CodexProcessError - codex app-server process lifecycle failure.
150 */
151export class CodexProcessError extends Schema.TaggedErrorClass<CodexProcessError>()(
152 "CodexProcessError",
153 {
154 sessionId: Schema.String,
155 detail: Schema.String,
156 cause: Schema.optional(Schema.Defect),
157 },
158) {
159 override get message(): string {
160 return `Codex process error for session ${this.sessionId}: ${this.detail}`;
161 }
162}
163
164/**
165 * ProviderValidationError - Invalid provider API input.
166 */
167export class ProviderValidationError extends Schema.TaggedErrorClass<ProviderValidationError>()(
168 "ProviderValidationError",
169 {
170 operation: Schema.String,
171 issue: Schema.String,
172 cause: Schema.optional(Schema.Defect),
173 },
174) {
175 override get message(): string {
176 return `Provider validation failed in ${this.operation}: ${this.issue}`;
177 }
178}
179
180/**
181 * ProviderUnsupportedError - Requested provider is not implemented.
182 */
183export class ProviderUnsupportedError extends Schema.TaggedErrorClass<ProviderUnsupportedError>()(
184 "ProviderUnsupportedError",
185 {
186 provider: Schema.String,
187 cause: Schema.optional(Schema.Defect),
188 },
189) {
190 override get message(): string {
191 return `Provider '${this.provider}' is not implemented`;
192 }
193}
194
195/**
196 * ProviderSessionNotFoundError - Provider-facing session not found.
197 */
198export class ProviderSessionNotFoundError extends Schema.TaggedErrorClass<ProviderSessionNotFoundError>()(
199 "ProviderSessionNotFoundError",
200 {
201 sessionId: Schema.String,
202 cause: Schema.optional(Schema.Defect),
203 },
204) {
205 override get message(): string {
206 return `Unknown provider session: ${this.sessionId}`;
207 }
208}
209
210/**
211 * ProviderCheckpointUnavailableError - Checkpointing unavailable for this session.
212 */
213export class ProviderCheckpointUnavailableError extends Schema.TaggedErrorClass<ProviderCheckpointUnavailableError>()(
214 "ProviderCheckpointUnavailableError",
215 {
216 sessionId: Schema.String,
217 detail: Schema.String,
218 cause: Schema.optional(Schema.Defect),
219 },
220) {
221 override get message(): string {
222 return `Provider checkpoint unavailable for session ${this.sessionId}: ${this.detail}`;
223 }
224}
225
226/**
227 * ProviderCheckpointRangeError - Requested checkpoint range is invalid.
228 */
229export class ProviderCheckpointRangeError extends Schema.TaggedErrorClass<ProviderCheckpointRangeError>()(
230 "ProviderCheckpointRangeError",
231 {
232 sessionId: Schema.String,
233 fromTurnCount: Schema.Number,
234 toTurnCount: Schema.Number,
235 detail: Schema.String,
236 cause: Schema.optional(Schema.Defect),
237 },
238) {
239 override get message(): string {
240 return `Provider checkpoint range error for session ${this.sessionId}: ${this.fromTurnCount}..${this.toTurnCount} (${this.detail})`;
241 }
242}
243
244/**
245 * ProviderFilesystemError - Filesystem checkpoint capture/restore failure.
246 */
247export class ProviderFilesystemError extends Schema.TaggedErrorClass<ProviderFilesystemError>()(
248 "ProviderFilesystemError",
249 {
250 sessionId: Schema.String,
251 detail: Schema.String,
252 cause: Schema.optional(Schema.Defect),
253 },
254) {
255 override get message(): string {
256 return `Provider filesystem checkpoint error for session ${this.sessionId}: ${this.detail}`;
257 }
258}
259
260export type CheckpointStoreError =
261 | CheckpointValidationError
262 | CheckpointGitCommandError
263 | CheckpointUnavailableError
264 | CheckpointRepositoryError;
265
266export type CodexServiceError =
267 | CodexValidationError
268 | CodexSessionNotFoundError
269 | CodexSessionClosedError
270 | CodexProtocolError
271 | CodexRequestError
272 | CodexProcessError;
273
274export type ProviderServiceError =
275 | ProviderValidationError
276 | ProviderUnsupportedError
277 | ProviderSessionNotFoundError
278 | ProviderCheckpointUnavailableError
279 | ProviderCheckpointRangeError
280 | ProviderFilesystemError
281 | CodexServiceError
282 | CheckpointStoreError;
apps/server/src/provider/Services/CheckpointStore.ts created+91
...@@ -0,0 +1,91 @@
1/**
2 * CheckpointStore - Repository interface for filesystem-backed workspace checkpoints.
3 *
4 * Uses Effect `ServiceMap.Service` for dependency injection and exposes typed
5 * domain errors for checkpoint lifecycle operations.
6 *
7 * @module CheckpointStore
8 */
9import { ServiceMap } from "effect";
10import type { Effect } from "effect";
11
12import type { CheckpointStoreError } from "../Errors.ts";
13
14export interface CaptureCheckpointInput {
15 readonly cwd: string;
16 readonly threadId: string;
17 readonly turnCount: number;
18}
19
20export interface EnsureRootCheckpointInput {
21 readonly cwd: string;
22 readonly threadId: string;
23}
24
25export interface RestoreCheckpointInput {
26 readonly cwd: string;
27 readonly threadId: string;
28 readonly turnCount: number;
29}
30
31export interface DiffCheckpointsInput {
32 readonly cwd: string;
33 readonly threadId: string;
34 readonly fromTurnCount: number;
35 readonly toTurnCount: number;
36}
37
38export interface PruneAfterTurnInput {
39 readonly cwd: string;
40 readonly threadId: string;
41 readonly maxTurnCount: number;
42}
43
44export interface CheckpointStoreShape {
45 /**
46 * Check whether cwd is inside a Git worktree.
47 */
48 readonly isGitRepository: (cwd: string) => Effect.Effect<boolean, CheckpointStoreError>;
49
50 /**
51 * Capture a checkpoint for a thread at a turn count.
52 */
53 readonly captureCheckpoint: (input: CaptureCheckpointInput) => Effect.Effect<void, CheckpointStoreError>;
54
55 /**
56 * Check whether a checkpoint ref exists.
57 */
58 readonly hasCheckpoint: (input: RestoreCheckpointInput) => Effect.Effect<boolean, CheckpointStoreError>;
59
60 /**
61 * Ensure a turn-0 checkpoint exists for a thread.
62 */
63 readonly ensureRootCheckpoint: (
64 input: EnsureRootCheckpointInput,
65 ) => Effect.Effect<boolean, CheckpointStoreError>;
66
67 /**
68 * Restore workspace/staging state to a checkpoint.
69 */
70 readonly restoreCheckpoint: (
71 input: RestoreCheckpointInput,
72 ) => Effect.Effect<boolean, CheckpointStoreError>;
73
74 /**
75 * Compute patch diff between two checkpoint turn counts.
76 */
77 readonly diffCheckpoints: (input: DiffCheckpointsInput) => Effect.Effect<string, CheckpointStoreError>;
78
79 /**
80 * Delete checkpoints newer than the provided turn count.
81 */
82 readonly pruneAfterTurn: (input: PruneAfterTurnInput) => Effect.Effect<void, CheckpointStoreError>;
83}
84
85/**
86 * CheckpointStore - Context tag for checkpoint persistence and restore operations.
87 */
88export class CheckpointStore extends ServiceMap.Service<
89 CheckpointStore,
90 CheckpointStoreShape
91>()("provider/CheckpointStore") {}
apps/server/src/provider/Services/Codex.ts created+110
...@@ -0,0 +1,110 @@
1/**
2 * CodexService - Service interface for codex app-server session lifecycle and RPC.
3 *
4 * Uses Effect `ServiceMap.Service` for dependency injection and returns typed
5 * domain errors for process, protocol, and session operations.
6 *
7 * @module CodexService
8 */
9import type {
10 ProviderApprovalDecision,
11 ProviderEvent,
12 ProviderSendTurnInput,
13 ProviderSession,
14 ProviderSessionStartInput,
15 ProviderTurnStartResult,
16} from "@t3tools/contracts";
17import { ServiceMap } from "effect";
18import type { Effect } from "effect";
19
20import type { CodexServiceError } from "../Errors.ts";
21
22export interface CodexThreadTurnSnapshot {
23 readonly id: string;
24 readonly items: ReadonlyArray<unknown>;
25}
26
27export interface CodexThreadSnapshot {
28 readonly threadId: string;
29 readonly turns: ReadonlyArray<CodexThreadTurnSnapshot>;
30}
31
32export interface CodexServiceShape {
33 /**
34 * Start a codex app-server-backed provider session.
35 */
36 readonly startSession: (
37 input: ProviderSessionStartInput,
38 ) => Effect.Effect<ProviderSession, CodexServiceError>;
39
40 /**
41 * Send a new turn to an active codex session.
42 */
43 readonly sendTurn: (
44 input: ProviderSendTurnInput,
45 ) => Effect.Effect<ProviderTurnStartResult, CodexServiceError>;
46
47 /**
48 * Interrupt an active turn.
49 */
50 readonly interruptTurn: (
51 sessionId: string,
52 turnId?: string,
53 ) => Effect.Effect<void, CodexServiceError>;
54
55 /**
56 * Read a thread snapshot (including turns).
57 */
58 readonly readThread: (sessionId: string) => Effect.Effect<CodexThreadSnapshot, CodexServiceError>;
59
60 /**
61 * Roll back a thread by N turns.
62 */
63 readonly rollbackThread: (
64 sessionId: string,
65 numTurns: number,
66 ) => Effect.Effect<CodexThreadSnapshot, CodexServiceError>;
67
68 /**
69 * Respond to an interactive approval request from codex app-server.
70 */
71 readonly respondToRequest: (
72 sessionId: string,
73 requestId: string,
74 decision: ProviderApprovalDecision,
75 ) => Effect.Effect<void, CodexServiceError>;
76
77 /**
78 * Stop one session.
79 */
80 readonly stopSession: (sessionId: string) => Effect.Effect<void, CodexServiceError>;
81
82 /**
83 * List active sessions.
84 */
85 readonly listSessions: () => Effect.Effect<ReadonlyArray<ProviderSession>>;
86
87 /**
88 * Check whether session id is currently active.
89 */
90 readonly hasSession: (sessionId: string) => Effect.Effect<boolean>;
91
92 /**
93 * Stop all sessions.
94 */
95 readonly stopAll: () => Effect.Effect<void, CodexServiceError>;
96
97 /**
98 * Subscribe to provider events emitted by codex app-server.
99 */
100 readonly subscribeToEvents: (
101 callback: (event: ProviderEvent) => void,
102 ) => Effect.Effect<() => void, CodexServiceError>;
103}
104
105/**
106 * CodexService - Context tag for codex session and RPC operations.
107 */
108export class CodexService extends ServiceMap.Service<CodexService, CodexServiceShape>()(
109 "provider/CodexService",
110) {}
apps/server/src/provider/Services/Provider.ts created+113
...@@ -0,0 +1,113 @@
1/**
2 * ProviderService - Service interface for provider sessions, turns, and checkpoints.
3 *
4 * Uses Effect `ServiceMap.Service` for dependency injection and returns typed
5 * domain errors for validation, session, codex, and checkpoint workflows.
6 *
7 * @module ProviderService
8 */
9import type {
10 ProviderEvent,
11 ProviderGetCheckpointDiffInput,
12 ProviderGetCheckpointDiffResult,
13 ProviderInterruptTurnInput,
14 ProviderListCheckpointsInput,
15 ProviderListCheckpointsResult,
16 ProviderRespondToRequestInput,
17 ProviderRevertToCheckpointInput,
18 ProviderRevertToCheckpointResult,
19 ProviderSendTurnInput,
20 ProviderSession,
21 ProviderSessionStartInput,
22 ProviderStopSessionInput,
23 ProviderTurnStartResult,
24} from "@t3tools/contracts";
25import { ServiceMap } from "effect";
26import type { Effect } from "effect";
27
28import type { ProviderServiceError } from "../Errors.ts";
29
30export interface ProviderServiceShape {
31 /**
32 * Start a provider session.
33 */
34 readonly startSession: (
35 input: ProviderSessionStartInput,
36 ) => Effect.Effect<ProviderSession, ProviderServiceError>;
37
38 /**
39 * Send a provider turn.
40 */
41 readonly sendTurn: (
42 input: ProviderSendTurnInput,
43 ) => Effect.Effect<ProviderTurnStartResult, ProviderServiceError>;
44
45 /**
46 * Interrupt a running provider turn.
47 */
48 readonly interruptTurn: (
49 input: ProviderInterruptTurnInput,
50 ) => Effect.Effect<void, ProviderServiceError>;
51
52 /**
53 * Respond to a provider approval request.
54 */
55 readonly respondToRequest: (
56 input: ProviderRespondToRequestInput,
57 ) => Effect.Effect<void, ProviderServiceError>;
58
59 /**
60 * Stop a provider session.
61 */
62 readonly stopSession: (input: ProviderStopSessionInput) => Effect.Effect<void, ProviderServiceError>;
63
64 /**
65 * List active provider sessions.
66 */
67 readonly listSessions: () => Effect.Effect<ReadonlyArray<ProviderSession>>;
68
69 /**
70 * List checkpoints for a provider session.
71 */
72 readonly listCheckpoints: (
73 input: ProviderListCheckpointsInput,
74 ) => Effect.Effect<ProviderListCheckpointsResult, ProviderServiceError>;
75
76 /**
77 * Diff two checkpoints for a provider session.
78 */
79 readonly getCheckpointDiff: (
80 input: ProviderGetCheckpointDiffInput,
81 ) => Effect.Effect<ProviderGetCheckpointDiffResult, ProviderServiceError>;
82
83 /**
84 * Revert a provider session to a checkpoint.
85 */
86 readonly revertToCheckpoint: (
87 input: ProviderRevertToCheckpointInput,
88 ) => Effect.Effect<ProviderRevertToCheckpointResult, ProviderServiceError>;
89
90 /**
91 * Stop all provider sessions.
92 */
93 readonly stopAll: () => Effect.Effect<void, ProviderServiceError>;
94
95 /**
96 * Dispose provider resources (logs/listeners).
97 */
98 readonly dispose: () => Effect.Effect<void, ProviderServiceError>;
99
100 /**
101 * Subscribe to provider event stream.
102 */
103 readonly subscribeToEvents: (
104 callback: (event: ProviderEvent) => void,
105 ) => Effect.Effect<() => void, ProviderServiceError>;
106}
107
108/**
109 * ProviderService - Context tag for provider orchestration.
110 */
111export class ProviderService extends ServiceMap.Service<ProviderService, ProviderServiceShape>()(
112 "provider/ProviderService",
113) {}
apps/server/src/provider/contracts.test.ts created+125
...@@ -0,0 +1,125 @@
1import { describe, expect, it } from "vitest";
2
3import { Effect, Layer, ManagedRuntime } from "effect";
4
5import {
6 CheckpointValidationError,
7 CodexValidationError,
8 ProviderFilesystemError,
9 ProviderUnsupportedError,
10 type CheckpointStoreError,
11 type CodexServiceError,
12 type ProviderServiceError,
13} from "./Errors.ts";
14import { CheckpointStore } from "./Services/CheckpointStore.ts";
15import { CodexService } from "./Services/Codex.ts";
16import { ProviderService } from "./Services/Provider.ts";
17
18describe("provider service contracts", () => {
19 it("resolves provider, codex, and checkpoint services from ServiceMap tags", async () => {
20 const providerLive: typeof ProviderService.Service = {
21 startSession: () =>
22 Effect.fail<ProviderServiceError>(new ProviderUnsupportedError({ provider: "x" })),
23 sendTurn: () =>
24 Effect.fail<ProviderServiceError>(new ProviderUnsupportedError({ provider: "x" })),
25 interruptTurn: () =>
26 Effect.fail<ProviderServiceError>(new ProviderUnsupportedError({ provider: "x" })),
27 respondToRequest: () =>
28 Effect.fail<ProviderServiceError>(new ProviderUnsupportedError({ provider: "x" })),
29 stopSession: () => Effect.void,
30 listSessions: () => Effect.succeed([]),
31 listCheckpoints: () =>
32 Effect.fail<ProviderServiceError>(new ProviderUnsupportedError({ provider: "x" })),
33 getCheckpointDiff: () =>
34 Effect.fail<ProviderServiceError>(new ProviderUnsupportedError({ provider: "x" })),
35 revertToCheckpoint: () =>
36 Effect.fail<ProviderServiceError>(new ProviderUnsupportedError({ provider: "x" })),
37 stopAll: () => Effect.void,
38 dispose: () => Effect.void,
39 subscribeToEvents: () => Effect.succeed(() => undefined),
40 };
41
42 const codexLive: typeof CodexService.Service = {
43 startSession: () =>
44 Effect.fail<CodexServiceError>(
45 new CodexValidationError({ operation: "startSession", issue: "not implemented" }),
46 ),
47 sendTurn: () =>
48 Effect.fail<CodexServiceError>(
49 new CodexValidationError({ operation: "sendTurn", issue: "not implemented" }),
50 ),
51 interruptTurn: () =>
52 Effect.fail<CodexServiceError>(
53 new CodexValidationError({ operation: "interruptTurn", issue: "not implemented" }),
54 ),
55 readThread: () =>
56 Effect.fail<CodexServiceError>(
57 new CodexValidationError({ operation: "readThread", issue: "not implemented" }),
58 ),
59 rollbackThread: () =>
60 Effect.fail<CodexServiceError>(
61 new CodexValidationError({ operation: "rollbackThread", issue: "not implemented" }),
62 ),
63 respondToRequest: () =>
64 Effect.fail<CodexServiceError>(
65 new CodexValidationError({ operation: "respondToRequest", issue: "not implemented" }),
66 ),
67 stopSession: () => Effect.void,
68 listSessions: () => Effect.succeed([]),
69 hasSession: () => Effect.succeed(false),
70 stopAll: () => Effect.void,
71 subscribeToEvents: () => Effect.succeed(() => undefined),
72 };
73
74 const checkpointLive: typeof CheckpointStore.Service = {
75 isGitRepository: () => Effect.succeed(false),
76 captureCheckpoint: () => Effect.void,
77 hasCheckpoint: () => Effect.succeed(false),
78 ensureRootCheckpoint: () => Effect.succeed(false),
79 restoreCheckpoint: () => Effect.succeed(false),
80 diffCheckpoints: () =>
81 Effect.fail<CheckpointStoreError>(
82 new CheckpointValidationError({
83 operation: "diffCheckpoints",
84 issue: "not implemented",
85 }),
86 ),
87 pruneAfterTurn: () => Effect.void,
88 };
89
90 const runtime = ManagedRuntime.make(
91 Layer.mergeAll(
92 Layer.succeed(ProviderService, providerLive),
93 Layer.succeed(CodexService, codexLive),
94 Layer.succeed(CheckpointStore, checkpointLive),
95 ),
96 );
97
98 const [provider, codex, checkpoint] = await Promise.all([
99 runtime.runPromise(Effect.service(ProviderService)),
100 runtime.runPromise(Effect.service(CodexService)),
101 runtime.runPromise(Effect.service(CheckpointStore)),
102 ]);
103
104 expect(provider).toBe(providerLive);
105 expect(codex).toBe(codexLive);
106 expect(checkpoint).toBe(checkpointLive);
107
108 await runtime.dispose();
109 });
110});
111
112describe("provider error taxonomy", () => {
113 it("keeps defect cause and message on tagged errors", () => {
114 const cause = new Error("filesystem failed");
115 const error = new ProviderFilesystemError({
116 sessionId: "sess-1",
117 detail: "capture failed",
118 cause,
119 });
120
121 expect(error._tag).toBe("ProviderFilesystemError");
122 expect(error.message).toContain("sess-1");
123 expect(error.cause).toBe(cause);
124 });
125});