@effect-agent/testing 0.0.1-beta.4 → 0.0.1-beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,488 @@
1
+ import {
2
+ CodeExecutionHost,
3
+ CodeExecutionLimits,
4
+ CodeExecutionNamespace,
5
+ CodeExecutionProtocolError,
6
+ CodeExecutionRequest,
7
+ CodeExecutionResourceUse,
8
+ CodeExecutionResult,
9
+ CodeExecutionTimeoutError,
10
+ CodeExecutor,
11
+ type CodeExecutorExecute,
12
+ CodeExecutorUnsupportedError,
13
+ CodeHostCall,
14
+ CodeHostCallLimitError,
15
+ CodeHostCallResult,
16
+ CodeOutputLimitError,
17
+ CodeProgramFailedError,
18
+ CodeSourceError,
19
+ SandboxImplementation,
20
+ } from "@effect-agent/sandbox";
21
+ import { Clock, Duration, Effect, Fiber, Layer, Option, Queue, Schema } from "effect";
22
+
23
+ /**
24
+ * The deterministic in-process executor substitute (C1 of ADR-0017). It runs
25
+ * the generated program on the host JavaScript engine with best-effort global
26
+ * shadowing only, so it self-identifies as `unisolated` and is never a
27
+ * security boundary (CAP-010, CAP-015). It exists to prove the public
28
+ * `CodeExecutor` contract and to drive deterministic capability tests.
29
+ */
30
+ export const inProcessCodeExecutorImplementation = SandboxImplementation.make({
31
+ isolation: "unisolated",
32
+ identity: "in-process-javascript",
33
+ });
34
+
35
+ // These two caps mirror the wire schema bounds (`BoundedLogs` is at most
36
+ // 4096 lines of at most 16 KiB each): capture must stay inside what
37
+ // `CodeExecutionResult` can carry. A line over the per-line cap is truncated
38
+ // with an explicit `…` marker; exceeding either the byte budget or the line
39
+ // cap fails the pass typed.
40
+ const MAX_LOG_LINES = 4_096;
41
+ const MAX_LOG_LINE_CHARACTERS = 16_000;
42
+ const MAX_THROWN_CHARACTERS = 4_000;
43
+
44
+ const utf8ByteLength = (value: string): number => new TextEncoder().encode(value).byteLength;
45
+
46
+ /**
47
+ * Ambient globals shadowed inside the harness. Shadowing blocks the obvious
48
+ * identifier paths only; a determined program can still escape, which is
49
+ * exactly why this executor reports `unisolated` and the isolated network and
50
+ * CPU enforcement conformance cases run only against isolated adapters.
51
+ */
52
+ const shadowedGlobals = [
53
+ "fetch",
54
+ "process",
55
+ "require",
56
+ "module",
57
+ "exports",
58
+ "global",
59
+ "globalThis",
60
+ "XMLHttpRequest",
61
+ "WebSocket",
62
+ "Deno",
63
+ "Bun",
64
+ ] as const;
65
+
66
+ class LogLimitSignal {
67
+ constructor(readonly observed: number) {}
68
+ }
69
+
70
+ class EvaluationThrew {
71
+ constructor(readonly inner: unknown) {}
72
+ }
73
+
74
+ class NotAFunction {
75
+ constructor(readonly actual: string) {}
76
+ }
77
+
78
+ interface LogCapture {
79
+ readonly lines: Array<string>;
80
+ bytes: number;
81
+ }
82
+
83
+ /**
84
+ * Total, defect-free rendering of untrusted values: a hostile Proxy can throw
85
+ * from property access, `toString`, and `Symbol.toPrimitive`, and an expected
86
+ * program failure must never escape the typed channel as a defect while its
87
+ * diagnostics are being serialized.
88
+ */
89
+ const formatLogValue = (value: unknown): string => {
90
+ try {
91
+ if (typeof value === "string") {
92
+ return value;
93
+ }
94
+ return JSON.stringify(value) ?? String(value);
95
+ } catch {
96
+ try {
97
+ return String(value);
98
+ } catch {
99
+ return "[unprintable value]";
100
+ }
101
+ }
102
+ };
103
+
104
+ const makeConsole = (capture: LogCapture, limits: CodeExecutionLimits) => {
105
+ const write = (...values: ReadonlyArray<unknown>): void => {
106
+ const joined = values.map(formatLogValue).join(" ");
107
+ const line =
108
+ joined.length > MAX_LOG_LINE_CHARACTERS
109
+ ? `${joined.slice(0, MAX_LOG_LINE_CHARACTERS - 1)}…`
110
+ : joined;
111
+ const bytes = utf8ByteLength(line);
112
+ if (capture.lines.length >= MAX_LOG_LINES || capture.bytes + bytes > limits.maxLogBytes) {
113
+ throw new LogLimitSignal(capture.bytes + bytes);
114
+ }
115
+ capture.lines.push(line);
116
+ capture.bytes += bytes;
117
+ };
118
+ return { debug: write, error: write, info: write, log: write, warn: write };
119
+ };
120
+
121
+ interface PendingHostCall {
122
+ readonly namespace: string;
123
+ readonly method: string;
124
+ readonly argument: unknown;
125
+ readonly resolve: (value: unknown) => void;
126
+ readonly reject: (reason: unknown) => void;
127
+ }
128
+
129
+ const buildNamespaceObject = (
130
+ namespace: CodeExecutionNamespace,
131
+ offer: (pending: PendingHostCall) => void,
132
+ ): Record<string, unknown> => {
133
+ const methods: Record<string, unknown> = {};
134
+ for (const method of namespace.methods) {
135
+ methods[method] = (argument: unknown) =>
136
+ new Promise((resolve, reject) => {
137
+ offer({ namespace: namespace.name, method, argument, resolve, reject });
138
+ });
139
+ }
140
+ return methods;
141
+ };
142
+
143
+ const boundedText = (value: unknown): string => {
144
+ try {
145
+ const text = value instanceof Error ? `${value.name}: ${value.message}` : formatLogValue(value);
146
+ return text.slice(0, MAX_THROWN_CHARACTERS);
147
+ } catch {
148
+ return "[unserializable thrown value]";
149
+ }
150
+ };
151
+
152
+ /** Schema decoding of hostile values may itself throw through trap getters. */
153
+ const safeDecodeJson = (value: unknown): Option.Option<Schema.Json> => {
154
+ try {
155
+ return Schema.decodeUnknownOption(Schema.Json)(value);
156
+ } catch {
157
+ return Option.none();
158
+ }
159
+ };
160
+
161
+ const boundedThrown = (value: unknown): Schema.Json => {
162
+ const decoded = safeDecodeJson(value);
163
+ if (Option.isSome(decoded)) {
164
+ try {
165
+ const encoded = JSON.stringify(decoded.value);
166
+ if (encoded !== undefined && encoded.length <= MAX_THROWN_CHARACTERS) {
167
+ return decoded.value;
168
+ }
169
+ } catch {
170
+ // fall through to the bounded string form
171
+ }
172
+ }
173
+ return boundedText(value);
174
+ };
175
+
176
+ const encodedJsonByteLength = (value: Schema.Json): number | undefined => {
177
+ try {
178
+ const encoded = JSON.stringify(value);
179
+ return encoded === undefined ? undefined : utf8ByteLength(encoded);
180
+ } catch {
181
+ return undefined;
182
+ }
183
+ };
184
+
185
+ /** Host outcomes are protocol input; a hostile value must not defect mid-decode. */
186
+ const decodeHostOutcome = (value: unknown): Option.Option<CodeHostCallResult> => {
187
+ try {
188
+ return Schema.decodeUnknownOption(CodeHostCallResult)(value);
189
+ } catch {
190
+ return Option.none();
191
+ }
192
+ };
193
+
194
+ const validateRequest = (
195
+ request: CodeExecutionRequest,
196
+ ): Effect.Effect<void, CodeExecutorUnsupportedError | CodeSourceError> =>
197
+ Effect.gen(function* () {
198
+ if (request.network._tag !== "NetworkDisabled") {
199
+ return yield* CodeExecutorUnsupportedError.make({
200
+ implementation: inProcessCodeExecutorImplementation,
201
+ feature: "network",
202
+ message:
203
+ "The unisolated in-process executor cannot enforce an egress allowlist; only NetworkDisabled is accepted, and even that is shadowed rather than enforced",
204
+ });
205
+ }
206
+ if (request.limits.cpuMillis !== undefined) {
207
+ return yield* CodeExecutorUnsupportedError.make({
208
+ implementation: inProcessCodeExecutorImplementation,
209
+ feature: "cpu-limit",
210
+ message:
211
+ "The unisolated in-process executor shares the host engine and cannot enforce a CPU limit",
212
+ });
213
+ }
214
+ const reservedNames = new Set<string>([...shadowedGlobals, "console"]);
215
+ const seen = new Set<string>();
216
+ for (const namespace of request.namespaces) {
217
+ if (reservedNames.has(namespace.name) || seen.has(namespace.name)) {
218
+ return yield* CodeExecutorUnsupportedError.make({
219
+ implementation: inProcessCodeExecutorImplementation,
220
+ feature: "namespaces",
221
+ message: `Namespace ${namespace.name} collides with a harness binding or another namespace`,
222
+ });
223
+ }
224
+ seen.add(namespace.name);
225
+ }
226
+ const sourceBytes = utf8ByteLength(request.source);
227
+ if (sourceBytes > request.limits.maxSourceBytes) {
228
+ return yield* CodeSourceError.make({
229
+ implementation: inProcessCodeExecutorImplementation,
230
+ reason: "oversized",
231
+ message: `Source is ${sourceBytes} bytes; the request allows ${request.limits.maxSourceBytes}`,
232
+ });
233
+ }
234
+ });
235
+
236
+ const serveHostCalls = (
237
+ host: CodeExecutionHost["Service"],
238
+ queue: Queue.Queue<PendingHostCall>,
239
+ limits: CodeExecutionLimits,
240
+ capture: LogCapture,
241
+ counter: { calls: number },
242
+ ): Effect.Effect<
243
+ never,
244
+ CodeHostCallLimitError | CodeOutputLimitError | CodeExecutionProtocolError
245
+ > =>
246
+ Effect.gen(function* () {
247
+ while (true) {
248
+ const pending = yield* Queue.take(queue);
249
+ counter.calls += 1;
250
+ if (counter.calls > limits.maxHostCalls) {
251
+ return yield* CodeHostCallLimitError.make({
252
+ implementation: inProcessCodeExecutorImplementation,
253
+ limit: limits.maxHostCalls,
254
+ logs: [...capture.lines],
255
+ });
256
+ }
257
+ const argument = safeDecodeJson(pending.argument);
258
+ if (Option.isNone(argument)) {
259
+ pending.reject(new TypeError("host call arguments must be JSON values"));
260
+ continue;
261
+ }
262
+ const argumentBytes = encodedJsonByteLength(argument.value);
263
+ if (argumentBytes === undefined || argumentBytes > limits.maxHostCallArgumentBytes) {
264
+ return yield* CodeOutputLimitError.make({
265
+ implementation: inProcessCodeExecutorImplementation,
266
+ surface: "host-call-argument",
267
+ limit: limits.maxHostCallArgumentBytes,
268
+ observed: argumentBytes ?? 0,
269
+ logs: [...capture.lines],
270
+ });
271
+ }
272
+ const rawOutcome = yield* host.call(
273
+ CodeHostCall.make({
274
+ namespace: pending.namespace,
275
+ method: pending.method,
276
+ argument: argument.value,
277
+ }),
278
+ );
279
+ const outcome = decodeHostOutcome(rawOutcome);
280
+ if (Option.isNone(outcome)) {
281
+ return yield* CodeExecutionProtocolError.make({
282
+ implementation: inProcessCodeExecutorImplementation,
283
+ message: "The execution host returned a value outside the CodeHostCallResult schema",
284
+ });
285
+ }
286
+ if (outcome.value._tag === "CodeHostCallFailure") {
287
+ pending.reject(outcome.value.error);
288
+ continue;
289
+ }
290
+ const resultBytes = encodedJsonByteLength(outcome.value.value);
291
+ if (resultBytes === undefined || resultBytes > limits.maxHostCallResultBytes) {
292
+ return yield* CodeOutputLimitError.make({
293
+ implementation: inProcessCodeExecutorImplementation,
294
+ surface: "host-call-result",
295
+ limit: limits.maxHostCallResultBytes,
296
+ observed: resultBytes ?? 0,
297
+ logs: [...capture.lines],
298
+ });
299
+ }
300
+ pending.resolve(outcome.value.value);
301
+ }
302
+ });
303
+
304
+ const classifyProgramFailure = (
305
+ thrown: unknown,
306
+ limits: CodeExecutionLimits,
307
+ capture: LogCapture,
308
+ ): CodeOutputLimitError | CodeSourceError | CodeProgramFailedError => {
309
+ const inner = thrown instanceof EvaluationThrew ? thrown.inner : thrown;
310
+ if (inner instanceof LogLimitSignal) {
311
+ return CodeOutputLimitError.make({
312
+ implementation: inProcessCodeExecutorImplementation,
313
+ surface: "logs",
314
+ limit: limits.maxLogBytes,
315
+ observed: inner.observed,
316
+ logs: [...capture.lines],
317
+ });
318
+ }
319
+ if (inner instanceof NotAFunction) {
320
+ return CodeSourceError.make({
321
+ implementation: inProcessCodeExecutorImplementation,
322
+ reason: "not-a-function",
323
+ message: `The source expression evaluated to ${inner.actual}; it must evaluate to one async function`,
324
+ });
325
+ }
326
+ // An async function converts a body-level `throw` into a rejection, so the
327
+ // split is by value shape: exception-like values read as `threw`, plain
328
+ // rejection values (an uncaught host failure envelope) read as `rejected`.
329
+ const reason = thrown instanceof EvaluationThrew || inner instanceof Error ? "threw" : "rejected";
330
+ return CodeProgramFailedError.make({
331
+ implementation: inProcessCodeExecutorImplementation,
332
+ reason,
333
+ thrown: boundedThrown(inner),
334
+ message: boundedText(inner),
335
+ logs: [...capture.lines],
336
+ });
337
+ };
338
+
339
+ const executeInProcess: CodeExecutorExecute = Effect.fn("InProcessCodeExecutor.execute")(
340
+ function* (request) {
341
+ yield* validateRequest(request);
342
+ const host = yield* CodeExecutionHost;
343
+ const capture: LogCapture = { lines: [], bytes: 0 };
344
+ const counter = { calls: 0 };
345
+ const queue = yield* Queue.unbounded<PendingHostCall>();
346
+
347
+ const factory = yield* Effect.try({
348
+ try: () =>
349
+ new Function(
350
+ ...shadowedGlobals,
351
+ "console",
352
+ ...request.namespaces.map((namespace) => namespace.name),
353
+ `"use strict";\nreturn (\n${request.source}\n);`,
354
+ ),
355
+ catch: (cause) =>
356
+ CodeSourceError.make({
357
+ implementation: inProcessCodeExecutorImplementation,
358
+ reason: "invalid",
359
+ message: boundedText(cause),
360
+ }),
361
+ });
362
+
363
+ const harnessConsole = makeConsole(capture, request.limits);
364
+ // Admission is enforced at call creation, not only at the single-consumer
365
+ // dequeue: a burst of unawaited calls can enqueue at most one entry past
366
+ // the cap (the entry the server fails the pass on); everything beyond is
367
+ // rejected synchronously, so the queue stays bounded against hostile
368
+ // programs.
369
+ let issuedHostCalls = 0;
370
+ const namespaceObjects = request.namespaces.map((namespace) =>
371
+ buildNamespaceObject(namespace, (pending) => {
372
+ issuedHostCalls += 1;
373
+ if (issuedHostCalls > request.limits.maxHostCalls + 1) {
374
+ pending.reject(new Error(`host-call limit of ${request.limits.maxHostCalls} exceeded`));
375
+ return;
376
+ }
377
+ Queue.offerUnsafe(queue, pending);
378
+ }),
379
+ );
380
+
381
+ const server = yield* serveHostCalls(host, queue, request.limits, capture, counter).pipe(
382
+ Effect.forkScoped,
383
+ );
384
+
385
+ const program = Effect.tryPromise({
386
+ try: async () => {
387
+ let candidate: unknown;
388
+ try {
389
+ candidate = factory(
390
+ ...shadowedGlobals.map(() => undefined),
391
+ harnessConsole,
392
+ ...namespaceObjects,
393
+ );
394
+ } catch (cause) {
395
+ throw new EvaluationThrew(cause);
396
+ }
397
+ if (typeof candidate !== "function") {
398
+ throw new EvaluationThrew(new NotAFunction(typeof candidate));
399
+ }
400
+ let outcome: unknown;
401
+ try {
402
+ outcome = (candidate as () => unknown)();
403
+ } catch (cause) {
404
+ throw new EvaluationThrew(cause);
405
+ }
406
+ return await Promise.resolve(outcome);
407
+ },
408
+ catch: (thrown) => classifyProgramFailure(thrown, request.limits, capture),
409
+ });
410
+
411
+ const startedAt = yield* Clock.currentTimeMillis;
412
+ // The wall-clock deadline interrupts only at asynchronous suspension
413
+ // points: a synchronous runaway shares the host thread and cannot be
414
+ // stopped in-process — exactly why the platform CPU enforcement cases
415
+ // belong to isolated adapters only (testing spec §8.1). The server fiber
416
+ // is interrupted when the pass settles so no host call outlives the
417
+ // program that issued it.
418
+ const returned = yield* Effect.raceFirst(program, Fiber.join(server)).pipe(
419
+ Effect.timeoutOrElse({
420
+ duration: request.limits.maxWallTime,
421
+ orElse: () =>
422
+ CodeExecutionTimeoutError.make({
423
+ implementation: inProcessCodeExecutorImplementation,
424
+ kind: "wall-clock",
425
+ maxWallTime: request.limits.maxWallTime,
426
+ logs: [...capture.lines],
427
+ }),
428
+ }),
429
+ Effect.ensuring(Fiber.interrupt(server)),
430
+ );
431
+ const finishedAt = yield* Clock.currentTimeMillis;
432
+
433
+ // An unawaited burst can outrun the server: the program may return before
434
+ // the over-limit entry is dequeued, so the admission counter is the
435
+ // authority — a pass that ISSUED more calls than the cap fails even when
436
+ // its promise settled first.
437
+ if (issuedHostCalls > request.limits.maxHostCalls) {
438
+ return yield* CodeHostCallLimitError.make({
439
+ implementation: inProcessCodeExecutorImplementation,
440
+ limit: request.limits.maxHostCalls,
441
+ logs: [...capture.lines],
442
+ });
443
+ }
444
+
445
+ const value = yield* Schema.decodeUnknownEffect(Schema.Json)(returned).pipe(
446
+ Effect.mapError(() =>
447
+ CodeProgramFailedError.make({
448
+ implementation: inProcessCodeExecutorImplementation,
449
+ reason: "non-json-result",
450
+ thrown: null,
451
+ message: "The program must return a JSON value",
452
+ logs: [...capture.lines],
453
+ }),
454
+ ),
455
+ );
456
+ const resultBytes = encodedJsonByteLength(value);
457
+ if (resultBytes === undefined || resultBytes > request.limits.maxResultBytes) {
458
+ return yield* CodeOutputLimitError.make({
459
+ implementation: inProcessCodeExecutorImplementation,
460
+ surface: "result",
461
+ limit: request.limits.maxResultBytes,
462
+ observed: resultBytes ?? 0,
463
+ logs: [...capture.lines],
464
+ });
465
+ }
466
+
467
+ return CodeExecutionResult.make({
468
+ implementation: inProcessCodeExecutorImplementation,
469
+ value,
470
+ logs: [...capture.lines],
471
+ resourceUse: CodeExecutionResourceUse.make({
472
+ wallTime: Duration.millis(Math.max(0, finishedAt - startedAt)),
473
+ hostCalls: counter.calls,
474
+ logBytes: capture.bytes,
475
+ resultBytes,
476
+ }),
477
+ });
478
+ },
479
+ );
480
+
481
+ /**
482
+ * Layer providing the unisolated in-process `CodeExecutor` substitute. The
483
+ * per-pass `CodeExecutionHost` stays in the caller's requirement channel, the
484
+ * same as every real adapter.
485
+ */
486
+ export const inProcessCodeExecutorLayer: Layer.Layer<CodeExecutor> = Layer.succeed(CodeExecutor)(
487
+ CodeExecutor.of({ execute: executeInProcess }),
488
+ );
@@ -34,7 +34,7 @@ export class ResearchDocument extends Schema.Class<ResearchDocument>("ResearchDo
34
34
  body: BoundedBody,
35
35
  }) {}
36
36
 
37
- export class DocumentUnavailable extends Schema.TaggedErrorClass<DocumentUnavailable>()(
37
+ export class DocumentUnavailable extends Schema.TaggedError<DocumentUnavailable>()(
38
38
  "DocumentUnavailable",
39
39
  {
40
40
  documentId: ResearchDocumentId,
@@ -212,7 +212,7 @@ export class SummaryFinding extends Schema.Class<SummaryFinding>("SummaryFinding
212
212
  summary: BoundedSummary,
213
213
  }) {}
214
214
 
215
- export class DocumentSummaryFailed extends Schema.TaggedErrorClass<DocumentSummaryFailed>()(
215
+ export class DocumentSummaryFailed extends Schema.TaggedError<DocumentSummaryFailed>()(
216
216
  "DocumentSummaryFailed",
217
217
  {
218
218
  childErrorTag: Schema.NonEmptyString,
@@ -84,19 +84,19 @@ export class TravelPlan extends Schema.Class<TravelPlan>("TravelPlan")({
84
84
  }) {}
85
85
 
86
86
  const unavailableFields = { query: Schema.String, message: Schema.String };
87
- export class FlightUnavailable extends Schema.TaggedErrorClass<FlightUnavailable>()(
87
+ export class FlightUnavailable extends Schema.TaggedError<FlightUnavailable>()(
88
88
  "FlightUnavailable",
89
89
  unavailableFields,
90
90
  ) {}
91
- export class LodgingUnavailable extends Schema.TaggedErrorClass<LodgingUnavailable>()(
91
+ export class LodgingUnavailable extends Schema.TaggedError<LodgingUnavailable>()(
92
92
  "LodgingUnavailable",
93
93
  unavailableFields,
94
94
  ) {}
95
- export class ActivityUnavailable extends Schema.TaggedErrorClass<ActivityUnavailable>()(
95
+ export class ActivityUnavailable extends Schema.TaggedError<ActivityUnavailable>()(
96
96
  "ActivityUnavailable",
97
97
  unavailableFields,
98
98
  ) {}
99
- export class GuidanceFailure extends Schema.TaggedErrorClass<GuidanceFailure>()("GuidanceFailure", {
99
+ export class GuidanceFailure extends Schema.TaggedError<GuidanceFailure>()("GuidanceFailure", {
100
100
  message: Schema.String,
101
101
  }) {}
102
102
 
@@ -196,7 +196,7 @@ export class SupplierBookingRecord extends Schema.Class<SupplierBookingRecord>(
196
196
  status: Schema.Literals(["confirmed", "cancelled"]),
197
197
  }) {}
198
198
 
199
- export class SupplierUnavailable extends Schema.TaggedErrorClass<SupplierUnavailable>()(
199
+ export class SupplierUnavailable extends Schema.TaggedError<SupplierUnavailable>()(
200
200
  "SupplierUnavailable",
201
201
  { message: Schema.String },
202
202
  ) {}
@@ -30,7 +30,7 @@ export class ItineraryHold extends Schema.Class<ItineraryHold>(
30
30
  status: Schema.Literal("held"),
31
31
  }) {}
32
32
 
33
- export class ItineraryHoldUnavailable extends Schema.TaggedErrorClass<ItineraryHoldUnavailable>()(
33
+ export class ItineraryHoldUnavailable extends Schema.TaggedError<ItineraryHoldUnavailable>()(
34
34
  "ItineraryHoldUnavailable",
35
35
  {
36
36
  quoteId: QuoteId,
@@ -119,7 +119,7 @@ export const phase3TravelPlannerEncodedFixture = Schema.encodeSync(Schema.Array(
119
119
  phase3TravelPlannerBatches,
120
120
  );
121
121
 
122
- export class TravelPlannerProjectionError extends Schema.TaggedErrorClass<TravelPlannerProjectionError>()(
122
+ export class TravelPlannerProjectionError extends Schema.TaggedError<TravelPlannerProjectionError>()(
123
123
  "TravelPlannerProjectionError",
124
124
  { message: Schema.String },
125
125
  ) {}
@@ -190,7 +190,7 @@ export const phase4TravelPlannerWorkerLayer = Layer.mergeAll(
190
190
  TravelGuidanceLayer,
191
191
  ).pipe(Layer.provide(CatalogLifecycle.layerNoDeps));
192
192
 
193
- export class TravelPlannerDurableEvidenceError extends Schema.TaggedErrorClass<TravelPlannerDurableEvidenceError>()(
193
+ export class TravelPlannerDurableEvidenceError extends Schema.TaggedError<TravelPlannerDurableEvidenceError>()(
194
194
  "TravelPlannerDurableEvidenceError",
195
195
  { message: Schema.String },
196
196
  ) {}
@@ -405,7 +405,7 @@ export const TravelPlannerPhase5 = Agent.define("travel-planner-phase-5", {
405
405
  metadata: { deploymentClass: "DN", phase: "P5" },
406
406
  });
407
407
 
408
- export class TravelPlannerBookingEvidenceError extends Schema.TaggedErrorClass<TravelPlannerBookingEvidenceError>()(
408
+ export class TravelPlannerBookingEvidenceError extends Schema.TaggedError<TravelPlannerBookingEvidenceError>()(
409
409
  "TravelPlannerBookingEvidenceError",
410
410
  { message: Schema.String },
411
411
  ) {}
@@ -25,7 +25,7 @@ export class DestinationFacts extends Schema.Class<DestinationFacts>("Destinatio
25
25
  advisory: Schema.NonEmptyString,
26
26
  }) {}
27
27
 
28
- export class DestinationGuideUnavailable extends Schema.TaggedErrorClass<DestinationGuideUnavailable>()(
28
+ export class DestinationGuideUnavailable extends Schema.TaggedError<DestinationGuideUnavailable>()(
29
29
  "DestinationGuideUnavailable",
30
30
  {
31
31
  destination: AirportCode,
@@ -177,7 +177,7 @@ export class DestinationResearchFindings extends Schema.Class<DestinationResearc
177
177
  summary: Schema.NonEmptyString,
178
178
  }) {}
179
179
 
180
- export class DestinationResearchFailed extends Schema.TaggedErrorClass<DestinationResearchFailed>()(
180
+ export class DestinationResearchFailed extends Schema.TaggedError<DestinationResearchFailed>()(
181
181
  "DestinationResearchFailed",
182
182
  {
183
183
  childErrorTag: Schema.NonEmptyString,