@effect-agent/testing 0.1.0-beta.24 → 0.1.0-beta.25
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.
- package/dist/index.d.mts +1 -87
- package/dist/index.mjs +34 -19
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -9
- package/src/certification.ts +2 -2
- package/src/chaos.ts +50 -25
- package/src/code-executor-conformance.d.ts +1 -1
- package/src/code-executor-conformance.ts +7 -5
- package/src/code-executor-substitute.ts +7 -4
- package/src/fixtures/docs-researcher/harness.ts +2 -1
- package/src/fixtures/docs-researcher/mcp.ts +12 -3
- package/src/fixtures/travel-planner/scenarios.ts +0 -3
- package/src/fixtures/travel-planner/subagents-durable.ts +5 -3
- package/src/fixtures/warehouse/index.ts +5 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effect-agent/testing",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.25",
|
|
4
4
|
"exports": {
|
|
5
5
|
".": {
|
|
6
6
|
"types": "./dist/index.d.mts",
|
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
}
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@effect-agent/capabilities": "0.1.0-beta.
|
|
12
|
-
"@effect-agent/core": "0.1.0-beta.
|
|
13
|
-
"@effect-agent/engine": "0.1.0-beta.
|
|
14
|
-
"@effect-agent/platform-node": "0.1.0-beta.
|
|
15
|
-
"@effect-agent/sandbox": "0.1.0-beta.
|
|
16
|
-
"@effect-agent/session": "0.1.0-beta.
|
|
17
|
-
"@effect-agent/storage-memory": "0.1.0-beta.
|
|
18
|
-
"@effect-agent/storage-sqlite": "0.1.0-beta.
|
|
11
|
+
"@effect-agent/capabilities": "0.1.0-beta.25",
|
|
12
|
+
"@effect-agent/core": "0.1.0-beta.25",
|
|
13
|
+
"@effect-agent/engine": "0.1.0-beta.25",
|
|
14
|
+
"@effect-agent/platform-node": "0.1.0-beta.25",
|
|
15
|
+
"@effect-agent/sandbox": "0.1.0-beta.25",
|
|
16
|
+
"@effect-agent/session": "0.1.0-beta.25",
|
|
17
|
+
"@effect-agent/storage-memory": "0.1.0-beta.25",
|
|
18
|
+
"@effect-agent/storage-sqlite": "0.1.0-beta.25",
|
|
19
19
|
"effect": "4.0.0-rc.110"
|
|
20
20
|
},
|
|
21
21
|
"description": "Scripted models, deterministic fixtures, and adapter conformance kits for testing Effect Agent applications.",
|
package/src/certification.ts
CHANGED
|
@@ -51,8 +51,8 @@ import {
|
|
|
51
51
|
type ResolvedBinding,
|
|
52
52
|
type SubmissionSnapshot,
|
|
53
53
|
} from "@effect-agent/session";
|
|
54
|
-
import type { CertificationCaseResult } from "@effect-agent/session/testing";
|
|
55
54
|
import {
|
|
55
|
+
type CertificationCaseResult,
|
|
56
56
|
CertificationReport,
|
|
57
57
|
CertificationSweepResult,
|
|
58
58
|
CertificationTierThreeReport,
|
|
@@ -670,7 +670,7 @@ const failureTagOf = <E>(cause: Cause.Cause<E>): string => {
|
|
|
670
670
|
if (Option.isSome(failure)) {
|
|
671
671
|
const error: unknown = failure.value;
|
|
672
672
|
if (typeof error === "object" && error !== null && "_tag" in error) {
|
|
673
|
-
return String(
|
|
673
|
+
return String(error._tag);
|
|
674
674
|
}
|
|
675
675
|
return String(error).slice(0, 256);
|
|
676
676
|
}
|
package/src/chaos.ts
CHANGED
|
@@ -10,9 +10,9 @@ import {
|
|
|
10
10
|
ConversationId,
|
|
11
11
|
IdGenerator,
|
|
12
12
|
RunId,
|
|
13
|
-
SubmissionId,
|
|
14
13
|
ToolCallId,
|
|
15
14
|
TurnId,
|
|
15
|
+
type SubmissionId,
|
|
16
16
|
} from "@effect-agent/core";
|
|
17
17
|
import { DurableStep, DurableStepError, ToolExecutionClass } from "@effect-agent/engine";
|
|
18
18
|
import {
|
|
@@ -42,6 +42,9 @@ import {
|
|
|
42
42
|
verifyConversationInvariants,
|
|
43
43
|
type CanonicalRecordEnvelope,
|
|
44
44
|
type BatchId,
|
|
45
|
+
type DurableBindingFailure,
|
|
46
|
+
type DurableSubmitFailure,
|
|
47
|
+
type DurableWorkerFailure,
|
|
45
48
|
type ProducerId,
|
|
46
49
|
type Receipt,
|
|
47
50
|
type ResolvedBinding,
|
|
@@ -51,7 +54,14 @@ import {
|
|
|
51
54
|
} from "@effect-agent/session";
|
|
52
55
|
import { Cause, Effect, Exit, Layer, Option, Ref, Schema, Stream } from "effect";
|
|
53
56
|
import { FastCheck } from "effect/testing";
|
|
54
|
-
import {
|
|
57
|
+
import {
|
|
58
|
+
LanguageModel,
|
|
59
|
+
Model,
|
|
60
|
+
Tool,
|
|
61
|
+
Toolkit,
|
|
62
|
+
type Prompt,
|
|
63
|
+
type Response,
|
|
64
|
+
} from "effect/unstable/ai";
|
|
55
65
|
|
|
56
66
|
/**
|
|
57
67
|
* P7 WP4 chaos machinery (plan §5): a Schema-first `ChaosPlan`, a seeded generator over
|
|
@@ -176,12 +186,14 @@ export class ChaosConvergenceFailure extends Schema.TaggedError<ChaosConvergence
|
|
|
176
186
|
/** Default root seed for chaos suites; override with the `CHAOS_SEED` environment variable. */
|
|
177
187
|
export const DEFAULT_CHAOS_SEED = 20260813;
|
|
178
188
|
|
|
189
|
+
const ChaosSeedFromEnvironment = Schema.FiniteFromString.check(Schema.isInt());
|
|
190
|
+
const decodeChaosSeedFromEnvironment = Schema.decodeUnknownOption(ChaosSeedFromEnvironment);
|
|
191
|
+
|
|
179
192
|
/** The root seed for this run: `CHAOS_SEED` when set to an integer, the default otherwise. */
|
|
180
193
|
export const chaosSeedFromEnv = (env: Record<string, string | undefined>): number => {
|
|
181
194
|
const raw = env["CHAOS_SEED"];
|
|
182
195
|
if (raw === undefined || raw === "") return DEFAULT_CHAOS_SEED;
|
|
183
|
-
|
|
184
|
-
return Number.isSafeInteger(parsed) ? parsed : DEFAULT_CHAOS_SEED;
|
|
196
|
+
return Option.getOrElse(decodeChaosSeedFromEnvironment(raw), () => DEFAULT_CHAOS_SEED);
|
|
185
197
|
};
|
|
186
198
|
|
|
187
199
|
export interface ChaosGeneratorOptions {
|
|
@@ -458,11 +470,11 @@ const DELEGATE_CALL_ID = "chaos-delegate-1";
|
|
|
458
470
|
const HEX = "0123456789abcdef";
|
|
459
471
|
const decodeDigest = Schema.decodeSync(Digest);
|
|
460
472
|
const laneDigests = (lane: number): DefinitionDigests => {
|
|
461
|
-
const digest = decodeDigest(HEX
|
|
473
|
+
const digest = decodeDigest(HEX.charAt(lane % 8).repeat(64));
|
|
462
474
|
return DefinitionDigests.make({ agent: digest, model: digest, tools: digest });
|
|
463
475
|
};
|
|
464
476
|
const childDigestStrings = (lane: number) => {
|
|
465
|
-
const char = HEX
|
|
477
|
+
const char = HEX.charAt(8 + (lane % 8));
|
|
466
478
|
return { agent: char.repeat(64), model: char.repeat(64), tools: char.repeat(64) } as const;
|
|
467
479
|
};
|
|
468
480
|
const childLaneDigests = (lane: number): DefinitionDigests => {
|
|
@@ -565,10 +577,12 @@ interface LaneFixture {
|
|
|
565
577
|
readonly ref: string;
|
|
566
578
|
readonly deskInPlay: boolean;
|
|
567
579
|
readonly submissionIndexes: ReadonlyArray<number>;
|
|
568
|
-
readonly submitOne: (flatIndex: number) => Effect.Effect<Receipt,
|
|
580
|
+
readonly submitOne: (flatIndex: number) => Effect.Effect<Receipt, DurableSubmitFailure>;
|
|
569
581
|
readonly drives: (
|
|
570
582
|
firstReceipt: Receipt | undefined,
|
|
571
|
-
) => ReadonlyArray<
|
|
583
|
+
) => ReadonlyArray<
|
|
584
|
+
Effect.Effect<ReadonlyArray<Settlement>, DurableWorkerFailure | DurableBindingFailure>
|
|
585
|
+
>;
|
|
572
586
|
readonly childConversationOf: (firstReceipt: Receipt) => ConversationId | undefined;
|
|
573
587
|
}
|
|
574
588
|
|
|
@@ -636,8 +650,8 @@ const makeLaneFixture = Effect.fn("Chaos.makeLaneFixture")(function* (
|
|
|
636
650
|
|
|
637
651
|
const plainLaneFixture = (
|
|
638
652
|
deskInPlay: boolean,
|
|
639
|
-
drive: Effect.Effect<ReadonlyArray<Settlement>,
|
|
640
|
-
submitOne: (flatIndex: number) => Effect.Effect<Receipt,
|
|
653
|
+
drive: Effect.Effect<ReadonlyArray<Settlement>, DurableWorkerFailure | DurableBindingFailure>,
|
|
654
|
+
submitOne: (flatIndex: number) => Effect.Effect<Receipt, DurableSubmitFailure>,
|
|
641
655
|
): LaneFixture => ({
|
|
642
656
|
index: laneIndex,
|
|
643
657
|
kind,
|
|
@@ -746,9 +760,9 @@ const makeLaneFixture = Effect.fn("Chaos.makeLaneFixture")(function* (
|
|
|
746
760
|
submitOptionsFor(flatIndex),
|
|
747
761
|
),
|
|
748
762
|
drives: (firstReceipt) => {
|
|
749
|
-
const drives: Array<
|
|
750
|
-
|
|
751
|
-
];
|
|
763
|
+
const drives: Array<
|
|
764
|
+
Effect.Effect<ReadonlyArray<Settlement>, DurableWorkerFailure | DurableBindingFailure>
|
|
765
|
+
> = [driveResolved(conversationId)];
|
|
752
766
|
if (firstReceipt !== undefined) {
|
|
753
767
|
drives.push(
|
|
754
768
|
driveResolved(
|
|
@@ -838,9 +852,9 @@ const resolutionPass = Effect.fn("Chaos.resolutionPass")(function* (
|
|
|
838
852
|
const kind =
|
|
839
853
|
plan.resolutionInjections.length === 0
|
|
840
854
|
? "never-happened"
|
|
841
|
-
: plan.resolutionInjections
|
|
842
|
-
injectionIndex(flatIndex, call.toolCallId, plan.resolutionInjections.length)
|
|
843
|
-
|
|
855
|
+
: (plan.resolutionInjections.at(
|
|
856
|
+
injectionIndex(flatIndex, call.toolCallId, plan.resolutionInjections.length),
|
|
857
|
+
) ?? "never-happened");
|
|
844
858
|
yield* tolerateTyped(
|
|
845
859
|
runtime.resolveUnknown(
|
|
846
860
|
UnknownResolutionCommand.make({
|
|
@@ -858,9 +872,9 @@ const resolutionPass = Effect.fn("Chaos.resolutionPass")(function* (
|
|
|
858
872
|
const decision =
|
|
859
873
|
plan.approvalDecisions.length === 0
|
|
860
874
|
? "approved"
|
|
861
|
-
: plan.approvalDecisions
|
|
862
|
-
injectionIndex(flatIndex, pending.toolCallId, plan.approvalDecisions.length)
|
|
863
|
-
|
|
875
|
+
: (plan.approvalDecisions.at(
|
|
876
|
+
injectionIndex(flatIndex, pending.toolCallId, plan.approvalDecisions.length),
|
|
877
|
+
) ?? "approved");
|
|
864
878
|
yield* tolerateTyped(
|
|
865
879
|
runtime.resolveApproval(
|
|
866
880
|
ApprovalDecisionCommand.make({
|
|
@@ -975,11 +989,19 @@ export const runChaosPlan = Effect.fn("Chaos.runChaosPlan")(function* (
|
|
|
975
989
|
lanes.push(yield* makeLaneFixture(plan, lane, kind, laneSubmissions.get(lane) ?? [], desk));
|
|
976
990
|
}
|
|
977
991
|
|
|
978
|
-
const
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
992
|
+
const lanesByIndex = new Map(lanes.map((lane) => [lane.index, lane]));
|
|
993
|
+
const states: Array<SubmissionState> = [];
|
|
994
|
+
for (const [flatIndex, spec] of plan.submissions.entries()) {
|
|
995
|
+
const laneIndex = spec.lane % plan.lanes;
|
|
996
|
+
const lane = lanesByIndex.get(laneIndex);
|
|
997
|
+
if (lane === undefined) {
|
|
998
|
+
return yield* ChaosConvergenceFailure.make({
|
|
999
|
+
seed: plan.seed,
|
|
1000
|
+
message: `submission ${flatIndex} addresses missing lane ${laneIndex}`,
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
states.push({ flatIndex, lane, receipt: undefined });
|
|
1004
|
+
}
|
|
983
1005
|
const appliedAborts = new Set<number>();
|
|
984
1006
|
|
|
985
1007
|
type ArmEntry =
|
|
@@ -1022,7 +1044,10 @@ export const runChaosPlan = Effect.fn("Chaos.runChaosPlan")(function* (
|
|
|
1022
1044
|
}
|
|
1023
1045
|
|
|
1024
1046
|
// Drive every lane (and discovered child lanes) in seeded order under the active arm.
|
|
1025
|
-
const order =
|
|
1047
|
+
const order = lanes
|
|
1048
|
+
.map((lane) => ({ lane, rank: random() }))
|
|
1049
|
+
.sort((left, right) => left.rank - right.rank || left.lane.index - right.lane.index)
|
|
1050
|
+
.map(({ lane }) => lane);
|
|
1026
1051
|
for (const lane of order) {
|
|
1027
1052
|
const firstFlat = lane.submissionIndexes[0];
|
|
1028
1053
|
const firstReceipt = firstFlat === undefined ? undefined : states[firstFlat]?.receipt;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CodeExecutor, SandboxImplementation } from "@effect-agent/sandbox";
|
|
1
|
+
import { CodeExecutor, type SandboxImplementation } from "@effect-agent/sandbox";
|
|
2
2
|
import { Effect, Schema } from "effect";
|
|
3
3
|
declare const CodeExecutorConformanceViolation_base: Schema.Class<CodeExecutorConformanceViolation, Schema.TaggedStruct<"CodeExecutorConformanceViolation", {
|
|
4
4
|
readonly caseName: Schema.String;
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
|
-
CodeExecutionError,
|
|
3
2
|
CodeExecutionHost,
|
|
4
3
|
CodeExecutionLimits,
|
|
5
4
|
CodeExecutionNamespace,
|
|
6
5
|
CodeExecutionRequest,
|
|
7
|
-
CodeExecutionResult,
|
|
8
6
|
CodeExecutor,
|
|
9
|
-
CodeHostCall,
|
|
10
7
|
CodeHostCallFailure,
|
|
11
|
-
CodeHostCallResult,
|
|
12
8
|
CodeHostCallSuccess,
|
|
13
9
|
NetworkAllowlist,
|
|
14
10
|
NetworkDisabled,
|
|
15
|
-
|
|
11
|
+
type CodeExecutionError,
|
|
12
|
+
type CodeExecutionResult,
|
|
13
|
+
type CodeHostCall,
|
|
14
|
+
type CodeHostCallResult,
|
|
15
|
+
type SandboxImplementation,
|
|
16
16
|
} from "@effect-agent/sandbox";
|
|
17
17
|
import { Deferred, Duration, Effect, Fiber, Schema } from "effect";
|
|
18
18
|
|
|
@@ -404,6 +404,8 @@ export const codeExecutorConformanceCases = (
|
|
|
404
404
|
"TEST-015 fails typed on a host outcome outside the protocol schema",
|
|
405
405
|
makeRequest("async () => warehouse.query({})", { namespaces: [warehouseNamespace] }),
|
|
406
406
|
{
|
|
407
|
+
// Deliberately violate the compile-time host contract to verify that an adapter
|
|
408
|
+
// independently decodes the runtime protocol boundary.
|
|
407
409
|
call: () => Effect.succeed({ bogus: true } as unknown as CodeHostCallResult),
|
|
408
410
|
},
|
|
409
411
|
"CodeExecutionProtocolError",
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CodeExecutionHost,
|
|
3
|
-
CodeExecutionLimits,
|
|
4
|
-
CodeExecutionNamespace,
|
|
5
3
|
CodeExecutionProtocolError,
|
|
6
|
-
CodeExecutionRequest,
|
|
7
4
|
CodeExecutionResourceUse,
|
|
8
5
|
CodeExecutionResult,
|
|
9
6
|
CodeExecutionTimeoutError,
|
|
@@ -17,6 +14,9 @@ import {
|
|
|
17
14
|
CodeProgramFailedError,
|
|
18
15
|
CodeSourceError,
|
|
19
16
|
SandboxImplementation,
|
|
17
|
+
type CodeExecutionLimits,
|
|
18
|
+
type CodeExecutionNamespace,
|
|
19
|
+
type CodeExecutionRequest,
|
|
20
20
|
} from "@effect-agent/sandbox";
|
|
21
21
|
import { Clock, Duration, Effect, Fiber, Layer, Option, Queue, Schema } from "effect";
|
|
22
22
|
|
|
@@ -346,6 +346,9 @@ const executeInProcess: CodeExecutorExecute = Effect.fn("InProcessCodeExecutor.e
|
|
|
346
346
|
|
|
347
347
|
const factory = yield* Effect.try({
|
|
348
348
|
try: () =>
|
|
349
|
+
// This substitute intentionally evaluates authored test programs in-process and reports
|
|
350
|
+
// an `unisolated` posture. Real adapters own the security boundary and never use this path.
|
|
351
|
+
// oxlint-disable-next-line typescript/no-implied-eval
|
|
349
352
|
new Function(
|
|
350
353
|
...shadowedGlobals,
|
|
351
354
|
"console",
|
|
@@ -399,7 +402,7 @@ const executeInProcess: CodeExecutorExecute = Effect.fn("InProcessCodeExecutor.e
|
|
|
399
402
|
}
|
|
400
403
|
let outcome: unknown;
|
|
401
404
|
try {
|
|
402
|
-
outcome =
|
|
405
|
+
outcome = candidate();
|
|
403
406
|
} catch (cause) {
|
|
404
407
|
throw new EvaluationThrew(cause);
|
|
405
408
|
}
|
|
@@ -18,7 +18,8 @@ import {
|
|
|
18
18
|
type IdempotencyKey,
|
|
19
19
|
type ResolvedBinding,
|
|
20
20
|
} from "@effect-agent/session";
|
|
21
|
-
import { Crypto
|
|
21
|
+
import type { Crypto } from "effect";
|
|
22
|
+
import { Effect, Layer, Ref, Schema, Stream } from "effect";
|
|
22
23
|
import { LanguageModel, Model, type Response } from "effect/unstable/ai";
|
|
23
24
|
|
|
24
25
|
import { DeterministicIdGeneratorLayer } from "../travel-planner/deterministic-layers.ts";
|
|
@@ -44,13 +44,22 @@ export const docsMcpIdentity = McpServerIdentity.make({
|
|
|
44
44
|
* real MCP server advertises on the wire. This inlines the single top-level
|
|
45
45
|
* `$ref` so the derivation described above still holds byte-for-byte.
|
|
46
46
|
*/
|
|
47
|
+
const JsonSchemaDefinitions = Schema.Record(
|
|
48
|
+
Schema.String,
|
|
49
|
+
Schema.Record(Schema.String, Schema.Unknown),
|
|
50
|
+
);
|
|
51
|
+
const decodeJsonSchemaDefinitions = Schema.decodeUnknownSync(JsonSchemaDefinitions);
|
|
52
|
+
const decodeToolJsonSchema = Schema.decodeUnknownSync(McpSchema.ToolJsonSchema);
|
|
53
|
+
|
|
47
54
|
const flattenTopLevelRef = (schema: JsonSchema.JsonSchema): McpSchema.ToolJsonSchema => {
|
|
48
55
|
const ref = schema["$ref"];
|
|
49
|
-
|
|
50
|
-
|
|
56
|
+
if (typeof ref !== "string") {
|
|
57
|
+
return decodeToolJsonSchema(schema);
|
|
58
|
+
}
|
|
51
59
|
|
|
60
|
+
const defs = decodeJsonSchemaDefinitions(schema["$defs"]);
|
|
52
61
|
const resolved = JsonSchema.resolve$ref(ref, defs);
|
|
53
|
-
return (resolved ?? schema)
|
|
62
|
+
return decodeToolJsonSchema(resolved ?? schema);
|
|
54
63
|
};
|
|
55
64
|
|
|
56
65
|
const fetchDocumentOutputSchema = flattenTopLevelRef(
|
|
@@ -15,8 +15,6 @@ export const phase1Trip = Schema.decodeSync(TripRequest)({
|
|
|
15
15
|
budgetCents: 350_000,
|
|
16
16
|
currency: "USD",
|
|
17
17
|
});
|
|
18
|
-
/** Backward-compatible fixture alias while consumers transition to the P1 name. */
|
|
19
|
-
export const phase0Trip = phase1Trip;
|
|
20
18
|
export const expectedTravelPlan: TravelPlanValue = Schema.decodeSync(TravelPlan)({
|
|
21
19
|
itineraries: [
|
|
22
20
|
{
|
|
@@ -82,4 +80,3 @@ export const phase1HappyPathTurns = [
|
|
|
82
80
|
termination: { _tag: "Complete" },
|
|
83
81
|
},
|
|
84
82
|
] satisfies readonly [ScriptedTurnInput, ScriptedTurnInput];
|
|
85
|
-
export const phase0HappyPathTurns = phase1HappyPathTurns;
|
|
@@ -20,14 +20,16 @@ import { Effect, Layer, Ref, Schema, Stream } from "effect";
|
|
|
20
20
|
import { LanguageModel, Model, type Response, type Toolkit } from "effect/unstable/ai";
|
|
21
21
|
|
|
22
22
|
import { DeterministicIdGeneratorLayer } from "./deterministic-layers.ts";
|
|
23
|
-
import {
|
|
23
|
+
import type {
|
|
24
24
|
DestinationBrief,
|
|
25
|
+
DestinationReport,
|
|
26
|
+
DestinationResearcherToolkit,
|
|
27
|
+
} from "./subagents.ts";
|
|
28
|
+
import {
|
|
25
29
|
DestinationFacts,
|
|
26
30
|
DestinationGuide,
|
|
27
31
|
DestinationRecommendation,
|
|
28
|
-
DestinationReport,
|
|
29
32
|
DestinationResearcher,
|
|
30
|
-
DestinationResearcherToolkit,
|
|
31
33
|
DestinationResearcherToolkitLayer,
|
|
32
34
|
destinationLookup,
|
|
33
35
|
destinationReportFor,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { ToolExecutionClass } from "@effect-agent/engine";
|
|
2
2
|
import { SqliteClient } from "@effect/sql-sqlite-node";
|
|
3
|
-
import { Context,
|
|
3
|
+
import { Context, Effect, Layer, Option, Predicate, Schema, type Duration } from "effect";
|
|
4
4
|
import { Tool, Toolkit } from "effect/unstable/ai";
|
|
5
5
|
import * as SqlClient from "effect/unstable/sql/SqlClient";
|
|
6
|
-
import { SqlError } from "effect/unstable/sql/SqlError";
|
|
6
|
+
import type { SqlError } from "effect/unstable/sql/SqlError";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* The C3 read-only SQL reference integration (plan §8, SEC-015): application
|
|
@@ -196,7 +196,7 @@ const decodeRow = Schema.decodeUnknownOption(Schema.Record(Schema.String, Schema
|
|
|
196
196
|
* on the reason's cause. No error-text matching.
|
|
197
197
|
*/
|
|
198
198
|
const isWriteDenial = (error: SqlError): boolean => {
|
|
199
|
-
const cause =
|
|
199
|
+
const cause = error.reason.cause;
|
|
200
200
|
return (
|
|
201
201
|
Predicate.isObject(cause) &&
|
|
202
202
|
"errcode" in cause &&
|
|
@@ -206,7 +206,7 @@ const isWriteDenial = (error: SqlError): boolean => {
|
|
|
206
206
|
};
|
|
207
207
|
|
|
208
208
|
const sqlFailureMessage = (error: SqlError): string => {
|
|
209
|
-
const cause =
|
|
209
|
+
const cause = error.reason.cause;
|
|
210
210
|
const detail =
|
|
211
211
|
Predicate.isObject(cause) && "errstr" in cause && typeof cause.errstr === "string"
|
|
212
212
|
? `: ${cause.errstr}`
|
|
@@ -275,7 +275,7 @@ export const warehouseDbLayer = (
|
|
|
275
275
|
return yield* denied;
|
|
276
276
|
}
|
|
277
277
|
const rawRows = yield* sql
|
|
278
|
-
.unsafe<Record<string, unknown>>(text, parameters
|
|
278
|
+
.unsafe<Record<string, unknown>>(text, [...parameters])
|
|
279
279
|
.withoutTransform.pipe(
|
|
280
280
|
Effect.catchTag("SqlError", (error) =>
|
|
281
281
|
Effect.fail<WarehouseQueryDenied | WarehouseQueryFailed>(
|